前面的文章分析了 Apollo 6.0 中的 Guardian 模块,引发了我对 Monitor 模块的好奇心。
我们可以看到 Guardian 除了接收 Control 模块的信息外,还要接收来自 Monitor 发过来的信息,事实上 Guardian 根据 Monitor 传输的 system status 信息判断是否需要紧急停车。
本篇文章的目的就是为了搞清楚 Monitor 的主要工作流程。
根据 Apollo 官方开发者文档提示,Monitor 模块主要作用有 2 大类:
每个大类下面又细分了许多小类,如下图所示:
可以看到 Monitor 需要监控的东西非常多,涉及到传感器,也涉及到了系统物理资源,而软件层面的进程、模块、时延、消息通道也需要监控。
接下来我们具体到代码层面分析。
Monitor 的代码路径在 modules/monitor 目录下。
按照定义,我们可以发现,Monitor 就是一个普通的定时器组件,单独继承了 Init 和 Proc 两个模板方法。成员变量也只有一个 RecurrentRunner 的 vector。
我们先分析 Init 方法。
在 monitor.cc 中有 init 方法实现,代码也很简单。
我们看到,初始化一个 MonitorManager 实例,并且创建不同类型的 xxxMonitor,并依次将它们压入 runners 这个 vector 容器当中。
需要特别处理的是 FunctionalSafetyMonitor,只有 FLAGS_enable_functional_safety 为真时,它才会被添加进去。
Proc() 中代码非常简短。
通过 MonitorManager 的 StartFrame 为起始,EndFrame 为结束,代表一次监控任务操作。在每一次监控周期当中触发 runners_ 中不同的 xxxMonitor 的 Tick() 方法就完事了。
所以,真正起监控作用的还是 xxxMonitor。
根据官方文档的提示,Monitor 运行时,先扫描不同的子 Monitor,然后通过 SummaryMonitor 做整体状态的监控报告,产生 4 类状态:
另外,刚刚提到过 FunctionalSafetyMonitor 也是值得关注的。
所以,如果想搞懂 Monitor 这个子系统的基础工作流程,我们只需要关注下面几个类:
先看定义:
核心关注点有 3 个:
MonitorManager 我不想过多分析,我关注的是 SystemStatus 变量。
它是在 proto 文件中定义的,编译后会产生 .h 文件,路径是 modules/monitor/proto/system_status.proto。我也其中找到了我想要关注的东西。
SystemStatus 中有 2 个 map,一个是保存不同组件的状态,另外一个是为了给 HMI 传输故障状态。
SystemStatus 还有一个 passenger_msg,用来向乘客传递一些信息,这些信息可以通过声光形式提示乘客。
SystemStatus 有一个变量 require_emergency_stop,如果经 Monitor 判断需要紧急停车时,它就会置为 true,然后传递给 Guardian。它正是本文需要分析的目标之一。
startframe 主要作用是读取 HMI 的状态,并保持同步。
endframe 只需要打印 logs。
前面分析,Monitor 中实际干活的是各类 xxxMonitor,它们都是 RecurrentRunner 的子类,按照单词字面意思就知道都是周期性任务执行器。
既然是周期性执行器,那么肯定有每次执行间隔时长,这个是 interval_ 变量指定,它指定了 Tick() 方法被定义触发,Tick() 会调用 RunOnce() 方法。
因为 Monitor 执行频率很高,为了减轻 CPU 负担,也根据各个模块的定时需求,Tick 不是每次都会被触发。在一个监控周期内,它只执行一次。
真正干活的是 RunOnce() 方法,但它需要在 RecurrentRunner 的子类中实现。
我们先分析 SummaryMonitor。
SummaryMonitor 对整个系统状态负责,核心方法是 EscalateStatus,它需要综合其他 Monitor 的监控信息然后根据情况决定整体系统的 status 定义。
按照定义,status 的权重 FATAL > ERROR > WARN > OK > UNKNOW,高的权重可以覆盖低权重。
前面一节讲过,RecurrentRunner 的子类会覆写 RunOnce 方法。
其它的 RecurrentRunner 不需要每次在 Tick() 方法中触发 RunOnce() 方法,但 SummaryMonitor 需要,因为它的职责是在每一个Tick中更新各个componentstatus。
然后,在合适的时机定期广播出来,代码也简单。
快接近目的地了,FunctionalSafetyMonitor 负责安全相关,它将触发紧急停车信号。
注释写得很明白,这个 Monitor 有 2 个目的:
代码逻辑也非常简单,整理成流程图是这样的:
整个处理过程经历了 4 个判断。
通过 checksafety() 方法检测系统是否安全,如果安全的话就清除紧急停车相关的标志,如果不安全则进入后续的流程。
通过 require_emergency_stop() 方法申请紧急停车,如果系统已经申请过了,那么就返回,如果系统还没有申请过紧急停车,则进入后续流程。
判断是否已经设置了安全模式触发时间,如果没有则设置安全时间然后返回,如果设置了的话则进入后续流程。
如果前面检测到了系统设置的安全模式触发时间,那么就需要判断它是否超时了。
如果安全模式触发后,在规定时间内系统没有响应,那么就申请紧急停车,通过设置 system_status 变量实现。
system_status->set_require_emergency_stop(true);
最终,紧急停车命令就产生了。当然,我们还需要关注之前的安全判断逻辑。
核心逻辑有 3 点:
前面讲过,ComponentStatus 有 5 个状态,判断的依据是 ERROR 和 FATAL 是不安全的,其它的是安全的。
并且,FunctionalSafetyMonitor 的安全判断也是基于其他监控模块自身上报的状态,它也是从系统整体角度出发的。
其他受监控的模块如何上报自己的状态呢?
答案是通过自身 updatestatus 方法和 SummaryManager.EscalateStatus() 方法。 比如,我们观察 CameraMonitor 代码。
RunOnce() 方法中调用了 UpdateStatus(),然后调用了 SummaryMonitor.EscalateStatus(),最终完成了本 Monitor 的状态更新。
有了前面的基础分析,我们现在可以绘制 Monitor 模块的骨架了。
上面是基础的静态结构,当然,动态行为我们也不难得到。
本文完,后续的文章依次介绍单个模块如何进行状态监控。