Latest release · v0.2.0

ros2probe: Observe ROS 2 without being observed.

ros2probe is a middleware-level observability layer for ROS 2. It taps wire traffic from the kernel network path, never creates a middleware participant, and reconstructs the full ROS graph, topic metrics, and message streams entirely in userspace. The v0.2.0 release expands ros2probe with Zenoh observability over TCP and UDP.

Approach
Participant-free wire tap
Network overhead
Zero bandwidth at steady state
Process restart
Not required
Supported
DDS (FastDDS, CycloneDDS), Zenoh
License
Apache-2.0eBPF: GPL-2.0 OR Apache-2.0
The problem

Observability tools for ROS 2 are themselves observed.

Existing graph, CLI, and recording tools all join the middleware graph. They perturb the very properties engineers are trying to measure.

Problem 01

ros2 daemon

Runs as a middleware participant to cache the graph, continuously participating in discovery. A full-mesh discovery domain scales as O(N²), and adding the daemon contributes another participant with O(N) marginal discovery exchanges. Graph state can also become inconsistent when nodes crash or networks partition.

Problem 02

ros2cli topic tools

Tools like ros2 topic hz, ros2 topic echo, and ros2 topic bw each create new subscribers just to observe. They trigger QoS negotiation, inject receive-side load, and expose the observer in ros2 topic list. The act of measurement deforms the system.

Problem 03

rosbag2

Registers as a full middleware participant with subscribers for every recorded topic. Observation and storage cannot be separated. Under constrained links the recorder itself starves the control plane.


Our approach

A participant-free wire tap with adaptive visibility.

ros2probe sits outside the middleware runtime. It reads wire frames at the kernel network path, filters them on a session-driven endpoint whitelist, and falls back gracefully when IP fragmentation or shared-memory transports get in the way. It currently parses RTPS and Zenoh wire formats using the same capture pipeline and parser-layer architecture.

C1

Non-intrusive Observation

Passive Observer architecture. No middleware participant is created at steady state. Logical topology integrity is preserved. Discovery, QoS negotiation, and data-plane load are untouched.

C2

Adaptive Kernel-side Selectivity

Session-level topic interest compiles into a kernel filter. In fragmentation-avoided deployments: perfect per-packet selectivity. Otherwise: a fragment-aware fallback that preserves correctness without assuming any specific middleware configuration.

C3

Discovery Race-free Selectivity

Data-centric, event-driven endpoint synchronisation: each discovery announcement immediately updates the kernel whitelist. Protocol ordering guarantees discovery state is registered before data payload processing begins, eliminating races without a time-based window.

C4

Adaptive Visibility

On-demand SHM visibility uses a mount-namespace-isolated DDS helper with SHM disabled and a local non-SHM Zenoh subscriber.

Get it running

Install.

Just download and run.

CLI only

curl -fsSL https://github.com/csi-dgist/ros2probe/releases/latest/download/install.sh | sh

CLI + GUI (x86-64 only)

curl -fsSL https://github.com/csi-dgist/ros2probe/releases/latest/download/install.sh | sh -s -- --gui

Uninstall

sudo rm /usr/local/bin/rp
RequirementNotes
Linux 5.15+eBPF socket filter, AF_PACKET TPACKET_V3
ROS 2 Humble+Jazzy also supported
OptionalJumbo frames and transport settings that avoid IP fragmentation

CLI at a glance

CommandDescription
rp runStart the ros2probe runtime daemon
rp guiLaunch the GUI
rp topicTopic introspection — list, hz, bw, delay, echo
rp nodeNode introspection commands
rp serviceService introspection commands
rp actionAction introspection commands
rp bagBag recording commands — record an MCAP bag (zstd / lz4 / none)
rp discoverRefresh the graph using an RTPS discovery trigger and/or a local Zenoh liveliness query
How it works

Architecture, features, and the research behind it.

Switch between tabs for the layered architecture, the feature surface, live demos, and the paper.

Every other ROS 2 observability tool attaches somewhere inside the application stack: rclcpp, rcl, rmw, or the middleware itself. ros2probe is the only layer that sits below the middleware, in the kernel network path.

Attachment view · where each tool hooks
┌────────────────────────────────────────────┐ ROS 2 application (your nodes, callbacks, lifecycle) ├────────────────────────────────────────────┤ rclcpp / rclpy <-- ros2_tracing rcl <-- ros2_tracing ├────────────────────────────────────────────┤ rmw abstraction <-- CARET ├────────────────────────────────────────────┤ Middleware runtime (DDS / Zenoh runtime ...) participants / readers / writers <-- ros2 topic echo rosbag2 recorder tooling transport stats ├────────────────────────────────────────────┤ Wire protocol RTPS / Zenoh Protocol ├────────────────────────────────────────────┤ Kernel network stack TCP / UDP / IPv4 / IPv6 / NIC eBPF socket filter <-- ros2probe TPACKET_V3 mmap ring └────────────────────────────────────────────┘ Every other tool = inline attachment ros2probe = out-of-band tap
Runtime view · it COPIES, it does not STEAL
ROS 2 normal flow ros2probe observer (nothing changes) (reads a copy) ┌───────────────┐ ┌───────────────┐ │ Node A (sub) │ │ rp CLI / gui │ │ receives │ │ shows │ │ normally │ │ live state │ └───────┬───────┘ └───────┬───────┘ ▲ ▲ ┌───────┴───────┐ ┌───────┴───────┐ │ Middleware │ │ ros2probe │ │ (DDS / Zenoh) │ │ daemon │ └───────┬───────┘ └───────┬───────┘ ▲ ▲ ┌───────┴───────┐ ┌───────┴───────┐ │ Kernel socket │ │ eBPF filter │ │ duplicates ├──copies─▶│ reads a copy │ └───────┬───────┘ └───────────────┘ ▲ ┌───────┴───────┐ The kernel duplicates │ NIC / wire │ the sk_buff. One copy │ (unchanged) │ follows the normal path └───────┬───────┘ down to the NIC. Another ▲ copy is handed to the ┌───────┴───────┐ eBPF filter. ros2probe │ Node B (pub) │ never removes or modifies └───────────────┘ the original packet.
The others · inline

Attach inside the stack

ros2_tracing adds tracepoints in rclcpp / rcl. CARET wraps rmw with LD_PRELOAD. rosbag2, ros2 topic echo, recorder tooling, and transport stats tools all create endpoints in the middleware itself. Each becomes part of the stack being observed.

ros2probe · out of band

Tap from below the middleware

A kernel-space eBPF socket filter sees wire frames as they leave or enter the NIC. Userspace reassembles, decodes, and reconstructs the graph. The middleware never sees ros2probe.

ros2probe pipeline

L1 · Capture

Memory-mapped TPACKET_V3 ring, per-interface workers

Kernel timestamps arrive with each frame. Loopback is included by default so intra-host traffic is captured too.

L2 · Wire decoder

DDS / Zenoh parser, fragment reassembly

Protocol decoding is shared at the same layer, with explicit wire-format parsers for DDS/RTPS and Zenoh rather than middleware symbol hooks.

L3 · Graph reconstruction

Participants, endpoints, nodes, topics

For DDS, discovery packets (ros_discovery_info) bind endpoint GUIDs to ROS node namespaces. For Zenoh, equivalent discovery/control metadata is used to map peers, endpoints, and topics. TTL sweep keeps state fresh when nodes disappear.

L4 · Applications

CLI · GUI · MCAP recorder

A single Unix socket command bus multiplexes all consumers. Sessions open and close, the kernel filter follows automatically.

Zero ROS subscriptions

ros2probe never joins the middleware graph. Your ros2 topic list stays clean.

Full graph reconstruction

Participants, endpoints, node names, and pub/sub relationships are derived from wire-level discovery metadata and protocol-native control packets for each supported middleware.

SHM topic visibility

SHM-only topics are made observable on demand through a mount-namespace-isolated DDS helper or a non-SHM Zenoh subscriber.

Live topic metrics

Publish rate (hz), bandwidth (bw), end-to-end delay, and decoded echo, all with sliding-window statistics.

MCAP recording

Bag files with optional zstd / lz4 compression, pause / resume, and per-topic selection.

CLI + GUI

Same data, two interfaces. rp for shells and scripts, rp gui for visual exploration.

Topic classification

tf, parameter_events, rosout, debug, and SHM-only topics are automatically classified and separated in every UI.

Data-centric discovery sync

Each discovery event immediately updates the kernel whitelist. Protocol ordering ensures discovery state is registered before related payloads are processed, with no time-based window needed.

Protocol-level decoding

Wire-level parsers for DDS/RTPS and Zenoh avoid vendor-specific symbol hooks and BTF/CO-RE coupling.

1. Start the runtime

# auto-escalates to root if needed (CAP_NET_ADMIN + CAP_NET_RAW)
rp run

Zenoh transport port 7447 is monitored for both TCP and UDP by default. Supplying the option replaces that default; list every port that should be monitored when a deployment uses custom or multiple ports.

# monitor Zenoh traffic on both ports
rp run --zenoh-transport-port 7447 7448

Run rp run first, then launch your ROS 2 nodes. The runtime needs to be observing before the participants announce themselves so it can capture discovery traffic. If you start rp run after nodes are already up, run rp discover to trigger a one-shot discovery sync for the existing graph. For a Zenoh-only refresh, rp discover --zenoh queries a locally reachable TCP or UDP endpoint on the transport ports configured when the runtime started.

Only unencrypted Zenoh TCP and UDP transports can be passively decoded. Encrypted transports such as TLS and QUIC are not supported.

2. Observe topics without touching the graph

rp topic list
rp topic info /chatter
rp topic hz    /chatter
rp topic bw    /scan
rp topic delay /imu
rp topic echo  /chatter --field data

3. Record a bag without joining the middleware graph

rp bag record /chatter /scan -o session.mcap --compression-format zstd

4. Open the desktop UI

rp gui

Same data, two interfaces

Scripts talk to rp over a Unix socket. Humans get a GUI with a dashboard, a topic monitor, and a bag recorder.

Not all of these tools are built for the same thing. ros2_tracing and CARET are performance-analysis tools for callback chains and executors. rosbag2 and recorder tooling are loggers. ros2 daemon is a CLI cache. Wireshark is a general-purpose packet tool, passive but ROS 2-unaware, so it cannot reconstruct the topic graph and its cost grows with total traffic, not with what you observe. ros2probe aims at live middleware-level observation. The table below makes this explicit in the first row.

ros2 daemon ros2 topic rosbag2 ros2_tracing CARET Wireshark ros2probe
Purpose CLI graph cache Live topic CLI Message logging Callback / executor tracing E2E callback-chain latency Packet-level protocol inspection Live middleware-level observation
Graph observation
Message recording
Callback / executor trace
Non-intrusive
Process restart not required
Middleware-neutral
Steady-state overhead high none (off) none (off) low high scales w/ traffic minimal
Observer Effect on graph yes yes yes limited limited none none

● full support  ·  △ partial / conditional  ·  ✗ not supported

ros2_tracing and CARET solve a different problem (internal performance analysis) and are complementary to ros2probe, not competitors.

Abstract

Robot Operating System 2 (ROS 2), the de facto standard middleware framework for robots, runs each robot as a graph of nodes communicating over the Data Distribution Service (DDS), a publish/subscribe substrate. Observing this inter-node communication in real time is essential to robot development, yet observing it has a price. A tool can receive data only by joining the DDS domain as a subscriber that discovery has matched to the publisher, so the act of observing folds the tool into the system it measures and perturbs it.

We define this protocol-inherent perturbation as the observer's probe effect. It inflates the discovery plane, adds deserialization cost on the observer, makes the loss it reports diverge from what the subscriber actually received, and near saturation displaces the subscriber's own messages. The only existing escape, capturing all wire traffic passively, discards the ROS 2 message semantics and scales with total traffic, not with what is observed.

We present ros2probe, a non-intrusive observation framework that removes the probe effect. It reconstructs the full ROS 2 communication state from the domain's discovery packets at no bandwidth cost, then drives an in-kernel filter restricted to the topics the user asks for, lifting only those packets at minimal cost and observing the messages the real subscriber receives. Its interfaces and recordings match the standard ROS 2 tools.

Across three hardware platforms (laptop, Jetson, and Raspberry Pi), two DDS implementations, and seven robot-operation workloads, ros2probe holds the discovery graph within 0.5% of an unobserved system, whereas domain-joining tools inflate discovery up to 2.6× and drop 38.5% of the subscriber's messages at saturation while ros2probe drops none. It reports loss with a recall of 1.0, cuts observer CPU and memory by up to 7× and 28×, and stays practical on the embedded robots where existing tools overload the system.

StatusPreprint · arXiv 2026
TrackMiddleware / systems
ArtifactsOpen-source runtime + CLI + GUI
SupportedFastDDS, CycloneDDS (paper scope) · Zenoh (v0.2.0+)
PlatformLinux 5.15+ · x86_64 · aarch64

Four contributions

C1

Non-intrusive Observation

Passive Observer architecture. No middleware participant at steady state. The monitoring tool does not become part of what it measures.

C2

Adaptive Kernel-side Selectivity

Session-level topic interest compiles to a kernel filter. Fragment-aware fallback preserves correctness regardless of middleware configuration.

C3

Discovery Race-free Selectivity

Data-centric, event-driven endpoint synchronisation: each discovery announcement immediately updates the kernel whitelist. Protocol ordering structurally prevents discovery-vs-data races.

C4

Adaptive Visibility

SHM-only topics become visible on demand through a namespace-isolated DDS helper with SHM disabled.

Experiments

Real hardware, real numbers.

Four experiments on a real cross-host testbed (a publisher laptop with a laptop, an NVIDIA Jetson Orin NX, and a Raspberry Pi 4B as receivers) on Ubuntu 22.04 (Linux 5.15) and ROS 2 Humble. Quantitative results are from DDS-focused tests using FastDDS 2.6.11 and CycloneDDS 0.10.5.

Experiment 01

Discovery transparency

SPDP / SEDP / Control discovery messages with every participant both publisher and subscriber, sweeping the participant count NP ∈ {1, 3, 5}, averaged over 10 runs. ros2probe creates no DDS participant, so it should leave the DDS discovery graph untouched.

baseline · rp run · vs ros2 daemon
Grouped bar charts of SPDP, SEDP, and Control discovery messages at NP = 1, 3, 5: rp run matches baseline while ros2 daemon inflates every message type.

Discovery messages under each condition (NP = 1, 3, 5). rp run matches the baseline at every scale, while the ros2 daemon inflates the total by 2.6×, 1.6×, and 1.5× across SPDP, SEDP, and Control.

Experiment 02

Resource overhead

Observer-process CPU and PSS memory under a fixed 64 KiB payload published at 100, 500, and 1000 Hz, on three receivers (laptop, Jetson, and Raspberry Pi 4B) for both rate measurement and recording.

rp hz vs ros2 hz · rp bag vs rosbag2
Bar charts of observer-process CPU and memory under rate measurement and recording across laptop, Jetson, and Raspberry Pi, showing ros2probe far below the ros2 tools.

Observer CPU and memory under rate measurement (a, b) and recording (c, d). rp hz parses only discovery/data headers in this DDS-focused benchmark, cutting observer memory by up to 28× (1.7–3.9 MB versus ~46 MB for ros2 hz) and CPU by up to 7×. The gap turns from efficiency into feasibility on the embedded platforms.

Experiment 03

Observation fidelity

Recording a topic under 10% and 20% injected packet loss (tc netem, BEST_EFFORT QoS), matching each tool's recorded set against the messages the subscriber actually received, by sequence number.

rp bag vs rosbag2
Table of drop rate and recall under 10% and 20% loss: rp bag reaches recall 1.00 at both levels, while rosbag2 reaches only 0.09 and 0.22.

Recall of reported losses against the subscriber's actual losses. Both tools report a similar aggregate drop rate, but rp bag reads the same wire frames as the subscriber and reaches recall 1.00, whereas rosbag2's independent unicast stream catches only 0.09 (10%) and 0.22 (20%).

Experiment 04

Probe effect

Received bandwidth and the original subscriber's drop rate over seven workloads (S1–S7, sensor message to near-GbE composite), swept across the full 2×2 matrix of {BEST_EFFORT, RELIABLE} QoS × {wired, wireless} link.

rp hz / rp bag vs ros2 hz / rosbag2
Scenario Topic Payload Rate Est. bandwidth
S1/imu320 B200 Hz~0.72 Mbps
S2/scan4.3 KB40 Hz~1.4 Mbps
S3/points644 KB20 Hz~103 Mbps
S4/points2.72 MB20 Hz~435 Mbps
S5/image_raw/compressed150 KB30 Hz~36 Mbps
S6/depth/image_raw600 KB30 Hz~147 Mbps
S7composite (see breakdown below)~830 Mbps

Workloads (S1–S7). Seven scenarios sweeping the offered bandwidth from a small IMU message (S1) up to a near-GbE autonomous-driving composite (S7).

S7 component topic Payload Rate Est. bandwidth
/cmd_vel72 B20 Hz~0.03 Mbps
/imu320 B200 Hz~0.72 Mbps
/points/front2.72 MB20 Hz~435 Mbps
/points/rear644 KB20 Hz~103 Mbps
/camera/front/compressed150 KB30 Hz~36 Mbps
/camera/left/compressed150 KB30 Hz~36 Mbps
/camera/right/compressed150 KB30 Hz~36 Mbps
/camera/rear/compressed150 KB30 Hz~36 Mbps
/depth/image_raw600 KB30 Hz~147 Mbps
Total~830 Mbps

S7 composite breakdown. A multi-sensor mix of teleoperation, IMU, two LiDARs, four cameras, and a depth stream, totaling ~830 Mbps.

Matrix table of received bandwidth and subscriber drop rate across QoS and link conditions for seven scenarios: ros2probe tracks the baseline everywhere while the DDS tools inflate bandwidth and induce loss near saturation.

Received bandwidth (BW, Mbps) and subscriber drop rate (Loss, %) across the 2×2 link × QoS matrix. rp hz / rp bag match the baseline in every cell, while the DDS tools roughly double the bandwidth and drive loss to 38.5% (ros2 hz) and 75.5% (rosbag2) at the saturating wired S7.

Cite

If you use ros2probe in your research.

Read the preprint on arXiv, and use the BibTeX entry below.

@article{yu2026ros2probe,
  title={ros2probe: Non-intrusive, Kernel-selective Observability for Robot Operating System 2 Middleware},
  author={Yu, Jisang and Lee, Sanghoon and Choi, Yeonwoo and Park, Kyung-Joon},
  journal={arXiv preprint arXiv:2606.10746},
  year={2026}
}
Coming Soon This isn't available yet.