结论:项目 02 的五个开发诊断脚本(count_events、qlog_size、mem_usage、watch_timings、live_cpu_and_temp)只产出启发式观测,不是系统健康证明:事件计数是消息出现次数而非独立故障次数,日志体积饼图是给定压缩顺序下的边际贡献而非可相加的存储配额,8 核 CPU 采样对超过 7 的核号会数组越界、对零累计时间差会除零。任何监测结果只能形成告警或实验准入证据,不能绕过 panda 安全钩子直接获得车辆控制权。本文为固定源码快照 c8fb906 的静态阅读,未使用真实路段、未启动订阅、未连接车端。
五个脚本各自算什么
这五个脚本被放在同一目录下,但输入域分成两类:三个作用于离线路段日志,两个订阅实时消息。count_events.py 用 LogReader(route, ReadMode.QLOG) 读取路段,经 migrate_all() 兼容迁移后统计 onroadEvents 各名称出现次数,并记录去掉三类踏板/覆盖事件后的集合变化、selfdriveState.alertType 变化和各相机状态条数。qlog_size.py 的 make_pie() 先压缩全体序列,再逐主题移除并重新压缩,把「总压缩长度减去移除后长度」作为该主题体积。mem_usage.py 从 LogReader 收集 procLog 与 deviceState,交 print_report() 汇总内存。
| 组件 | 输入与计算 | 结果及边界 |
|---|---|---|
count_events.py |
LogReader 读路段,migrate_all() 后按名称统计事件 |
是消息出现次数,不是独立故障次数 |
qlog_size.make_pie() |
全体压缩后逐主题移除再压缩,取差额 | 边际贡献,不可相加;分别打印 real total 与 breakdown total |
mem_usage.py |
收集 procLog/deviceState,调用 print_report() |
末帧系统内存与各进程 RSS/PSS 均值峰值;非泄漏或 GPU 用量直接测量 |
watch_timings.ServiceTiming.add() |
保留 logMonoTime 列表、最近 100 个间隔与 valid |
间隔超过 lag_threshold / frequency 记滞后 |
live_cpu_and_temp.py |
SubMaster 订阅 deviceState/procLog |
按每核累计 busy/total 差算 CPU;只有更新了 procLog 才打印 |
把「脚本跑出数字」等同于「系统健康」是最常见的误用。count_events.py 在每条消息上先用旧的 start_time 计算相对时间再更新起点,若第一条消息恰为要输出的事件或告警,时间可以是负无穷;它遇到第一次点火关闭的 pandaStates 就停止遍历,此后的消息均不参与持续时间与频率统计。相机期望帧数为 int(frequency * duration / decimation),短路段若得到 0,输出百分比时会除零。再加上 migrate_all() 会把整个输入转成列表、按 logMonoTime 排序且相机状态迁移缺省关闭,因此「缺少相机状态」不能只凭该脚本的计数判定相机物理故障。
日志体积归因为什么不可当配额
qlog_size.py 默认由 LogReader 读取 rlog,并打印标题为 qlog 的重压缩结果;没有 --as-qlog 时,这个标题并不代表输入或输出是实际 qlog。开关启用后按当前 SERVICE_LIST 的 decimation 过滤,initData/sentinel 总保留、未注册主题丢弃——这是用现在的配置模拟抽样,不是重建历史录制时的 qlog。loggerd 实际在每个已订阅服务上连续递增计数器并按模数选取,日志中还有编码器等特殊写入路径。
make_pie() 将所有消息和每个主题排除后的其余消息重复序列化、拼接、压缩,时间与内存开销随主题数增长;压缩上下文使各差额不具可加性,空输入或总大小为零也没有有效饼图的保护。因此不能把饼图比例当作日志服务的精确配额,更不能据此直接关闭高频安全证据主题。
内存与 CPU 采样:缺测、越界与残差标签
print_report() 没有 procLog 时只打印提示;有数据时仅用最后一帧是否存在任一非零 PSS 来选全报告指标,不同帧/进程的缺测可能混入零值。进程名来自命令行首项,可能合并同名进程;均值只在出现的样本上取,不能当作连续驻留时间加权平均。kernel/ION/GPU 一行是从已用内存扣除缓存和进程估算的残差标签,不是 GPU 计数器。
live_cpu_and_temp.py 固定为 8 核数组,cpuNum 超过 7 会越界,累计时间差为零会除零;首份 procLog 相对全零基线得到的是累计历史占比,不是当前瞬时负载。进程 CPU 以 PID 找上份采样却未校验 PID 复用,--mem 按名称写字典时同名进程会覆盖而非求和;温度与 RAM 由最近一次 deviceState 更新保留,打印时没有新鲜度提示。
实时消息间隔:能看见的与看不见的
monitor_services() 关闭 conflate 并逐轮排空队列,ServiceTiming 将全部时间戳和滞后事件长期保留,长时间运行内存会增长。valid 展示的是最近最多 100 条 Event 的全部有效性,即使 Ctrl-C 的频率统计使用全程数据也是如此。它只观察收到的相邻消息时间戳,既没有第一条消息前或最后一条消息后的静默时长,也无法从一个大间隔区分发布端迟到、订阅端丢包、队列覆盖或时钟问题。若订阅了已注册但频率为 0 Hz 的服务,收到消息后 1.0 / frequency 会触发除零异常,而不是跳过滞后判断。
观测插件化时的最小契约
这些建议不是当前工具的已实现能力。观测插件应声明数据源(路段或实时、rlog 或 qlog、服务版本、时间域)、样本区间和缺测状态;CPU 与温度要随每份 deviceState 标注年龄,日志体积归因要保留「估算」而非「账单」语义。对新硬件,核数、PSS 可用性、传感器温度字段和相机期望帧率应由平台能力描述,不写死在诊断界面。原始路段、车辆标识与进程命令行可能含敏感信息,插件仅应获得最小只读数据范围。
| 观测项 | 必须声明的元数据 | 不能据此推断 |
|---|---|---|
| 事件计数 | 路段来源、迁移版本、遍历终止条件 | 独立故障次数、相机物理健康 |
| 体积归因 | 压缩顺序、压缩级别、是否 qlog 模拟 | 可相加的存储配额 |
| CPU/温度 | 核数、样本年龄、PID 复用策略 | 瞬时负载、GPU 用量、驻留均值 |
| 消息间隔 | 时间域、队列策略、有效位窗口 | 丢包与迟到的成因归属 |
常见问题
事件计数能当作故障次数吗?
不能。count_events.py 统计的是 onroadEvents 各名称在消息里出现的次数,不是独立故障次数;脚本遇到第一次点火关闭的 pandaStates 就停止遍历,此后的消息都不参与统计,所以它不是一份完整的故障台账。
日志体积饼图能直接用来做存储配额吗?
不能。make_pie() 在给定压缩顺序与压缩级别下求每个主题的边际贡献,压缩上下文使各差额不具可加性,脚本本身也分别打印 real total 与 breakdown total。它适合定位「谁更占地方」,不适合当作精确配额,更不能据此关闭高频安全证据主题。
为什么 8 核 CPU 采样会异常退出?
live_cpu_and_temp.py 固定为 8 核数组,cpuNum 超过 7 会越界,累计时间差为零会除零。另外首份 procLog 相对全零基线得到的是累计历史占比而非瞬时负载,参数缺失或核数与平台不符时应先改由平台能力描述。
消息时间间隔变大就一定是丢包吗?
不一定。watch_timings 只观察收到的相邻消息时间戳,无法从一个大间隔区分发布端迟到、订阅端丢包、队列覆盖或时钟问题,也看不到第一条消息前与最后一条消息后的静默时长。判定成因需要结合队列策略与其他进程日志。
延伸阅读
- 开发工具链与调试脚本 —— 承接本文五个脚本在开发工具链中的定位。
- 消息日志契约与主题抽样 —— 解释 loggerd 写入相位与 decimation 为何影响体积归因。
- CAN 报文解析的「健康位」在说什么:校验、计数器与总线空窗 —— 对照本文有效位与缺测语义。
- 功耗监测与容量关机契约 —— 说明 CPU/温度采样在状态机里如何被真正消费。
- openpilot 的 100 Hz 控制回路:开源 ADAS 能给车载设备什么 —— 对照本文消息时序观测与实时性边界。
参考资料
- 项目 02 ——
openpilot-research/DEV_METRICS_LOG_SIZE_RESOURCE_TIMING_TOOLS_DEEP_DIVE.md(取用了五个脚本的输入输出、事件与体积失效边界、内存/CPU 越界除零,以及观测插件化建议)。 - 项目 02 ——
openpilot-research/MESSAGE_LOG_CONTRACTS_DEEP_DIVE.md(仅用于确认 loggerd 写入相位与 decimation 的抽样位置)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复