apollo: Monitor模块工作机制

是什么?

在这里插入图片描述
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 点:

  1. 只对自动驾驶状态下的安全监控负责
    在这里插入图片描述
  2. 检测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秒后(时间可配置)没有任何改善和恢复,则会立即触发紧急停车。

参考

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值