
数据可视化桌面应用数据分析【免费下载链接】PlotJugglerThe Time Series Visualization Tool that you deserve.项目地址https://gitcode.com/gh_mirrors/pl/PlotJuggler点击查看免费下载本篇技术指南系统讲解 PlotJuggler 4 如何把进入程序的消息字节以标准 MCAP 文件形式保存并回放pj_runtime中的会话录制器Record/Stop已在 PJ4 #616 落地源缓存Source cache的设计决策确定于 2026-09-01并在当前仓库中已有对应实现与测试。读完本文你将掌握委托式摄取delegated-ingest接缝如何天然映射为 MCAP 数据模型、录制与缓存共用一套机制的架构、逐字节预算的丢包策略与无阻塞设计、以及缓存优先的布局恢复cache-first restore的完整链路。1. 设计意图两类需求共享一套机制PlotJuggler 4 面对两个真实的用户诉求而它们可以收敛到同一个机制上保留正在流式接收的数据。实时数据源ROS 2、Foxglove bridge、MQTT、UDP、ZMQ、pj_bridge 等是受内存保留缓冲约束的 eager 数据。用户希望把一个会话保存成文件、之后重新打开、或者交给同事复现。同一份下载不要重复两次。云连接器Mosaico、mcap_cloud 以及未来的连接器会拉取一个可复现的请求。一个已保存的布局应当能离线、瞬时地从本地工件artifact恢复数据。两者都归结为一句话把进入 PlotJuggler 的消息保存下来并用打开 bag 的那套机制回放它们。由于宿主host是唯一能看到每个插件 ingest 的组件因此宿主拥有这套机制插件只拥有真正属于自己的部分传输、请求身份、信任、凭据。这一意图贯穿全文录制器与源缓存共享pj_runtime中同一套原始字节捕获核心只是溢出策略、生命周期与产物归属不同。2. 目标与非目标2.1 目标标准 MCAP 文件是唯一产物格式录制或缓存工件都是标准 MCAP 文件任何 MCAP 工具以及 PlotJuggler 自带的加载器都能打开。永远不会有 PlotJuggler 私有格式。录制零插件改动缓存仅需一个插件调用所有走委托式摄取的源都无需感知即可被录制与缓存。录制永不拖慢 ingest缓存命中永不拖慢读取性能隔离是硬性目标。故障隔离慢磁盘、预算耗尽、写一半崩溃都不允许损害实时会话或其他源的文件。2.2 非目标不录制解码后的样本或 PlotJuggler 内部状态变换、绘图——那是布局layout的职责。不做录制一切在线数据模式录制内容只包含会话实际订阅的主题。不做解析器策略timestamp 字段、数组限制的格式级保真——策略属于解释层存在于布局中§4 决策 D9。浏览器WebAssembly录制不在范围内核心部分可编译到 wasm只缺一个 sink。3. 架构委托式摄取接缝即 MCAP 数据模型3.1 接缝The seampj_runtime的DataSourceRuntimeHost为每个数据源实现 SDK 的委托式摄取 ABI 先调用ensureParserBinding(topic, encoding, type_name, schema_bytes, parser_config)建立绑定再调用pushMessage(binding, log_time, bytes)推送消息。这个接缝本身就是 MCAP 数据模型委托式摄取MCAP 对应物ensureParserBinding签名Schema{nametype_name, encoding, dataschema_bytes}Channel{topic, message_encodingencoding, metadata}pushMessage(binding, log_time, bytes)Message{channel, log_time, publish_timelog_time, databytes}源身份文件本身一个录制只含一个源在pj.recording中命名一次——parser_config不被记录D9因此录制器就是把接缝镜像进一个 MCAP writer而回放就是data_load_mcap加载器加上已安装的解析器与打开一个 rosbag 毫无区别。这意味着解析器修复天然适用于旧录制文件。数据流如下pushMessage(binding, t, bytes) plugin ──────────────► DataSourceRuntimeHost ──► parser ──► datastore (不变的实时路径) │ └── RecordTap ──► Recorder ──► McapRecordingWriter ──► *.mcap (per source) byte-budgeted queue, 1 MiB chunks, 大消息 writer thread 隔离在独立 chunk3.2 组件总览组件模块职责RecordTappj_runtimepush 路径上的一个回调。非拥有型视图仅调用期有效。relaxed atomic 让无 tap 场景零开销。Recorderpj_runtime每源一个队列 writer 线程。溢出策略是模式化的直播流用 drop-largest§3.3捕获场景可选用阻塞§3.4。McapRecordingWriterpj_runtime受检文件 sinkexclusive create、errno 锁存、close 前 fsync、schema 去重、每通道序号、打开与关闭时各写一条pj.recording元数据。RecordingServicepj_runtime一次 Record 按下 N 个 recorder每个源一个文件、归入一个批处理文件夹事务化启动、同步停止、每次按下只上报一个结果。SourceCacheStore已实现pj_runtime缓存根目录、索引身份 → 路径、大小、哈希、最近命中、跨进程锁、跳过 pinned 工件的 LRU 淘汰。SourceCaptureService已实现pj_runtime决定是否为一次 descriptor 导入启动捕获、以无损Recorder写入 partial 文件、干净完成后发布。Shell 接线pj_app流式条上的 Record/Stop、偏好设置文件夹、预算、开关、ABI 中唯一的新增 vtable 条目、布局恢复时的 cache-first 钩子。3.3 录制Record/StopRecord 捕获每一个活跃的委托式摄取源每个源写入自己的 MCAPrecordings dir/pj_UTC yyyyMMdd_HHmmss/sanitized source.mcap。文件夹原子化创建同一次按下产生的所有文件共享同一个capture_id。只记录已订阅的主题需求驱动的订阅让录制成为我当时在看什么。队列按源进行字节预算绝不阻塞 ingest满时丢弃最大的排队消息若排队中没有更大的则丢弃新消息平局时淘汰最新者。录制带着洞继续只有 sink 错误才会结束它。Stop同步收尾上限为队列预算加每个文件一次fsync并按文件上报唯一结果clean、lossy有丢包、truncatedsink 错误、incomplete无法收尾mcap recover可重建索引。Stop 时刻的文件就是最终产物实时源继续运行录制文件像任何 MCAP 一样被打开。3.4 源缓存宿主驱动且透明。提供方在下载开始时只需声明一次这个数据集是请求 Xattach_source_record(dataset, identity, descriptor_json)——这是唯一的 SDK 新增调用。其余全由宿主完成捕获每次 descriptor 导入受偏好开关与预算约束、干净完成后把工件发布进索引、布局恢复时先查索引再问提供方。命中就是一次标准的data_load_mcap加载——无网络、无信任提示且提供方插件甚至不必安装。未命中则是今天的受信任导入被静默捕获。捕获是无损的push 线程在预算处阻塞而非丢包。下载是唯一宁可慢一点也要保证工件完整的场景。RecorderOptions::OverflowPolicy::kBlock提供该队列行为仅当没有其他消息在排队或复制时才放行一条超大消息writer 在途消息不计入队列预算。取消时先requestStop()再 join 生产者随后stop()排空并收尾。sink 故障同样会释放等待中的生产者。捕获服务在发布前还必须验证传输成功、主题覆盖完整、无取消或 lazy 跳过、录制摘要干净——仅有一个结构合法的 MCAP footer 不足以证明完成。命中验证很廉价文件存在、大小匹配、footer 完好加载失败会淘汰该条目并回退到一次下载。索引是权威的工件本身始终是普通录制文件。淘汰 pin 是宿主内部的数据集正在读取的工件永不被淘汰。没有 lease id 跨 ABI因此任何工件生命周期都不依赖插件 DSO 是否仍被加载。缓存拥有独立的文件夹与预算与录制文件夹分开录制是用户文件、永不淘汰缓存是易失的。已实现细节as builtSourceCacheStore存储lease、先 pin 后验证、发布、隔离 quarantine、预算SourceCaptureService捕获无损 recorder 之上的计数 tap、发布门、工件内嵌的pj.capture完成清单、cache-first 的resolve()由 SDK 0.30 完成契约DataSourceRuntimeHost上的attach_source_recordcomplete_ingestToolboxRuntimeHost上的discard_parser_ingest驱动。发布要求完整门禁——显式 COMPLETED 终态且声明主题覆盖零消息主题仅在空主题 attestation 标志下允许、已提交事务、无取消、无锁存回调失败、无纯 lazy 跳过、recorder 干净关闭。所有者接线as builtToolboxRuntimeHost的 parser-ingest 生命周期驱动捕获服务——每个带capture_service provider manifest idParserIngestDeps的上下文在创建时即武装armedRELEASE 是提交点发布门在 release 调用方线程上运行随后执行 store 的预算清理DISCARD 与宿主销毁中止捕获宿主侧停止拒绝发布。值得记录的 release已附加 descriptor 干净 COMPLETED 终态无论捕获是否发布通过ParserIngestDeps::on_capture_finalized上报携带已发布工件路径shell 据此附加数据集的SourceRecord发布成功时把工件注册为该数据集的持久化后备文件source path loadedSources 条目使布局保存遍历时输出fileInfomaterialize见pj_app/src/ToolboxHostWiring.h的wireSourceCapture。Cache-first 恢复as builtLayoutImportBatch在任何提供方接触之前先咨询SourceCaptureService::resolve()命中通过标准 ticketed 加载器加载 pinned 工件无网络、无信任提示、无需安装提供方插件产生的数据集在其生命周期内持有工件的读 pinSessionManager::pinDatasetResource在 removeDataset/merge 时释放命中后标准加载失败则隔离该条目并重新规划走实时提供方。CONTENDED 未命中SourceCacheStore::MissKind::kContended经SourceCaptureService::resolve上浮依据文件系统的活跃发布者证据判定——该身份旁出现新鲜的 partial 文件mtime 在一分钟以内这也能在尚无工件存在的首次发布时检测到竞争仅持有锁或 SDK 的重试提示绝不判定为 contended不可读的锁文件不能告诉用户无限期等待。已接受的局限停滞但存活的下载其 partial 在窗口外安静下来会被判定为 absent 并回退提供方——这是安全的历史行为。contended 未命中会使该源失败并给出可操作诊断另一个 PlotJuggler 实例正在下载此请求——刻意不做重试循环、不重复下载普通 absent 未命中保留提供方回退。偏好设置在 Recording 页暴露缓存文件夹 预算SourceCacheStore::loadSettings/saveSettings下次启动生效以及 Capture downloads for offline restore 开关Settings::capture_enabled→SourceCaptureService::setCaptureEnabled该开关只门控 ARMING 且立即生效关闭后新建的 parser-ingest 上下文不武装捕获在途武装捕获正常结束已有缓存命中继续解析。M3 的剩余工作Mosaico 提供方中的 #275 收割半部信任/凭据/呈现。4. 关键决策与接受的取舍决策接受的代价只录原始消息绝不录解码样本。直接写解码数据的源LSL、dummy、拆分前的 Mosaico在开始说消息之前不可录制。只录已订阅主题。没有录全部模式UI 中明确说明。每源一个文件按按下分组。每次按下有 N 个 writer 线程和 N 个队列预算想要一个共享文件时用mcap merge。流式丢最大、永不阻塞。慢磁盘在大主题上产生洞用户被告知而非被拖慢。捕获阻塞、永不丢。慢磁盘拖慢一次下载换来的工件永远完整。Stop 即文件无直播转文件切换。绘图仍绑定实时源打开录制是独立的普通操作。解析器策略在布局中不在文件中D9。直接打开的录制使用加载器对话框记住的设置布局持久化该选择。溯源最小化索引权威。丢失缓存索引代价是一次重新下载不是数据丢失。宿主驱动缓存一个插件调用。提供方无法定制缓存内容开关是唯一的用户控制。默认捕获所有导入。一次性探索造成的磁盘消耗由预算、LRU 与偏好开关仅 arming、立即生效约束。contended 未命中失败绝不重新下载。两个实例并发恢复同一布局第二个实例使该源失败并提示等待并重载而不是重复下载同一字节或在重试循环上空转。廉价命中验证失败即自愈。同尺寸的损坏文件会先到达加载器才被淘汰。录制永不淘汰缓存 LRU 淘汰。两个文件夹、两个预算需要向用户解释。这些决策沉淀在代码中Recorder.h的RecorderOptions定义了两个溢出策略OverflowPolicy::kDropLargest与kBlock默认队列预算queue_budget_bytes 64ull 2064 MiB并注明不是 RSS 上限——通道添加字符串、分配器开销与 writer 在途消息不计数。Recorder的状态机kIdle → kRunning → kStopping → kFinished/kTruncated明确区分用户停止与数据被失败截断且kTruncated在stop()后依然保留——状态本身就能区分干净录制与丢数据录制。丢包计数器RecordingStats::dropped_messages与纯 lazy 跳过skipped_messages是两个不同的洞skipped 从未到达 recorderdropped 是到达后被预算淘汰。丢包策略的精确契约D4onMessage入队后立即返回绝不等待 writer也绝不因队列满而回答kStopRecording——丢一条消息只结束一条消息不结束录制。当新消息放不进queue_budget_bytes每条消息成本 payload 承载它的 item时对集合 {已排队消息} ∪ {新消息} 执行统一规则丢弃最大成员重复直到新消息放下或被丢弃。平局淘汰最新者同尺寸的排队候选中丢弃最近入队的仅等于最大排队消息的新消息也会被拒绝。于是慢磁盘持续饱和时积压前沿的消息仍被写入洞留在实时边缘直到 writer 追上。通道添加永不丢弃每条仅几个字符串且其后的消息引用它们。实现上的三个关键点Recorder.h队列用std::list而非 deque淘汰要从中间删除而 writer 从头部排空只有 list 迭代器能同时存活于两者。SizeIndex std::mapstd::pairuint64_t,uint64_t, Queue::iterator按 (成本, 序号) 索引排队消息最大者即最后一项任何消息 O(log n) 可删——若每次淘汰都扫描队列找最大者恰好在 sink 饱和最需要便宜的时候让每条消息都付出扫描代价。每个消息的成本统一由queuedCost(payload) payload sizeof(MessageItem)定义入队时计费、出队或淘汰时退费。其推论均为刻意设计比整个预算还大的消息永远不会被录制任何队列状态都腾不出空间即使队列为空也会被丢弃只在 dropped 计数器中留下痕迹64 MiB 默认下需要真正巨大的消息。丢弃是洞不是重排幸存消息仍按 FIFO 排空每个通道保持 log-time 顺序。大主题先付代价sink 饱和时一帧视频或一帧点云先于数百条标量样本被丢弃——这正是廉价通道保持连续的原因。会计是总量按被录源计而非按主题RecordingStats/RecordingSummary携带dropped_messages每个SourceRecordingResult携带它CaptureResult在捕获层求和每条收尾pj.recording记录携带该文件自己的计数。预算按源因此丢包策略也按源饱和的 sink 只丢弃拖垮它的那个源的消息其他源的文件不受影响。截断truncation只留给真正的失败sinkwrite/addChannel/close错误、MCAP id 耗尽、或逃出 tap/writer 线程的异常。此时 recorder 转入Truncatedwriter 排空已持有的数据后退出每个 tap 返回kStopRecording并脱离。writer不写摘要——截断让 sink 保持打开所有者轮询state()发现Truncated后调用stop()join writer 再关闭 sinkclose()写入pj.recording.truncatedtrue含原因如disk full、write error: …。插件的pushMessage在任何情况下都正常返回。5. 崩溃安全分块写入 每块刷 chunk index崩溃留下的文件只是缺少 summary 段。加载器以线性模式扫描块打开mcap recover是兜底方案而非计划。受检 sinkmcap 自带的FileWriter会吞掉短写、flush 与 close 失败。录制器通过自己的mcap::IWritable写入锁存首个 OS 错误并保持size()推进在每次写后与 close 时检查锁存——这就是磁盘满变成截断原因而非静默损坏文件的原因。sink 直接以std::filesystem::path打开Windows 上不做string()exclusive-create模式已存在的文件或符号链接使打开失败而非被截断或跟随。有界丢失窗口块大小显式为1 MiBmcap 默认为 768 KiBmcap_server对离线下载用 4 MiB 因为崩溃丢失无关紧要。崩溃最多丢失打开中的那个块。大消息隔离小消息共享 1 MiB 块但200 KiB 及以上的消息独占一块writer 在其前与后都 flush 打开的块。惰性读取器要解压整块才能取到一条消息因此把视频帧与数百条标量样本放同一块会让双方互相付费隔离后标量抓取保持廉价大消息可独立抓取与压缩。等于或大于块大小的消息本来就被 writer 自身的溢出检查隔离——这里只是把同一处理降到固定阈值。以最终文件名写入录制直接写到name.mcap与rosbag2、mcap record一致。未干净停止的录制与完成录制只差缺失 summary 段读取器线性扫描、mcap recover重建索引无需重命名用户就能拿到文件。sink 在 close 前 fsync因此返回的stop()意味着字节已在稳定存储上。元数据放置McapWriter::write(Metadata)会关闭打开的块因此pj.recording只在打开与关闭时写入绝不在流中间。每通道sequence计数器符合普通 MCAP 工具的预期。McapRecordingWriter.h 的注释还揭示两条重要细节mcap 惰性发出 schema 与 channel 记录第一条引用它们的消息时——从未承载消息的绑定在文件中不留痕迹回放不得期待每个绑定都有空 channel以及write()拒绝负log_time_nsMCAP 时间戳无符号静默改写为 epoch 0 会埋掉上游时钟 bug。文件级pj.recording元数据打开时写入起始事实、关闭时写入终局事实含truncated读取器取最后一条。未 close 而丢弃的文件不带truncated或stopped_utc。字段形状如下JSON{ version: 1, app_version: …, capture_id: …, source_display_name: …, source_plugin_id: …, capture_ordinal: 1, started_utc: …, stopped_utc: …, terminal_cause: stopped|source_ended|truncated|shutdown, truncated: false, truncated_reason: , messages: …, payload_bytes: …, dropped_messages: … }version保持1没有任何已发布构建曾产出录制因此每源布局是该格式的第一个公开形状。capture_id是同一按下中每个文件共享的 UUIDcapture_ordinal是该文件在按下中的 1 基位置——文件夹被改名或文件被拆散后批次仍可重建。每通道metadata为空一个文件只含一个源身份是文件级事实而非每个通道重复schema 携带type_name/encoding/schema_bytes原样读取文件不需要任何 PJ 特有物mcapCLI 与任何 MCAP 工具看到的都是普通通道。MCAP 的 schema/channel id 是uint16_t因此addChannel在任一种超过 65535 时拒绝录制以该原因截断而非把 id 卷绕到活记录上。6. 录制服务RecordingService的运行时语义RecordingService.h 定义了 GUI 线程上最多一个活跃捕获的所有者语义目标而非宿主RecordingTarget用不透明的target_keypj_app 用DatasetId描述一个源附带回调——attach(tap)源消失时返回 falsenull 时脱离与可选的skipped_lazy()。服务从不接触DataSourceRuntimeHost资格判定D1直接 writer 没有可 tap 的委托式摄取由调用方决定——pj_app 的流式管理器按kCapabilityDelegatedIngest位过滤并把被排除的显示名随目标一起交回shell 因此能告知用户会缺什么。目标按target_key排序这固定了文件名、capture_ordinal与一切上报顺序。设置QSettings 键Preferences::recording_directory空 AppLocalDataLocation/recordings与Preferences::recording_queue_budget_mib默认 64预算被夹到 8..4096 MiB手改 .ini 无法武装出病态录制。改动在下次start()生效运行中的捕获保留开始时的选项。预算按源录 N 个源可能在途持有 N 倍预算——这是每源文件的既定成本无服务级上限、不分摊。命名D6recordings dir/pj_UTC yyyyMMdd_HHmmss/用普通mkdir建在精确名字上存在即失败所以创建文件夹就是认领它之后_2、_3…最多 100 次尝试。内部每个源得到sanitized display name.mcapNFC 归一化、路径分隔符与 Windows 保留字符映射为_、_/空白串折叠、尾部点与空格修剪、截到 80 字符、空时用source。归并到同名的a/bvsa:b或仅大小写差异加_2、_3…按 case-folded 比较writer 的 exclusive create 是最后防线。同步收尾stop()、源结束、轮询发现的截断与析构函数都会脱离 tap、冻结该源的skipped_lazy差值、然后在 GUI 线程排空并关闭 recorder。每个 recorder 恰好停止一次其结果存在即为守卫结果按启动顺序合并每次按下恰好发一次stopped()。等待上限为排队积压至多每源预算 × 仍在录制的源数除以 sink 写吞吐加每个文件一次fsync串行发生在 GUI 线程。drop-largest 让排队半部在实际中保持很小把收尾移到 worker 是刻意的推迟项。每源终止onSourceEnded(target_key, reason)以source_ended原因收尾一个源捕获其余部分继续批次在最后一个源结束时结束。原因保留进该源的结果非空即使其结果不 clean。轮询500 ms 定时器从每个存活Recorder::stats()发布progress()外加已收尾源的冻结总量——总量永不因 recorder 退役而下降。同一 tick 发现kTruncated并收尾该源Recorder契约刻意无截断回调。stopped()的含义CaptureResult携带capture_id、文件夹、每个源的SourceRecordingResult路径、terminal_cause、计数器以及按此顺序判定的互斥结果桶incompletesink 无法收尾无 footer 或字节不持久只能线性扫描打开mcap recover重建索引→truncated失败截断数据但文件已关闭→lossy完整但有洞drop、skip 或带原因结束的源→clean。各桶计数之和等于源数调用方应测试clean_count sources.size()而非布尔值。两个丢失计数是不同的洞skipped_messages是该目标skipped_lazy()在 start 到收尾之间的差值dropped_messages是预算淘汰的§5。事务化启动每个文件夹、文件、recorder 都在任何 tap 附加前创建。任一失败sink 打不开、attach 返回 false 或抛异常都会脱离所有已触及目标、按路径关闭并删除已建文件、文件夹空则移除、返回指明失败源名的错误且不发started()。测试缝setSinkFactoryForTest()替换下次start()构建 recorder 所依赖的工厂生产环境不设置它每次得到一个McapRecordingWriter。可移植性服务本身可移植Qt Core filesystem无 mcap只有默认 sink 平台绑定——录制是运行时能力RecordingService::isSupported()回答此构建是否有 sinkshell 的 Record UI 与偏好页都只依赖这一查询。无 sink 构建的start()在触碰文件系统前就以recording is not available in this build失败除非测试注入了自己的 sink。7. 源缓存的实现细节身份、门禁与恢复SourceCacheStore.h 与 SourceCaptureService.h 展示了设计 已实现的完整形态宿主通用身份方案sourceCacheIdentity(provider_id, descriptor_json)是版本化、长度分帧的身份派生——基于不可伪造的 provider id来自宿主插件绑定绝不来自插件数据加上精确的规范 descriptor 字节。所有缓存键的产消双方都经此函数分帧永不可能不一致下游字节精确匹配store 对字符串取摘要。sourceCacheIdentityDigest()输出短摘要sha256/128: 32 个十六进制字符是数据集级SourceRecord/布局materialize identity携带的快路径键——消费者必须确认完整 descriptor 字节后才能信任匹配。无独立索引文件工件的存在、其摘要派生文件名与 LRU 时间戳就是索引。丢失缓存目录不丢数据——每个条目只是一次重新下载。摘要是对身份字符串的摘要绝不是内容摘要验证是廉价结构检查而非重哈希因此提供方必须让身份内容决定两个可能返回不同数据的请求必须铸造不同身份否则缓存会把过期字节当命中。跨进程排他依赖 advisory 文件锁在 flock 失效的网络文件系统某些 NFS 挂载上网络家目录上的缓存根会失去该保护。默认预算 10 GBkDefaultBudgetBytes 10 * 1000 * 1000 * 1000默认根目录AppLocalDataLocation/source_cache——独立于录制文件夹C6。Settings.budget_gb加载时夹到 1..1000。先 pin 后验证lookup()在检查工件之前取 pin淘汰器无法在检查与使用之间移除文件。contended 身份正在发布或隔离中是带重试提示的未命中绝不报错也绝不成为未 pin 的命中验证器拒绝会说明原因并把文件留在原地下次发布 rename 覆盖它。发布beginPublish(identity)让写入者把工件写到事务的partialPath()关闭后再以同一身份交给publish()。SDK 的 commit 可能已发布文件却在对并发独占尝试的锁交接中失败恢复步骤是恰好一次 lookup在publish()内执行调用方永远看不到该窗口。隔离quarantine是自愈路径加载失败后淘汰工件及其 LRU 时间戳下次 lookup 是 absent 未命中、下次导入重新下载。要求该身份的排他锁工件被别处 pin 时拒绝隔离并给原因——上报绝不强制。LRU 淘汰cleanup()从最旧开始逐出至预算内跳过 pinned 工件与进行中的发布pin 使大小超预算时报告target_metfalse与超预算字节数。必须在 worker 线程调用绝不在 GUI 线程或 lookup 路径内。CaptureManifest工件内嵌的pj.capture元数据携带版本、provider_id、身份、attests_empty_topics、每个主题的消息计数仅 attestation 下允许 0与总数——恢复方无需提供方调用即可验证工件与布局源记录的一致性。发布门publication gatefinalize()只在全部满足时发布——无 veto、显式 COMPLETED 终态、已附加 descriptor且捕获在其上成功武装、事务已提交、未取消参数或闩锁、零锁存回调失败、零纯 lazy 跳过、ledger 等于宿主的 tap 合格计数、覆盖声明集合零消息主题仅在空主题 attestation 下集合外无记录总体至少一条消息、recorder 干净关闭且写入计数等于 ledger。被摄入的会话数据从不被触碰——拒绝只中止缓存事务。Cache-firstresolve()派生身份 → 查 pin 工件 → 读回 manifest 并严格验证provider 与身份匹配、MCAP 统计中的每主题消息数等于 manifest、无未声明通道、总量一致。验证失败的工件被隔离先释放 pin因他读者持 pin 而拒绝时上报 store 的原因。miss_kind区分 CONTENDED 未命中另一实例正在为该身份发布——调用方必须失败而非启动重复下载与普通 absent 未命中提供方回退正确。8. 被否决的备选方案插件侧缓存pj-official-plugins #275 最初的写法捕获 tee、.pjmosaico格式、加载器插件、DSO 生命周期 lease、预算。否决理由每个连接器都要重新实现格式、加载器、淘汰与锁定lease 生命周期绑定在插件 DSO 而非读取文件的数据集上专有格式必须永远可回放。从它存活下来的部分类型化请求 descriptor 与身份方案由字节逐字节一致的文件向量固定、信任白名单、凭据来源守卫含防止凭据降级的 grpc/grpctls 存储键别名规则、布局提供文本的呈现卫生、无头导入任务形状、及其测试语料descriptor 向量、凭据矩阵、温腿天然零网络的三腿 E2E。提供方驱动缓存 ABIlookup / adopt / attach / complete 加 lease id——第一个 M3 草案。否决每个提供方要搞对五个调用且命中仍需提供方运行。一个调用现在覆盖了它。tap 数据存储写桥解码样本。否决与 raw-only 矛盾需要 PJ 私有 schema回放永远无法重新解析。把解析器配置放进 MCAPpj-official-plugins #280。否决数据 vs 解释——两个人可能用不同策略读同一录制策略变更不能重写多 GB 文件。Stop 时提升promote-at-stop把实时源切换到其录制。放弃一个一次性便利换取新交互、clean/lossy 策略与 stop-then-replace 序列及其自身故障模式。缓存发布后提升对象转 lazy。推迟真实内存收益但每次下载结束时二次解析保留为内存成问题时的已知补救。.partial rename fsync 目录。放弃MCAP 天然可恢复且没有其他 recorder 这么做。9. 后果与已知限制可录制 会说消息。直接写连接器必须拆分传输与解析Mosaicoparser_arrow 纯传输插件才能可录制、可缓存——这也正是宿主能路由它们经过已安装解析器的前提。纯 lazy push 留下洞。文件支撑的源惰性推对象字节稍后取会被计数而不会录制。只有文件支撑的源可录制后才有意义。捕获期间峰值内存不变。对象在工件发布前都是 eager 的。recorder-as-disk-spill 扩展从正在写的文件服务 lazy 抓取可消除之更重要的是用磁盘而非 RAM 约束直播流的对象历史——它是已识别的后续工作不是承诺。前缀不录制。宿主在接缝之下应用源前缀回放文件得到文件源自己的命名。秘密。recorder 原样存储字节连接器绝不能把认证材料作为消息推送规则为插件作者记录在案。10. 测试与验证矩阵录制部分有五个ctest二进制pj_runtime/tests/与 session_recorder_design.md §8 一致二进制覆盖recorder_test队列/丢包契约排序、首消息开通道、largest-message-loses 的各形态丢最大排队者、拒更大的新来者、永不录比整个预算大的消息、按大小而非年龄选择、满队列 push 永不等待 writer、sink 错误/抛异常的截断、stop()幂等、并发生产者下的丢包会计、tap 存活超所有者。mcap_recording_writer_test用mcap::McapReader读回产物通道metadata 为空、schema 去重、两条pj.recording记录含capture_id/capture_ordinal/terminal_cause/payload_bytes、exclusive create、实例复用、16 位 id 守卫。recording_service_test捕获布局与生命周期批文件夹与每源一文件按排序键、净化表与区分同名冲突的序号、各失败形态的事务回滚attach 失败、attach 抛异常、recorder 打不开、拒绝重复键与空批次、一源截断而批次继续、结束的源加全局 Stop 恰好每源一结果且只发一次stopped、总量永不下降、零消息文件保持 clean、析构函数持久收尾、设置往返与夹取、不可关闭 sink 导致的 incomplete 结果。data_source_runtime_host_record_tap_test接缝的宿主端每条消息携带绑定视图含 tap 之前铸造的绑定、kStopRecording、抛异常 tap、纯 lazy 会计。recording_pipeline_test组装流水线真实宿主 真实解析器插件 → tap → recorder → MCAP由RecordingService驱动并读回流中开始录制、无录制前泄漏、两个实时源落进一次捕获的两个文件同capture_id、序号 1 和 2、各持自己的主题、第二次录制干净开始、ingest 不受影响。源缓存侧已有source_cache_store_test.cpp、source_capture_service_test.cpp与toolbox_capture_wiring_test.cpppj_runtime/tests/对应存储、发布门与宿主接线的验证。延期项见 session_recorder_design.md §7.1经data_load_mcap的值相等回放测试跨仓库、10k msg/s 节流 writer 压力测试、kill-writer 崩溃文件用例。11. 从哪里继续阅读工作设计、决策历史D1–D9与里程碑 session_recorder_design.mdpj_recording元数据 JSON、丢包契约、崩溃安全、测试矩阵的权威来源。代码pj_runtime/include/pj_runtime/下的 RecordTap.h、Recorder.h、McapRecordingWriter.h、RecordingService.h、RecordingTypes.h、RecordingSink.h、SourceCacheStore.h、SourceCaptureService.hshell 接线在pj_app/src/MainWindow.cpp、pj_app/src/StreamingSourceManager.cpp与pj_app/src/ToolboxHostWiring.h。请求身份与 descriptor 类型SDK 的pj_base/sdk/source/request_cache.hpp的RequestArtifactCache/ReadLease/WriteTransaction。测试pj_runtime/tests/下的recorder_test.cpp、mcap_recording_writer_test.cpp、recording_service_test.cpp、data_source_runtime_host_record_tap_test.cpp、recording_pipeline_test.cpp、source_cache_store_test.cpp、source_capture_service_test.cpp、toolbox_capture_wiring_test.cpp。一句话总结这套机制的价值宿主在委托式摄取接缝上做一次镜像就同时得到了标准 MCAP 会话录制与离线即时恢复的源缓存——录制永不拖慢 ingest、缓存命中永不拖慢读取回放永远复用data_load_mcap与已安装解析器这套现成机制。赞分享数据可视化桌面应用数据分析【免费下载链接】PlotJugglerThe Time Series Visualization Tool that you deserve.项目地址https://gitcode.com/gh_mirrors/pl/PlotJuggler点击查看免费下载相关推荐PlotJuggler 4 会话录制器Session Recorder设计全解基于 MCAP 的主机侧原始数据采集架构PlotJuggler 4 会话录制器Session Recorder设计全解基于 MCAP 的主机侧原始数据采集架构 导读 本文围绕 PlotJugg数据可视化桌面应用数据分析DeepSeek Harness 无缓冲反馈遥测FEEDBACK_ONLY 模式下基于规范会话日志的按需捕获设计DeepSeek Harness 无缓冲反馈遥测FEEDBACK_ONLY 模式下基于规范会话日志的按需捕获设计 本文聚焦 DeepSeek Harness人工智能AI AgentAgent 框架DeepSeekHighlight iframe 会话录制同源与跨域 iframe 的捕获原理与配置实践Highlight iframe 会话录制同源与跨域 iframe 的捕获原理与配置实践 本文基于 Highlight 官方文档 iframe Recordi可观测性后端上一篇从 WTFJS 手册看透 JavaScript 的诡异行为50 陷阱案例、强制类型转换与 ECMA-262 规范溯源下一篇掌握GitHub加速插件让你的下载速度提升10倍的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考