跳转至

50_CLI调试与性能工具

ROS2 CLI、调试与性能分析工具链

难度:⭐~⭐⭐⭐ | 建议用时:1 周 | 前置要求:设计哲学与架构演进(ROS2 架构与 QoS)、SLAM导航与仿真生态(SLAM 与 Nav2 基础)、基础 Linux 命令行

教学目标:掌握ROS2日常开发中的完整调试工具箱——从命令行内省、QoS诊断、TF调试、bag录制回放,到LTTng性能分析和GDB远程调试。这是SLAM工程师每天都要用的工具参考。


前置自测

📋 答不出 ≥ 2 题 → 先回前置章节复习

  1. [设计哲学与架构演进] QoS 的 Reliability 不匹配时会发生什么?有错误消息吗?(提示:沉默失败,不报错不传数据)
  2. [SLAM导航与仿真生态] REP-105 的标准 TF 树中,map→odom 变换由谁发布?(提示:SLAM 节点或 AMCL)
  3. [Linux 基础] gdb 和 valgrind 分别用于解决什么类型的问题?(提示:GDB 用于断点/崩溃调试,Valgrind 用于内存泄漏/性能分析)
  4. [设计哲学与架构演进] use_sim_time 在 ROS2 中是全局参数还是按节点设置?(提示:按节点设置,不像 ROS1 的全局参数)
  5. [SLAM导航与仿真生态] rosbag2 的默认存储格式是什么?MCAP 相比 SQLite3 有什么优势?(提示:MCAP 是默认格式,优势在自包含、仅追加写入和压缩支持)

本章知识地图

本章的所有工具和方法都围绕一个核心问题:当 ROS2 系统不按预期工作时,如何系统化地找到原因?

这个问题可以分解为八个子问题,每个对应本章的一个核心工具或方法:

系统不工作
  ├── Q1: 节点和话题是否存在?
  │     → ros2 node list / ros2 topic list -t
  │
  ├── Q2: QoS 是否兼容?
  │     → ros2 topic info --verbose(最重要的单一命令)
  │
  ├── Q3: 数据是否按预期流动?
  │     → ros2 topic hz / bw / echo
  │
  ├── Q4: TF 链是否完整且时间一致?
  │     → view_frames / tf2_echo / tf2_monitor
  │
  ├── Q5: 节点是否处于正确的生命周期状态?
  │     → ros2 lifecycle get / ros2 control list_controllers
  │
  ├── Q6: 问题能否离线复现?
  │     → ros2 bag record / play(带 QoS 覆盖和 --clock)
  │
  ├── Q7: 延迟尖峰来自哪个回调?
  │     → ros2 trace / babeltrace / tracetools_analysis
  │
  └── Q8: 代码级错误在哪一行?
        → GDB / ASan / TSan / Valgrind

从 Q1 到 Q8,诊断粒度逐步细化,工具复杂度逐步增加。大多数问题在 Q1-Q4 就能解决——只有性能问题和崩溃才需要深入到 Q7-Q8。

带着这个地图阅读本章,你会更清楚每个工具在整体调试流程中的位置。不需要记住所有命令——只需要记住"当我怀疑问题在 QoS 层时用 Q2 的工具"。

本章目标

学完本章后,你应该能够:

  1. 构建 一条从症状到根因的证据链,使用 CLI 工具逐层排除假设
  2. 诊断 QoS 不匹配、TF 断连、生命周期状态错误等最常见的 ROS2 问题
  3. 录制和回放 SLAM 数据集,正确处理 QoS 覆盖和仿真时间
  4. 使用 ros2 trace 和 LTTng 定位回调级延迟瓶颈
  5. 配合 GDB 和 AddressSanitizer 调试 C++ 节点的崩溃和内存问题

0. 调试工具不是命令背诵,而是证据链构建 ⭐

ROS2 系统的调试难点在于:一个症状往往跨越多个层级。比如“机器人不动”可能来自 /cmd_vel 没发布、QoS 不兼容、controller 未激活、tf 缺失、生命周期状态错误、底盘驱动拒绝命令或仿真时间没有推进。只背命令会让调试变成随机尝试;真正有效的做法是建立证据链。

一个推荐的排查顺序是:

节点是否存在
  ↓
话题/服务/action 接口是否存在
  ↓
QoS 是否兼容
  ↓
消息是否按预期频率流动
  ↓
消息内容是否合理
  ↓
tf 与时间是否一致
  ↓
生命周期状态是否正确
  ↓
性能瓶颈在哪里

这条链的好处是每一步都能用工具验证,而不是依赖直觉猜测。

排查层级 主要命令 典型判断
图结构 ros2 node list、ros2 topic list -t 节点和接口是否存在
通信语义 ros2 topic info --verbose QoS 是否匹配
数据流 ros2 topic hz/bw/echo 频率、带宽、内容是否合理
坐标 tf2_tools、tf2_echo 变换是否存在、时间是否一致
状态 ros2 lifecycle、ros2 param 节点是否激活、参数是否正确
记录回放 ros2 bag 能否复现实验
性能 ros2 trace、LTTng、perf 延迟和抖动来自哪里

本章的每个命令都应放在这条证据链中理解。命令不是目的,排除假设才是目的。

0.1 从症状到证据链:五步闭环 ⭐

调试时最容易犯的错误是“看到一个症状,立刻改一个参数”。这种做法偶尔能碰巧解决问题,但无法沉淀经验,也无法解释为什么问题消失。更稳妥的流程是把调试写成五步闭环:

症状
  ↓
假设
  ↓
证据采集
  ↓
判断
  ↓
最小改动
步骤 要回答的问题 例子
症状 用户能观察到什么异常 机器人收到导航目标但不动
假设 哪一层最可能断开 /cmd_vel 没发布或控制器未激活
证据采集 用什么命令验证 ros2 topic hz /cmd_vel、ros2 lifecycle get
判断 证据支持还是排除假设 /cmd_vel 有数据但底盘控制器 inactive
最小改动 只改一处并重新验证 激活控制器,再测 /odom 是否变化

这个流程像医生问诊:不能因为病人说“头晕”就直接开药,而是要量血压、问病史、看化验结果。ROS2 调试也是一样,“机器人不动”只是症状,不是诊断结论。

以“导航目标已发送,但底盘不动”为例,证据链可以这样组织:

# 1. 确认导航 action 是否接受目标
ros2 action info /navigate_to_pose

# 2. 确认速度命令是否产生
ros2 topic hz /cmd_vel
ros2 topic echo /cmd_vel --once

# 3. 确认底盘控制器状态
ros2 control list_controllers
ros2 lifecycle get /controller_server

# 4. 确认里程计是否响应
ros2 topic hz /odom
ros2 topic echo /odom --once

# 5. 确认 TF 链是否连通
ros2 run tf2_ros tf2_echo odom base_link
证据 结论
action 未接受目标 问题在导航接口或目标格式
action 接受但 /cmd_vel 为 0 问题在局部规划器或代价地图
/cmd_vel 非零但控制器 inactive 问题在生命周期或控制器加载
控制器 active 但 /odom 不变 问题在硬件接口、仿真插件或底盘驱动
/odom 变化但 TF 缺失 问题在 TF 发布或 frame 名称

如果不做证据链,容易出现“同时改 QoS、参数、启动文件和代码”的情况。问题可能消失,但你无法知道哪一项真正起作用;下次换一台机器人或换一个网络环境,问题还会回来。

调试记录建议只写三类内容:

  1. 观察到的症状。
  2. 执行过的命令和关键输出。
  3. 每一步排除或确认了哪一个假设。

练习:

  1. 为“RViz 能看到激光但 SLAM 不建图”写一条不少于 6 步的证据链。
  2. 为“ros2 topic list 能看到话题但 echo 没输出”列出 3 个互斥假设。
  3. 解释为什么“重启后好了”不是一个完整诊断结论。

核心知识点:

从排查流程出发理解 CLI 工具 ⭐⭐

这一节解决什么问题:ROS2 CLI 工具不是"需要背诵的命令列表",而是"排查问题时的证据采集工具"。本节按照排查流程组织工具介绍:先检查图结构(节点和话题是否存在),再检查通信语义(QoS 是否匹配),再检查数据流(消息是否正常流动),最后检查时间和坐标一致性。每个命令都应放在"我要排除哪个假设"的上下文中理解。

当 SLAM 系统出问题时——比如"机器人在 RViz 中不动"或"地图出现双层墙"——直觉反应通常是检查算法参数或代码逻辑。但经验表明,大多数问题(估计 70% 以上)出在基础设施层面:QoS 不匹配导致数据不到达、TF 链断裂导致坐标变换失败、use_sim_time 不一致导致时间戳错位、bag 录制时遗漏了关键话题。CLI 工具就是专门为排查这些基础设施问题设计的。

把 CLI 工具理解为"证据采集工具"而非"命令手册"有一个实际好处:你不需要记住所有命令的所有参数。你只需要知道"当我怀疑 QoS 不匹配时,用什么命令采集证据"(答案是 ros2 topic info --verbose)。排查假设驱动工具选择,而不是反过来。

ROS2 的 CLI 架构位于 ros2/ros2cli 仓库(https://github.com/ros2/ros2cli)。每个命令都遵循 ros2 <verb> <subverb> [args] 的命名模式。所有命令、主题名称和消息类型均支持 Tab 键补全——请务必充分利用这一功能。

节点与主题的内省:确认图结构是否完整 ⭐

# 列出当前活跃节点,默认不显示隐藏节点
ros2 node list
ros2 node list --include-hidden-nodes

# 查看节点的发布、订阅、服务和 action 接口
ros2 node info /my_slam_node

# 列出话题并显示消息类型
ros2 topic list -t

# QoS 调试的关键命令:显示每个端点的 QoS 配置
ros2 topic info /scan --verbose

ros2 topic info 上的 --verbose 标志是解决 QoS 不匹配问题的 最重要的调试命令。它会显示每个发布者和订阅者的可靠性、持久性、历史记录深度以及活跃度设置。当 ros2 topic echo 显示无结果时,此命令会告诉你原因。

带 QoS 覆盖的主题回显:为什么 echo 什么也没有 ⭐⭐

QoS 不匹配是 ROS2 中最常见也最令人困惑的问题。它的危险性在于完全沉默——不报错、不警告、不建立连接、不传输数据。一个初学者看到 ros2 topic echo /scan 没有输出,第一反应通常是"传感器坏了"或"驱动没启动"。实际上,在大多数情况下传感器和驱动都正常运行,只是 CLI 的默认 QoS 和驱动的 QoS 不兼容。

为什么会不兼容?因为 ROS2 的 QoS 系统引入了"请求-提供"模型。发布方"提供"一种可靠性级别(比如 BestEffort——不保证送达但延迟低),订阅方"请求"一种可靠性级别(比如 Reliable——保证送达但可能有延迟)。当订阅方请求的可靠性高于发布方能提供的可靠性时,DDS 认为这是不兼容的——因为发布方无法满足订阅方的要求。这种不兼容不是错误,而是 DDS 的设计决策:宁可不连接,也不给出无法满足的承诺。

大多数传感器驱动程序以 BEST_EFFORT 可靠性进行发布(低延迟对传感器更重要)。CLI 默认使用 RELIABLE,因此除非您进行覆盖,否则 ros2 topic echo /scan 将静默地什么也收不到:

# 传感器话题常用:LiDAR、相机、IMU 往往是 best effort
ros2 topic echo /scan --qos-reliability best_effort

# 类似 ROS1 latch 的话题:地图或机器人描述需要 transient local
ros2 topic echo /map --qos-durability transient_local

# 使用预定义 QoS 配置
ros2 topic echo /scan --qos-profile sensor_data

# 输出 CSV,便于快速画图分析
ros2 topic echo /imu --csv > imu_data.csv

# 隐藏大数组,适合 PointCloud2 或 Image
ros2 topic echo /camera/image --no-arr

QoS 案例:激光雷达存在但 echo 没有输出 ⭐⭐

这是一个几乎每个 ROS2 初学者都会遇到的问题,值得详细讲解排查过程。它完美地展示了"证据链思维"的价值——按顺序采集证据、逐步排除假设,比"随便试试"高效得多。

症状:ros2 topic list -t 能看到 /scan [sensor_msgs/msg/LaserScan],RViz 或驱动日志也显示雷达在运行,但 ros2 topic echo /scan 一直没有输出。

面对这个症状,初学者通常会走三条弯路:检查硬件连接(浪费 30 分钟)、重装驱动包(浪费 1 小时)、或修改驱动代码增加调试输出(引入新的不确定性)。正确的第一反应不应是修改驱动代码,而是先确认 QoS 是否兼容。传感器驱动为了低延迟常用 best_effort,CLI 默认订阅方式可能请求 reliable,两端无法匹配时不会传输数据。

# 查看端点数量和类型
ros2 topic info /scan

# 查看每个发布者/订阅者的 QoS
ros2 topic info /scan --verbose

# 用传感器 QoS 重新订阅
ros2 topic echo /scan --qos-profile sensor_data

# 或显式指定可靠性
ros2 topic echo /scan --qos-reliability best_effort

证据链判断:

观察结果 结论 下一步
publisher 数量为 0 驱动未发布或命名空间不一致 检查启动文件和重映射
publisher 存在,reliability 为 best effort CLI QoS 不匹配 使用 sensor_data profile
echo 有输出但频率低 可能是驱动频率或 CPU 问题 ros2 topic hz /scan
echo 正常但算法收不到 算法订阅 QoS 不匹配 查算法节点端点 QoS

QoS 调试要特别注意“方向性”。发布方提供能力,订阅方提出请求。订阅方请求 reliable,发布方只提供 best_effort 时无法匹配;订阅方请求 best_effort,发布方提供 reliable 时通常可以匹配。理解这个方向,比记住命令更重要。

练习:

  1. 用 ros2 topic info --verbose 找出一个传感器话题的可靠性、持久性和 history depth。
  2. 解释为什么 /tf_static 通常需要 transient_local,而 /scan 通常不需要。
  3. 构造一个 QoS 不匹配案例,并用 CLI 证明修复前后端点连接变化。

发布、服务、操作和参数:从 CLI 直接操纵系统 ⭐

CLI 不只是"读取"工具——它也可以"写入"。ros2 topic pub 可以从命令行直接发布消息,ros2 service call 可以调用服务,ros2 param set 可以修改节点参数。这些写入能力在调试中非常有价值:当你怀疑"控制器收到命令后是否正常响应"时,可以用 topic pub 发送一个已知的速度命令,观察机器人是否按预期运动——这比通过完整的导航栈发送目标更直接、更可控,能有效隔离问题是在上层规划还是在下层执行。

参数管理是 ROS2 和 ROS1 最大的架构差异之一。ROS1 有全局参数服务器——任何节点都可以读写任何参数,参数没有归属。ROS2 改为节点级参数——每个参数属于特定节点,修改参数需要指定节点名。这个变化让参数的所有权更清晰,但也意味着 ros2 param 命令必须指定节点:ros2 param set /slam_toolbox max_range 80.0,而不是 rosparam set max_range 80.0。

# 以 10 Hz 发布速度命令
ros2 topic pub --rate 10 /cmd_vel geometry_msgs/msg/Twist \
  "linear: {x: 1.0}, angular: {z: 0.5}"

# 查看任意消息类型的 YAML 模板
ros2 interface proto geometry_msgs/msg/Twist

# 调用服务
ros2 service call /spawn turtlesim/srv/Spawn \
  "{x: 5.0, y: 5.0, theta: 0.0, name: 'turtle2'}"

# 发送 action 目标,并显示反馈
ros2 action send_goal --feedback /navigate_to_pose \
  nav2_msgs/action/NavigateToPose "{pose: {header: {frame_id: 'map'}, pose: {position: {x: 1.0, y: 2.0}}}}"

# ROS2 参数属于节点,不再是全局参数服务器
ros2 param dump /slam_toolbox > slam_params.yaml
ros2 param set /controller_server max_vel_x 0.5
ros2 param load /my_node params.yaml

# 使用参数文件、重映射和参数覆盖启动节点
ros2 run my_pkg my_node --ros-args \
  --params-file config.yaml \
  -r /cmd_vel:=/robot1/cmd_vel \
  -p use_sim_time:=true

系统诊断和生命周期管理 ⭐⭐

系统级诊断工具帮助你在"不知道问题在哪一层"时快速获取全局画面。ros2 doctor 相当于给 ROS2 系统做一次全面体检——检查 RMW 实现、DDS 配置、网络多播状态、节点图完整性和 QoS 兼容性。当你刚拿到一台新机器人、或在新网络环境下部署、或两台机器人之间看不到对方的话题时,ros2 doctor --report 是最好的起点。

生命周期管理在调试中也很关键。回顾 硬件集成与RL部署:ROS2 的 Lifecycle 节点可以处于 Unconfigured、Inactive、Active 等状态。一个常见的困惑是"节点存在但不工作"——ros2 node list 能看到节点,话题也在发布,但控制器不响应命令。原因可能是节点处于 Inactive 状态——它已配置但未激活,不会处理输入。ros2 lifecycle get 可以确认这一点。

# 系统健康检查
ros2 doctor --report --include-warnings

# 生命周期节点管理
ros2 lifecycle nodes
ros2 lifecycle get /amcl
ros2 lifecycle set /amcl activate

# 组件容器管理
ros2 component types
ros2 component load /ComponentManager nav2_controller nav2_controller::ControllerServer
ros2 component list /ComponentManager

# 网络多播诊断:两个终端分别执行
ros2 multicast receive    # Terminal 1
ros2 multicast send       # Terminal 2

# CLI 状态异常时重启 daemon
ros2 daemon stop && ros2 daemon start

ros2 CLI 完整命令族系统化讲解 ⭐⭐

ROS2 的 CLI 工具组织成一致的 ros2 <verb> <subverb> 模式。理解命令族的结构比记住每个命令的参数更重要——因为你可以通过 ros2 <verb> --help 随时查看参数,但你需要知道"当我想做 X 时应该用哪个 verb"。

七个核心命令族:

命令族 用途 最常用子命令 对应排查层级
ros2 node 节点内省 list, info 图结构
ros2 topic 话题操作 list, info, echo, hz, bw, pub, delay 数据流
ros2 service 服务操作 list, call, type, find 请求-响应
ros2 action 动作操作 list, info, send_goal 长时间任务
ros2 param 参数管理 list, get, set, dump, load 节点配置
ros2 bag 数据录制回放 record, play, info 数据集管理
ros2 doctor 系统诊断 --report 全局健康检查

进阶命令族:

命令族 用途 典型场景
ros2 lifecycle 生命周期管理 确认节点状态、手动激活/停用
ros2 component 组件管理 动态加载/卸载 Composition 节点
ros2 interface 消息类型查询 查看消息/服务/动作的字段定义
ros2 multicast 网络多播测试 排查 DDS 发现问题
ros2 security 安全管理 SROS2 密钥和权限
ros2 trace 性能追踪 LTTng 回调级延迟分析
ros2 control ros2_control 管理 控制器切换、硬件接口查询

ros2 topic 完整子命令详解:

ros2 topic 是使用频率最高的命令族。它的子命令覆盖了话题层面的完整诊断需求:

# 列出话题(加 -t 显示类型)
ros2 topic list -t

# 查看话题详情(加 --verbose 显示 QoS)
ros2 topic info /scan --verbose

# 查看消息内容
ros2 topic echo /scan                    # 默认 QoS
ros2 topic echo /scan --qos-profile sensor_data  # 传感器 QoS
ros2 topic echo /scan --once             # 只看一条
ros2 topic echo /scan --no-arr           # 隐藏数组(适合大消息)
ros2 topic echo /scan --csv              # CSV 格式输出

# 频率和带宽
ros2 topic hz /scan                      # 发布频率
ros2 topic hz /scan --window 100         # 增大统计窗口
ros2 topic bw /scan                      # 带宽(MB/s)
ros2 topic delay /scan                   # 端到端延迟(需要 Header)

# 发布消息
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.5}, angular: {z: 0.3}}"
ros2 topic pub --once /cmd_vel ...       # 只发一条
ros2 topic pub --rate 10 /cmd_vel ...    # 以 10 Hz 发布

# 查看消息类型定义
ros2 topic type /scan                    # 返回消息类型名
ros2 interface show sensor_msgs/msg/LaserScan  # 显示字段定义
ros2 interface proto sensor_msgs/msg/LaserScan # 显示 YAML 模板

ros2 param 详解:

参数是 ROS2 中最常需要动态调整的配置。与 ROS1 的全局参数服务器不同,ROS2 的参数属于节点——必须指定节点名才能操作参数。

# 列出节点的所有参数
ros2 param list /slam_toolbox

# 获取参数值
ros2 param get /slam_toolbox resolution

# 设置参数(实时生效,如果节点支持动态参数)
ros2 param set /controller_server max_vel_x 0.3

# 导出参数到文件
ros2 param dump /slam_toolbox > slam_params.yaml

# 从文件加载参数
ros2 param load /slam_toolbox slam_params.yaml

# 查看参数描述(如果节点提供了描述)
ros2 param describe /slam_toolbox resolution

ros2 doctor 详解:

ros2 doctor 是 ROS2 的全系统健康检查工具。它检查 RMW 实现、DDS 配置、网络多播状态、节点图完整性和 QoS 兼容性。当你"不知道问题在哪一层"时,先跑一次 ros2 doctor。

# 基本健康检查
ros2 doctor

# 详细报告(包含 RMW、网络、QoS 分析)
ros2 doctor --report

# 包含警告级别的问题
ros2 doctor --report --include-warnings

# 只查看特定部分
ros2 doctor --report | grep -A5 "RMW"
ros2 doctor --report | grep -A5 "NETWORK"

报告中最有价值的部分是 NETWORK 和 QOS——前者告诉你多播是否可用(很多"节点看不到"的问题来自多播被阻断),后者告诉你是否存在 QoS 不兼容。

DDS 性能诊断 ⭐⭐⭐

当 CLI 基础命令(topic hz/bw/delay)已经确认了"话题频率不稳定"或"延迟偶发变大",但原因不在应用层时,需要深入 DDS 层面诊断。

Fast DDS 诊断工具:

# Fast DDS Discovery Server 状态
fastdds discovery --server-id 0 --ip-address 192.168.1.1 --port 11811

# 查看 DDS 参与者信息(需要 SUPER_CLIENT 配置)
ros2 node list  # 使用 SUPER_CLIENT 配置的 Fast DDS

# Fast DDS 共享内存统计
ls /dev/shm/fastrtps_*  # 查看 SHM 段

CycloneDDS 诊断:

# CycloneDDS 配置验证
export CYCLONEDDS_URI=file:///path/to/cyclonedds.xml
ros2 run demo_nodes_cpp talker  # 启动后查看日志中的 DDS 配置确认

# 内核网络缓冲区检查
sysctl net.core.rmem_max    # 接收缓冲区上限
sysctl net.core.wmem_max    # 发送缓冲区上限
# 大消息(点云/图像)需要增大缓冲区
sudo sysctl -w net.core.rmem_max=4194304
sudo sysctl -w net.core.wmem_max=4194304

DDS 性能问题的常见模式:

症状 DDS 层面可能原因 诊断方法
大消息延迟高 UDP 分片 + 内核缓冲区不足 增大 rmem_max/wmem_max
WiFi 下话题间歇消失 多播不可靠 切换到 Discovery Server 或 Zenoh
启动慢(>10s 才发现节点) 发现协议被网络设备拦截 ros2 multicast send/receive
频率偶发下降 DDS 背压(Reliable 模式重传) 检查 QoS 是否应该用 BestEffort
同一话题数据到达顺序乱 History depth 太大 + 多线程 减小 depth 或用 SingleThreadedExecutor

rosbag2 高级用法 ⭐⭐

基础的 ros2 bag record 和 ros2 bag play 在前面已经覆盖。本节介绍高级用法——MCAP 格式的优化、录制过滤、回放同步和离线分析。

MCAP 格式深入:

MCAP(https://mcap.dev/)是 ROS2 Iron 以来的默认 bag 格式。相比 SQLite3,它有三个关键优势:

  1. 自包含:消息定义嵌入文件内——不需要额外的 ROS 工作空间就能解析消息。这意味着你可以把 .mcap 文件发给任何人,他们不需要安装你的自定义消息包就能查看。

  2. 仅追加写入:MCAP 使用日志结构存储——数据只会追加到文件末尾,不会修改已写入的数据。如果录制过程中程序崩溃,已录制的数据不会损坏。SQLite3 在崩溃时可能损坏数据库。

  3. 压缩:MCAP 支持 Zstd 和 LZ4 压缩。Zstd 压缩率通常 20-50%(即文件大小减少 50-80%),对传感器数据效果尤为明显。

# 使用 MCAP 格式和 Zstd 压缩录制
ros2 bag record -s mcap --storage-preset-profile zstd_fast \
  -o slam_dataset /scan /imu /odom /tf /tf_static

# 录制所有话题,排除噪音话题
ros2 bag record -a -x "/rosout|/parameter_events" -s mcap

# 快照模式:循环缓冲区,按需保存
ros2 bag record -a --snapshot-mode --max-cache-size 50000000
# 触发快照保存
ros2 service call /rosbag2_recorder/snapshot rosbag2_interfaces/srv/Snapshot

# 文件分片:按大小(100 MB)或时间(300 秒)分割
ros2 bag record -a -b 104857600 -d 300

# 录制时跳过前 10 秒(等待系统稳定)
ros2 bag record -a --start-paused
# 手动按空格开始录制

# MCAP CLI 工具
mcap info recording.mcap              # 详细信息
mcap cat recording.mcap --topics /scan | head  # 查看内容
mcap filter input.mcap -o filtered.mcap --include-topics /scan,/tf  # 过滤
mcap merge a.mcap b.mcap -o merged.mcap  # 合并

回放高级技巧:

# 从特定时间开始回放
ros2 bag play bag/ --start-offset 30.0

# 指定回放速率
ros2 bag play bag/ --rate 0.5          # 半速(调试用)
ros2 bag play bag/ --rate 2.0          # 倍速

# 增大读取缓冲区(高吞吐话题)
ros2 bag play bag/ --read-ahead-queue-size 2000

# 选择性回放话题
ros2 bag play bag/ --topics /scan /imu /tf /tf_static

# 话题重映射
ros2 bag play bag/ --remap /velodyne_points:=/points

# 循环回放
ros2 bag play bag/ --loop

# 回放时的键盘控制
# 空格:暂停/继续
# 左/右箭头:调整速率(±10%)
# 暂停时右箭头:逐条消息步进

ROS1 bag 转换:

# 安装 rosbags(纯 Python,不需要 ROS)
pip install rosbags

# ROS1 bag → ROS2 MCAP
rosbags-convert ros1_data.bag

# 也可用 ros2 bag convert 做格式转换
ros2 bag convert -i input.db3 -o convert_config.yaml

ros2 trace(LTTng)性能追踪 ⭐⭐⭐

当 ros2 topic hz 显示频率正常但控制系统仍有偶发抖动时,需要更精细的工具——ros2 trace 提供回调级的时间戳追踪,开销仅 0.0033 ms/tracepoint,不会显著影响系统行为。

LTTng 追踪的工作原理:

ros2_tracing 在 rclcpp/rclpy 的关键路径上插入了追踪点(tracepoint)——callback_start、callback_end、rclcpp_publish、rmw_take、executor_wait。每个追踪点记录纳秒级时间戳和上下文信息。追踪数据写入 CTF(Common Trace Format)文件,可以用 babeltrace、TraceCompass 或 Python 的 tracetools_analysis 分析。

# 检查追踪是否可用
ros2 run tracetools status
# 输出应包含 "Tracing enabled"

# 开始追踪会话
ros2 trace --session-name slam-latency --events ros2:callback_start ros2:callback_end

# 在另一个终端运行你的系统
ros2 launch my_robot_bringup slam.launch.py

# 按 Enter 停止追踪

# 查看原始事件
babeltrace ~/.ros/tracing/slam-latency/ | head -50

# 用 Python 分析
pip install tracetools-analysis bokeh
python3 -c "
from tracetools_analysis.loading import load_file
from tracetools_analysis.processor.ros2 import Ros2Handler
events = load_file('~/.ros/tracing/slam-latency/')
handler = Ros2Handler.process(events)
# handler.data 包含回调持续时间、发布-订阅延迟等
"

callback 延迟诊断方法 ⭐⭐:

trace 数据最有价值的用法是定位"哪个回调偶发变慢"。分析步骤:

  1. 提取回调持续时间:从 callback_start 和 callback_end 事件计算每次回调的持续时间。
  2. 计算统计量:p50、p95、p99、max。
  3. 识别尾延迟模式:p99 远大于 p50 通常意味着偶发阻塞(锁、内存分配、系统调用)。
  4. 关联系统事件:用 LTTng 的内核追踪点查看尾延迟期间是否发生了线程切换、页面错误或 I/O 等待。
诊断结论 trace 特征 典型原因
回调本身慢 callback_end - callback_start 大 算法耗时、锁等待、内存分配
回调排队等待 callback_start 被推迟 Executor 线程不够、前一个回调阻塞
DDS 传输慢 rclcpp_publish 到 rmw_take 间隔大 网络延迟、QoS 重传、大消息分片
Executor 空转 executor_wait 时间长 无事件到达(正常)或事件丢失(异常)

本质洞察:性能调试的核心技能不是"会用 trace 工具",而是"能从 trace 数据中区分正常变异和异常尖峰"。一个 SLAM 回调的处理时间在 5-15 ms 之间波动是正常的(取决于点云密度和回环检测);偶发一次 200 ms 是异常的(可能是内存分配或日志阻塞)。区分两者需要看分布形状,而不只是看最大值。

⚠️ 性能追踪陷阱

⚠️ 工程陷阱:在追踪期间运行完整系统导致 trace 文件过大

错误做法:开启 trace 后运行完整的 Nav2 + SLAM + 30 个传感器节点,录制 10 分钟。

现象/后果:trace 文件数 GB,分析时内存不足或加载耗时数分钟。大量无关节点的事件淹没了问题节点的信息。

正确做法:只运行能复现问题的最小节点集。如果问题在控制器回调中,只启动 ros2_control + 硬件接口。录制时间控制在 30-60 秒。

⚠️ 概念误区:认为 trace 开销为零可以一直开着

新手想法:"0.0033 ms/tracepoint 几乎没开销,生产系统一直开着 trace。"

实际上:单个 tracepoint 开销确实很小,但高频系统中 tracepoint 数量可能很大——一个 1 kHz 控制循环每秒产生 2000+ 个 tracepoint(start + end),加上 publish 和 take 事件可能到 5000+。磁盘 I/O 和 CTF 写入在极端情况下可能成为瓶颈。

正确做法:开发和调试阶段开启 trace;生产部署只在排查问题时临时开启。或使用 snapshot 模式——循环缓冲区只保留最近 N 秒的事件,出问题时手动保存。

练习

  1. [实操题] 对一个订阅 /scan 的节点采集 30 秒 trace。用 babeltrace 找出 callback_start 和 callback_end 事件,手动计算三个回调的持续时间。
  2. [分析题] 设计一个自动化脚本,从 trace 文件中提取所有回调的持续时间,计算 p50/p95/p99/max,并标记超过阈值的异常回调。
  3. [综合题] 一个控制系统的 update() 回调平均 0.4 ms,但 p99 是 8 ms。设计排查方案,区分是回调内部计算慢(算法/锁/分配)还是外部因素(系统调度/页面错误/DDS 阻塞)。

ROS1 到 ROS2 命令映射:不只是改了命令名 ⭐

从 ROS1 迁移到 ROS2 时,初学者倾向于把 ROS2 命令理解为"ROS1 命令的重命名"——rosnode list 变成了 ros2 node list,rostopic echo 变成了 ros2 topic echo。表面上确实如此,但底层有三个架构差异会影响日常使用。

第一个差异是去中心化。ROS1 依赖 rosmaster——一个中心节点注册服务器。如果 rosmaster 崩溃或网络不可达,所有节点都无法发现彼此。ROS2 使用 DDS 的分布式发现机制,不需要中心服务器。这意味着 ROS2 中不会出现"忘记启动 roscore"这种问题,但也意味着网络配置(多播、ROS_DOMAIN_ID)变得更重要。

第二个差异是参数归属。ROS1 的参数是全局的——任何节点都可以读写任何参数,没有归属概念。ROS2 的参数属于节点——ros2 param set 必须指定节点名。这让参数管理更清晰,但 rosparam dump 的行为不再适用。

第三个差异是 QoS。ROS1 的话题通信只有一种模式(TCP 可靠传输)。ROS2 引入了 QoS 策略——Reliability、Durability、History 等都可以配置。这带来了灵活性,但也引入了前面讨论的 QoS 不匹配问题。

ROS1 ROS2 主要区别
rosnode list/info ros2 node list/info 无需 rosmaster
rostopic echo /t ros2 topic echo /t 传感器需指定 QoS 标志
rostopic pub ros2 topic pub YAML 格式,QoS 标志
rosservice call ros2 service call YAML 格式
rosparam get/set ros2 param get/set /node 按节点管理,非全局服务器
rosmsg show / rossrv show ros2 interface show 消息/服务/操作的统一命令
rosrun pkg node ros2 run pkg node --ros-args --ros-args 需指定作用域
roslaunch ros2 launch Python/XML/YAML 启动文件
roswtf ros2 doctor / ros2 wtf 增强的 DDS 诊断
nodelets ros2 component 相同概念,新 API
不适用 ros2 lifecycle 新的受管节点状态

日常使用中需注意的架构差异:ROS2 没有 rosmaster(采用 DDS 分布式发现机制),参数是节点级的(而非全局的),--ros-args 限定了所有 ROS 参数的作用域,且 colcon build 取代了 catkin_make。 在每个终端中添加 setup.bash——初学者最常见的错误仍是忘记这一点。


rosbag2 和 MCAP:数据集的录制、回放与 SLAM ⭐⭐

这一节解决什么问题:SLAM 开发的核心工作流是"录制一次真实数据,在办公室反复回放调试"。bag 系统就是这个工作流的基础设施。但录制一个"能用"的 bag 远不止 ros2 bag record 一条命令——QoS 覆盖、话题选择、时间源统一、格式选择都直接影响 bag 是否能被 SLAM 算法正确消费。录了一个空 bag(因为 QoS 不匹配)或回放时 SLAM 不动(因为 /clock 没发布),是初学者最常浪费时间的两类问题。

bag 系统(https://github.com/ros2/rosbag2)是 SLAM 开发中对工作流至关重要的工具。 MCAP(https://github.com/foxglove/mcap)已成为 ROS2 Iron 的默认格式,它提供包含嵌入式消息定义的自包含文件,具备仅追加写入的安全性,并且通过 Zstd 压缩,比 SQLite3 节省 20–50% 的空间。

采用合适的 QoS 和压缩方式进行记录 ⭐⭐

# Basic MCAP recording with compression preset
ros2 bag record -s mcap -o slam_run /scan /imu /odom /tf /tf_static \
  --qos-profile-overrides-path slam_qos.yaml \
  --storage-preset-profile zstd_fast

# Record all topics, excluding noise
ros2 bag record -a -x "/rosout|/parameter_events" -s mcap -o full_run

# Regex filtering
ros2 bag record -a -e "/sensor/.*|/tf.*"

# File splitting at 100 MB or 300 seconds
ros2 bag record -a -b 104857600 -d 300

# Snapshot mode: circular buffer, dump on demand
ros2 bag record -a --snapshot-mode --max-cache-size 50000000
ros2 service call /rosbag2_recorder/snapshot rosbag2_interfaces/srv/Snapshot

对于 SLAM 记录而言,QoS 覆盖文件是不可或缺的。若缺少该文件,rosbag2 将默认采用 RELIABLE 订阅模式,而大多数传感器驱动程序发布的是 BEST_EFFORT 模式——这会导致系统在无提示的情况下丢弃所有消息:

# slam_qos.yaml — use this for every SLAM recording session
/scan:
  reliability: best_effort
  durability: volatile
/imu:
  reliability: best_effort
  durability: volatile
/camera/color/image_raw:
  reliability: best_effort
  durability: volatile
/tf_static:
  durability: transient_local
  reliability: reliable
  history: keep_all

SLAM 数据集记录的必备主题 ⭐⭐

主题 类型 频率 用途
/scan LaserScan 10–40 Hz 2D 激光雷达
/points PointCloud2 10–20 Hz 3D 激光雷达
/imu Imu 100–400 Hz 惯性测量单元 (加速度计 + 陀螺仪)
/odom Odometry 20–100 Hz 车轮/视觉里程计
/tf TFMessage 不定 动态变换
/tf_static TFMessage 1 条消息 静态变换(锁定)
/camera/color/image_raw Image 15–30 Hz RGB 摄像头
/camera/camera_info CameraInfo 与图像相同 摄像头内参

SLAM 开发中的回放 ⭐⭐

# The canonical SLAM replay command
ros2 bag play slam_dataset/ --clock 100 --rate 1.0 --loop \
  --topics /scan /imu /odom /tf /tf_static

# All consuming nodes MUST use sim time
ros2 launch slam_toolbox online_async_launch.py use_sim_time:=True

# Remap topics if bag uses non-standard names
ros2 bag play bag/ --remap /velodyne_points:=/points

# Start 30 seconds in, increase read-ahead buffer for high throughput
ros2 bag play bag/ --start-offset 30.0 --read-ahead-queue-size 2000

# Inspect bag contents
ros2 bag info slam_dataset/
mcap info recording.mcap    # More detail for MCAP files

回放期间的键盘控制:空格键暂停/继续,方向键以10%为增量调整速率,暂停时按右箭头键可逐条消息跳转。

Bag 案例:回放有数据但 SLAM 不动 ⭐⭐

症状:ros2 bag info 显示 /scan、/odom、/tf 都存在,ros2 bag play 也在输出进度,但 SLAM 节点没有建图,RViz 中机器人不动或时间停在 0。

这个问题通常不是 bag 文件“坏了”,而是时间源、QoS 或 TF 链路没有满足 SLAM 节点的输入契约。按证据链排查:

# 1. 先看 bag 里到底有哪些话题和时间跨度
ros2 bag info slam_dataset/

# 2. 回放时发布 /clock
ros2 bag play slam_dataset/ --clock 100 --rate 1.0

# 3. 确认 SLAM 节点使用仿真时间
ros2 param get /slam_toolbox use_sim_time

# 4. 确认传感器数据确实流动
ros2 topic hz /scan --window 20

# 5. 确认 TF 在 bag 时间下连通
ros2 run tf2_tools view_frames
ros2 run tf2_ros tf2_echo odom base_link
证据 常见原因 修复
/clock 不存在 回放未加 --clock 使用 ros2 bag play --clock
use_sim_time=false 节点使用墙钟时间 所有消费节点设为 true
/scan 无订阅数据 QoS 不兼容 添加 QoS 覆盖
TF 分成两棵树 静态变换缺失 发布 base_link 到传感器帧
/tf_static 回放后算法仍缺帧 持久性不匹配 使用 transient_local

一个常用的回放启动顺序是:

  1. 启动 SLAM 和 RViz,并统一 use_sim_time:=true。
  2. 等待节点订阅创建完成。
  3. 回放 bag,并发布 /clock。
  4. 用 tf2_echo 和 topic hz 验证输入到达。
  5. 暂停 bag,单步检查某一帧数据是否触发算法更新。

如果不统一时间源,会出现一个典型错觉:数据在流动,但算法认为所有数据都来自“未来”或“过去”,因此拒绝处理。SLAM、RViz、TF listener、robot_state_publisher 必须处于同一个时间系统。

练习:

  1. 录制一个只包含 /scan 但不包含 /tf_static 的 bag,观察回放时 SLAM 或 RViz 的错误信息。
  2. 解释为什么 bag 回放时不能只给 SLAM 节点设置 use_sim_time,还要给 RViz 和 TF 相关节点设置。
  3. 设计一个 slam_qos.yaml,同时覆盖 /scan、/imu 和 /tf_static。

格式转换与bag工具 ⭐⭐

# SQLite3 → MCAP (create a YAML config)
ros2 bag convert -i input.db3 -o convert_config.yaml

# MCAP CLI for quick operations
mcap info recording.mcap
mcap filter input.mcap -o filtered.mcap --include-topics /scan,/tf

# ROS1 → ROS2 bag conversion (pip install rosbags)
rosbags-convert input.bag

值得注意的第三方bag工具包括用于grep风格bag搜索(支持CSV/Parquet输出)的grepros (pip install grepros),支持 grep 风格的 bag 搜索并输出 CSV/Parquet 格式;来自 Tier4(https://github.com/tier4/ros2bag_extensions)的 ros2bag_extensions,用于 Autoware 中的过滤/合并/切片操作;以及纯 Python 库 rosbags,可在不依赖 ROS 工作空间的情况下进行 bag 操作。


TF2调试:系统化工作流 ⭐⭐

这一节解决什么问题:TF(变换树)是 ROS2 中所有空间信息的基础——SLAM 节点需要知道传感器相对于机器人底盘的位置(静态 TF),导航节点需要知道机器人在地图中的位姿(动态 TF),RViz 需要知道每个可视化元素应该画在哪里。TF 链一旦断裂或时间戳不一致,所有依赖空间信息的功能都会失效,而且症状千变万化——可能是"SLAM 不建图"、"导航不规划路径"、"RViz 中机器人消失"或"向未来外推"错误信息刷屏。

TF 问题之所以难排查,是因为它是一个"无声依赖"——没有任何代码显式地 #include "tf" 或 import tf,但几乎所有空间感知功能都隐式地依赖 TF 树的完整性和时间一致性。一个缺失的 base_link→laser_frame 静态变换,会导致 SLAM 节点无法把激光雷达数据转换到机器人坐标系,但错误信息可能只是"无法找到变换"或干脆没有任何输出。

TF问题是SLAM和导航系统中最常见的错误类型。该工具套件位于ros2/geometry2 (https://github.com/ros2/geometry2)。

三个必备的TF调试命令 ⭐

# 1. Visualize the entire TF tree (generates frames.pdf)
ros2 run tf2_tools view_frames
# Opens frames.pdf showing all frames, publishers, rates, and timestamps

# 2. Query a specific transform in real-time
ros2 run tf2_ros tf2_echo map base_link
# Output: Translation: [1.234, 5.678, 0.000], Rotation (quaternion): [0, 0, 0.383, 0.924]

# 3. Monitor all transforms for timing statistics
ros2 run tf2_ros tf2_monitor
# Shows per-frame: publisher, rate, average delay, max delay
ros2 run tf2_ros tf2_monitor odom base_link  # Monitor specific chain

# Publish a static transform (new syntax)
ros2 run tf2_ros static_transform_publisher \
  --x 0.1 --y 0.0 --z 0.2 --roll 0 --pitch 0 --yaw 0 \
  --frame-id base_link --child-frame-id laser_frame

诊断”向未来外推”问题 ⭐⭐

“向未来外推”(extrapolation into the future)是 TF 系统中最常见的错误消息之一。初学者看到这个错误时通常很困惑——“我没有请求未来的数据,为什么说我在向未来外推?”理解这个错误需要意识到 TF 系统的工作方式:它维护一个时间缓冲区,记录不同时间点的变换。当你查询时间 T 的变换时,TF 系统会在缓冲区中找到 T 前后的两个变换进行插值。如果 T 超过了缓冲区中最新数据的时间戳,TF 系统就认为你在”向未来外推”——它没有未来的数据来做插值。

这个错误的根因通常不是”代码写错了”,而是时间源不一致。最常见的情况是:部分节点使用仿真时间(从 /clock 话题获取,值通常接近 0 或从 bag 文件的时间戳开始),部分节点使用墙钟时间(系统时钟,Unix 纪元以来的秒数,约 17 亿)。两者的差距可能达到几十年——TF 系统当然找不到”17 亿秒”时间点的变换。

此错误表示监听器请求时间 T 时的变换,但最新可用数据的时间为 T-delta。原因与解决方法:

  • 在 now() 而非 tf2::TimePointZero 进行查询:使用 this->now() 会请求当前墙钟时间点的变换。 如果 TF 广播器存在哪怕几毫秒的延迟,数据也尚未可用。解决方法:除非确实需要特定时间的查询,否则请使用 tf2::TimePointZero(最新可用数据)。
  • use_sim_time 设置不一致:如果部分节点使用模拟时间(从接近 0 开始),而其他节点使用墙钟时间(Unix 纪元约 17 亿),时间戳之间的差距将非常巨大。解决方法:在回放数据包或使用模拟时,请在每个节点上统一设置 use_sim_time:=true。
  • 未发布 /clock:当 use_sim_time 为 true 且没有任何节点发布 /clock 时,节点会在时间 0 处冻结。解决方法:使用 ros2 bag play --clock 100 或确保 Gazebo 发布时钟。

诊断“向过去外推”问题 ⭐⭐

请求的时间戳早于 TF 缓冲区中的最旧数据。原因包括 TF 缓冲区过短(默认 10 秒),或数据处理存在较大延迟。解决方法:在代码中增加缓冲区时长:tf2_ros::Buffer(this->get_clock(), tf2::Duration(std::chrono::seconds(30)))。

双树断开问题 ⭐⭐

如果 view_frames 显示两棵独立的树(例如 map→odom→base_link 和一个断开的 camera_link→camera_optical_frame),则表示缺少某些变换。在 SLAM 系统中,最常见的缺失环节是 base_link→sensor_frame 静态变换。 修复:请确认您的 URDF 通过 robot_state_publisher 发布了所有传感器帧,或显式添加 static_transform_publisher 节点。

多机器人 TF 命名空间 ⭐⭐⭐

对于多机器人系统,请在所有帧 ID 前添加机器人命名空间前缀。在启动文件中配置:

Node(
    package='robot_state_publisher',
    executable='robot_state_publisher',
    namespace='robot1',
    parameters=[{'frame_prefix': 'robot1/'}],
)

ROS2 启动系统的实际应用 ⭐⭐

这一节解决什么问题:真实机器人系统通常由 10-30 个节点组成,手动在每个终端启动节点既不可复现也不可维护。启动文件把"以什么参数、什么顺序、什么条件启动哪些节点"编码成可版本控制的脚本。好的启动文件组织能让调试更高效——按功能拆分后,可以只启动出问题的子系统而不加载整个栈。

ROS2 启动文件(https://github.com/ros2/launch、https://github.com/ros2/launch_ros))是构建动作有向无环图的 Python 脚本。核心概念模型:DeclareLaunchArgument 定义了可从命令行传递的内容,而 LaunchConfiguration 则在启动描述中检索这些值。

展示所有主要功能的实用启动文件 ⭐⭐

import os
from launch import LaunchDescription
from launch.actions import (DeclareLaunchArgument, ExecuteProcess,
    IncludeLaunchDescription, GroupAction, TimerAction, RegisterEventHandler)
from launch.conditions import IfCondition, UnlessCondition
from launch.event_handlers import OnProcessExit
from launch.substitutions import (LaunchConfiguration, PathJoinSubstitution,
    FindExecutable, PythonExpression, Command)
from launch_ros.actions import Node, ComposableNodeContainer, LoadComposableNodes
from launch_ros.descriptions import ComposableNode
from launch_ros.substitutions import FindPackageShare

def generate_launch_description():
    use_sim_time = LaunchConfiguration('use_sim_time')
    use_composition = LaunchConfiguration('use_composition')

    params_file = PathJoinSubstitution([
        FindPackageShare('my_robot_bringup'), 'config', 'params.yaml'
    ])

    return LaunchDescription([
        DeclareLaunchArgument('use_sim_time', default_value='false'),
        DeclareLaunchArgument('use_composition', default_value='true'),

        # Standard node
        Node(
            package='slam_toolbox',
            executable='async_slam_toolbox_node',
            name='slam_toolbox',
            parameters=[params_file, {'use_sim_time': use_sim_time}],
            remappings=[('/scan', '/lidar/scan')],
            output='screen',
            condition=UnlessCondition(use_composition),
        ),

        # Composable nodes in a container (zero-copy intra-process)
        ComposableNodeContainer(
            name='slam_container',
            namespace='',
            package='rclcpp_components',
            executable='component_container_mt',
            composable_node_descriptions=[
                ComposableNode(
                    package='slam_toolbox',
                    plugin='slam_toolbox::AsyncSlamToolboxNode',
                    name='slam_toolbox',
                    parameters=[params_file, {'use_sim_time': use_sim_time}],
                ),
            ],
            condition=IfCondition(use_composition),
            output='screen',
        ),

        # Include another launch file
        IncludeLaunchDescription(
            PathJoinSubstitution([
                FindPackageShare('nav2_bringup'), 'launch', 'navigation_launch.py'
            ]),
            launch_arguments={'use_sim_time': use_sim_time}.items(),
        ),

        # Delayed action
        TimerAction(period=5.0, actions=[
            ExecuteProcess(cmd=['ros2', 'lifecycle', 'set', '/amcl', 'activate']),
        ]),

        # Event handler: do something after a process exits
        RegisterEventHandler(
            OnProcessExit(
                target_action=Node(package='my_pkg', executable='calibrator'),
                on_exit=[Node(package='my_pkg', executable='controller')],
            )
        ),
    ])

启动文件调试 ⭐⭐

ros2 launch my_pkg my_launch.py --show-args     # List available arguments
ros2 launch my_pkg my_launch.py --print          # Print actions without executing
ros2 launch my_pkg my_launch.py --debug          # Verbose launch logging
python3 path/to/my_launch.py                      # Catch Python syntax errors

XML 和 YAML 替代方案 ⭐

<!-- my_launch.launch.xml -->
<launch>
  <arg name="use_sim_time" default="false"/>
  <node pkg="slam_toolbox" exec="async_slam_toolbox_node" name="slam_toolbox">
    <param name="use_sim_time" value="$(var use_sim_time)"/>
    <remap from="/scan" to="/lidar/scan"/>
  </node>
</launch>

最佳实践:按功能模块拆分启动文件。Nav2 的 nav2_bringup(https://github.com/ros-navigation/navigation2)是黄金标准——将仿真、机器人初始化、导航栈和定位分别放在独立文件中,并通过 IncludeLaunchDescription 进行组合。 rosetta_launch 仓库 (https://github.com/MetroRobots/rosetta_launch) 提供了 ROS1/ROS2 并行启动示例,以辅助迁移。


C++/Python 节点的日志记录、GDB 和安全检查器 ⭐⭐

这一节解决什么问题:当 CLI 工具和 trace 已经把问题缩小到"某个回调内部"时,需要更精细的工具来定位具体代码行。日志宏帮助记录运行时决策变量;GDB 帮助在崩溃现场检查变量和调用栈;AddressSanitizer 帮助发现内存错误——这三者是逐层深入的,不是可以互相替代的。

日志、GDB 和 Sanitizer 对应不同粒度的问题:日志回答"系统做了什么决策"(宏观),GDB 回答"崩溃时变量是什么值"(微观),Sanitizer 回答"是否存在内存安全违规"(静态错误检测)。排查时应按照从宏观到微观的顺序使用——先用日志定位问题区域,再用 GDB 深入检查,最后用 Sanitizer 排除隐藏的内存错误。不要一开始就把复杂系统放进 GDB 单步运行——单步会改变时序,可能让并发问题消失。

ROS2 日志记录宏 ⭐

// Standard levels
RCLCPP_INFO(this->get_logger(), "Processing frame %d", frame_id);

// Throttled (1000ms) — essential for high-frequency callbacks
RCLCPP_WARN_THROTTLE(this->get_logger(), *this->get_clock(), 1000,
    "Dropping frames: queue full");

// Once only — perfect for initialization messages
RCLCPP_INFO_ONCE(this->get_logger(), "Controller initialized");

// Stream style
RCLCPP_ERROR_STREAM(this->get_logger(), "Transform failed: " << ex.what());
# Python equivalents
self.get_logger().info(f'Processing frame {frame_id}')
self.get_logger().warning('Dropping frames', throttle_duration_sec=1.0)
self.get_logger().info('Controller initialized', once=True)

运行时日志级别控制:

# Global debug level
ros2 run my_pkg my_node --ros-args --log-level debug

# Per-node granularity
ros2 run my_pkg my_node --ros-args --log-level my_node:=DEBUG --log-level rclcpp:=WARN

# Custom format
export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity} {time}] [{name}]: {message} ({function_name}:{line_number})"

# Disable rosout topic publishing (reduces overhead)
ros2 run my_pkg my_node --ros-args --disable-rosout-logs

日志文件写入 ~/.ros/log/(可通过 ROS_LOG_DIR 配置)。若需基于 GUI 的过滤,请使用 rqt_console。

使用 GDB 调试 ⭐⭐

# Build with debug symbols first
colcon build --packages-select my_package --cmake-args -DCMAKE_BUILD_TYPE=Debug

# Method 1: ros2 run with prefix
ros2 run --prefix 'gdb -ex run --args' my_package my_node

# Method 2: In launch file (opens xterm for GDB interaction)
Node(
    package='my_package',
    executable='my_node',
    prefix=['xterm -e gdb -ex run --args'],
    output='screen',
)

# Method 3: Attach to running node
ps aux | grep my_node
gdb -p <PID>

# Method 4: gdbserver for VSCode remote debugging
ros2 run --prefix 'gdbserver localhost:3000' my_package my_node

使用组件容器的 GDB:可组合节点共享一个进程,因此无法为单个组件添加前缀。调试时请禁用组合功能(use_composition:=False),或通过 PID 将 GDB 附加到容器进程。

AddressSanitizer 及其他 ⭐⭐⭐

# AddressSanitizer (catches buffer overflows, use-after-free)
colcon build --packages-select my_package \
  --cmake-args -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \
               -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \
               -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
               -DCMAKE_BUILD_TYPE=Debug

# Valgrind memory leak detection
ros2 run --prefix 'valgrind --tool=memcheck --leak-check=full' my_package my_node

# Valgrind CPU profiling (analyze with kcachegrind)
ros2 run --prefix 'valgrind --tool=callgrind' my_package my_node

# rr time-travel debugging (step backward through execution)
rr record ~/ros2_ws/install/my_package/lib/my_package/my_node
rr replay
# (rr) reverse-continue    # step backward to find root cause

性能监控与 DDS 调优 ⭐⭐⭐

这一节解决什么问题:控制系统关心的不是"平均快不快",而是"最慢的那次有多慢"。一个 1 kHz 控制器如果平均 0.5 ms 完成,但每 1000 次有一次需要 20 ms,这个控制器在工程上是不合格的。性能监控工具帮助你找到这些尖峰(p99、max)及其来源——是回调本身慢、是 executor 排队、还是系统级调度被打断。

性能问题和功能问题有一个根本区别:功能问题通常是确定性的(输入 A 总是产生错误输出 B),性能问题通常是统计性的(99% 的时候正常,1% 的时候异常慢)。这意味着性能排查不能靠"跑一次看看",需要收集统计分布,找到尖峰的模式。ros2 topic hz 给出的平均值只是冰山一角——真正有价值的是 max period、p95 和 p99。

快速性能检查 ⭐⭐

ros2 topic hz /scan           # Is the sensor publishing at expected rate?
ros2 topic bw /camera/image   # How much bandwidth does this consume?
ros2 topic delay /scan        # End-to-end latency (requires Header timestamps)

ros2_tracing 用于生产级延迟分析 ⭐⭐⭐

该追踪框架(https://github.com/ros2/ros2_tracing)采用 LTTng,平均每个追踪点的开销仅为 0.0033 毫秒,因此非常适合实时系统:

# Verify tracing is enabled
ros2 run tracetools status

# Collect traces
ros2 trace --session-name my-session
# (run your application, then press Enter to stop)

# Quick inspection
babeltrace ~/.ros/tracing/my-session/ | head -100

# Analyze with Jupyter notebooks from tracetools_analysis
pip install tracetools-analysis bokeh

跟踪记录会捕获 callback_start/callback_end、rclcpp_publish、rmw_take 以及执行器调度事件。这使您能够构建火焰图风格的可视化图表,直观展示回调链中时间的消耗情况。

ros2 trace、LTTng 与 GDB 的分层使用 ⭐⭐⭐

性能问题和崩溃问题要分层处理。ros2 trace 适合回答“时间花在哪里”;LTTng 适合回答“系统级事件如何排列”;GDB 适合回答“某个时刻变量为什么是这个值”。三者不是替代关系,而是从宏观到微观逐层缩小范围。

工具 适合的问题 不适合的问题 输出证据
ros2 topic hz/bw/delay 频率、带宽、端到端延迟粗测 回调内部耗时 统计值
ros2 trace 回调调度、发布订阅路径、executor 行为 单行 C++ 变量值 trace event
LTTng / babeltrace 线程切换、内核调度、系统级时间线 复杂表达式调试 时间戳事件流
GDB 崩溃、断点、变量、调用栈 长时间性能统计 backtrace 和变量值
ASan / TSan 内存越界、悬空引用、数据竞争 正常逻辑错误 运行时报告

一个推荐的分层流程:

症状:控制周期偶发超过 20 ms
  ↓
topic hz/delay:确认外部频率确实有尖峰
  ↓
ros2 trace:定位是哪一个 callback 或 executor 等待变长
  ↓
LTTng/babeltrace:确认是否发生线程调度或系统调用阻塞
  ↓
GDB/日志降采样:检查该 callback 内部的具体分支

收集 trace 时,应尽量保持实验最小化。只运行必要节点,只保留能复现问题的输入。否则 trace 文件会很大,事件时间线中混入过多无关节点。

# 创建命名明确的 trace 会话
ros2 trace --session-name controller-latency

# 另一个终端运行最小复现实验
ros2 launch my_robot_bringup control_only.launch.py

# 停止后查看事件
babeltrace ~/.ros/tracing/controller-latency/ | head -50

阅读 trace 时先找三个量:

  1. 回调开始到结束的持续时间。
  2. 消息发布到订阅回调开始的间隔。
  3. 同一 executor 中回调是否排队等待。

如果 trace 显示回调本身很短,但回调开始时间明显推迟,问题更可能在 executor、线程竞争或 DDS 层。如果回调开始及时但结束很晚,问题更可能在用户代码、锁、内存分配或算法耗时。

GDB 则应在范围缩小后使用。例如 trace 显示 pointcloud_callback 偶发耗时 80 ms,就可以只对该节点加断点或条件断点:

ros2 run --prefix 'gdb -ex run --args' my_perception pointcloud_node

# GDB 中常用命令
break PointCloudNode::pointcloudCallback
run
bt
print cloud->width
print cloud->height
continue

不要一开始就把复杂系统放进 GDB 单步运行。单步会改变时序,可能让并发问题消失。对实时和性能问题,先用 trace 观察自然运行状态,再用 GDB 检查已经定位到的代码路径。

练习:

  1. 对一个订阅 /scan 的节点采集 trace,找出订阅回调的开始和结束事件。
  2. 设计一个性能问题的分层排查表,至少包含 topic 统计、trace、GDB 三层。
  3. 解释为什么 GDB 单步调试不适合直接证明控制周期满足实时要求。

DDS 中间件调优 ⭐⭐⭐

ROS2 的通信性能很大程度上取决于底层 DDS 中间件的配置。默认配置适合大多数轻量场景,但当系统需要传输大消息(如 PointCloud2、Image)或在无线网络中工作时,默认配置可能导致丢包、高延迟或消息到达不稳定。DDS 调优不是"高级用户才需要"的操作——对于 SLAM 系统,传输一帧 16 线激光雷达的 PointCloud2 可能需要多个 UDP 分片,默认内核缓冲区可能不够大,导致偶发丢帧。

CycloneDDS 和 Fast DDS 是 ROS2 中最常用的两个 DDS 实现。它们的设计权衡不同:CycloneDDS 追求低延迟和简单配置,适合点对点和小规模系统;Fast DDS 提供更精细的 QoS 控制和更丰富的安全特性,适合大规模或需要 DDS Security 的部署。对于 SLAM 开发,CycloneDDS 通常是更好的默认选择。

# 切换到 CycloneDDS(更低延迟、更简单的配置)
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp

# CycloneDDS config for large messages (point clouds, images)
export CYCLONEDDS_URI=file:///path/to/cyclonedds.xml
<!-- cyclonedds.xml for high-throughput SLAM -->
<CycloneDDS xmlns="https://cdds.io/config">
  <Domain>
    <General>
      <NetworkInterfaceAddress>eth0</NetworkInterfaceAddress>
    </General>
    <Internal>
      <SocketReceiveBufferSize min="10MB"/>
      <Watermarks>
        <WhcHigh>500kB</WhcHigh>
      </Watermarks>
    </Internal>
  </Domain>
</CycloneDDS>
# Linux kernel tuning for large messages
sudo sysctl -w net.core.rmem_max=4194304
sudo sysctl -w net.core.wmem_max=4194304

CycloneDDS 通常比 Fast DDS 提供更低的点对点延迟和更优的 Wi-Fi 表现,而 Fast DDS 则提供更精细的控制和更丰富的 QoS 功能。对于涉及大型点云的 SLAM 工作负载,通常选择增加套接字缓冲区的 CycloneDDS 更为合适。


按症状分类的实用调试流程 ⭐⭐

这一节解决什么问题:前面的内容按工具分类——先讲 CLI、再讲 TF、再讲 bag、再讲 trace。本节转换视角,按症状分类——“机器人不动时怎么查”、”bag 回放不工作时怎么查”、”控制周期偶发超时怎么查”。这更接近实际工作中的使用方式:你不是从”我要学 ros2 topic info”出发的,而是从”我的 SLAM 系统出了问题”出发的。

每个症状对应的排查流程都遵循 0.1 节介绍的五步闭环:症状 -> 假设 -> 证据采集 -> 判断 -> 最小改动。下面的流程图已经为你预组织了最常见的假设和对应的证据采集命令。用它们的方式是:先看症状匹配哪一条,按顺序执行证据采集命令,每一步的结果要么排除一个假设要么确认一个假设,直到找到根因。

“主题未收到消息” —— QoS 诊断检查清单 ⭐⭐

  1. ros2 topic info /topic —— 确认发布者数量 > 0
  2. ros2 topic info /topic --verbose —— 比较发布方与订阅方的 QoS 配置文件
  3. 检查双方的 ROS_DOMAIN_ID 是否匹配:echo $ROS_DOMAIN_ID
  4. 检查 RMW_IMPLEMENTATION 是否匹配:echo $RMW_IMPLEMENTATION
  5. 测试网络:ros2 multicast receive + ros2 multicast send
  6. 验证消息类型:ros2 topic type /topic

最常见的原因是QoS可靠性不匹配:订阅者请求 RELIABLE,但发布者提供 BEST_EFFORT。此时不会建立连接,也不会输出错误信息。

“rosbag 重放对 SLAM 无效” ⭐⭐

  1. ros2 bag info bag/ — 验证 /scan、/odom、/tf、/tf_static 是否存在
  2. 尝试使用 --clock:ros2 bag play bag/ --clock 100
  3. 在每个节点(SLAM、RViz、所有变换)上设置 use_sim_time:=true
  4. 回放期间检查 TF 树:ros2 run tf2_tools view_frames
  5. 如有需要,为回放添加 QoS 覆盖设置
  6. 验证 robot_description 是否可用(静态变换依赖于它)

“节点出现在 ros2 节点列表中但无响应” ⭐⭐

  1. ros2 node info /node — 检查订阅是否存在
  2. ros2 topic hz /input_topic — 数据是否正在到达?
  3. ros2 lifecycle get /node — 节点生命周期可能处于 UNCONFIGURED 或 INACTIVE 状态
  4. ros2 param list /node — 检查参数是否设置正确
  5. 运行 --log-level debug 以查看内部状态

Lifecycle 案例:控制器加载了但机器人不执行 ⭐⭐

症状:ros2 control list_controllers 能看到控制器,/joint_states 也在发布,但机器人不响应轨迹或速度命令。

生命周期类问题的关键是区分“对象存在”和“对象激活”。ROS2 图里能看到节点,只说明进程和接口存在;控制器处于 inactive 时可能不会处理命令,硬件接口处于未激活状态时也可能拒绝写入。

# 查看控制器状态
ros2 control list_controllers

# 查看硬件接口是否 claimed
ros2 control list_hardware_interfaces

# 查看生命周期状态
ros2 lifecycle nodes
ros2 lifecycle get /controller_manager

# 激活控制器
ros2 control switch_controllers \
  --activate joint_trajectory_controller \
  --deactivate forward_position_controller
证据 解释
控制器 unconfigured 尚未加载参数或未完成配置
控制器 inactive 已配置但不处理命令
command interface 未 claimed 没有控制器持有写接口
state interface 存在但 command interface 不存在 硬件只导出了状态,没有导出命令
切换控制器失败 接口冲突或硬件状态不允许

最小修复原则是:先激活一个最简单的控制器,例如 forward position 或 joint state broadcaster,再引入复杂轨迹控制器。这样可以把问题限定在控制器生命周期,而不是混入规划、轨迹插值和硬件协议。

练习:

  1. 用 ros2 control list_hardware_interfaces 判断某个关节的 position 命令接口是否被 claim。
  2. 解释为什么 joint_state_broadcaster active 不能证明命令链路可用。
  3. 设计一个控制器切换失败的证据链,区分接口冲突和硬件未激活。

“TF 错误淹没控制台” ⭐⭐

  1. ros2 run tf2_tools view_frames — 查找断开连接的树或缺失的帧
  2. ros2 run tf2_ros tf2_monitor — 检查每个帧的速率和延迟
  3. 验证所有节点间 use_sim_time 的一致性
  4. 检查同一帧上是否存在重复的 TF 广播器
  5. 验证 URDF 是否已加载:ros2 topic echo /robot_description

Performance 案例:平均频率正常但控制偶发卡顿 ⭐⭐⭐

症状:ros2 topic hz /joint_states 显示平均接近 1 kHz,但机器人偶发抖动,日志中偶尔出现控制周期超时。

平均频率会掩盖尖峰。控制系统关心的是 p95、p99 和最大周期,而不是只看均值。排查时应把频率统计、trace 和系统监控放在同一条证据链中:

# 粗看话题频率和延迟
ros2 topic hz /joint_states --window 200
ros2 topic delay /joint_states

# 采集回调级 trace
ros2 trace --session-name control-spike

# 查看进程线程和 CPU 占用
top -H -p $(pgrep -f controller_manager)

# 如怀疑内存错误,重新用 ASan 构建后复现
colcon build --packages-select my_controller \
  --cmake-args -DCMAKE_BUILD_TYPE=Debug \
               -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer"
证据 更可能的原因
hz 均值正常但 max period 大 周期抖动
trace 中 callback 排队 executor 线程不足或回调阻塞
callback 自身耗时长 算法、锁、内存分配或日志
CPU 单核打满 线程亲和性或计算负载问题
ASan 报错 内存越界或释放后使用

性能调试的最小改动应该按风险排序:先关闭高频日志和可视化,再降低非关键话题频率,再调整 executor 和 callback group,最后才重写算法。每次只改一项,并保留同一输入数据重放。

练习:

  1. 解释为什么 ros2 topic hz 的平均值不能证明控制周期没有尖峰。
  2. 用 trace 判断一个慢周期来自 callback 排队还是 callback 内部计算。
  3. 设计一个性能回归测试,要求同一 bag 回放下比较修改前后的 p99 延迟。

辅助工具与多终端工作流 ⭐

ROS2 开发的日常工作通常需要同时运行和监控多个终端——一个运行 launch 文件、一个播放 bag、一个打开 RViz、一个监控话题频率、一个查看 TF、还有一个留给临时调试命令。手动在 6 个终端之间切换既低效又容易遗忘状态。多终端管理工具把这些窗口组织成可复现的布局。

tmux 是 ROS 开发中不可或缺的工具。catmux 项目(https://github.com/fmauch/catmux)提供了基于 YAML 的 tmux 会话配置,专为 ROS 工作流设计,允许您定义多面板会话,并在每个面板中设置特定命令,从而实现可复现的调试环境。

Foxglove Studio (https://github.com/foxglove/studio) 提供基于浏览器的可视化功能,支持 3D 视图、图表、主题图以及数据包回放。通过 foxglove-bridge 包 (https://github.com/foxglove/ros-foxglove-bridge) 进行连接:ros2 launch foxglove_bridge foxglove_bridge_launch.xml。

PlotJuggler (https://github.com/facontidavide/PlotJuggler,ROS 插件位于 https://github.com/PlotJuggler/plotjuggler-ros-plugins)) 擅长时间序列可视化,并支持直接加载 bag 文件及实时流式传输。

在测试方面,launch_testing(基于 unittest)和 launch_pytest(基于 pytest,属于 ros2/launch 仓库)提供了启动级别的集成测试。 ros2-easy-test 项目(https://github.com/felixdivo/ros2-easy-test)通过 @with_single_node 和 @with_launch_file 装饰器简化了这一过程。使用 ament_cmake_ros 实现测试隔离,并为每个测试自动分配 ROS_DOMAIN_ID。


值得研究的 GitHub 项目 ⭐

核心基础设施 ⭐

仓库 学习重点
ros2/ros2cli 所有 CLI 工具的源代码;研究插件架构
ros2/ros2_tracing 基于 LTTng 的追踪,附带 tracetools_analysis 笔记本
ros2/launch + ros2/launch_ros 启动系统;包含 launch_testing 和 launch_pytest
ros2/rosbag2 Bag 录制/回放;MCAP 存储插件
ros2/geometry2 TF2 库、tf2_ros、tf2_tools
foxglove/mcap MCAP 格式规范及工具

SLAM 与导航 ⭐⭐

仓库 相关性说明
SteveMacenski/slam_toolbox 具有 RViz 调试插件的 2D SLAM 黄金标准
introlab/rtabmap_ros 支持多会话映射与闭环的 3D SLAM
ros-navigation/navigation2 Nav2 堆栈;请参阅 nav2_bringup 了解启动模式
moveit/moveit2 运动规划;出色的启动文件组织
turtlebot/turtlebot4 完整的机器人调试流程,具备结构良好的启动系统

基于强化学习的运动控制与具身智能 ⭐⭐

仓库 描述
isaac-sim/IsaacLab 基于Isaac Sim的统一机器人学习框架,支持ROS2
unitreerobotics/unitree_rl_gym 完整管道:Isaac Gym → MuJoCo sim2sim → 真实机器人(Unitree G1/H1)
BoosterRobotics/booster_gym 人形机器人的端到端强化学习行走
jackvice/RoboTerrain 基于 ROS2 + Gazebo + Stable-Baselines3 的越野移动机器人强化学习
ncbdrck/sb3_ros_support 用于在 ROS 环境下训练强化学习代理的 Stable-Baselines3 封装库
ros-controls/ros2_control 机器人控制环路的硬件抽象层
nasa-jpl/rosa NASA JPL 基于 LLM 的 ROS 调试代理
Auromix/ROS-LLM 用于具身智能的 LLM + ROS2 框架

可视化、调试和学习资源 ⭐

仓库 描述
foxglove/studio 开源机器人可视化工具(3D、图表、日志)
facontidavide/PlotJuggler 支持 ROS bag 的时间序列绘图
fkromer/awesome-ros2 精选的 ROS2 包和资源列表
oleg-Shipitko/awesome-ros-tools 精选的 ROS 开发工具列表
Ly0n/awesome-robotic-tooling 覆盖整个技术栈的专业机器人开发工具
MetroRobots/rosetta_launch ROS1/ROS2 并行启动文件示例
fmauch/catmux 适用于 ROS 多终端工作流的 tmux 会话管理器
colcon/colcon-sanitizer-reports 用于 colcon 测试的 ASan/TSan 集成
AIT-Assistive-Autonomous-Systems/ros2bag_tools 用于裁剪、过滤和绘制数据包的 CLI 命令
tier4/ros2bag_extensions Autoware 中使用的数据包过滤/合并/切片工具

本章知识树回顾

读完全章后,你脑中应该形成以下知识树:

根:如何系统化地调试 ROS2 系统?
  │
  ├── 证据链思维
  │     ├── 症状→假设→证据采集→判断→最小改动
  │     └── 每步要么排除假设要么确认假设
  │
  ├── CLI 工具(证据采集)
  │     ├── 图结构:ros2 node/topic/service/action list/info
  │     ├── 通信语义:ros2 topic info --verbose(QoS 诊断)
  │     ├── 数据流:ros2 topic hz/bw/delay/echo
  │     ├── 参数:ros2 param list/get/set/dump
  │     ├── 系统诊断:ros2 doctor --report
  │     └── 生命周期:ros2 lifecycle nodes/get/set
  │
  ├── TF 调试
  │     ├── view_frames(树结构)
  │     ├── tf2_echo(实时变换)
  │     ├── tf2_monitor(延迟统计)
  │     └── 常见问题:外推/双树/时间不一致
  │
  ├── 数据录制回放
  │     ├── MCAP 格式(自包含、压缩、仅追加)
  │     ├── QoS 覆盖文件(防止录制空 bag)
  │     ├── --clock + use_sim_time(时间源统一)
  │     └── 高级用法:快照模式、分片、过滤、合并
  │
  ├── 性能追踪
  │     ├── ros2 trace(LTTng 回调级)
  │     ├── DDS 性能诊断(缓冲区、多播)
  │     ├── callback 延迟分析(p50/p95/p99/max)
  │     └── 分层诊断:topic→trace→LTTng→GDB
  │
  ├── 代码级调试
  │     ├── 日志宏(THROTTLE/ONCE)
  │     ├── GDB(断点、调用栈、变量检查)
  │     ├── ASan/TSan(内存/线程安全)
  │     └── Valgrind(泄漏检测、性能分析)
  │
  └── Launch 系统
        ├── Python 可编程启动
        ├── Composition 支持
        ├── 事件驱动排序
        └── 调试命令(--show-args/--print/--debug)

这棵树的根是"证据链思维"——所有工具都是为排除或确认假设服务的。记住这个原则,比记住命令参数重要得多。当你遇到新问题时,先问"我的假设是什么",再问"用什么工具采集证据"。

rqt 工具族系统化讲解 ⭐⭐

rqt 是 ROS2 中基于 Qt 的 GUI 工具框架。它的核心架构是"一个窗口多个可停靠插件"——你可以在同一个窗口中同时打开 rqt_graph、rqt_plot、rqt_console 和 rqt_reconfigure,组成自定义的监控面板。

rqt_graph:可视化 ROS2 计算图。显示节点之间的话题连接,帮助理解数据流。三种显示模式:

  • Nodes only:只显示节点名,适合看系统架构
  • Nodes/Topics (active):显示有活跃数据流的话题,最常用
  • Nodes/Topics (all):显示所有已注册的话题,包括没有连接的

工程用法:当"某个节点收不到数据"时,先看 rqt_graph 确认发布者和订阅者之间是否有话题连接。如果连接存在但数据不流动,转到 QoS 诊断。

# 启动 rqt_graph
ros2 run rqt_graph rqt_graph
# 或在 rqt 中加载
rqt --force-discover

rqt_plot:实时绘制话题中的数值字段。适合快速查看里程计位置、关节角度、控制器输出等标量时间序列。但对于复杂分析(多轨迹叠加、FFT、bag 加载),PlotJuggler 远优于 rqt_plot。

# 绘制里程计位置的 x 和 y
ros2 run rqt_plot rqt_plot /odom/pose/pose/position/x /odom/pose/pose/position/y

rqt_console:聚合所有节点的日志输出,提供按节点名、严重级别和消息内容过滤的能力。对于"SLAM 不建图但不知道为什么"的场景,在 rqt_console 中过滤 WARN 和 ERROR 通常能看到具体原因。

rqt_tf_tree:实时显示 TF 树的结构和每个帧的发布频率。与 view_frames 生成静态 PDF 不同,rqt_tf_tree 是动态更新的——你可以观察 TF 帧在运行时的出现和消失。

rqt_reconfigure:在线修改节点参数。对 SLAM 和导航调参特别有价值——可以在机器人运行时动态调整 costmap 膨胀半径、控制器速度限制、SLAM 分辨率等参数,不需要重启节点。

# 启动 rqt_reconfigure
ros2 run rqt_reconfigure rqt_reconfigure

rqt 自定义面板:

# 保存当前 rqt 布局为 perspective
rqt --perspective-file my_slam_dashboard.perspective

# 下次启动时加载保存的布局
rqt --perspective-file my_slam_dashboard.perspective

推荐为不同调试场景创建不同的 perspective: - slam_debug.perspective:rqt_graph + rqt_tf_tree + rqt_console(过滤 slam_toolbox) - nav_tuning.perspective:rqt_reconfigure + rqt_plot(cmd_vel) + rqt_console(过滤 Nav2) - hardware_debug.perspective:rqt_plot(joint_states) + rqt_console(过滤 controller_manager)

⚠️ rqt 使用陷阱

⚠️ 工程陷阱:rqt_plot 用于高频数据导致 CPU 暴涨

错误做法:用 rqt_plot 绘制 1 kHz 的关节状态,同时显示 12 个关节的位置和速度。

现象/后果:rqt_plot 的 CPU 占用急剧上升(可能超过一个核心),系统变慢,甚至影响 ros2_control 的实时性。

根本原因:rqt_plot 的渲染不是为高频数据设计的。每个数据点都触发重绘,12 个曲线 × 1 kHz = 12000 次/秒的渲染更新。

正确做法:用 PlotJuggler 替代——它使用 OpenGL 加速渲染,能处理百万级数据点。或用 rqt_plot 时只显示 1-2 个关键变量,并降低话题发布频率。

练习

  1. [实操题] 启动一个 SLAM + Nav2 系统,用 rqt_graph 查看完整的节点和话题连接图。标注出 /scan 从驱动到 costmap 的完整路径。
  2. [实操题] 用 rqt_reconfigure 在线修改 Nav2 的 inflation_radius 参数。观察 RViz 中 costmap 的膨胀区域如何实时变化。
  3. [设计题] 为一个四足 RL 部署系统设计 rqt perspective,包含哪些插件、监控哪些话题。

工具链总结与方法论 ⭐⭐

回顾本章的核心教学目标:CLI 工具不是需要死记硬背的命令列表,而是排查 ROS2 系统问题的证据采集工具箱。每个工具对应证据链中的一个环节——ros2 node list 确认图结构、ros2 topic info --verbose 确认 QoS 兼容性、tf2_echo 确认坐标变换、ros2 trace 确认回调级时序。把工具和排查假设关联起来,比记住命令参数重要得多。

高效调试 ROS2 系统与耗费数小时盲目猜测的区别在于系统化的方法:始终优先检查 QoS 兼容性(ros2 topic info --verbose),始终验证 TF 树的连通性(view_frames),以及始终确认时间源的一致性(use_sim_time,适用于所有节点)。 现有工具可诊断所有常见故障模式——从用于环境验证的 ros2 doctor,到用于微秒级回调分析的 LTTng 追踪,再到用于检测 C++ 插件中内存损坏的 AddressSanitizer。

对于 SLAM 工作流而言,最具影响力的习惯莫过于为数据包记录创建一个 QoS 覆盖 YAML 文件,并在不同会话中重复使用它。 若缺少此配置,您将记录空数据包并浪费数小时的实地测试时间。对于基于强化学习的控制,通过 ros2_control 实现的 Isaac Lab → ROS2 管道提供了最成熟的模拟到实景转换路径,而搭载 sb3_ros_support 的 Stable-Baselines3 则提供了最低的入门门槛。


调试方法论:从初学者到高效诊断者 ⭐⭐

这一节解决什么问题:把前面所有工具和技巧整合成一套可重复使用的方法论。目标不是让你记住更多命令,而是让你在面对任何 ROS2 系统问题时,有一个稳定的"思考框架"——先问什么、先做什么、怎么判断证据是否充分。

调试的三个认知误区 ⭐

误区一:"改了之后好了就是修好了"

很多调试以"重启后恢复正常"结束。这不是修复——这是临时恢复。如果不理解为什么重启能解决问题(是 DDS 缓存残留?是节点启动顺序变化?是 daemon 状态异常?),下次同样的条件组合出现时,问题会重现。

误区二:"平均值正常就没问题"

ros2 topic hz /joint_states 显示平均 999.8 Hz 不代表控制周期没有问题。控制系统关心的是 p99 和 max——即使 99% 的周期正常,1% 的异常周期仍会导致机器人抖动。统计分布比均值重要得多。

误区三:"工具越多越好"

同时打开 rqt_graph、rqt_console、PlotJuggler、RViz、四个终端跑 topic echo——这种"撒网"式调试效率极低。正确方式是根据假设选择一个工具,采集证据后决定下一步,而不是同时用所有工具希望"碰巧看到问题"。

调试的四个问题层级 ⭐⭐

ROS2 系统的问题可以按层级分类,每个层级对应不同的工具和方法:

层级 典型问题 首选工具 诊断时间
配置层 QoS 不匹配、参数错误、话题名拼写错 CLI(topic info、param get) 1-5 分钟
集成层 TF 断裂、时间源不一致、Lifecycle 状态错误 TF 工具、lifecycle 命令 5-15 分钟
算法层 SLAM 精度差、控制器增益不对、costmap 配置错误 PlotJuggler、RViz、bag 回放 15-60 分钟
系统层 延迟尖峰、线程竞争、内存泄漏 ros2 trace、GDB、ASan 30 分钟-数小时

调试效率的关键是正确判断问题在哪个层级,然后使用该层级的工具。大多数初学者犯的错误是在系统层花时间排查配置层的问题——比如用 GDB 调试"SLAM 不建图",实际上只是 QoS 不匹配。

跨领域类比:这个分层与医学诊断类似。患者说"头疼",医生不会立刻做 MRI——先量血压(配置层)、问病史(集成层)、做血检(算法层),只有排除了简单原因后才用昂贵的影像学检查(系统层)。ROS2 调试也是一样——先用 CLI 排除配置问题,再用 TF 工具排除集成问题,最后才用 trace 和 GDB 深入系统层。

练习

  1. [分析题] 一个 SLAM 系统在真机上建图质量比仿真差很多。按照四个问题层级设计排查计划,每个层级检查什么、用什么工具。
  2. [设计题] 为你的团队设计一个"调试记录模板",包含哪些必填字段?为什么每个字段都是必要的?
  3. [思考题] 解释为什么"同时打开所有调试工具"反而降低效率。提出一个按假设驱动的工具选择策略。

ROS2 高级调试模式与常见编程陷阱 ⭐⭐

ROS2 Action 编程模式 ⭐⭐

ROS2 的三种通信模式(Topic、Service、Action)不是"三种难度递增的 API"——它们面向不同时间尺度和交互模式的问题。理解它们的设计动机比记住它们的 API 更重要。

Topic 面向持续数据流——传感器以固定频率发布数据,不关心谁在听。它是"广播"模式:发布者和订阅者解耦,数量可以多对多。Service 面向瞬时请求-响应——一个节点请求另一个节点完成一个短暂操作(如查询参数、生成一个模型),调用者阻塞等待结果。它是"打电话"模式:一对一,同步,快速完成。

但机器人系统中有一类任务既不是持续数据流,也不是瞬时操作——比如"导航到某个位姿"(可能需要 30 秒到几分钟)、"执行一次抓取操作"(需要多步骤)。这类任务需要进度反馈("已完成 60%")和取消能力("用户改变了目标")。Topic 无法取消,Service 无法反馈进度。

Action 填补了 Topic(连续流、发后不管)和 Service(同步阻塞、不可取消)之间的空白。底层实现为 3 个 Service(send_goal/cancel_goal/get_result)+ 2 个 Topic(feedback/status)。.action 文件用 --- 分隔三段(Goal/Result/Feedback)。C++ Action Server 需实现三个非阻塞回调:handle_goal(接受/拒绝)、handle_cancel(接受取消)、handle_accepted(分离线程执行)。执行线程通过 goal_handle->publish_feedback() 发送进度、goal_handle->succeed() 或 goal_handle->canceled() 结束。Nav2 的 NavigateToPose Action 是最典型案例。

通信模式 Topic Service Action
持续时间 连续流 < 1 秒 秒级到分钟级
可取消 否 否 是
有反馈 否 否 是
适用场景 传感器数据、cmd_vel 参数查询、模型生成 导航、操作、对接

本质洞察:Topic、Service 和 Action 不是三种"难度递增"的通信方式,而是面向不同时间尺度和交互模式的工具。Topic 用于持续数据流(不关心谁在听),Service 用于瞬时请求-响应(阻塞调用者),Action 用于长时间异步任务(需要进度反馈和取消能力)。选择依据是任务的持续时间和交互需求,而不是"高级用户才用 Action"。

Callback Group 经典死锁模式 ⭐⭐

Callback Group 和 Executor 的交互是 ROS2 中最容易产生死锁的区域。死锁不同于普通 bug——程序不崩溃、不报错,只是永远"卡住"。如果你的 ROS2 节点在某个时间点之后不再响应任何话题、服务和定时器,且进程仍在运行,很可能就是 Callback Group 死锁。

死锁的根本原因是资源循环等待。在 ROS2 中,最常见的循环等待模式是:回调 A 在等待 Service B 的响应,但 Service B 的响应回调需要在 Executor 的同一个线程上执行——而这个线程此刻被回调 A 占用着。回调 A 等 Executor 调度 B 的回调,Executor 等回调 A 释放线程——经典的循环等待,永远不会解开。

在 SingleThreadedExecutor 的回调中调用 spin_until_future_complete() 会死锁——线程阻塞等待响应,但响应回调只能在同一个已阻塞的线程上执行。

解决方案: 1. 将 client 和 timer 放在不同 MutuallyExclusive 组 + MultiThreadedExecutor 2. 完全异步(推荐)——client_->async_send_request(request, [](auto response) { /* 处理结果 */ })

规则:在 SingleThreadedExecutor 中,永远不要在回调内阻塞等待另一个回调的结果。


⚠️ 调试工具常见陷阱

⚠️ 工程陷阱:ros2 topic echo /scan 无输出就认为传感器坏了

错误做法:看到 echo 没输出,立刻去检查硬件连接或驱动代码。

现象/后果:浪费大量时间在硬件和驱动排查上,实际上问题只是 QoS 不匹配。

根本原因:CLI 的 echo 默认用 Reliable 订阅,但大多数传感器驱动以 BestEffort 发布。不匹配时不报错、不连接、不传数据——完全沉默。

正确做法:先用 ros2 topic info /scan --verbose 确认发布方存在及其 QoS 策略,再用 ros2 topic echo /scan --qos-reliability best_effort 或 --qos-profile sensor_data 重试。

⚠️ 工程陷阱:录制 SLAM bag 时忘记 QoS 覆盖

错误做法:直接 ros2 bag record /scan /imu /tf /tf_static,不指定 QoS 覆盖文件。

现象/后果:录制的 bag 中传感器话题为空(0 条消息),浪费了整次实地测试。

根本原因:rosbag2 默认以 Reliable 订阅,传感器的 BestEffort 发布不兼容。/tf_static 需要 TransientLocal durability。

正确做法:始终使用 QoS 覆盖文件:ros2 bag record --qos-profile-overrides-path slam_qos.yaml ...。创建并复用 slam_qos.yaml,覆盖 /scan、/imu、/camera/* 为 BestEffort,/tf_static 为 TransientLocal。

⚠️ 概念误区:认为 ros2 topic hz 平均频率正常就没有延迟问题

新手想法:"ros2 topic hz /joint_states 显示平均 999.8 Hz,控制周期没问题。"

实际上:平均频率会掩盖偶发尖峰。控制系统关心的是 p95、p99 和最大周期。一个平均 1 kHz 但 p99 为 15 ms 的系统,仍会导致偶发控制抖动。

正确做法:用 --window 参数增大统计窗口,关注 max period;用 ros2 trace 采集回调级时间戳分析分布。

⚠️ 工程陷阱:用 ros2 bag play 回放但 SLAM 不动

错误做法:ros2 bag play slam_dataset/ 不加任何参数。

现象/后果:数据在流动,但 SLAM 节点不更新地图,RViz 中时间停在 0。

根本原因:SLAM 节点设置了 use_sim_time:=true 但 /clock 未发布,或反过来 SLAM 未设置仿真时间。

正确做法:ros2 bag play --clock 100 slam_dataset/,且所有消费节点(SLAM、RViz、TF listener、robot_state_publisher)都设置 use_sim_time:=true。

🧠 思维陷阱:问题"重启后好了"就结束调试

新手想法:"重启 launch 文件后恢复正常,问题解决了。"

实际上:"重启后好了"只是排除了永久性故障(如配置文件错误),但没有解释瞬态故障的根因。节点启动顺序变化、DDS 缓存残留、时序竞争等问题可能在下次部署时重现。

正确思维:重启是临时恢复,不是诊断结论。应记录重启前的状态(日志、topic info、lifecycle state),形成最小复现步骤。

本质洞察:ROS2 调试的核心能力不是记住所有命令,而是掌握证据链思维——每个调试步骤要么排除一个假设,要么确认一个假设。好的调试者在修复问题时能说出"这个症状由 X 引起,我通过 Y 命令确认了 X,排除了 Z 和 W"。坏的调试者"改了几个东西,不知道哪个起作用了"。

本质洞察:ros2 doctor 的价值不在于"自动修复问题",而在于"一次性采集系统级证据"。它检查 RMW 实现、DDS 配置、网络多播状态、节点图完整性和 QoS 兼容性。当你不知道问题在哪一层时,ros2 doctor --report 是最好的起点——它相当于给整个 ROS2 系统做了一次体检。


跨章综合练习

  1. [设计哲学与架构演进 + SLAM导航与仿真生态 + CLI调试与性能工具] 一个 SLAM 系统在仿真中地图质量好,但在真机上出现双层墙。设计一条完整的证据链:从 ros2 topic hz 验证传感器频率,到 tf2_monitor 检查 TF 延迟,到 ros2 bag record 录制数据后 ros2 bag play 离线回放,到 PlotJuggler 分析里程计漂移。最终判断问题是传感器外参、里程计质量还是回环检测失效。

  2. [硬件集成与RL部署 + CLI调试与性能工具] 一个四足 RL 控制器上机后出现偶发抖动(每 10 秒一次持续 50 ms 的关节跳变)。设计分层排查方案:先用 ros2 topic hz --window 200 排除话题频率问题,再用 ros2 trace 定位是回调排队还是回调内部耗时,最后用 GDB 条件断点检查耗时回调的具体代码路径。

  3. [设计哲学与架构演进 + CLI调试与性能工具] 在一个多机器人系统中,robot1 能看到 robot2 的话题但无法 echo。使用 设计哲学与架构演进 的 DDS/RMW 知识和 CLI调试与性能工具 的调试工具,设计排查流程:检查 ROS_DOMAIN_ID、RMW_IMPLEMENTATION 一致性,用 ros2 multicast 测试网络,用 ros2 topic info --verbose 比较 QoS。


本章小结

知识点 核心要点 工程意义
证据链思维 症状→假设→证据采集→判断→最小改动 避免盲目修改,形成可复现的调试经验
CLI 命令族 七大核心命令族 + 七个进阶命令族 按"排查哪个假设"选择工具
QoS 诊断 ros2 topic info --verbose 是第一工具 解决 90% 的"话题存在但无数据"问题
TF 调试 view_frames + tf2_echo + tf2_monitor 定位坐标链断裂和时间戳不一致
TF 常见问题 外推到未来/过去/双树断开/命名空间冲突 每种症状有确定的排查路径
Bag 录制 QoS 覆盖文件 + --clock + use_sim_time 统一 避免录到空 bag 和回放不工作
MCAP 格式 自包含、仅追加、Zstd 压缩 ROS2 默认格式,比 SQLite3 更安全
rosbag2 高级 快照模式、分片、过滤、合并、格式转换 数据集管理的完整工具链
ros2 trace LTTng 回调级追踪,0.0033 ms/tracepoint 定位延迟瓶颈在回调、executor 还是 DDS
callback 延迟诊断 p50/p95/p99/max 分布分析 区分正常变异和异常尖峰
DDS 性能诊断 内核缓冲区、多播测试、CycloneDDS XML 大消息传输和网络问题的根因定位
GDB/ASan Debug 构建 + prefix 启动 + 条件断点 崩溃和内存问题的最终定位工具
rqt 工具族 rqt_graph/rqt_tf_tree/rqt_reconfigure/rqt_console GUI 级系统监控和在线调参
DDS 调优 CycloneDDS XML + 内核缓冲区 大消息(点云/图像)的传输优化
Launch 系统 Python 可编程、Composition、事件驱动 可复现的启动配置
Lifecycle 诊断 ros2 lifecycle get + ros2 control list_controllers 区分"对象存在"和"对象激活"
分层诊断流程 topic→trace→LTTng→GDB 逐层缩小范围 从宏观到微观系统化定位

本质洞察:ROS2 调试的核心能力不是"记住所有命令的参数",而是"面对未知症状时能系统化地缩小问题范围"。好的调试者在修复问题时能说出"这个症状由 X 引起,我通过 Y 命令确认了 X,排除了 Z 和 W"。坏的调试者"改了几个东西,不知道哪个起作用了"。证据链思维是区分两者的关键。

与其他章节的衔接

本章提供的调试能力贯穿整个 ROS2 工程化实践系列:

章节 本章提供的调试支持
设计哲学与架构演进 QoS 诊断确认通信语义是否正确配置;RMW 验证确认中间件一致性
SLAM导航与仿真生态 TF 调试确认 SLAM→Nav2 的坐标链完整;bag 录制回放是 SLAM 开发的核心工作流
硬件集成与RL部署 ros2_control 诊断确认控制器和硬件接口状态;ros2 trace 定位控制回调的延迟尖峰

调试工具不是"学完就忘"的章节——它是你在整个 ROS2 开发生涯中每天都会用到的基础能力。建议把本章的证据链模板和排查清单打印出来贴在工位旁边,直到内化为习惯。

从本章到实践的过渡

本章的所有工具和方法论都是为了一个目标:让你在面对"系统不工作"时不再恐慌,而是有条理地缩小问题范围直到找到根因。

实践中建议建立以下习惯:

  1. 每个项目第一天就创建 slam_qos.yaml——避免录制空 bag 浪费实验时间。
  2. 每次调试记录三行日志——症状、执行的命令、排除或确认的假设。
  3. 每周回顾一次调试记录——看是否有重复出现的问题类型,是否需要修改配置或流程。
  4. 为常用调试流程写脚本——比如一个 debug_slam.sh 同时运行 view_frames、topic hz /scan、topic info /scan --verbose。
  5. 用 catmux 创建调试工作空间——一键打开 6 个终端面板,各自运行不同的监控命令。
# catmux 配置示例:slam_debug.yaml
windows:
  - name: launch
    panes:
      - ros2 launch my_robot_bringup slam.launch.py
  - name: monitor
    panes:
      - ros2 topic hz /scan
      - ros2 topic hz /odom
  - name: tf
    panes:
      - ros2 run tf2_ros tf2_echo map odom
      - ros2 run tf2_ros tf2_monitor
  - name: debug
    panes:
      - ros2 topic info /scan --verbose
      - # 留空,用于临时命令

这样每次开始调试会话时,只需要 catmux_create_session slam_debug.yaml,就能立刻进入结构化的调试环境,而不是在多个终端之间手动切换和输入命令。

累积项目:本章新增模块

ROS2 工程化实践项目续:

本章新增模块:调试工具链与数据管理

  1. 创建 slam_qos.yaml 覆盖文件,覆盖所有 SLAM 相关传感器话题
  2. 录制一段 SLAM 数据集(MCAP 格式),包含 /scan、/imu、/odom、/tf、/tf_static
  3. 用 --clock 回放并验证 slam_toolbox 能正确建图
  4. 用 view_frames 生成 TF 树 PDF,确认符合 REP-105
  5. 创建一个 catmux 会话配置,包含 SLAM launch、bag play、RViz、topic monitor 四个面板
  6. 采集一次 ros2 trace,用 babeltrace 查看回调事件
  7. 用 PlotJuggler 加载 bag 文件,分析里程计位姿与 SLAM 位姿的差异
  8. 创建一个调试记录模板,记录至少三次调试过程

项目检查清单:

检查项 通过标准
QoS 覆盖文件有效 录制 bag 中传感器话题非空
MCAP 格式正确 mcap info 显示所有话题和消息数
回放 SLAM 可建图 离线回放时地图逐步扩展
TF 树符合 REP-105 view_frames 显示单棵树 map→odom→base_link
catmux 配置可用 一键启动调试工作空间
trace 采集成功 babeltrace 显示 callback_start/callback_end 事件
调试记录完整 每条记录包含症状、排查命令和结论

调试工具的投资回报分析 ⭐

学习调试工具需要时间投入。以下是各工具的投资回报估计,帮助你决定优先学习什么:

工具 学习成本 使用频率 每次节省的时间 投资回报
ros2 topic info --verbose 5 分钟 每天多次 10-60 分钟 极高
view_frames + tf2_echo 10 分钟 每周几次 30-120 分钟 极高
rosbag2 + QoS 覆盖 30 分钟 每次实验 1-3 小时 极高
PlotJuggler 1 小时 每次分析 30-60 分钟 高
rqt_reconfigure 10 分钟 调参时 5-15 分钟/次重启 高
ros2 trace + LTTng 2-3 小时 性能问题时 1-4 小时 中(低频但高价值)
GDB + ASan 2-3 小时 崩溃时 2-8 小时 中(低频但不可替代)
catmux 30 分钟 每天 5 分钟/次启动 中

建议学习顺序:先掌握前三项(CLI QoS 诊断 + TF + bag),它们解决 90% 的日常问题。然后学 PlotJuggler 和 rqt_reconfigure,提升调参效率。最后在遇到性能问题或崩溃时学习 trace 和 GDB。

本质洞察:调试工具的价值不在于"学了多少工具",而在于"能多快把问题从'不知道怎么回事'变成'知道是哪一层的问题'"。第一步的速度决定了整个调试的效率——而 ros2 topic info --verbose 和 view_frames 这两个命令就能完成 70% 以上的"第一步定位"。把它们练到条件反射的程度,是最值得的时间投资。

🔧 故障排查手册

症状 可能原因 排查步骤 相关小节
ros2 topic echo 无输出 QoS 不匹配(最常见) 1. ros2 topic info /topic --verbose 2. 比较 Reliability 策略 3. 用 --qos-profile sensor_data CLI 参考
ros2 node list 为空 daemon 状态异常或 domain ID 错误 1. ros2 daemon stop && ros2 daemon start 2. echo $ROS_DOMAIN_ID CLI 参考
bag 回放时 SLAM 不动 /clock 未发布或 use_sim_time 不统一 1. ros2 bag play --clock 100 2. 所有节点 use_sim_time:=true 3. 确认 bag 含 /tf_static Bag 章节
TF "extrapolation into the future" use_sim_time 不一致或 TF 广播延迟 1. 统一 use_sim_time 2. 用 tf2::TimePointZero 查询 3. 增加 TF 缓冲区 TF 调试
控制器 active 但机器人不动 命令接口未 claimed 或硬件未使能 1. ros2 control list_hardware_interfaces 2. 检查驱动器错误码 Lifecycle 案例
控制周期偶发超过阈值 回调排队、日志阻塞或内存分配 1. ros2 trace 定位慢回调 2. 关闭高频日志 3. 检查 executor 线程数 Performance 案例
bag 录制传感器话题为 0 条消息 QoS 覆盖文件缺失 创建 slam_qos.yaml,传感器话题设为 BestEffort Bag 章节

调试经验总结:SLAM 工程师的日常工具链 ⭐⭐

经过本章的学习,一个 SLAM/导航工程师的日常调试工具链可以组织成以下层次。每一层解决一类问题,从最常见到最深层:

第一层:CLI 快速诊断(90% 的问题在这里解决)

# 五步快速诊断
ros2 node list                          # 节点是否存在
ros2 topic list -t                      # 话题是否存在
ros2 topic info /scan --verbose         # QoS 是否匹配
ros2 topic hz /scan                     # 频率是否正常
ros2 run tf2_tools view_frames          # TF 是否完整

如果这五步都正常但系统仍有问题,进入第二层。

第二层:数据录制和离线分析

# 录制问题发生时的数据
ros2 bag record -s mcap --qos-profile-overrides-path slam_qos.yaml \
  /scan /imu /odom /tf /tf_static -o debug_session

# 离线分析
mcap info debug_session/
ros2 bag play debug_session/ --clock 100  # 可复现地回放

离线分析的优势是可以反复测试同一段数据,修改参数后重新回放观察差异。这比在真机上反复跑实验高效得多。

第三层:性能追踪

# 当频率统计正常但系统仍有偶发问题时
ros2 trace --session-name debug-latency
# 运行最小复现场景
# 分析 trace 中的回调持续时间和排队延迟

第四层:代码级调试

# 当 trace 定位到特定回调后
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug
ros2 run --prefix 'gdb -ex run --args' my_pkg my_node

本质洞察:调试效率与"在正确的层级使用正确的工具"成正比。用 GDB 调试 QoS 问题是浪费时间——ros2 topic info --verbose 一条命令就能解决。用 ros2 topic hz 调试回调内部的性能尖峰也不行——它只看到频率的统计值,看不到单次回调的耗时分布。匹配问题层级和工具层级,是高效调试的关键。

调试记录模板 ⭐

建议为每次非平凡的调试会话保留一份简短记录。记录不需要很长——三行足够:

## 2026-05-16 SLAM 不建图

**症状**:slam_toolbox 启动后 /map 话题无输出

**排查过程**:
1. `ros2 topic info /scan --verbose` → 发布者 BestEffort,订阅者 Reliable → QoS 不匹配
2. 修改 slam_toolbox 参数添加 `qos_overrides./scan:reliability=best_effort`
3. 重启后 /map 开始发布

**根因**:slam_toolbox 默认 QoS 与传感器驱动不兼容
**修复**:在 slam_params.yaml 中添加 QoS 覆盖

这类记录的价值不在于"当时"——而在于"三个月后另一台机器人遇到相同问题时"。如果没有记录,你会重新经历一遍相同的排查过程。

调试工具选择决策树 ⭐

问题是什么类型?
  │
  ├── 数据不流动(话题无输出)
  │     └── ros2 topic info --verbose → QoS 诊断
  │
  ├── 坐标/位姿不对
  │     └── view_frames + tf2_echo → TF 链路诊断
  │
  ├── 偶发性能问题(抖动、延迟尖峰)
  │     └── ros2 trace → 回调级延迟分析
  │
  ├── 节点崩溃
  │     └── GDB + ASan → 代码级调试
  │
  ├── 不确定问题在哪层
  │     └── ros2 doctor --report → 系统级健康检查
  │
  └── 需要复现问题
        └── ros2 bag record → 离线回放

每个分支只需要记住一个"入口命令"——后续的排查步骤会根据第一步的结果自然展开。

延伸阅读

资源 难度 说明
ros2/ros2cli 源码 ⭐⭐ 理解 CLI 插件架构
ros2/ros2_tracing ⭐⭐⭐ LTTng 集成与 tracetools_analysis
ros2_tracing 论文 ⭐⭐⭐ Bedard et al., 追踪框架的设计与性能分析
MCAP 规范 ⭐⭐ ROS2 默认 bag 格式的技术规范
PlotJuggler ⭐ 时间序列可视化的事实标准
PlotJuggler ROS 插件 ⭐ ROS2 话题和 bag 支持
catmux ⭐ tmux 会话管理,ROS 多终端工作流
Foxglove Bridge ⭐⭐ 远程 WebSocket 可视化桥接
grepros ⭐⭐ grep 风格的 bag 搜索工具
ros2bag_extensions (Tier4) ⭐⭐ Autoware 中使用的 bag 过滤/合并/切片
rqt 插件列表 ⭐ 所有可用 rqt 插件的概览
LTTng 官方文档 ⭐⭐⭐ 底层追踪框架的完整参考
TraceCompass ⭐⭐⭐ LTTng trace 的 GUI 分析工具
colcon-sanitizer-reports ⭐⭐ ASan/TSan 与 colcon 测试集成

阅读建议:

  • CLI 新手:不需要先读文档——直接在真实系统上练习 ros2 topic list -t → ros2 topic info --verbose → ros2 topic echo 的三步流程。10 分钟的实践胜过 1 小时的阅读。
  • 性能调试:先读 ros2_tracing 论文理解追踪架构,再在自己的系统上采集一次 trace,用 babeltrace 查看事件。TraceCompass 提供 GUI 分析,适合不熟悉命令行的用户。
  • bag 管理:先跑通 ros2 bag record + ros2 bag play --clock 的基本流程,再逐步添加 QoS 覆盖、MCAP 压缩和快照模式。grepros 工具对从大 bag 中搜索特定消息非常有用。
  • rqt 工具:先用 rqt_reconfigure 在线调一次参数,体会"不重启节点就能改参数"的便利。然后尝试创建自定义 perspective,一键恢复调试布局。
  • DDS 调优:只在遇到大消息传输问题时才需要。先增大内核缓冲区(sysctl 命令),如果不够再调 CycloneDDS XML。

调试工具与其他章节的工具链整合

本系列四章的工具形成了一个完整的工具链,覆盖从架构理解到代码调试的全部需求:

设计哲学与架构演进 → 提供架构层面的理解
  │ 理解 QoS/DDS/Lifecycle/RMW 的设计动机
  │
SLAM导航与仿真生态 → 提供应用层面的配置
  │ 理解 Nav2/SLAM/Gazebo 的配置和调参
  │
硬件集成与RL部署 → 提供硬件层面的安全保障
  │ 理解 ros2_control/总线/嵌入式/RL 部署
  │
CLI调试与性能工具 → 提供贯穿所有层的诊断能力
  │ 从 CLI 快速诊断到 LTTng 深度追踪
  │
  ↓
完整的 ROS2 工程化能力

当系统出问题时,你需要的不只是"一个工具",而是"知道在哪个层级用哪个工具"。本章提供的证据链思维和分层诊断方法论,是把四章知识串联成实际调试能力的关键。

本质洞察:ROS2 调试的最终目标不是"让系统跑起来",而是"让系统可靠地跑"。"跑起来"只需要配置正确;"可靠地跑"需要理解每个失败模式、每个边界条件和每个降级路径。CLI 工具帮你确认配置正确,trace 帮你确认性能可靠,GDB 帮你确认代码安全——三者合在一起,才构成"可靠"的完整定义。

本章结语

回顾本章的四个核心教学目标:

  1. 构建证据链 ---- 你应该能从任何症状出发,按"假设→证据→判断→最小改动"的循环逐步缩小问题范围,而不是盲目改参数。

  2. 诊断 QoS/TF/Lifecycle ---- 你应该能在 5 分钟内用 ros2 topic info --verbose、view_frames 和 ros2 lifecycle get 排除 90% 的基础设施问题。

  3. 录制和回放数据 ---- 你应该能创建 QoS 覆盖文件、用 MCAP 格式录制 SLAM 数据集、用 --clock 正确回放,并理解 use_sim_time 的必要性。

  4. 定位性能瓶颈 ---- 你应该能用 ros2 trace 采集回调级时间戳,区分"回调本身慢"和"回调排队等待",并知道何时需要深入到 GDB 或 ASan。

这四个能力不是一次性学会的——它们需要在真实项目中反复练习才能内化。建议在接下来的两周内,每次遇到 ROS2 问题时有意识地使用本章的方法论,而不是依赖直觉或随机尝试。两周后你会发现,调试速度提升了至少 3 倍——不是因为你记住了更多命令,而是因为你学会了"在正确的层级问正确的问题"。

📎 QoS 配置原理与 SLAM 传感器/地图的最佳 QoS 策略,详见软件工程/ROS2高级集成。RViz2 架构与插件开发详见SLAM导航与仿真生态。

📎 ros2_control 的硬件调试命令(ros2 control list_controllers、ros2 control list_hardware_interfaces)的详细用法和案例分析在本章的 Lifecycle 案例中有展开。硬件接口的设计和实现详见硬件集成与RL部署。

📎 DDS 的架构层面理解(SPDP/SEDP 发现、QoS 请求/提供模型、RMW 抽象)在设计哲学与架构演进的 §2、§13、§14 中有完整讲解。本章聚焦的是"出问题时怎么用这些知识定位原因",而不是重复解释架构本身。

📎 硬件层面的调试(ros2_control 控制器状态、硬件接口生命周期、通信总线延迟)在硬件集成与RL部署的 §2 和 §7 中有从硬件视角的详细分析。本章提供的是 CLI 工具层面的诊断命令,两章配合使用效果最佳。