ROS 2 Node Graph & Introspection Guide 2026
Master ROS 2 debugging tools: ros2 node/topic/service/action CLI, rqt_graph, namespace remapping, programmatic graph API, ros2 doctor, and tf2 frame debugging.
The ROS 2 Computation Graph
The ROS 2 computation graph is the live network of nodes, topics, services, and actions. Every process is a node; nodes exchange data over topics (pub/sub) or services/actions (request/reply). The CLI tools below let you inspect this graph at runtime without reading source code — essential for debugging integration issues between packages.
Node Introspection — ros2 node
Inspect running nodes: list all nodes, get detailed info about publishers, subscribers, services, and actions provided by a node.
# List all active nodes
ros2 node list
# Verbose list with namespaces shown
ros2 node list -a # includes hidden /_ros2cli_* nodes
# Detailed node info: pub/sub/services/actions/parameters
ros2 node info /my_robot/lidar_node
# Output:
# /my_robot/lidar_node
# Subscribers:
# /parameter_events: rcl_interfaces/msg/ParameterEvent
# Publishers:
# /my_robot/scan: sensor_msgs/msg/LaserScan
# /rosout: rcl_interfaces/msg/Log
# Service Servers:
# /my_robot/lidar_node/describe_parameters
# /my_robot/lidar_node/get_parameters
# ...
# Action Servers:
# (none)
# Action Clients:
# (none)
# Show all nodes with their types (pub/sub/svc counts)
ros2 node list --count-subscribers --count-publishers 2>/dev/null || ros2 node listTopic Introspection — ros2 topic
Inspect topics: list, echo messages, get interface type, check publish rate (Hz), bandwidth, and inject test messages from the CLI.
# List all active topics
ros2 topic list
# List with message type shown
ros2 topic list -t
# Show topic type
ros2 topic type /scan
# → sensor_msgs/msg/LaserScan
# Echo messages on a topic (Ctrl-C to stop)
ros2 topic echo /scan
# Echo with --no-arr to skip large arrays, --once for a single message
ros2 topic echo /scan --no-arr
ros2 topic echo /scan --once
# Show full message definition (field names + types)
ros2 topic info /scan -v
ros2 interface show sensor_msgs/msg/LaserScan
# Measure publish rate (Hz)
ros2 topic hz /scan
# → average rate: 10.003, min: 0.099s, max: 0.101s
# Measure bandwidth
ros2 topic bw /scan
# → Subscribed to [/scan]: 183.86 KB/s from 1 publisher
# Count subscribers and publishers
ros2 topic info /scan
# → Type: sensor_msgs/msg/LaserScan
# → Publisher count: 1
# → Subscription count: 2
# Publish a test message from CLI (YAML syntax)
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.5, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"
# Publish once (--once) at rate (--rate N)
ros2 topic pub --once /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"
ros2 topic pub --rate 10 /my_topic std_msgs/msg/String "data: hello"Service & Action Introspection
Inspect services and actions at runtime: list active servers, show types, call services, and send action goals from the CLI.
# ─── Services ────────────────────────────────────────────────
ros2 service list
ros2 service list -t # with types
ros2 service type /my_node/set_params
ros2 interface show rcl_interfaces/srv/SetParameters
# Call a service (YAML request body)
ros2 service call /set_bool std_srvs/srv/SetBool "{data: true}"
# → requester: making request: std_srvs.srv.SetBool_Request(data=True)
# → response: std_srvs.srv.SetBool_Response(success=True, message='')
# ─── Actions ─────────────────────────────────────────────────
ros2 action list
ros2 action list -t # with types
ros2 action type /navigate_to_pose
ros2 action info /navigate_to_pose # shows servers + clients
# Send a goal to an action server
ros2 action send_goal /navigate_to_pose \
nav2_msgs/action/NavigateToPose \
"{pose: {header: {frame_id: map}, pose: {position: {x: 1.0, y: 2.0, z: 0.0}, orientation: {w: 1.0}}}}"
# Send goal with feedback stream
ros2 action send_goal -f /navigate_to_pose \
nav2_msgs/action/NavigateToPose \
"{pose: {header: {frame_id: map}, pose: {position: {x: 1.0, y: 0.0, z: 0.0}, orientation: {w: 1.0}}}}"rqt_graph — Visual Node Graph
rqt_graph renders the live ROS 2 computation graph: nodes as circles, topics as rectangles, arrows showing pub/sub connections.
# Install rqt_graph (if not already installed)
sudo apt install ros-${ROS_DISTRO}-rqt-graph
# Launch rqt_graph (opens a GUI window)
ros2 run rqt_graph rqt_graph
# Or open it from the rqt umbrella tool
rqt --standalone rqt_graph
# Useful rqt_graph options (set in the GUI toolbar):
# - "Nodes only" — shows only nodes, hides topics
# - "Nodes/Topics (active)" — only shows active connections
# - "Nodes/Topics (all)" — includes latched/inactive
# - Hide Dead Sinks checkbox — removes nodes with no subscribers
# - Hide TF checkbox — hides the busy /tf topic
# - Refresh button — force redraw (graph auto-updates)
# rqt_console: live log viewer with filter by severity/node
ros2 run rqt_console rqt_console⚡ In headless environments (CI, Docker without display), use DISPLAY=:0 or Xvfb. For programmatic graph snapshots, parse ros2 node/topic list output instead.
Namespaces and Topic Remapping
Namespaces scope nodes and their topics. Remapping redirects a topic/service/parameter name at launch time without changing source code.
# Run a node in a namespace
ros2 run my_pkg my_node --ros-args --remap __ns:=/robot1
# → node name becomes /robot1/my_node
# → topics become /robot1/scan, /robot1/cmd_vel, etc.
# Remap a specific topic
ros2 run my_pkg lidar_node --ros-args --remap /scan:=/robot1/scan
# Remap multiple things
ros2 run my_pkg my_node --ros-args \
--remap __ns:=/robot1 \
--remap __node:=front_lidar \
--remap /scan:=/robot1/front/scan \
-p scan_rate:=20.0
# In a launch file (Python):
from launch_ros.actions import Node
Node(
package="my_pkg",
executable="my_node",
namespace="robot1", # sets __ns
name="front_lidar", # overrides __node
remappings=[
("/scan", "/robot1/front/scan"), # topic remap
],
parameters=[{"scan_rate": 20.0}],
)Programmatic Graph Introspection (Python API)
Access the node graph programmatically: get_node_names, get_publisher_names_and_types_by_node, get_topic_names_and_types — useful for monitoring nodes.
import rclpy
from rclpy.node import Node
class GraphInspector(Node):
def __init__(self):
super().__init__("graph_inspector")
def inspect(self) -> None:
# ─── Nodes ────────────────────────────────────────────
nodes = self.get_node_names()
self.get_logger().info(f"Active nodes ({len(nodes)}):")
for n in nodes:
self.get_logger().info(f" {n}")
# ─── Topics with types ────────────────────────────────
topics = self.get_topic_names_and_types()
self.get_logger().info(f"Active topics ({len(topics)}):")
for name, types in topics:
self.get_logger().info(f" {name} [{', '.join(types)}]")
# ─── Publishers on a specific node ────────────────────
pubs = self.get_publisher_names_and_types_by_node(
"lidar_node", "robot1" # node_name, namespace
)
for name, types in pubs:
self.get_logger().info(f" pub: {name} {types}")
# ─── Subscribers on a specific node ───────────────────
subs = self.get_subscriber_names_and_types_by_node(
"lidar_node", "robot1"
)
# ─── Services ─────────────────────────────────────────
services = self.get_service_names_and_types()
# ─── Actions (via action introspection) ────────────────
# Actions use topic patterns under the hood:
# /action_name/_action/feedback_with_type
# /action_name/_action/status
def main(args=None):
rclpy.init(args=args)
node = GraphInspector()
node.inspect()
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()⚡ get_node_names_and_namespaces() returns (name, namespace) tuples. get_topic_names_and_types() returns all discovered topics, not just those with active subscriptions.
ros2 doctor — System Health Check
ros2 doctor checks your ROS 2 environment for common configuration issues: DDS settings, clock sync, network, environment variables.
# Full environment health report
ros2 doctor
# Verbose report (shows all checks, even passing ones)
ros2 doctor --report
# Check specific subsystem
ros2 doctor --report | grep -A5 "network"
# Show which DDS implementation is active
ros2 doctor --report | grep -i dds
# Common issues ros2 doctor catches:
# - RMW_IMPLEMENTATION not set (using default cyclone/fastrtps)
# - ROS_DOMAIN_ID mismatch between processes
# - PYTHONPATH conflicts
# - Missing DDS discovery peers
# - System clock not synced (NTP issues)
# Check environment variables
echo $ROS_DOMAIN_ID # default 0 — processes in same domain discover each other
echo $RMW_IMPLEMENTATION # e.g., rmw_cyclonedds_cpp, rmw_fastrtps_cpp
echo $ROS_LOCALHOST_ONLY # 1 = only discover nodes on localhosttf2 Frame Graph Debugging
The tf2 system broadcasts coordinate frame transforms. Use CLI tools to view the frame tree, check transform availability, and debug TF issues.
# View the complete TF tree (requires running TF publishers)
ros2 run tf2_tools view_frames
# → writes frames.pdf to current directory
# Echo a specific transform
ros2 run tf2_ros tf2_echo map base_link
# → At time 1719000000.0
# → Translation: (1.23, 0.45, 0.00)
# → Rotation: (0.000, 0.000, 0.123, 0.992)
# Check if a transform is available within a timeout
ros2 run tf2_ros tf2_echo map base_link 3.0 # 3s timeout
# Monitor TF topic directly
ros2 topic echo /tf
ros2 topic echo /tf_static
# TF buffer lookup in Python
from tf2_ros import Buffer, TransformListener
import rclpy
rclpy.init()
node = rclpy.create_node("tf_checker")
tf_buffer = Buffer()
tf_listener = TransformListener(tf_buffer, node)
# Wait then look up transform
try:
t = tf_buffer.lookup_transform("map", "base_link",
rclpy.time.Time())
print(f"x={t.transform.translation.x:.3f}")
except Exception as e:
print(f"TF error: {e}")CLI Cheatsheet
| Command | Description |
|---|---|
| ros2 node list | List all active nodes |
| ros2 node info /node_name | Show pubs/subs/services/actions of a node |
| ros2 topic list -t | List topics with message types |
| ros2 topic echo /topic --once | Print one message from a topic |
| ros2 topic hz /topic | Measure publish rate |
| ros2 topic bw /topic | Measure topic bandwidth |
| ros2 topic pub /topic Type '{...}' | Inject a message from CLI |
| ros2 interface show pkg/msg/Type | Show message/service/action definition |
| ros2 service call /svc Type '{...}' | Call a service from CLI |
| ros2 action send_goal /act Type '{...}' | Send an action goal from CLI |
| ros2 doctor --report | Full environment health check |
| ros2 run tf2_tools view_frames | Generate TF frame tree PDF |