Appearance
诊断监控与故障定位
在知识图谱中的位置:模块四 · 04_工程实践 · 第 3 节 难度:⭐⭐⭐ | 前置知识:模块一·基础概念(话题/QoS/TF)、01_ROS2应用架构设计模式.md(错误隔离)、02_ROS2测试体系.md(QoS 校验)
1. 概述
机器人在真机上出问题,往往是"传感器有数据、节点在跑、但行为不对"这类软故障,而不是崩溃。ROS 2 崩溃会留下 core 和日志,而软故障只会留下"话题没数据 / 频率不对 / TF 过期"这些症状。本节讲三件事:① 如何把系统"变得可观测"(日志、诊断、可视化三件套);② 典型故障的定位 playbook(话题无数据、节点卡死、TF 过期);③ 性能剖析与生命周期看门狗。
核心心法:先看拓扑(有没有)→ 再看契约(对不对)→ 最后看数据流(通不通),这个顺序能解决 80% 的 ROS 2 故障。
2. 核心概念
2.1 可观测三件套
| 维度 | 工具 | 回答的问题 |
|---|---|---|
| 日志(Logs) | RCLCPP_* 宏 + logger 层级 + rqt_console | 节点内部发生了什么 |
| 诊断(Diagnostics) | diagnostic_updater / diagnostic_aggregator | 子系统"健不健康" |
| 可视化(Introspection) | rqt_graph / rqt_plot / ros2 topic hz | 数据在系统里怎么流动 |
三者互补:日志是"事后取证",诊断是"实时健康状态",可视化是"运行时拓扑与流量"。
2.2 日志宏族与 logger 层级
ROS 2 日志基于 rcutils,按严重级别分级(DEBUG < INFO < WARN < ERROR < FATAL),每个 logger 有自己的名字和级别阈值,低于阈值的日志被丢弃。logger 名用点号层级组织,继承父级设置:
cpp
auto logger = this->get_logger(); // logger 名 = 节点名,如 "nav_controller"
auto child = this->get_logger().get_child("odom"); // 子 logger = "nav_controller.odom"
RCLCPP_INFO(logger, "pose received: x=%.2f", msg.x);
RCLCPP_WARN(child, "odom covariance too large");
RCLCPP_ERROR(logger, "planning failed: %s", e.what());2.3 diagnostics 包:主动健康上报
diagnostic_updater(https://index.ros.org/p/diagnostic_updater/,源码 https://github.com/ros/diagnostics)让节点周期上报自己的健康状态,而不是等上层来猜。节点把自己的关键指标(CPU、内存、传感器帧率、电池、温度)打包成 diagnostic_msgs/DiagnosticStatus(OK / WARN / ERROR / STALE),发布到 /diagnostics。diagnostic_aggregator 再把多个节点的状态聚合成一张整机健康图,供状态机/看门狗/仪表盘消费。
2.4 故障定位的"三层顺序"
① 拓扑层(实体存在吗)→ ros2 node list / ros2 topic list / rqt_graph;② 契约层(QoS/类型/命名对不对)→ ros2 topic info --verbose;③ 数据流层(数据真的在流吗)→ ros2 topic echo/hz/bw。绝大多数故障死在①和②,根本到不了"算法逻辑"。
3. 技术原理/方法
3.1 日志:级别控制与环境变量
- 运行时设级别:
ros2 run my_pkg my_node --ros-args --log-level DEBUG(只影响该节点)。 - per-logger 设级别:代码里
logger.set_level(rclcpp::Logger::Level::Debug)只放开某个子 logger,避免全局 DEBUG 刷屏。 - 控制台输出:环境变量
RCUTILS_CONSOLE_OUTPUT_FORMAT自定义格式,RCUTILS_COLORIZED_OUTPUT=0关彩色,RCUTILS_LOGGING_BUFFERED_STREAM=0关缓冲(真机崩溃时日志不丢)。 - 日志设计规范:官方设计文档 https://design.ros2.org/articles/logging.html。
说明:ROS 2 的日志级别控制主要通过
--log-level参数与 logger API,官方文档未提供名为ROS_LOG_LEVEL的环境变量(那是 ROS 1 生态的概念);本节以--log-level为准。
3.2 诊断:diagnostic_updater + diagnostic_aggregator
cpp
#include <diagnostic_updater/diagnostic_updater.hpp>
// 节点内
diagnostic_updater::Updater updater{this};
updater.setHardwareID("lidar");
updater.add("scan_frequency", this, &MyNode::checkScanRate);
// 周期调用 updater.force_update(); 或 setPeriod + 定时调用 updater.update()
void MyNode::checkScanRate(diagnostic_updater::DiagnosticStatusWrapper& stat) {
if (scan_hz_ < 5.0) {
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::ERROR, "scan rate too low");
} else {
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "normal");
}
stat.add("scan_hz", scan_hz_);
}bash
# 观测
ros2 topic echo /diagnostics # 原始诊断
ros2 topic echo /diagnostics_agg # 聚合后(需 aggregator)
ros2 run diagnostic_aggregator aggregator_node # 示例启动diagnostic_aggregator 支持分析器(analyzer):比如"任一 Error 级别状态即整体 Error",供上层状态机订阅 /diagnostics_agg 做降级决策。
3.3 可视化与运行时探测
bash
rqt_graph # 拓扑图:节点/话题连线,一眼看循环依赖、断链
rqt_console # 日志查看器:按级别/logger 过滤,实时监控
rqt_plot # 话题数值曲线:看频率/幅值抖动
rqt_tf_tree # TF 树:找 frame 断链
ros2 topic list -t # 列出话题+类型
ros2 topic info /scan --verbose # 看发布者/订阅者 + 各自 QoS(契约核对)
ros2 topic hz /scan # 实际发布频率
ros2 topic bw /scan # 带宽
ros2 topic echo /scan --once # 单帧数据
ros2 node info /node # 节点订阅/发布/服务/Action 清单3.4 性能剖析:ros2 tracing 与 perf
- ros2 tracing(https://github.com/ros2/ros2_tracing):基于 LTTng 的内核级/用户级追踪,能拿到回调执行时长、DDS 收发时间戳、executor 调度等精确时序,教程 https://docs.ros.org/en/jazzy/Tutorials/Advanced/ROS2-Tracing-Trace-and-Analyze.html。
- perf:Linux 通用 CPU 剖析,
perf record -g ./node && perf report,找热点函数;perf sched看调度抖动。
bash
ros2 trace # 开始追踪(需 tracetools 环境)
# ... 复现问题 ...
ros2 trace stop # 停止并生成 trace 数据,用 tracecompass/babeltrace 分析3.5 生命周期与看门狗:transition 作为运行态信号
managed node(rclcpp_lifecycle)的状态机本身就是健康信号:active = 正常运行,inactive = 降级/停止,finalized = 终结。看门狗可以:
- 订阅
/diagnostics,某个关键状态 ERROR 超过 N 秒 → 触发降级。 - 调用
ros2 lifecycle set /node configure/inactivate让故障节点退出 active。 - 用
on_exit事件(见 01_ROS2应用架构设计模式.md)重启进程。
状态查询/操作:
bash
ros2 lifecycle list /node # 列出可用状态
ros2 lifecycle get /node # 当前状态
ros2 lifecycle set /node activate # 激活官方生命周期设计:https://design.ros2.org/articles/node_lifecycle.html。
4. 实践指南
4.1 典型故障定位 playbook
Playbook ① 话题无数据
bash
ros2 topic list | grep scan # ① 话题存在吗?没有→节点没起/话题名写错
ros2 node info /lidar_node # ② 节点订阅/发布清单里有没有它
ros2 topic info /scan --verbose # ③ 发布者/订阅者 QoS 是否匹配?
# BEST_EFFORT vs RELIABLE 静默丢数据
ros2 topic echo /scan -q # ④ 发布者到底发没发(-q 只看匹配的订阅)
ros2 topic hz /scan # ⑤ 频率是否正常(0 → 发布者内部卡死)根因排序:话题名/命名空间不匹配 > QoS 不匹配 > 发布者内部异常(用
rqt_console看 ERROR)> 发布者 executor 阻塞。
Playbook ② 节点卡死(executor 死锁/阻塞)
bash
ros2 node info /node # 节点还在吗?(还在=卡,不在=崩了)
ros2 topic hz /node/out # 输出停更但节点在 → executor 阻塞
# 症状:某回调里 sleep/阻塞 IO/加锁不释放,单线程 executor 全被卡住排查:① 检查是否有长阻塞回调(网络、文件、std::this_thread::sleep_for)占住 SingleThreadedExecutor;② 是否有死锁(回调里拿锁,另一个线程持锁等回调);③ 改用 MultiThreadedExecutor + ReentrantCallbackGroup,把慢回调隔离到独立回调组/线程;④ ros2 tracing 看是哪个回调卡住。
Playbook ③ TF 过期(frame 链断)
bash
ros2 run tf2_ros tf2_echo map base_link # 查 map→base_link 变换
ros2 run rqt_tf_tree rqt_tf_tree # 看整棵 TF 树,找断点
ros2 topic hz /tf # TF 发布频率排查:① tf2_echo 报"frame does not exist" → 某段 TF 没发布(里程计/定位没起);② 报"lookup would require extrapolation" → TF 还在但过期(频率低、丢帧、时间戳不连续),检查发布者频率与 /use_sim_time 一致性;③ 静态 TF 用 StaticTransformPublisher 一次性发布,别塞进高频 /tf。
4.2 最佳实践
- 关键节点全部上报 diagnostics(传感器帧率、CPU、内存、电池),这是量产的"仪表盘"而不是锦上添花。
- 日志级别分层:默认 INFO,调试时
--log-level DEBUG,真机 ERROR 要能触发告警。 - 命名规范让你可 grep:logger 用
get_child建层级(node.driver.lidar),排查时rqt_console按前缀过滤。 - bag 是复现利器:真机出问题先
ros2 bag record -a抓现场,回放复现 + 定位,见 02_ROS2测试体系.md。
4.3 陷阱
- QoS 不匹配无显式报错:ROS 2 不会主动弹错误,只会静默收不到——这是"话题无数据"的头号根因。
ros2 topic echo有输出 ≠ 你的节点收得到:echo 会自动适配 QoS,你的订阅者 QoS 可能仍不匹配。用--verbose逐项核对。- STALE 诊断状态:诊断消息默认有失效时间(stale),节点崩溃后
/diagnostics上的状态会变 STALE,看门狗要处理 STALE 而非只看 ERROR。 - 日志缓冲:真机崩溃时,缓冲的日志可能没刷盘,
RCUTILS_LOGGING_BUFFERED_STREAM=0关缓冲换实时性。
4.4 调优
- 日志开销:
RCLCPP_DEBUG在编译时可用-DRCUTILS_LOGGING_MIN_SEVERITY裁剪(生产关 DEBUG 减少开销);高频回调里避免打 INFO。 - 诊断频率:诊断周期别和业务同频(如 100Hz),1Hz 足够,避免
/diagnostics刷爆带宽。 - 追踪开销:LTTng 追踪有 CPU 开销,只在复现时开启,别常驻生产。
5. 方案对比
| 监控方案 | 实时性 | 实现成本 | 适用场景 | 局限 |
|---|---|---|---|---|
| 日志(RCLCPP_*) | 事后 | 极低 | 开发/调试/取证 | 无主动健康判断 |
| 诊断(diagnostics) | 实时(1Hz 级) | 低 | 量产健康监控、看门狗 | 需各节点配合上报 |
| 可视化(rqt) | 实时人工 | 极低 | 开发联调 | 需人看,不能自动告警 |
| 追踪(ros2 tracing/perf) | 离线分析 | 中 | 性能瓶颈、调度抖动 | 开销大、数据量大 |
| 生命周期看门狗 | 实时自动 | 中 | 故障自动降级/恢复 | 需设计状态机与降级策略 |
绝对不适用场景:当问题是偶发、难复现、涉及跨进程时序(如某次 DDS 延迟尖峰导致控制抖动)时,不要只靠加日志——日志会掩盖时序问题且难以海量检索,应直接上 ros2 tracing 抓精确时序;反之,稳态健康监控用 tracing 则是杀鸡用牛刀,diagnostics 才是正确工具。
6. 工具链
| 工具/包 | 用途 | 来源 |
|---|---|---|
rclcpp 日志宏 + --log-level | 日志分级 | 官方 |
rqt_console / rqt_graph / rqt_plot / rqt_tf_tree | 可视化 | 官方(desktop 装) |
diagnostic_updater / diagnostic_aggregator | 健康上报与聚合 | ros/diagnostics |
ros2 topic/node/lifecycle CLI | 运行时探测 | 官方 |
ros2 tracing(tracetools) | LTTng 追踪 | ros2/ros2_tracing |
perf | CPU/调度剖析 | Linux 通用 |
ros2 bag | 现场录制/回放复现 | 官方 |
7. 参考资料
- ROS 2 日志与 logger 配置(含环境变量):https://docs.ros.org/en/jazzy/Concepts/Intermediate/About-Logging.html ✅
- ROS 2 日志设计:https://design.ros2.org/articles/logging.html ✅
- diagnostics 包索引:https://index.ros.org/p/diagnostic_updater/ ✅;源码:https://github.com/ros/diagnostics ✅
- ros2 tracing 教程:https://docs.ros.org/en/jazzy/Tutorials/Advanced/ROS2-Tracing-Trace-and-Analyze.html ✅;仓库:https://github.com/ros2/ros2_tracing ✅
- 生命周期设计:https://design.ros2.org/articles/node_lifecycle.html ✅
- ros2 bag 录制/回放:https://docs.ros.org/en/jazzy/Tutorials/Beginner-CLI-Tools/Recording-And-Playing-Back-Data/Recording-And-Playing-Back-Data.html ✅
8. 学习路径
- 入门:跑两个节点,用
rqt_graph看拓扑,rqt_console看日志,ros2 topic info --verbose看 QoS。 - 上手:给一个节点加
RCLCPP_*分层日志 +--log-level DEBUG,体会 logger 层级。 - 进阶:给传感器节点加
diagnostic_updater,用/diagnostics看健康状态。 - 实战:按 4.1 三个 playbook 依次制造并定位"话题无数据 / 节点卡死 / TF 过期"三种故障。
- 串联:进入 04_持续构建发布与部署.md,把这些监控手段纳入发布与部署流水线。
9. 核心面试三问
Q1:真机上"话题有名字但收不到数据",你按什么顺序排查?
答:严格按"拓扑→契约→数据流"三层:①
ros2 topic list/ros2 node info确认实体存在(节点是否在跑、话题名/命名空间是否重映射正确);②ros2 topic info /xx --verbose核对发布者与订阅者 QoS(reliability/durability/depth)是否一致——这是 ROS 2 头号静默故障;③ros2 topic echo/hz确认发布者真在发、频率正常。特别注意echo有输出不代表你的节点收得到(echo 自动适配 QoS),必须逐项核对契约。
Q2:日志、诊断(diagnostics)、追踪(tracing)三者区别是什么?各在什么时机用?
答:日志是事后取证(开发调试、崩溃定位,成本最低);诊断是实时健康状态(节点主动上报 OK/WARN/ERROR,供看门狗/仪表盘做运行时判断,量产必备);追踪是精确时序剖析(LTTng 抓回调耗时、DDS 收发、executor 调度,用于偶发性能/时序问题,开销大不能常驻)。判断标准:稳态健康用 diagnostics,偶发时序用 tracing,其余用日志。
Q3:节点"卡死但没崩溃"(进程在,输出停更),你怎么定位?
答:先区分"崩溃"和"卡死"——
ros2 node info还在就是卡死。卡死最常见是 executor 被阻塞:① 单线程 executor 里某个回调做了长阻塞(sleep、网络、文件 IO)或死锁(回调内拿锁),把所有回调都卡住;② 用ros2 tracing定位是哪个回调没返回;③ 修复手段:长任务移出回调(放独立线程/线程池)、改MultiThreadedExecutor+ 独立回调组隔离慢回调、锁的持有时间最小化。核心是"executor 是单点,任何阻塞回调都会放大成整个节点卡死"。
下一步:04_持续构建发布与部署.md · 模块导读:README.md