离线与实时两条数据管线:rlog/qlog 选择、时间归零与视窗边界

作者:

在

结论:JotPluggler 把「离线看路段」和「实时看数据流」做成两条互斥管线:启动时路段与 --stream 互斥。离线路段的两次 Auto 选择可能读到不同文件——selected_log_path() 的 Auto 优先 rlog,而 detect_route_metadata() 的 Auto 优先 qlog,所以固定实验必须同时记录二者与 DBC。实时侧,Buffer (seconds) 在这条调用链上更像视窗宽度而非内存淘汰上限,长时流的内存上界尚无证据。

启动:一条命令里的路段与实时流互斥

main() 解析路段或 --stream(两者互斥)、窗口尺寸、布局和 PNG 输出。无输出路径且未显式 --show 时默认开窗口;路线模式可同步或异步加载。两个容易被误读的点:--output 是截图路径而非日志导出,且 save_framebuffer_png() 先写 PPM,再调用外部 convert,因此截图依赖额外程序并会产生文件。

异步加载由 AsyncRouteLoader::Impl::start() 启动工作线程调用 load_route_data(),用原子计数发布下载/解析进度,consume() 在线程结束后交付结果或错误。它会先 join() 旧工作线程,因此切换路线可能等待旧加载完成;源文档指出这里未见取消令牌。进度数字是加载阶段信息,不是数据完整性证明。

离线链:从选择到并行抽取

离线链的每一步都有明确的失败模式:

函数/组件 源码行为 需要注意
load_route_data() 解析路线/片段选择,查询服务端或扫描本地目录,按 /r、/q、/a 选择日志;探测 carFingerprint 和 DBC,再并行抽取,构建 RouteData 与四类相机索引 两次 Auto 选择可能不同(见下节)
load_route_series_parallel() 按路段并行加载 C++ LogReader,每段抽取曲线/CAN/日志/时间轴/缩略图;大段可再分块;最后按段序合并 任一路段缺失或加载失败会记录首个错误并整体抛出
LogReader 对非空损坏前缀仍可能返回成功 图表「加载完成」不等于完整日志
build_route_data() / apply_route_data() 对四类数据计算公共时间原点并归零,整理路径、时间轴、GPS;重建浏览索引、自定义曲线和相机索引 时间归零基准不等于路线首帧

线程预算来自 load_worker_budget(),可用环境变量 JOTP_LOAD_WORKERS 调整。时间归零的细节值得单独记:build_route_data() 以可用数据算出的 x_min 为基准,不一定等于路线首帧或首段开始;跨工具比较时必须保存原始单调时间与该偏移,否则同一段路在两份数据里会「对不上」。

rlog/qlog 的两次 Auto 选择可能不一致

这是最容易导致复盘出错的一处:selected_log_path() 的 Auto 优先 rlog、缺失时用 qlog;但 detect_route_metadata() 的 Auto 优先 qlog 读取车型。也就是说,实际抽取的日志与读取车型的日志可能不是同一个文件。对于要复现的实验,必须同时记录这两个输入以及所用的 DBC,否则「车型识别结果」和「曲线数据」可能来自不同来源而不自知。

实时流:四个阶段的批次交接

实时侧从 start_stream_session() 切换模式开始,默认重置旧路线数据并启动 StreamPoller;暂停保留数据时只暂停消费,stop_stream_session() 区分保留与清空。之后是四段式的批次交接:

阶段 组件 职责
订阅 run_cereal_source() 本地地址固定 127.0.0.1;订阅 services,跳过编码数据、缩略图和原始音频;检查 Cap’n Proto word 对齐
累加 StreamAccumulator 以首个事件单调时间为 offset;carParams 更新指纹/DBC;生成数值曲线与日志/告警时间轴
交接 merge_batch() / consume() 按曲线路径和 CAN 标识合并后台待取批次;UI 线程移走批次和错误
应用 apply_stream_batch() 追加到 AppSession::route_data,更新浏览、时间轴、CAN、GPS 与时间范围

这里要分清职责:这是数据订阅,不是 CAN 发送网关。另外 StreamAccumulator::appendEvent() 会通过 with_parseable_event() 静默跳过本地 schema 无法解析的事件,因此 received_messages 只代表收到的消息数,不代表成功解析、提取或绘图——需要单独计数丢弃原因。

时间与资源边界

ensure_shared_range() 与 update_follow_range() 把 stream_buffer_seconds 用作显示窗口跨度;advance_playback() 操作的是 UI tracker,并非重新发布历史消息——不能把画面播放位置当作控制回放进度。

当前追踪到的 apply_stream_batch() 对曲线、日志和 CAN 样本做追加,未见按 stream_buffer_seconds 裁剪旧样本;StreamPoller 保存该值用于快照而非消费端内存淘汰。所以「Buffer (seconds)」在这条调用链上更像视窗宽度,长时流的内存上界尚无证据——源文档把这列为需要长跑内存测试进一步验证的静态观察。

还有一处进程级副作用:run_cereal_source() 在工作线程调用 setenv/unsetenv("ZMQ"),会影响同进程其他环境读取。源文档的建议是:若插件框架允许并发源,应改为显式传输配置,不能靠全局环境切换。

插件化与验证

可以把 RouteCatalog/LogLoader、EventExtractor、DbcResolver、StreamSource 和 ViewModel 作为只读插件分层,但必须统一 schema + 时间基准 + DBC 版本 + 来源 manifest。实时插件需独立声明网络地址、消息白名单、队列上限、后台背压和保留时长;不能因其能读 CAN/sendcan 主题就获得物理 CAN TX 权限。日志中包含位置、告警和车况,远端流应附授权与隐私策略。

测试现状要如实标注:test_help() 只检查可执行文件帮助输出;源文档未见对路段缺口、损坏日志、实时断连、长时内存、同时间戳/乱序、DBC 热切换的同目录专门测试,也未运行编译或 GUI 验证。JotPluggler 其余 UI/图表/地图/自定义曲线组件仍待逐专题分析,不能把本篇视作整个目录已完成。

常见问题

为什么同一路段的车型识别和曲线数据可能对不上?

因为 selected_log_path() 的 Auto 优先 rlog、缺失时 qlog,而 detect_route_metadata() 的 Auto 优先 qlog 读取车型,两次选择的输入文件可能不同。固定实验需要同时记录二者及 DBC。

Buffer (seconds) 会限制内存增长吗?

就当前追踪到的调用链而言不能这样理解。stream_buffer_seconds 被用作显示窗口跨度,apply_stream_batch() 未见按该值裁剪旧样本,StreamPoller 仅用它做快照。因此长时流的内存上界尚无证据,需要长跑内存测试验证。

图表显示「加载完成」就代表日志完整吗?

不代表。LogReader 对非空损坏前缀仍可能返回成功,而图表加载完成只反映流程结束;源文档明确「加载完成」不等于完整日志。此外任一路段缺失会记录首个错误并整体抛出。

实时流的播放位置就是控制回放进度吗?

不是。advance_playback() 操作的是 UI tracker,并非重新发布历史消息。把画面播放位置当作控制回放进度会产生误判。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/JOTPLUGGLER_DATA_PIPELINES_DEEP_DIVE.md(取用了离线链、rlog/qlog 双 Auto 选择、实时流批次交接与时间/资源边界)。
  • 项目 02 —— openpilot-research/CAR_PROTOCOL_IMPLEMENTATIONS.md(仅用于确认 DBC 与车型指纹在离线链中的探测位置)。

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注