I run a ROS 2 planner stack on a Jetson AGX Orin and kept hitting the same wall: I want to know whether every topic is flowing at the rate it should and which nodes are alive, and the stock tools could not tell me without changing the system under test.
So I measured what watching a topic actually costs, and then built a probe that avoids it. Both are open source (Apache-2.0) and I would like your feedback.

pulse-top --demo: the probe’s log, live. Sparklines per topic, intra-process rates, structured warnings with ages. There is also a 40-second video of the whole story.
What watching a topic costs today
We benchmarked the stock CLI against a 50 Hz publisher of 100 KB messages, with an in-process probe as the ruler (method and raw data):
ros2 topic hzon one topic: 7 % of a core.ros2 topic echoon the same topic: 31 % of a core.- Pointing
hzat an intra-process topic is worse than expensive: the publisher starts serializing every message for the new external subscriber, which raised the watched process’s CPU by 52 % in our runs. The act of looking changes the thing you are looking at.
And on Humble-class binaries, intra-process traffic (composable nodes carrying point clouds, for example) is invisible to hz, echo and the built-in topic statistics entirely (rclcpp#2911; fixed on rolling by rclcpp#3130, no Humble backport).
What ros2_pulse does instead
libros2_pulse.so is an LD_PRELOAD shim over the tracetools instrumentation layer that rclcpp already calls on every publish and every callback. It counts in-process with a lock-free hot path and appends per-window rates to a small rolling file. It needs no rebuild of your nodes, no root and no tracing session, and it adds no DDS traffic.
export LD_PRELOAD=libros2_pulse.so
ros2 launch your_stack your.launch.py
tail -f /tmp/topic_freq.<pid>.log
# ts_ns=1782887153899445923 window_s=5.000
TOPIC /scan 20.000000
RECV /points inter=0.000000 intra=30.000000 # intra-process, invisible to other tools on Humble
NODE /perception
WARN TOPIC /scan hz=1.200000 expected=[18,22]
You can declare expected rates per topic and get WARN lines when a window violates them, opt into per-endpoint jitter (largest inter-arrival gap), and switch the output to JSON Lines for a Prometheus/OTel sidecar or a log shipper.
The cost, measured: the counting hot path is about 0.3 ns per message on a fixed endpoint (about two orders of magnitude under a single LTTng-UST tracepoint), and the whole probe is about 2 % of workload CPU on a deliberately harsh 4,900 msg/s stress, less on real graphs. On the Orin itself, the optional jitter clock read adds 51 ns per message. Full methodology, paired trials with error bars, in the benchmarks.
pulse-top
A terminal dashboard that tails the probe’s logs across processes: sparklines per topic, intra-process rates, warnings with ages, stall detection.
pip3 install ros2-pulse-top
pulse-top --demo # try it without a robot
Install
The probe builds as a normal colcon package on Humble, Jazzy and Kilted (CI runs all three on stock ros:<distro> images):
cd ~/ros2_ws/src && git clone https://github.com/TanayK07/ros2_pulse.git
cd ~/ros2_ws && colcon build --packages-select ros2_pulse && source install/setup.bash
apt packages are on the way: rosdistro PRs for all three distros are open (humble, jazzy, kilted), so apt install ros-<distro>-ros2-pulse should work after the next sync.
Related work, and where this sits: CARET (Tier IV) hooks the same tracetools layer, which was a useful independent validation of the mechanism, but targets deep offline latency analysis with LTTng and a forked rclcpp. ros2_tracing is also offline-first. This probe is the always-on, read-it-now complement, not a replacement for either.
- Repo: GitHub - TanayK07/ros2_pulse: Near-zero-overhead ROS 2 topic/node frequency probe (inter + intra process) via an LD_PRELOAD tracetools shim. · GitHub
- Docs: ros2_pulse
- Benchmarks (x86 and Jetson AGX Orin): Benchmarks - ros2_pulse
It has been running against a 77-node production planner stack on an Orin. I would love to hear what breaks on your graphs, and whether the expected-rate spec covers what you would actually alert on.