Skip to content

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.

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_ID
  • RMW_IMPLEMENTATION (rmw_fastrtps_cpp in the image; rmw_fastrtps_dynamic_cpp is installed too)
  • ROS_AUTOMATIC_DISCOVERY_RANGE and ROS_STATIC_PEERS (Jazzy and later), ROS_LOCALHOST_ONLY (Humble)
  • ROS_DISCOVERY_SERVER, ROS2_EASY_MODE
  • FASTDDS_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 (--explain prints their descriptions; ros2 transport codes lists every code);
  • the shared memory: line describes the /dev/shm of 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.