Getting started¶
This page takes you from a plain ROS 2 installation to the first ros2 transport list,
with statistics and the web viewer. Everything runs on Linux; the tool observes Fast DDS,
so the nodes you look at must use rmw_fastrtps_cpp (the default RMW of Jazzy, Lyrical
and Rolling) or rmw_fastrtps_dynamic_cpp.
| ROS 2 distribution | Fast DDS | Notes |
|---|---|---|
| Humble (Ubuntu 22.04) | 2.6 | prediction only: the Humble binary has no statistics module, so --stats measures nothing; same-host peers show only their SHM locator (see how-it-works) |
| Jazzy (Ubuntu 24.04) | 2.14 | primary target |
| Lyrical (Ubuntu 26.04) | 3.6 | current LTS |
| Rolling | 3.x | best effort (CI allows failures) |
Kilted was supported through 1.0.0 and is out of scope from 1.1.0 on, as it reaches EOL in
December 2026; the 1.0.0 tag still builds and runs there.
1. Build it¶
Two packages live in src/: fastdds_transport_viz (the C++ tool) and ros2transport
(the ros2 transport command). Build them in a colcon workspace like any other ROS 2
package.
Native (recommended)¶
Prerequisites: a ROS 2 desktop or base installation, python3-colcon-common-extensions
and rosdep (sudo rosdep init && rosdep update once).
mkdir -p ~/ws/src && cd ~/ws
git clone https://github.com/atinfinity/fastdds_transport_viz.git src/fastdds_transport_viz
source /opt/ros/jazzy/setup.bash # or humble / lyrical / rolling
rosdep install --from-paths src --ignore-src -y
colcon build --symlink-install
source install/setup.bash
rosdep installs the build dependencies (rclcpp, Fast DDS headers, nlohmann-json),
the runtime ones (rmw_fastrtps_cpp, demo_nodes_cpp for the examples) and the test
dependencies. source install/setup.bash is needed in every shell that runs the tool;
it also registers the ros2 transport command.
Docker: prebuilt image (GHCR)¶
Every release from 2.1.0 on is published as a ready-to-run image, for x86_64 and arm64:
| Tag | Contents |
|---|---|
ghcr.io/atinfinity/fastdds_transport_viz:jazzy (:latest) |
the newest release on ROS 2 Jazzy |
ghcr.io/atinfinity/fastdds_transport_viz:humble, :lyrical |
the newest release on Humble / Lyrical |
ghcr.io/atinfinity/fastdds_transport_viz:<X.Y.Z>-<distro> |
one release, e.g. 2.1.0-jazzy |
The image is ros:<distro>-ros-core with both packages installed (and demo_nodes_cpp
for the examples); it runs ros2 transport list unless given another command. To observe
the nodes on the Docker host, run it on the host's network and IPC namespace, with the
tag of the nodes' distribution:
docker run --rm --net host --ipc host ghcr.io/atinfinity/fastdds_transport_viz:jazzy \
ros2 transport list -v
Without --net host the container discovers nothing on the host. Without --ipc host it
has a /dev/shm of its own: the pairs are still predicted, but the shared-memory line
reports !shm-not-visible and describes the container instead of the host. This works on a
Linux Docker host only: the host network of Docker Desktop (macOS, Windows) is that of its
VM, not of the machine the nodes run on.
The container starts with a clean environment, so pass on with -e whatever the nodes
set for discovery and the RMW; a difference hides nodes or changes the prediction:
ROS_DOMAIN_IDRMW_IMPLEMENTATION(rmw_fastrtps_cppin the image;rmw_fastrtps_dynamic_cppis installed too)ROS_AUTOMATIC_DISCOVERY_RANGEandROS_STATIC_PEERS(Jazzy and later),ROS_LOCALHOST_ONLY(Humble)ROS_DISCOVERY_SERVER,ROS2_EASY_MODEFASTDDS_DEFAULT_PROFILES_FILE/FASTRTPS_DEFAULT_PROFILES_FILE, with the file mounted at the same path (-v /path/profiles.xml:/path/profiles.xml:ro)
The web viewer of section 5, on http://127.0.0.1:8765/ of the
host (the container is on the host's network, so the default 127.0.0.1 is the host's):
docker run --rm -it --net host --ipc host ghcr.io/atinfinity/fastdds_transport_viz:jazzy \
ros2 run fastdds_transport_viz transport_viz_web --stats --interval 1
The tool runs as root in the container, so the SHM files its participant creates in the
host's /dev/shm belong to root. Stop it with Ctrl-C (hence -it): a container that is
killed instead (docker kill, or a docker stop that times out) leaves segments that show
as !shm-stale-files, and a normal user cannot delete them. Remove them with the image of
the same distribution; fastdds shm clean of Humble and Jazzy (Fast DDS 2.x) handles the
fastrtps_* files only, that of Lyrical (3.x) the fastdds_* files only:
docker run --rm --ipc host ghcr.io/atinfinity/fastdds_transport_viz:jazzy fastdds shm clean
Docker: development image¶
The repository ships a compose.yaml with a development image (ros:jazzy, or
ROS_DISTRO=humble / lyrical / rolling), the repository mounted at /ws, and ipc: host so that
the tool sees the host's shared memory:
docker compose build
docker compose run --rm dev bash
colcon build --symlink-install && source build/$ROS_DISTRO/install/setup.bash
The rest of this page works the same inside that shell. The container has its own
network namespace, so it can observe nodes running in the same container but not nodes
on the Docker host; development.md explains the
hostnet service for that.
2. First run¶
Start two demo nodes and look at them:
ros2 run demo_nodes_cpp talker &
ros2 run demo_nodes_cpp listener &
ros2 transport list -v --explain
TOPIC TYPE PUBS SUBS TRANSPORT LATENCY HZ LOSS REASON
/chatter std_msgs/msg/String 1 1 SHM x1 - - same-host-guid,datasharing-disabled-writer,both-shm-locators
/talker@local -> /listener@local SHM - - same-host-guid,datasharing-disabled-writer,both-shm-locators
shared memory: /dev/shm 2.19 MB used of 16.7 GB (16.7 GB free) | Fast DDS 2.19 MB in 3 segment(s), 6 port(s), 0 data-sharing histories
Reason codes:
both-shm-locators
Both endpoints announce a shared-memory locator ...
What you are looking at:
- one row per topic, then (with
-v) one row per writer → reader pair with the predicted transport and the reason codes that led to it (--explainprints their descriptions;ros2 transport codeslists every code); - the
shared memory:line describes the/dev/shmof the environment the tool runs in (how-it-works.md).
Make a second listener that cannot use shared memory and watch the verdict change:
FASTDDS_BUILTIN_TRANSPORTS=UDPv4 ros2 run demo_nodes_cpp listener &
ros2 transport list -v # second pair: UDPv4, reader-no-shm-locator
The tool needs nothing from the observed nodes for this: the verdicts come from the discovery data every Fast DDS participant announces anyway.
Run it where the nodes run
Discovery data is only visible from the same DDS domain, and shared memory only
from the same IPC namespace. Run the tool with the same environment variables
(ROS_DOMAIN_ID, FASTDDS_BUILTIN_TRANSPORTS, XML profile, Discovery Server
settings) and in the same network/IPC namespace as the nodes you observe. See
how-it-works.md.
3. Measure instead of predict (--stats)¶
Predictions can be confirmed with the Fast DDS statistics module, which the observed nodes have to enable through an environment variable before they start:
export FASTDDS_STATISTICS="RTPS_SENT_TOPIC;RTPS_LOST_TOPIC;HISTORY_LATENCY_TOPIC;PHYSICAL_DATA_TOPIC;DATA_COUNT_TOPIC;RESENT_DATAS_TOPIC;HEARTBEAT_COUNT_TOPIC;ACKNACK_COUNT_TOPIC;NACKFRAG_COUNT_TOPIC;GAP_COUNT_TOPIC"
export FASTRTPS_DEFAULT_PROFILES_FILE=$(ros2 pkg prefix fastdds_transport_viz)/share/fastdds_transport_viz/config/statistics.xml
ros2 run demo_nodes_cpp talker &
ros2 run demo_nodes_cpp listener &
ros2 transport list -v --stats
The pair row now carries measured=SHM 47pkt 3.20 kB, LATENCY the write-to-notification
latency of the pair (mean and max), HZ the samples per second that reached the reader
(≥ when part of the statistics was lost and the value is a lower bound), LOSS lost and
resent packets, and hosts are shown by name with process ids. The profile file
lifts a resource limit of the statistics writers; statistics.md explains
the topics and the pitfall it avoids.
4. Keep watching¶
ros2 transport list --watch --stats --interval 2
re-observes every two seconds and marks what changed since the previous frame (+
appeared, ~ changed, - disappeared). Keys: q quit, p pause, v pairs, e
legend, a all topics, l locators, f fixes (--advise).
5. See it in the browser¶
ros2 run fastdds_transport_viz transport_viz_web --stats --interval 1
# transport_viz_web: listening on http://127.0.0.1:8765/ (serving .../share/fastdds_transport_viz/web)
Open the URL: hosts are columns, nodes are boxes, pairs are arrows colored by transport,
updated live. ros2 transport list --json > snapshot.json produces a document that
the same page (web/index.html) can open offline. See web-viewer.md.
The server listens on 127.0.0.1 only. In the Docker environment, start the shell with the
viewer's port published and listen on every address inside the container, so that the
browser on the host reaches it:
docker compose run --rm --service-ports dev bash
ros2 run fastdds_transport_viz transport_viz_web --bind 0.0.0.0 --stats --interval 1
The same --bind 0.0.0.0 lets a laptop look at a robot.
6. Command reference¶
ros2 transport list [--domain N] [--timeout S] [--quiet S] [--topic REGEX] [--node REGEX]
[--all] [-v] [--explain] [--locators] [--advise] [--stats] [--json | --csv]
[--color auto|always|never] [--watch [--interval S]]
ros2 transport diff BEFORE.json AFTER.json [--key node|guid] [--changes-only] [--json]
[--topic REGEX] [--node REGEX] [--all] [-v] [--explain] [--locators]
[--advise] [--color auto|always|never]
ros2 transport codes
ros2 transport execs the transport_viz binary of fastdds_transport_viz; the binary
can be run directly as ros2 run fastdds_transport_viz transport_viz with the same options
plus --list-codes. Exit codes: 0 on success, 2 on a usage error, and 1 when the RMW is
neither rmw_fastrtps_cpp nor rmw_fastrtps_dynamic_cpp (the message names it) or, from
ros2 transport, when the binary cannot be found or started.
ros2 transport diff compares two saved --json documents without observing anything
(how-it-works.md). Its exit codes follow
diff(1): 0 without changes, 1 when something changed, 2 on an error.
First checks when something is off¶
ros2 transport list --advise prints, under each pair and in the legend, what to change
to get past the reason codes in use (fix reader-no-shm-locator: Enable SHM on the
reader's participant: unset FASTDDS_BUILTIN_TRANSPORTS ...); ros2 transport codes lists
the remedy of every code. The table below covers what is not a reason code.
| Symptom | Check |
|---|---|
ros2: error: argument Call ... invalid choice: 'transport' |
source install/setup.bash in this shell (build/$ROS_DISTRO/install/setup.bash in the Docker image); ros2transport must be built in the same workspace. |
RMW is rmw_cyclonedds_cpp; this tool observes Fast DDS ... (exit 1) |
the tool only works on Fast DDS: set RMW_IMPLEMENTATION=rmw_fastrtps_cpp (or rmw_fastrtps_dynamic_cpp, or unset it on a distro whose default is Fast DDS) in the shell that runs the tool. |
| no topics at all | same ROS_DOMAIN_ID as the nodes? ROS_AUTOMATIC_DISCOVERY_RANGE=OFF limits every participant to itself. With a Discovery Server the tool needs the same ROS_DISCOVERY_SERVER (it becomes a SUPER_CLIENT automatically). |
| nodes on another machine are missing | the other machine must be reachable by multicast, or listed in ROS_STATIC_PEERS, or both sides use a Discovery Server; see development.md. |
!stats-not-enabled-on-writer with --stats |
the node was started without FASTDDS_STATISTICS (the variable must be set before the node starts), or its participant has the SHM transport only: the tool's statistics readers listen on UDP or TCP. |
!stats-samples-lost, or warning: ... statistics samples were lost |
the tool did not receive part of the statistics and at least one pair with proven deliveries shows no measured packet (stats.pairs_delivered_unmeasured), so a pair can read (unmeasured, delivered) or none(delivered) although it carries traffic. A loss that cost no measurement shows as N sample(s) lost without this warning. Enable statistics on fewer nodes, or keep FASTDDS_STATISTICS to the aliases you need (RTPS_SENT_TOPIC;RTPS_LOST_TOPIC is enough for the transports). A longer --timeout does not help: it collects more of the loss, not less. --json counts them in stats.samples_lost, of which stats.samples_lost_latency is the best-effort HISTORY_LATENCY part that costs no pair its measurement and is left out of the warning; stats.samples_lost_at_start is the normal burst from before the readers matched and never warns. |
!shm-not-visible |
the nodes use another /dev/shm (another container or host); the shared-memory line describes the tool's environment only. |
!shm-ipc-namespace-split on a NONE pair |
the two nodes have the same host id but different /dev/shm (host network, separate IPC namespaces), so the listener receives nothing although Fast DDS selected SHM or data-sharing. Give both containers ipc: host, or disable SHM (and data-sharing) on one side (--advise prints the remedy). |
!shm-stale-files |
crashed processes left segments behind; fastdds shm clean removes them. |
pairs the system does have are missing, or warning: discovery was still in progress (...) |
the run stopped while the nodes were still announcing their endpoints: it ends after --quiet seconds without discovery events, and a large system falls silent between two batches. Re-run with the values the message names (--quiet 3 --timeout 10 is a good start; with --stats, --quiet 3 --timeout 60). --json says the same in discovery.complete (false), and such a document is not worth diffing. |
| the tool shows up as a node | it does not: its own node /_transport_viz_<pid> and participant are filtered out. If you see it, please open an issue with --json output. |
Next: How it works for the decision rules, or Architecture if you want to change the tool.