A ROS2 graph static analysis tool that works with jig and clingwrap.
breadcrumb analyzes ROS2 launch files without running your system, extracting and visualizing the complete communication graph of nodes, topics, services, and actions. It's built on top of the clingwrap static analysis system and jig node interface definition file format.
An example graph visualisation (see breadcrumb_example)
- Static Analysis: Understand your system architecture without launching nodes
- Complete Visualization: See all publishers, subscribers, services, and actions in one view
- Multiple Perspectives: Generate full-system, namespace-grouped, or inter-group communication graphs
- QoS Compatibility Checking: Detect QoS mismatches between publishers and subscribers before runtime
- Export Options: Output to JSON for programmatic analysis or DOT for GraphViz visualization
- Launch files written using the clingwrap package
- Node interfaces defined using the jig
interface.yamlformat (Note: nodes don't actually have to be written using jig - see Packages Not Using Jig)
# Analyze a single launch file
breadcrumb my_robot.launch.py
# Analyze multiple launch files
breadcrumb robot.launch.py sensors.launch.py navigation.launch.py
# Export to JSON
breadcrumb my_robot.launch.py -o graph.json
# Generate GraphViz visualization
breadcrumb my_robot.launch.py -o graph.dot
# Generate grouped namespace views
breadcrumb my_robot.launch.py -o graph.dot --graph-type grouped_by_namespace
# Include hidden entities (names starting with _)
breadcrumb my_robot.launch.py --include-hiddenWant to see breadcrumb in action? Check out breadcrumb_example, a complete ROS2 cartpole control system that demonstrates:
- How jig, clingwrap, and breadcrumb work together
- Mixed C++ and Python nodes with automatic code generation
- Modular launch file architecture with namespace organization
- Static graph analysis and visualization of the complete system
- Real-time web dashboard for monitoring and control
The example includes a working physics simulation, state feedback controller, and web UI - everything you need to understand how these tools integrate in a real application.
When you run breadcrumb, it performs the following steps:
-
Extract Nodes from Launch Files: Breadcrumb loads each provided launch file and calls
generate_launch_description()to extract static information from the clingwrapLaunchBuilder. This returns a list of all nodes (regular and composable) that would be launched, along with their namespaces, parameters, and remappings. -
Search for Interface Definitions: For each discovered node, breadcrumb searches for a
interface.yamlfile that describes the node's communication interface. It looks in the following locations (in order):<node_package>/share/<node_package>/interfaces/<launching_package>/share/<launching_package>/interfaces/(if different from node package)breadcrumb/share/breadcrumb/interfaces/(built-in interfaces for common tools)
-
Build the Graph: Using the node information from launch files and the interface definitions, breadcrumb constructs a complete graph by:
- Resolving fully qualified node names with namespaces
- Applying ROS2 name resolution rules (global, private, relative) to topics/services/actions
- Applying remappings specified in launch files
- Connecting publishers to subscribers, service providers to clients, etc.
-
Output: The final graph can be exported as JSON for programmatic analysis or rendered as GraphViz DOT diagrams for visualization.
When you build a package with jig, the build system automatically generates interface.yaml files from your node definitions and installs them to share/<package>/interfaces/. This happens during the colcon build process via CMake install rules that jig sets up. Even if you're not using jig to write your nodes, you can manually create and install these interface files to make your nodes compatible with breadcrumb (Packages Not Using Jig).
A single comprehensive view showing all nodes and their connections.
breadcrumb my_robot.launch.py -o graph.dot --graph-type full_systemGroups nodes by their top-level namespace.
Generates multiple DOT files:
- Top-level graph: Shows namespace groups and inter-group communication
- Per-group graphs: Detailed view of each namespace with internal and external connections
breadcrumb my_robot.launch.py -o graph.dot --graph-type grouped_by_namespaceThis creates:
graph_toplevel.dot- Overview of all groupsgraph_full_system.dot- Complete system graphgraph_group_<namespace>.dot- One per discovered namespace
breadcrumb my_robot.launch.py -o graph.dot --graph-type grouped_and_full_systemAfter generating DOT files, render them with GraphViz:
# Generate PNG image
dot -Tpng graph_full_system.dot -o graph.png
# Generate SVG (scalable)
dot -Tsvg graph_full_system.dot -o graph.svg
# Generate PDF
dot -Tpdf graph_full_system.dot -o graph.pdf
# Process all generated DOT files
for file in graph_*.dot; do
dot -Tpng "$file" -o "${file%.dot}.png"
done- Nodes: Blue rectangles with node name and package
- Topics: Green ellipses with topic name and message type
- Services: Yellow diamonds with service name and type
- Actions: Red hexagons with action name and type
Edge Labels:
pub: Publisher relationshipsub: Subscriber relationshipprovide: Service provider (dashed line)call: Service/action client (dashed line)serve: Action server (dotted line)
Edge Colors:
- Black: Compatible QoS or no QoS defined
- Orange (bold): QoS incompatibility detected between publisher and subscriber
Export graph data as JSON for programmatic analysis or custom visualizations:
breadcrumb my_robot.launch.py -o graph.jsonJSON Structure:
{
"nodes": [
{
"fqn": "/robot1/sensors/lidar",
"name": "lidar",
"namespace": "robot1/sensors",
"package": "sensor_drivers",
"executable": "lidar_driver",
"plugin": null,
"node_type": "regular",
"source_launch_file": "/path/to/sensors.launch.py",
"parameters": {"frame_id": "lidar_link"}
}
],
"topics": [
{
"name": "/robot1/sensors/scan",
"msg_type": "sensor_msgs/msg/LaserScan",
"publishers": [
{
"node": "/robot1/sensors/lidar",
"qos": {
"history": 10,
"reliability": "RELIABLE",
"durability": "VOLATILE"
},
"compatible": true,
"warnings": []
}
],
"subscribers": [
{
"node": "/robot1/navigation/planner",
"qos": {
"history": 10,
"reliability": "RELIABLE",
"durability": "VOLATILE"
},
"compatible": true,
"warnings": []
}
]
}
],
"services": [...],
"actions": [...]
}Breadcrumb supports parsing and validating QoS (Quality of Service) settings for publishers and subscribers, helping you detect incompatibilities before runtime.
Define QoS settings in your interface.yaml files:
publishers:
- topic: sensor_data
type: sensor_msgs/msg/LaserScan
qos:
history: 10 # Required: integer >= 1 or "ALL"
reliability: RELIABLE # Required: RELIABLE or BEST_EFFORT
durability: VOLATILE # Optional: TRANSIENT_LOCAL or VOLATILE
deadline_ms: 100 # Optional: integer >= 0
lifespan_ms: 1000 # Optional: integer >= 0
liveliness: AUTOMATIC # Optional: AUTOMATIC or MANUAL_BY_TOPIC
lease_duration_ms: 500 # Optional: integer >= 0
subscribers:
- topic: commands
type: std_msgs/msg/String
qos:
history: 5
reliability: BEST_EFFORTQoS fields can reference node parameters using ${param:name} syntax:
parameters:
qos_depth:
type: int
default_value: 10
description: QoS history depth
publishers:
- topic: output
type: std_msgs/msg/String
qos:
history: ${param:qos_depth}
reliability: RELIABLEParameters are resolved in order:
- Launch file parameters: Values passed via the launch file
- Default values: Fallback to
default_valuefrom theparameters:section
Breadcrumb automatically checks QoS compatibility between publishers and subscribers on the same topic. Incompatible connections are flagged with warnings.
ROS2 Compatibility Rules:
| Field | Rule | Incompatible Example |
|---|---|---|
| Reliability | Publisher must be >= Subscriber | BEST_EFFORT pub -> RELIABLE sub |
| Durability | Publisher must be >= Subscriber | VOLATILE pub -> TRANSIENT_LOCAL sub |
| Deadline | Subscriber deadline >= Publisher deadline | Sub 100ms, Pub 200ms |
| Liveliness | Publisher must be >= Subscriber | AUTOMATIC pub -> MANUAL_BY_TOPIC sub |
| Lease duration | Subscriber lease >= Publisher lease | Sub 100ms, Pub 200ms |
Note: Compatibility is only checked when both publisher and subscriber have QoS defined. If either side has no QoS (qos: null or omitted), the check is skipped.
In JSON output: Each publisher/subscriber includes compatible (boolean) and warnings (list of strings):
{
"node": "/my_node",
"qos": {"history": 10, "reliability": "BEST_EFFORT"},
"compatible": false,
"warnings": ["-> /other_node: Reliability mismatch: publisher is BEST_EFFORT but subscriber requires RELIABLE"]
}In DOT output: Incompatible connections are shown with orange, bold edges.
You don't necessarily need to use jig to make your nodes compatible with breadcrumb. You can manually create and install interface definitions for any node. Of course, ensuring these interfaces stay up to date is tedious - one of the reasons why the jig code auto-generation was created.
Create an interfaces/ directory in your package and add YAML files describing your nodes:
interfaces/my_node.yaml:
node:
name: my_node
package: my_package
publishers:
- topic: output
type: std_msgs/msg/String
qos:
history: 10
reliability: RELIABLE
subscribers:
- topic: input
type: std_msgs/msg/String
qos:
history: 10
reliability: RELIABLEFor composable nodes, include the plugin field:
interfaces/my_composable_node.yaml:
node:
name: my_composable
package: my_package
plugin: my_package::MyComposableNode
publishers:
- topic: data
type: sensor_msgs/msg/PointCloud2
qos:
history: 5
reliability: BEST_EFFORT
durability: VOLATILEAdd the following to your CMakeLists.txt to install interface definitions:
# Install interface definitions
install(
DIRECTORY interfaces/
DESTINATION share/${PROJECT_NAME}/interfaces
)After rebuilding with colcon build, breadcrumb will be able to find these interface files.
If your launch files include external nodes that you don't control (e.g., third-party packages, vendor drivers), you can define their interfaces in your own package. This is particularly useful when:
- You're launching nodes from packages you don't maintain
- The external package doesn't provide interface definitions
- You want to include third-party nodes in your system graph
Example: Your package launches a vendor's camera driver
Create an interface definition in your launching package:
interfaces/vendor_camera_driver.yaml:
node:
name: camera_driver
package: vendor_camera_pkg
publishers:
- topic: image_raw
type: sensor_msgs/msg/Image
qos:
history: 1
reliability: BEST_EFFORT
durability: VOLATILE
- topic: camera_info
type: sensor_msgs/msg/CameraInfo
qos:
history: 1
reliability: RELIABLE
durability: TRANSIENT_LOCAL
services:
- name: set_camera_settings
type: vendor_camera_pkg/srv/CameraSettingsWhen breadcrumb searches for this node's interface, it will:
- Look in
vendor_camera_pkg/share/vendor_camera_pkg/interfaces/(not found) - Look in your launching package's
share/<your_package>/interfaces/(found!) - Use your interface definition for the graph
This allows you to document and visualize external nodes as part of your system without modifying their source code.
Licensed under the Apache License, Version 2.0. See LICENSE for details.