是什么?
Apollo系统中,Monitor模块是用来做系统监控的
我们可以看到Guardian除了接收Control模块的信息外,还需要接受来自Monitor发来的信息,事实上,Guardian根据Monitor传输来的system status信息判断是否需要紧急停车。
Monitor 的职责
monitor模块主要作用有两大类:
- 硬件状态检测
- 软件模块状态监控
每个大类下面又细分了许多小类,如下图所示:
可以看到Monitor需要监控的东西非常多,包括对模块进程的监控、硬件传感器数据的监控、对特定数据channel的监控、对模块节点发现协议层面的监控、对系统CPU/内存/磁盘空间等资源占用的监控、针对特定模块的定制化监控等。
概览
Monitor模块间的数据流
如上图,Monitor模块的输入数据主要包括
- DreamView模块的HMIStatus和HMIMode
- 其他monitor监控类中需要检测的数据
- 底盘数据主要是拿来判断当前是否处于自动驾驶模式
Monitor模块的输出数据主要是SystemStatus,其主要的订阅模块就是DreamView和Guardian,其中DreamView用来更新HMI上各个模块的状态,Guardian用来处理紧急停车的事件。
Monitor模块的核心类
Monitor模块中几个主要的类如下:
- Monitor类,Apollo模块的组件类,主要负责周期性的触发监控事件,调用每个注册的RecurrentRunner子类的RunOnce函数
- MonitorManager类,单例模式,用来缓存一些公共数据,并且封装了一些常用的接口函数
- RecurrentRunner以及其子类,这些是真正执行监控任务病处理相关逻辑的类,如果我们需要扩展监控新的模块和数据信息,就需要添加RecurrentRunner子类,并在RunOnce函数中实现相关监控处理逻辑。
接下来我们具体到代码层面分析。
从节点入口看起
Monitor 的代码路径在 modules/monitor 目录下。
我们先从这个节点的入口看起:
按照定义,我们可以发现,Monitor就是一个普通的定时器组件,单独继承了Init和Proc两个模板方法。成员变量也只有一个RecurrentRunner的vector。
我们先分析init方法
Monitor 的 Init() 初始化
在 monitor.cc 中有 init 方法实现,代码也很简单。
可以看到,它是初始化一个MonitorManager实例,并且创建不同类型的xxxMonitor,并且依次将它们压入runners这个vector容器中。
这里需要注意的是SummaryMonitor应该放在最后,因为它会汇总其他monitor类的检测结果并填入Component::summary,最后,将最终的SystemStatus发布出去
需要特别处理的是FunctionalSafetyMonitor,只有 FLAGS_enable_functional_safety 为真时,它才会被添加进去。
Monitor 的 Proc() 方法
)
可以看到:
- 首先调用MonitorManager::Instance()->StartFrame获取当前的HMIMode和当前是否处于自动驾驶模式
- 然后,依次触发 runners_ 中不同的 xxxMonitor 的 Tick() 方法就完事了。
- 在Tick中会调用纯虚函数RunOnce,每个子类通过RunOnce来实现不同的监控检测任务
- 每个子类的调用时间间隔可以在构造对象的时候自己指定;
- 最后,调用MonitorManager::Instance()->EndFrame()发布monitor日志到/apollo/monitor主题。
注意:
- 通过 MonitorManager 的 StartFrame 为起始,EndFrame 为结束,代表一次监控任务操作
所以,真正起监控作用的还是 xxxMonitor。
根据官方文档的提示,Monitor运行时,先扫描不同的子Monitor,然后通过SummaryMonitor做整体状态的监控报告,产生4种状态:
- Fatal
- Error
- Warn
- OK
- Unkown
另外,刚刚提到过 FunctionalSafetyMonitor 也是值得关注的。
所以,如果想搞懂 Monitor 这个子系统的基础工作流程,我们只需要关注下面几个类:
- MonitorManager
- RecurrentRunner
- SummaryMonitor
- FunctionalSafetyMonitor
工作流程
MonitorManager
先看定义:
核心关注点有 3 个:
- SystemStatus变量,之前分析Guardian模块有注意到,Monitor发送它的状态,里面有是否需要紧急停车的请求。
- in_autonomous_driving变量,用来判断是否在自动驾驶模式。这个很重要,因为有些异常如果是人工驾驶状态是可以忽略的
- MonitorManager采用单例模式,这样保证获取到的实例是同一个。
MonitorManager暂不过多分析,终点关注的是 SystemStatus 变量。
它是在 proto 文件中定义的,编译后会产生 .h 文件,路径是 system_status.proto。需要重点关注如下:
system_status中有两个map,一个是保存不同组件的状态,一个是为了给HMI传输故障状态。
SystemStatus中还有一个passenger_msg,用来向乘客传递一些信息,这些信息可以通过声光形式提示乘客。
SystemStatus 有一个变量 require_emergency_stop,如果经Monitor判断需要紧急停车时,就会置于true,然后传递给Guardian。它正是本文需要分析的目标之一。
MonitorManager::StartFrame
startframe 主要作用是读取 HMI 的状态,并保持同步。
- 首先,从DreamView模块获取HMIStatus
- 然后,判断当前HMIMode是否有变化
- 如果已经改变了就清空当前SystemStatus里面需要监控的模块信息,并重新根据HMIMode加载相关的模块信息
- 如果没有改变就只是清楚上一次每个模块的summary结果。
需要监控哪些模块
问题:DreamView是如何处理HMIStatus和HMIMode的呢?
Monitor模块对于需要监控的模块配置信息来自于DreamView模块,具体可以参考dreamview/conf/hmi_modes文件夹中HMIMode的配置文件。
我们接下来看看DreamView中关于HMIMode的处理流程:https://www.zhihu.com/people/igear-ai/posts
MonitorManager::EndFrame
endframe 只需要打印 logs。
RecurrentRunner
前面分析,Monitor 中实际干活的是各类 xxxMonitor,它们都是 RecurrentRunner 的子类,按照单词字面意思就知道都是周期性任务执行器。
既然是周期性执行器,那么肯定有每次执行间隔时长,这个是 interval_ 变量指定,它指定了 Tick() 方法被定义触发,Tick() 会调用 RunOnce() 方法。
真正干活的是 RunOnce() 方法,但它需要在 RecurrentRunner 的子类中实现。
SummaryMonitor
其核心方法是 EscalateStatus,它需要综合其他Monitor的监控信息然后根据情况决定整体系统的status定义
(1)主要是遍历SystemStatus::components中的每一个模块,并根据该模块的process_status、module_status、channel_status、resource_status和other_status计算一个综合状态更新到模块的summary状态中去;最后,SummaryMonitor会把更新后的SystemStatus发布出去。
按照定义,status 的权重 FATAL > ERROR > WARN > OK > UNKNOW,高的权重可以覆盖低权重。
(2)在获取到整体的status之后,在合适的时机定期广播出来
其它的 RecurrentRunner 不需要每次在 Tick() 方法中触发 RunOnce() 方法,但 SummaryMonitor 需要,因为它的职责是在每一个Tick中更新各个componentstatus。
FunctionalSafetyMonitor
快接近目的地了,FunctionalSafetyMonitor 负责安全相关,它将触发紧急停车信号。
注释写得很明白,这个 Monitor 有 2 个目的:
- 通知驾驶员采用行动
- 触发Guardian模块,如果预期的安全措施没有发生
RunOnce()
接下来我们看下RunOnce
代码逻辑也非常简单,整理成流程图是这样的:
整个处理过程经历了 4 个判断。
第 1 个判断:通过checksafety()方法检测系统是否安全,如果安全的话就清除紧急停车相关的标志,如果不安全则进入后继的流程。
第 2 个判断:通过require_emergency_stop()方法判断是否之前早就申请过了紧急停车,如果系统已经申请过了,那么就返回,否则进入后继流程
第 3 个判断:判断是否已经设置了安全模式触发时间,如果没有则返回,负责进入后继流程
第 4 个判断:如果前面检测到了系统设置的安全模式触发时间,那么就需要判断它是否超时了。
如果安全模式触发之后,在规定的时间内系统没有响应,就申请紧急停车,通过设置system_status变现。
system_status->set_require_emergency_stop(true);
最终,紧急停车命令就产生了。当然,我们还需要关注之前的安全判断逻辑。
CheckSafety()
核心逻辑有 3 点:
- 只对自动驾驶状态下的安全监控负责
- 检测HMI中配置的各个module状态
3. 检测受监控的其他component模块状态
IsSafe()
ComponentStatus有5个状态,判断依据是ERROR 和 FATAL 是不安全的,其他是安全的
并且,FunctionalSafetyMonitor的安全判断也是基于其他监控模块自身上报的状态
小结
主要是根据每个模块的状态,判断是否需要触发进入安全模式和紧急停车。现在判断的依据是:
- 首先,必须是进入自动驾驶模式;
- 其次,遍历所有的SystemStatus::components和SystemStatus::hmi_modules中的模块,如果模块的summary状态异常(ERROR或FATAL)且该模块的required_for_safety属性为true(默认值为true),则会触发进入安全模式,当进入安全模式10秒后(时间可配置)没有任何改善和恢复,则会立即触发紧急停车。
Monitor 的骨架和安全监控机制
上面是基础的静态结构,当然,动态行为我们也不难得到。
总结
- 相对 Guardian 模块,Monitor 模块复杂一点,但也比较简单。
- Monitor 模块核心类是 Monitor、MonitorManager、SummaryMonitor、FunctionalSafetyMonitor,它们能形成一个完整的监控逻辑。
- Monitor 监控硬件、进程、模块、资源几大类。
模块 | 实现 |
---|---|
EsdCanMonitor | 用来监控ESD-CAN设备的,目前主要的检测方法是打开CAN设备,通过设备提供的ioctl获取当前设备的状态;最后更新Component::other_status的状态 |
GpsMonitor | 用来监控当前的gps状态,目前主要检测方法是获取GnssBestPose数据,并根据SolutionType判断GPS当前的状态;最后,会更新Component::other_status的状态。 |
SocketCanMonitor | 用来监控Socket CAN设备,目前主要的检测方法是通过Socket接口打开和绑定到CAN设备,而且CAN设备的ifname写死是can0;最后,会更新Component::other_status的状态。 |
ResourceMonitor | 用来监控所有的SystemStatus::components(HMIMode::monitored_components)中的模块,并找出那些在配置文件中定义了resource熟悉的模块,并检测它们当前的resource占用情况,如果超出了设定值报错,目前支持磁盘占用/CPU占用/内存占用/磁盘负载的检测;最后,会更新对应模块的Component::resource_status状态。 |
CameraMonitor | 用来监控摄像头设备的,目前是订阅多个camera输出的主题,病通过frame id来判定当前状态(这里的frame_id是摄像头的类型,具体参考modules/drivers/camera/conf下protobuf文件的定义),而且只允许存在一个摄像头;最后,会更新Component::other_status的状态。 |
ChannelMonitor | 用来监控所有SystemStatus::components(HMIMode::monitored_components)中的模块,并找出那些在配置文件中定义了channel属性的模块,会去订阅并检测channel是否有数据、处理延时情况、发送频率等信息;最后,会更新对应模块的Component::channel_status状态。 |
LatencyMonitor | 用来统计系统中所有模块的延时情况的,并为ChannelMonitor提供发送频率的统计数据,目前主要是通过订阅/apollo/common/latency_records主题,获取模块的处理延时记录,并计算一段时间内的发送频率。注意,如果需要跟踪某个模块的处理延时,还需要在该模块的实现中创建apollo::common::LatencyRecorder实例,并通过它记录每次Proc方法的处理延时。 |
LocalizationMonitor | 用来监控定位数据的状态,目前主要通过订阅LocalizationStatus消息来判断状态;最后,会更新Component::other_status的状态。 |
ModuleMonitor | 用来监控所有SystemStatus::components(HMIMode::monitored_components)中的模块,并找出那些在配置文件中定义了module属性的模块,使用node名称调用NodeManager检查模块当前是否存在(这里涉及到fast_rtps协议的发现机制,以后有机会再展开讨论),这里主要还是考虑到有可能模块进程还在,但是已经不响应任何rtps协议消息了,最后更新 |
ProcessMonitor | 用来监控所有SystemStatus::components、SystemStatus::hmi_modules和other_components中模块对应的进程是否存在,这是一种最基本的检测方式,最后更新Component:: process_status状态。 |
RecorderMonitor | 用来监控SmartRecorder的状态,当前通过订阅SmartRecorderStatus来判断recorder的状态;最后,会更新Component::other_status的状态。 |
SummaryMonitor | 主要是遍历SystemStatus::components中的每一个模块,并根据该模块的process_status、module_status、channel_status、resource_status和other_status计算一个综合状态更新到模块的summary状态中去;最后,SummaryMonitor会把更新后的SystemStatus发布出去。 |
FunctionalSafetyMonitor | 主要是根据每个模块的状态,判断是否需要触发进入安全模式和紧急停车。现在判断的依据是:首先,必须是进入自动驾驶模式;其次,遍历所有的SystemStatus::components和SystemStatus::hmi_modules中的模块,如果模块的summary状态异常(ERROR或FATAL)且该模块的required_for_safety属性为true(默认值为true),则会触发进入安全模式,当进入安全模式10秒后(时间可配置)没有任何改善和恢复,则会立即触发紧急停车。 |