ROS 2 Topic Monitoring Without Probe Effect: ros2_pulse vs ros2 topic hz

The quick answer

If you only need to check one topic for a few seconds, start with ros2 topic hz:

bash
ros2 topic hz /camera/image_raw

This command is fast and familiar, but it creates a ROS 2 subscription. The number on screen is the rate received by that new subscriber, not a direct reading from the publisher. CPU load, message size, QoS, transport, and the extra subscription itself can change what you observe.

Use ros2_pulse v0.4.1 when you need to monitor a running stack from the application side without adding a DDS reader to every topic. It can report publish and receive rates, including intra-process delivery, by counting ROS 2 tracetools events inside processes launched with the probe.

  • Use ros2 topic hz for a quick spot check on one or two topics.
  • Use ros2_pulse when an observer subscription could disturb a high-rate or intra-process pipeline.
  • Use built-in ROS 2 topic statistics when you control the subscriber code and need message-period or message-age metrics.
  • Use ros2probe for host or wire traffic, and ros2_tracing for callback, executor, and latency analysis.

ros2_pulse removes the DDS traffic created by an external monitoring subscriber. It still performs in-process counting and periodic log writes, so “without probe effect” does not mean zero CPU cost.

What does ros2 topic hz actually measure?

The ros2 topic hz implementation creates a subscription, records the time between received messages, and calculates an average rate over a window. The ROS 2 topic tutorial makes the same distinction: the displayed rate belongs to the CLI subscription and may differ from the publisher rate because of platform resources or QoS.

That makes ros2 topic hz useful for answering:

“At what rate can this observer receive the topic with its current QoS?”

It does not directly answer:

“At what rate is the publisher calling publish() before I add an observer?”

For a 10 Hz pose topic on an unloaded development laptop, the difference may be irrelevant. For images, point clouds, multiple camera streams, or composable nodes using intra-process communication, the distinction can matter.

Why does ros2 topic hz show a lower rate than expected?

Do not change the publisher immediately. Check the receive path in this order.

1. Inspect the publisher QoS

Use the verbose topic view before forcing a QoS profile:

bash
ros2 topic info /camera/image_raw --verbose

Look at the publisher's reliability, durability, history, and depth. Sensor streams commonly use best-effort reliability and volatile durability. If your ROS 2 distribution exposes QoS flags for topic hz, you can match that profile explicitly:

bash
ros2 topic hz /camera/image_raw \
  --qos-reliability best_effort \
  --qos-durability volatile

Run ros2 topic hz --help on the target distribution if an option is rejected. A QoS mismatch can produce no samples, while a compatible but demanding profile can still change throughput or buffering behavior.

2. Check message size and host load

ros2 topic hz receives and processes the serialized messages delivered to its subscription. Large sensor_msgs/msg/Image and sensor_msgs/msg/PointCloud2 streams can make the CLI subscriber CPU-bound or add work to the publisher. If you also need a bandwidth estimate, run this as a separate diagnostic test:

bash
ros2 topic bw /camera/image_raw

ros2 topic bw also creates a receiving subscription, so do not run both observers at once when you are trying to measure their effect. If the reported rate drops only when monitoring starts, repeat the test with the camera resolution or publish rate reduced. That separates a measurement-path limit from a timer or sensor-driver problem.

3. Check whether the original path was intra-process

Composable nodes can pass messages inside one process without using the normal inter-process transport path. Adding an external subscriber can require serialization and delivery that the original pipeline did not need. In that case, the monitor changes the workload as soon as it joins the graph.

This is the strongest reason to choose ros2_pulse: it observes tracetools calls already made inside the process instead of subscribing to the topic.

4. Decide which rate you need

A publisher rate, a subscriber callback rate, and an end-to-end sensor rate are different measurements. A publisher may call publish() at 30 Hz while a subscriber processes 20 callbacks per second because of executor load, queueing, or dropped best-effort samples. Name the side you are measuring before treating two numbers as contradictory.

ros2_pulse vs ros2 topic hz and other ROS 2 monitoring tools

Tool Use it when What it measures Main limitation
ros2 topic hz You need a quick rate check for one topic Rate received by a new CLI subscription The observer joins the graph and may not match the publisher rate
ROS 2 topic statistics You control the subscriber and need message period or age Statistics collected by an enabled subscription and published as MetricsMessage It must be enabled in the receiving node and measures that subscription
ros2_pulse You need multi-topic, inter-process, or intra-process rates without extra DDS readers Publish and callback events inside preloaded processes Must be present when each process starts; Python receive callbacks are not covered in v0.4.1
ros2probe You need bandwidth, packet, or host-network visibility Kernel and wire-level ROS 2 traffic Different setup and permissions; not the right layer for every intra-process question
ros2_tracing You need callback, executor, scheduling, or causal timing ROS 2 tracepoints collected for deeper analysis More setup and post-processing than a live frequency check

Start with the smallest tool that can answer the question. A transport tool will not explain a callback that is queued inside an executor, and an in-process rate counter will not prove which DDS transport carried a sample.

Install ros2_pulse v0.4.1 from source

The following workflow targets Linux with ROS 2 Humble, Jazzy, or Kilted, which are CI-tested by the v0.4.1 project. Source the ROS environment first, then confirm that libtracetools contains the instrumentation symbols used by the probe:

bash
source /opt/ros/jazzy/setup.bash
nm -D "$(ros2 pkg prefix tracetools)"/lib/libtracetools.so* \
  | grep -c ros_trace

Continue when the command prints a number greater than 0. If the package cannot be found, source the correct ROS installation. If the result is 0, the current ROS build does not expose the instrumentation required by this method.

Build the pinned release in its own workspace:

bash
mkdir -p ~/ros2_pulse_ws/src
git clone --depth 1 --branch v0.4.1 \
  https://github.com/TanayK07/ros2_pulse.git \
  ~/ros2_pulse_ws/src/ros2_pulse

cd ~/ros2_pulse_ws
source /opt/ros/jazzy/setup.bash
rosdep install --from-paths src --ignore-src -y
colcon build --packages-select ros2_pulse
source install/setup.bash

Replace jazzy with humble or kilted when that is the distribution on the robot. Pinning the tag keeps the commands, output fields, and limitations in this guide aligned with one release.

Run a three-terminal smoke test

Test the probe with the standard C++ talker and listener before adding it to a large launch file. This makes preload and environment errors much easier to identify.

Terminal 1: monitor the publisher

bash
source /opt/ros/jazzy/setup.bash
source ~/ros2_pulse_ws/install/setup.bash
export LD_PRELOAD=libros2_pulse.so
export ROS_TOPIC_STATS_OUTPUT_FILE=/tmp/pulse-talker.log
export ROS_TOPIC_STATISTICS_PUBLISH_PERIOD=5.0
ros2 run demo_nodes_cpp talker

Terminal 2: monitor the subscriber

bash
source /opt/ros/jazzy/setup.bash
source ~/ros2_pulse_ws/install/setup.bash
export LD_PRELOAD=libros2_pulse.so
export ROS_TOPIC_STATS_OUTPUT_FILE=/tmp/pulse-listener.log
export ROS_TOPIC_STATISTICS_PUBLISH_PERIOD=5.0
ros2 run demo_nodes_cpp listener

Terminal 3: watch both logs

bash
tail -F /tmp/pulse-talker.log /tmp/pulse-listener.log

Wait for two complete five-second windows. The talker log should update with TOPIC /chatter, and the listener log should update with RECV /chatter. You should also see recently active node names in NODE lines.

How to read the ros2_pulse output

Output What it tells you What to check next
TOPIC /name rate Inter-process publish-side rate seen in this process Compare with the expected publisher loop or timer rate
PUB /name inter=... intra=... Publish-side inter/intra buckets on Iron and newer On Humble, use the receive-side intra value instead
RECV /name inter=... intra=... C++ callback deliveries in this process A low receive rate with a normal publish rate points downstream
NODE /name A node associated with recent measured topic traffic Use a supervisor or heartbeat for an idle, service-only node
JITTER ... max_dt_ms=... Largest observed gap when gap tracking is enabled Compare the gap with the control or perception deadline
WARN ... A configured rate, gap, or node rule failed Inspect the named topic or node; the warning is a symptom, not the root cause

A missing value is not automatically zero. For example, v0.4.1 counts Python publishers at the C layer, but Python subscriber callbacks do not appear in RECV. On Humble, publish-side intra-process data is also unavailable, while receive-side intra-process delivery is visible.

View many topics with pulse-top

Raw logs are convenient for scripts, but a live stack is easier to scan in a terminal dashboard. The separate ros2-pulse-top package reads JSON Lines produced by the probe; it does not create another ROS 2 node or subscription.

Install the dashboard and test its built-in demo:

bash
pipx install ros2-pulse-top
pulse-top --demo

For a real stack, enable JSON Lines before launching the probed processes:

bash
export ROS_TOPIC_STATS_FORMAT=jsonl
export LD_PRELOAD=libros2_pulse.so
ros2 launch my_robot bringup.launch.py

Then run pulse-top to read the default per-process logs, or pass an explicit log path. The dashboard groups topics and nodes, shows recent rates and gaps, and highlights configured warnings without adding traffic to the ROS graph.

Add rate and stall alerts

An average rate can hide a short but important freeze. A 50 Hz stream that stops for 400 ms may still have an acceptable average over a long window. Use both a rate range and a maximum gap when the application has a timing limit.

Create /tmp/pulse-expected.yaml:

yaml
topics:
  /scan: {min_hz: 18, max_hz: 22, max_gap_ms: 100, side: recv}
nodes: [/perception]

Point the listener or launch file at the rule set:

bash
export ROS_TOPIC_STATS_EXPECTED=/tmp/pulse-expected.yaml
export ROS_TOPIC_STATS_OUTPUT_FILE=/tmp/pulse.log
export LD_PRELOAD=libros2_pulse.so
ros2 launch my_robot bringup.launch.py

Healthy windows contain the normal topic and node lines without WARN. Tune the limits from a stable baseline; startup, sensor warm-up, and mode changes can legitimately produce different rates.

For CI or a watchdog, pulse-check returns 0 for pass, 1 for a rule violation, and 2 when the input is invalid or insufficient. Use --skip-last on a completed log so the short final flush is not treated as a normal measurement window.

Troubleshooting ros2_pulse

Symptom Likely cause Fix
ERROR: ld.so: object 'libros2_pulse.so' ... cannot be preloaded The workspace was not sourced or the loader cannot find the library Source ~/ros2_pulse_ws/install/setup.bash or set LD_PRELOAD to the absolute library path
The node runs but no probe log appears The preload was ignored, the output path is not writable, or the reporting window has not completed Check startup stderr, use a writable /tmp path, and wait for two full windows
TOPIC appears but RECV does not You are inspecting only the publisher process, the subscriber is Python, or callbacks have not run Preload the subscriber process too; remember that v0.4.1 does not report rclpy receive callbacks
Intra-process publish rate is missing on Humble Humble lacks the publish-side intra tracepoint used by newer distributions Read the RECV ... intra=... value from the receiving C++ process
A live node disappears from NODE The node has not published or received measured topic traffic recently Use an application heartbeat or process supervisor for idle service and timer nodes
ros2 topic hz is lower than ros2_pulse The CLI subscription has different QoS, cannot keep up, or changed an intra-process path Inspect endpoint QoS, host load, message size, and whether the pipeline uses composition
Rate looks healthy but the robot still reacts late Frequency does not reveal queueing or callback/executor latency Record a trace with ros2_tracing and inspect callback and scheduling timing

How much overhead does ros2_pulse add?

The project publishes benchmark results, but they are test-case results rather than a universal deployment budget. In the author's v0.4.1 benchmark, ros2 topic hz consumed about 7% of one CPU core while observing a 50 Hz, 100 KB topic. Adding that observer to the benchmark's intra-process path increased the watched process's CPU because the path began serializing for the new subscriber.

The same benchmark reports roughly 2% workload CPU for ros2_pulse in a deliberately heavy test with about 4,900 messages per second across 53 topics. Message sizes, rate, CPU, RMW implementation, composition, security, and logging can all change the result. If monitoring overhead matters to your robot, measure CPU, latency, and missed deadlines with the probe disabled and enabled on the complete application.

Frequently asked questions

Does ros2 topic hz create a subscriber?

Yes. The command creates a ROS 2 subscription and calculates frequency from the messages that subscription receives. Its output can therefore differ from the publisher's call rate.

Why does ros2 topic hz show no output?

First confirm the topic name and type with ros2 topic list -t, then inspect endpoints with ros2 topic info /topic_name --verbose. No output usually points to discovery, ROS domain, QoS compatibility, or a publisher that is not currently sending samples.

Why is ros2 topic hz lower than the camera frame rate?

The CLI reports what its own subscription receives. Large images, CPU pressure, QoS, network limits, serialization, or an observer that changes an intra-process path can all reduce that rate. Compare the publisher-side counter and the subscriber callback rate before changing the camera settings.

Can ros2_pulse attach to a process that is already running?

No. LD_PRELOAD is applied when the process starts, so restart the target process or launch file with the probe configured.

Can ros2_pulse monitor intra-process ROS 2 topics?

Yes for the supported C++ paths. On Humble, inspect receive-side intra-process rates. On newer supported tracepoint layouts, publish-side intra data can also appear.

Does ros2_pulse work with Python ROS 2 nodes?

Python publishers are counted because publishing reaches the C layer. Python subscription callbacks are not reported in RECV by v0.4.1, so a missing Python receive rate is an unsupported measurement rather than proof of no traffic.

Is ROS 2 topic statistics the same as ros2 topic hz?

No. ros2 topic hz adds a temporary CLI subscription. Built-in topic statistics are enabled on a subscription you control and publish metrics such as received message period and message age. Choose built-in statistics when those receiver-side metrics belong in the application itself.

Does a stable topic rate prove that the ROS 2 pipeline is healthy?

No. A stable rate does not prove message correctness, low end-to-end latency, absence of drops, or a particular DDS transport. If the frequency is correct but callbacks are late, use ros2_tracing. If you need to confirm UDP, shared memory, or Fast DDS Data Sharing, use the ROS 2 Fast DDS transport verification guide.

Which tool should you use?

Use ros2 topic hz when you want the quickest answer and one temporary subscriber is acceptable. Use ros2_pulse when you need a multi-topic view, intra-process visibility, or a rate measurement that does not add DDS readers to the observed topics. Use built-in topic statistics when you own the subscription and want message-age and period metrics in the application.

When the symptom moves beyond frequency, change tools instead of stretching one measurement too far: ros2probe for host or wire traffic, and ros2_tracing for callback and executor timing.

Leave a comment

Your email address will not be published. Required fields are marked *

Sidebar

Blog Categories
Latest post

This section doesn’t currently include any content. Add content to this section using the sidebar.

Register for our newsletter

Get the latest information about our products and special offers.

Website Feedback

Help us improve OpenELAB

Found a website issue or have an idea? Tell us what would make your experience better.