外观
Session 数据模型总览:从 CapturePlan 到 Review/Export
先看结论
- Session 不是单一文件,而是一个本地目录:
manifest.json+raw-events.jsonl(+ 可选core-profile-chunks.bin)+screenshots/+ 可选exports/。具体文件名以当前实现为准,见第 3 节。 - 时间轴是所有指标、截图、标注共用的派生坐标层,不是新指标:优先用 host monotonic 锚点;一个 Session 只选一种时钟,不在同一条时间轴里混用 monotonic 与 wall-clock fallback;乱序、回退、空 Session 都有显式质量状态,不静默重排或补 0。
- "不可篡改"目前的准确含义是 append-only + SHA-256 校验,篡改可被检测(replay 时 hash 不匹配会 fail-closed ),不是密码学签名意义上的不可伪造。
- Screenshot 是否落盘与其余性能指标采集完全隔离:独立子进程、独立 DVT 长连接,未勾选 Screenshot 时磁盘 0 写入,但顶部实时预览仍以 1 秒节拍刷新。
图:Raw、Metric、Report 共用有序 Session 写入链;Screenshot 是独立通道,只在统一时间轴层对齐。
0. 当前实现摘要
一次 Session 从 CapturePlan(本次采集要订阅哪些 source/指标、以什么策略)开始,经过 RawSessionRecorder 把原始事件 append-only 写入 raw-events.jsonl 并同步维护 SHA-256 摘要,IncrementalMetricRecorder 增量派生 Metric,可选的 Online Report 在同一个有序 SessionWriter 内消费同一批事件产出实时曲线;停止时对 Raw / Metric 分别落 manifest.json 并对 Online Report 执行 seal。回放侧由 PerformanceSessionStore 只读加载 manifest/事件,交给 SessionTimelineBuilder 生成统一播放坐标,SessionExporter 从同一批 timeline 点位生成 JSONL / CSV 导出,保证报告与导出文件不会出现不同的时钟锚点或窗口坐标。
1. 这个领域到底观察什么
Session 数据模型回答"一次测试执行的完整证据链保存在哪里、以什么顺序写入、如何在不篡改原始事实的前提下重放和导出"。它不产生新的性能数值,而是所有指标、截图、Note 的容器与坐标系统。
2. 系统原理:Raw → Metric → Report 三层 + 独立的 Screenshot 通道
text
[采集侧]
DTX / Diagnostics 原始事件
│
▼ RawSessionRecorder.append()(append-only,同步维护 SHA-256)
raw-events.jsonl + manifest.json
│
▼ IncrementalMetricRecorder.append()(同一个 SessionWriter 内顺序消费)
Metric(派生指标帧)
│
▼(可选)Online Report.append()
实时曲线 / 停止时 seal()
[独立通道,不与上面共享连接或采样上下文]
Screenshot Service(com.apple.instruments.server.services.screenshot)
│
▼ 独立长连接子进程,固定 1 秒节拍
Live Preview(内存最新单帧) + (按需)screenshots/ 持久化序列SessionWriter.append() 把 Raw → Metric → Report 绑定在同一次调用里,任何一环失败都会把 writer 标记为 failed,不允许在部分写入后静默重试——这是为了防止 Raw 已落盘而 Metric/Report 没跟上导致的证据不一致。Screenshot 走独立子进程与独立 DVT 连接,即使截图侧超时、重连或崩溃,也不影响 Performance DTX 或其他指标采集(详见 Session 完整性与回放确定性)。
3. 本领域涉及的文件与概念地图
| 概念 | 实际产物 | 说明 |
|---|---|---|
| Raw 事件 | raw-events.jsonl | Append-only 原始事件流,逐行 JSON |
| Raw Manifest | manifest.json | 记录 rawSHA256、rawByteCount、eventCount、schema 等字段,用于 replay 校验 |
| Raw Checkpoint | raw-checkpoint.json | 周期性检查点,支持长会话中断后恢复 |
| CoreProfile 原始分块(可选) | core-profile-chunks.bin | 仅 storagePolicy=full 时落盘;产品默认 derivedOnly 不写此文件,实时解码仍正常工作 |
| 截图序列(可选) | screenshots/shot-000001.heic(或 .jpg fallback;历史归档可能是 .png) | 仅用户勾选 Screenshot 指标时落盘,未勾选时目录不创建、磁盘 0 写入;当前默认持久化格式为 HEIC 1800px/Q75,本机无 HEIC 编码器时降级 JPEG,不降级 PNG |
| 导出产物(按需) | exports/*.jsonl、exports/*.csv | 由 SessionExporter 从 review.timeline 生成,首行/首行元数据携带时钟来源与算法版本 |
具体字段与真实产出示例见 Session 完整性与回放确定性;不要以旧版文档中的示意文件名(如曾经出现过的 session-plan.json / session-summary.json)作为当前真实结构,一律以当前代码为准。
4. Perfowl 当前怎样取得这些事实
Raw 事件来自各 source 的 DTX/Diagnostics 原始回包,由 RawSessionRecorder(MacApp/Sidecar/perfowl_collector_core/recording.py)以单线程所有权模型 append-only 写入并同步累加 hashlib.sha256() 摘要;Metric 由 IncrementalMetricRecorder 在同一 SessionWriter.append() 调用内派生;时间轴由 SessionTimelineBuilder(MacApp/Sources/PerfowlCore/SessionTimeline.swift)在 Swift 侧构建;导出由 SessionExporter.swift 生成。
5. Scope / Identity / Runtime Condition
- Session 目录本身是进程级证据容器,只对应一次采集执行(一个
sessionID); - Raw 事件的写入所有权是单线程独占(
RawSessionRecorder内部用threading.RLock保护),不存在多写者竞争同一 Session 目录; - CoreProfile 原始分块的持久化策略(
derivedOnly/full)与 Screenshot 是否落盘,均通过capturePlanHash排除,切换这些策略不会触发 Collector 重启或重新 Prepare App(Rule 0 边界)。
6. 指标应该怎样组合阅读
先看 Session 完整性与回放确定性 理解 manifest/hash/seal 的证据边界,再看 Session 历史会话回放与时间轴 理解播放坐标如何构建,再看 截图时间轴与界面上下文对照指南 理解截图独立通道,最后看 Session 资产管理、标注与测试报告导出 理解导出格式。
7. 常见异常形态:观察 → 候选原因 → 交叉验证 → 不能确认
| 观察到的现象 | 候选原因(需交叉验证) | 下一步验证 | 不能确认的结论 |
|---|---|---|---|
时间轴质量为 estimated | 该 Session 缺少 host monotonic 字段,回退到 wall-clock 锚点 | 检查 manifest/事件是否为旧版本归档 | 不能认为数据本身有误,estimated 是质量标注不是错误 |
时间轴质量为 degraded | 检测到事件乱序(后到事件的 monotonic 小于前一个) | 结合原始事件的 ordinal 与 source 类型排查上游乱序原因 | 不能确认具体是哪个 source 导致乱序,需要进一步看事件级证据 |
| 长会话磁盘占用远小于预期 | coreProfileRawRetention=derivedOnly(产品默认)未落盘完整 CoreProfile blob | 检查 manifest 的 coreProfileRawRetentionStatus 字段 | 不代表采集数据变少,只是原始 blob 不落盘(实时解码仍正常) |
Session 目录里没有 screenshots/ | 用户未勾选 Screenshot 指标(按设计不落盘) | 检查 Session 的 screenshotPersistenceEnabled 字段 | 不代表截图采集失败,顶部实时预览仍在工作 |
8. 连续性、质量、missing / 0 / warmup
- Raw 事件写入失败(quota/磁盘不足/writer 内部异常)会在
RawSessionRecorder内部降级为truncated,不允许异常外溢到SessionWriter.append()之外; - 时间轴坐标无法建立时保持空值并携带质量原因(
missing/estimated/degraded),不用0填充; - 详见 Session 完整性与回放确定性 的 manifest 字段语义。
9. 当前证据与平台覆盖
时间轴基础(SessionTimelineBuilder)与 Store 回放加载均已通过 Swift 全量测试(E2:49/49、53/53)。CoreProfile 存储分级策略已完成 E4 近 5 分钟真机磁盘验证(Build 58:同类 Session 磁盘占用从约 10.11 GiB 降至约 203 MiB,降幅约 98%),但 30 分钟长测与 full 调试模式真机对照仍待执行。Screenshot 长连接架构已完成 E2 自动化验证与 Build 59 签名包交付,随后的 E4 真机验证发现过 PNG payload 损坏、未勾选时无实时序列等实现偏差;修复方案(改用 HEIC/JPEG 持久化、IPC 四步完整性校验)已完成代码实现与 E2 回归(Sidecar 430/430、Swift 167/167),但该修复版本自身的 E4 真机验证截至最新已整理的 devlog 时间线尚未执行(缺可用设备),修复状态详见 Session 架构演进与实验。真机、设备与账号相关标识均未进入仓库证据。
10. 常见误区
- 把旧文档里出现过的示意文件名(
session-plan.json/session-summary.json)当作当前真实的 Session 目录结构; - 把"append-only + SHA-256 校验"理解为密码学签名意义上的不可篡改;
- 认为 Session 磁盘占用小就是"采集数据变少了",而不检查是否是
derivedOnly存储策略在生效; - 认为未勾选 Screenshot 时顶部预览也会停止刷新——预览与落盘是两件独立的事。