全量采集与流式报告改造——可执行开发任务书

文档类型:devlog / plan。本文已完成代码核查与设计,产品实施待明确批准。partial 表示本改造尚未实施验收,不表示方案已批准。本文中的接口、文件名、协议扩展和性能数字,除明确标为“当前实现”外,均为拟开发合同。

面向执行者:按任务卡顺序开发;先写测试,后修改实现。验收条件未达到时保留未完成状态。禁止通过修改提示、隐藏处理进度、删减指标、补零、降低精度或丢弃尾部数据来换取“完成”。

0. 交接入口、基线与目标

仓库根目录:/Users/itreenewbee/工作/代码/perfowl。

核查基线:fb5d3bd0f896d842bb527cd1d0bb8bc1ff4bc6c0。执行者启动时重新检查 HEAD 和差异,禁止覆盖他人的未提交文件。本文代码路径均相对上述根目录;文末包含逐项指标源账本。

0.1 用户需求转成六条硬合同

  1. 选择设备后建立设备服务;选择应用后一次准备所有适用采集源。
  2. 全量采集覆盖当前 Registry 的全部64个定义;将来增加定义必须同步覆盖。图表隐藏与否不影响采集、保存、报告和导出。
  3. 「+」只控制显示;增删图表不改变采集计划、不发送准备命令、不改变启动方式、不重开设备连接。
  4. 截图属于附件,不计入64个指标;默认按既有1秒节拍保存,与是否显示截图分离。
  5. 报告从采集开始持续生成;停止只处理有限尾部、同步写盘、发布最终清单,不重新遍历整场历史。
  6. USB条件下只有明确不适用的能耗项目允许跳过;所有其他不支持或失败项目仍在报告账本中保留状态与原因,禁止悄悄消失。

0.2 成功体验

选择设备 → 设备服务就绪
选择应用 → 全量采集服务准备完成(展示覆盖情况)
点击开始 → 全部适用指标记录 + 报告持续更新
点击+ → 只增减可见图表;新增图表可读取开始以来已有数据
点击停止 → 立即反馈 → 有界尾部完成 → 报告已保存

“报告已保存”必须同时满足产品会话与采集子会话的保存合同;后台仍在制作必需报告时,不展示该状态。

0.3 本批不扩大的范围

1. 已核实的现状与修改依据(E1)

代码位置 当前行为 本批处理
MacApp/Sources/PerfowlApp/PerfowlWorkbenchView.swift 的 collectorPreparationKey 把显示选择 selectedMetricIDs 拼入准备任务标识 移除显示选择;改为设备、应用、采集策略版本与准备代次
MacApp/Sources/PerfowlCore/PerformanceSessionController.swift 的 prepareCollector 显示计划变化可走 invalidateCollectorReadiness(),终止预热进程 显示更新不进入准备API;按设备/应用分层失效
MacApp/Sources/PerfowlCore/CollectorReadiness.swift 的 metricPlan 对显示选择排序生成准备匹配值 新增全量采集计划哈希;旧字段仅供旧会话解码
MacApp/Sources/PerfowlCore/PerformanceSessionController.swift 的 trySendPrewarmApp 启动图表选择决定冷启动与普通启动 独立测试方式,默认包含启动采集
MacApp/Sidecar/perfowl_collector_core_cli.py 普通源已基本全量;startupTiming 仍控制启动trace;准备应用只允许一次 新协议显式全量计划、独立启动策略、可取消的应用重绑定
MacApp/Sidecar/perfowl_collector_core/capture_policy.py 普通源与显示选择已有部分解耦 复用去重逻辑,补齐能力账本与条件门禁
MacApp/Sidecar/perfowl_collector_core/session_writer.py Raw与普通Metric共用顺序writer 升级为所有必需报告的增量处理入口
MacApp/Sidecar/perfowl_collector_core/metric_engine.py 的 IncrementalMetricRecorder 已边采集边写普通指标与哈希 保留并复用,不再创建第二条重复派生链
MacApp/Sidecar/perfowl_collector_core/delivery.py 停止后9阶段串行finalize 新增在线封存入口;旧finalizer保留作审计/恢复
MacApp/Sidecar/perfowl_collector_core/recording.py 的 validate_recorded_session 回读Raw及blob,并将blob恢复到事件集合 正常在线封存不调用;独立审计仍保持校验
MacApp/Sidecar/perfowl_collector_core/metric_snapshot.py 停止时回读并校验完整metric文件 在线封存使用writer持有的状态;旧文件独立读取继续校验
MacApp/Sidecar/perfowl_collector_core/frame_derivation.py 离线构造完整序列并做median/sort 增量帧明细与精确索引,保留离线实现作对照
MacApp/Sidecar/perfowl_collector_core/checkpoint_policy.py 周期checkpoint可选,默认0关闭 新会话显式启用有界周期检查点,保留旧会话兼容
MacApp/Sources/PerfowlCore/PerformanceSessionStore.swift 产品层另有事件/CSV/manifest写入 加入统一最终序号与封存确认,覆盖双层保存

重要结论:已有“部分增量”基础,不是从零重写。真正缺口是显示与准备仍耦合、特殊指标未全量、报告聚合未在线化、结束时仍全历史复算。

2. 全量采集、显示与测试方式的数据合同

2.1 新增四个独立类型

新建 MacApp/Sources/PerfowlCore/CapturePlan.swift;Python侧扩展 capture_policy.py,保持同一规范化JSON和测试向量。

struct CapturePlan: Codable, Equatable, Sendable {
    let schemaVersion: Int               // 1
    let policyVersion: String            // all-supported.v1
    let requestedMetricIDs: [String]     // Registry全部ID,去重、字典序
    let registryContractHash: String
    let launchMode: TargetLaunchMode     // 独立配置
    let startupEnabled: Bool             // 默认true
    let screenshotsEnabled: Bool         // 默认true
    let energyPolicy: String             // capability-and-power-gated.v1
}
struct DisplayPreferences: Codable, Equatable, Sendable {
    var visibleMetricGroups: Set<WorkbenchMetricID>
}
struct PreparationToken: Codable, Equatable, Sendable {
    let deviceGeneration: UInt64
    let appGeneration: UInt64
    let capturePlanHash: String
    let capabilityRevision: UInt64
}

ResolvedCapturePlan 由采集端根据真实源能力生成:包含请求计划哈希、64项逐项状态、去重源列表、sourceEpoch、适用窗口及失败原因。客户端展示它,不在客户端猜测哪些源支持。

2.2 哈希规则

2.3 原有字段迁移

3. 能力与缺失账本:别把64项等同于64路连接

3.1 来源事实源

3.2 每项报告结构(新增覆盖账本,不替换既有quality)

{
  "metricID": "METRIC_ID",
  "requested": true,
  "sourceIDs": ["SOURCE_ID"],
  "preparationState": "prepared",
  "collectionState": "recording",
  "valueQuality": "missing",
  "reason": "no-sample-yet",
  "sampleCount": 0,
  "firstSourceSequence": null,
  "lastSourceSequence": null,
  "coverage": [],
  "sourceEpoch": 1
}

3.3 USB能耗策略

  1. 电流、电压、功率依据实际外供电状态与现有物理门禁判断;USB连接不是替代原始电源字段的理由。
  2. CPU/GPU/网络等energy cost是系统相对成本,不等于物理电池功率;只要服务可用,仍采集。
  3. 某源在当前连接条件明确不适用:记录 not-applicable 和现有reason,停止该源的重复尝试。
  4. 温度、温控、energy、battery分项判断;禁止一条“USB”分支关闭整组。
  5. 不确定支持性时进行一次有界探测,失败记录原因;持续采集中不每秒重试失败源。
  6. 连接条件发生变化必须产生capability revision;禁止把新的支持结果倒填到过去窗口。

4. 两级准备生命周期与连接复用

4.1 状态机

Device: absent → connecting → ready → disconnected/failed
App:    none → binding → preparing-all → ready/ready-with-limitations
Session: idle → starting → recording → stopping → sealing → saved/partial/failed

“ready-with-limitations”是产品准备状态,不是全站devlog状态词。表现为可开始,并明确列出不适用/失败项。设备连接、目标身份或Raw持久化基础失败才阻断开始;非必需单源失败不拖垮全部普通采集。

4.2 失效矩阵(必须逐格测试)

变化 设备连接 应用绑定 全源准备 报告状态
+/移除图表 保持 保持 保持 保持
同应用重复选择 保持 保持 幂等返回 保持
A应用切B应用 复用健康连接 appGeneration+1 清理进程相关状态并重绑;复用设备级源 重新生成未开始草稿
应用版本改变 保持 appGeneration+1 重验目标能力 新草稿
连接路由/设备重连 deviceGeneration+1 失效 重新准备 不复用旧测试边界
采集策略/测试方式改变 尽量复用 保持身份 仅重配受影响源 新计划哈希
采集中显示变化 保持 保持 保持 仅UI视图变化
采集中目标进程重启 保持 targetGeneration+1 原有重绑定规则 重置差分/帧窗口;禁止混代

4.3 重复prepare_app协议

现有采集端通过一个只能完成一次的Future接收prepare_app,第二次会返回app_already_prepared。执行者必须修正协议,而不是只删除前端if。

4.4 准备与实际采样分开

5. 流式存储与报告架构

5.1 必须复用的基础

保留RawSessionRecorder、IncrementalMetricRecorder、SessionWriter的顺序关系。新增在线报告对象接到同一writer的成功写入路径,不创建不受控制的额外队列。

设备源 → 有界入队 → Raw顺序writer
                     ↓ normalized Raw(分配全局seq)
                     ↓ 已有Metric engine(只consume一次)
                     ↓ 增量普通报告/帧报告/启动报告/质量与映射审计
                     ↓ 追加明细 + 更新精确索引 + 哈希
                     ↓ 批量提交 + 周期checkpoint
                     ↓ 小型报告快照 → UI

5.2 新模块建议及接口

# online_report.py
class OnlineReportSession:
    def append(self, raw, metric_frames, decoded_outputs) -> None: ...
    def snapshot(self) -> dict: ...
    def checkpoint(self, durable_boundary) -> dict: ...
    def seal(self, frozen_boundary) -> dict: ...

# streaming_stats.py
class RunningStats:
    def add(self, value: float) -> None: ...
    def remove(self, value: float) -> None: ...  # 仅处理精确同键替换所需场景
    def merge(self, other) -> None: ...
    def result(self) -> dict: ...

# exact_order_index.py
class ExactOrderIndex:
    def add(self, series_id: str, value: float, count: int = 1) -> None: ...
    def select(self, series_id: str, rank: int) -> float: ...
    def range_count(self, series_id: str, low: float, high: float) -> int: ...
    def largest_sum(self, series_id: str, count: int) -> float: ...
    def commit(self, sequence: int) -> None: ...

# live_seal.py
class LiveSealContext: ...  # 仅同一writer生命周期内构造,禁止从未校验JSON伪造

def seal_live_session(context: LiveSealContext, progress=None) -> dict: ...

decoded_outputs必须来自当前实际decoder输出,不得在OnlineReportSession里再创建一套全量decode。现有RawMetricEngine内置实时frame decoder,应扩展其内部返回结果/协作接口暴露frame events,更新所有调用点测试,保持外部Metric schema兼容。

启动decoder目前分布在早期drain和常规consume路径;迁移时先画清调用顺序,将同一sourceEpoch的消费统一到有序路径。传入append前如果某blob已消费,禁止重复consume。启动tap与frame tap分别保留独立decoder状态。

5.3 writer失败合同

normalized = raw.append(event)
frames, decoded = engine.consume_once(normalized)
metrics.append_derived(frames)
reports.append(normalized, frames, decoded)
advance_all_consumed_sequence(normalized.sequence)

伪代码表达唯一顺序,不要求直接照搬已有类签名。任一阶段失败时writer poison=true,禁止在同一状态里重试或继续宣称seal成功。Raw已写入而报告失败属于可恢复中断,不把它当完全成功。

没有生成metric的Raw也必须推进全部消费者的consumedRawSequence,防止最后一条为metadata时错误认为报告没消费完。

5.4 新文件布局

在现有采集子会话目录新增,不移动既有文件:

capture-plan.json                  # 请求计划、解析后的64项覆盖、版本
report-live.json                   # 小型原子快照,生命周期内可替换
analysis-state.sqlite              # 精确有序索引、join状态、增量状态;单写者
frame-events-live.jsonl            # 追加帧明细,保留sourceEpoch/generation
report-checkpoint.json             # 最近持久化共同边界
report-final.json                  # 最终汇总及64项覆盖账本
report-seal.json                   # 最终文件/边界承诺

原 metrics-derived.jsonl、启动/帧/自校准结果文件和 foundation-delivery.json 保留兼容投影。UI新代码优先读取新报告;旧reader仍获得所需旧名称小型文件。

最终报告引用完整明细,不嵌入整场样本数组。frame明细在采集中写入;停止不得再创建完整samples列表。HTML/ZIP导出按需读取这些明细,不影响本地报告已保存状态。

5.5 截图与产品层存储

6. 普通统计公式(实现前冻结,不允许凭印象重写)

6.1 原始公式保留原则

本批改变计算执行时机与状态组织,不擅自改变现有数值意义。对每个字段记录:输入、过滤、scope、单位、窗口、重置、聚合、取整、版本、出处。Appendix A生成64项来源账本;D0任务用固定源函数生成逐字段公式版本快照与golden后才开发;这是机械提取/校验,不要求执行者重新设计公式。

6.2 在线均值和总体方差

对通过原quality过滤的有限值x,维护n、mean、M2、min、max。

n' = n + 1
δ = x - mean
mean' = mean + δ/n'
M2' = M2 + δ*(x - mean')
populationVariance = M2/n
standardDeviation = sqrt(populationVariance)

n=0时结果null;样本总体方差采用除n而非n−1。浮点微小负方差只允许在明确误差阈值内夹到0,超过阈值记录数值失败。原始值和整数计数不提前舍入。

最小值、最大值和阈值次数O(1)更新。缺失、warmup、counter-reset、invalid-window的过滤沿用原报告字段规则,禁止使用统一“只接受valid”过滤而丢掉candidate。

6.3 网络报告特殊规则

现有报告按同一时间戳Send/Recv求交集:只有进程send序列和recv序列都非空时选择进程scope,否则选择设备scope。即使进程两侧各自非空但时间戳交集为空,也保持该scope下的missing,不再退到设备scope。该细节与当前代码严格一致。

6.4 时间与归因

7. 精确中位数、最慢1%和范围统计:固定实施路线

7.1 首版技术选择

使用单writer拥有的SQLite数据库,保存带子树计数与子树和的AVL树,内存只保留有界节点缓存。使用运行环境已有标准库sqlite3,不增加包依赖。

普通SQL ORDER BY ... OFFSET ... 或 SUM(count) 扫描整张频率表不作为rank查询实现;否则停止仍与历史规模线性相关。

这是高风险独立组件。先完成D1原型、正确性和真实性能门禁,再接入产品。门禁失败回到方案评审,禁止执行者自行改成近似分位数或停机排序。

7.2 节点及表

CREATE TABLE order_nodes (
  series_id TEXT NOT NULL,
  node_id INTEGER NOT NULL,
  value_key BLOB NOT NULL,
  value REAL NOT NULL,
  frequency INTEGER NOT NULL,
  left_id INTEGER,
  right_id INTEGER,
  height INTEGER NOT NULL,
  subtree_count INTEGER NOT NULL,
  subtree_sum REAL NOT NULL,
  PRIMARY KEY(series_id, node_id),
  UNIQUE(series_id, value_key)
);
CREATE TABLE order_roots (
  series_id TEXT PRIMARY KEY,
  root_id INTEGER,
  total_count INTEGER NOT NULL,
  committed_sequence INTEGER NOT NULL
);

7.3 必须实现并测试的操作

  1. add(value):查找/插入;更新到根的count/sum/height;做必要旋转。O(log U)。
  2. select(rank):比较左子树count、当前frequency,向左/右下降。O(log U)。
  3. prefix_count(value, inclusive):查询≤value或<value的数量。O(log U)。
  4. range_count(a,b)=prefix_count(b,true)-prefix_count(a,false)。
  5. largest_sum(k):先取右子树;可整棵取用subtree_sum;不足时取当前节点部分frequency,再递归左树。O(log U)。
  6. commit(sequence):索引与其业务序号在同一事务提交。
  7. restore(checkpoint):只恢复同合同版本、同边界、已验证checkpoint;禁止给旧状态套新版本。

7.4 最终查询公式

median(x):
  n奇数: select((n+1)/2)
  n偶数: (select(n/2) + select(n/2+1))/2

k = max(1, ceil(n*0.01))
onePercentLowFPS = 1000 / (largest_sum_frameTime(k)/k)

medianFPS = median(frameFPS)
MedRange = 100 * range_count_frameFPS(0.8*medianFPS, 1.2*medianFPS)/n

7.5 复杂度与代价如实记录

累计N条数据、U个不同值:总处理O(N log U),磁盘状态O(U),内存缓存有界;停止约O(K log U + B),K为固定统计查询数量,B为尾部待处理量。不是严格O(1),但移除了O(N)回读/排序。

若索引磁盘增长、随机写或CPU导致总资源门禁失败,本任务保持partial,提案另行评审。执行者不自行引入新的近似公式。

8. 帧与启动报告在线化

8.1 帧数据

8.2 启动数据

8.3 映射审计与自校准

9. 报告模板、实时快照与最终状态

9.1 模板内容

提前建立64项覆盖结构、原29项统计摘要结构、9个启动阶段结构、附件/事件入口、公式版本表与报告布局。模板中的sampleCount为0,测量结果为null、no-sample-yet。

应用切换丢弃未开始模板内存,替换为新目标模板;磁盘只保留可复用schema资源,不制造历史Session。

9.2 实时快照

9.3 状态文案

事件 界面文字
全源准备中 正在准备全部可采集指标
部分源不适用 采集服务已就绪 · 查看不可采集项
正常运行 正在采集全部适用指标
点击停止 停止请求已发送
收口中 采样正在结束 · 正在保存末尾数据
已持久化封存 采集结束 · 报告已保存
尾部不完整但可读 采集结束 · 报告已保存,部分数据不完整
磁盘失败 保存失败 · 已保留可恢复数据

全量覆盖标签显示“请求64项、准备X项、不适用Y项、失败Z项”;分别按定义与能力计数,不拿9类源计数冒充64项。

10. 停止边界协议与缓冲处理

10.1 时间点和序号

增加以下审计字段:

stopRequestedHostNs        # 点击停止时主机单调时钟
stopAcknowledgedHostNs     # 采集端接收停止请求
stopCutoffMode             # source-time / host-receive
lastAcceptedRawSequence    # 最终接受原始事件边界
lastMetricConsumedRawSeq
lastReportConsumedRawSeq
lastProductWireSequence
sourceReleaseCompletedNs
reportDurableHostNs
productSessionDurableHostNs

所有跨进程host时间必须确认同机monotonic定义一致;否则用分别测量的持续时间或建立桥,不直接相减。

10.2 接纳尾部规则

  1. 停止后立即禁止发起新的周期采样/截图任务,并请求关闭源。
  2. 停止前发出而晚返回的数据:有可信source→host桥,按测量结束边界判断;无可信桥,采用host接收边界并标注窗口语义。
  3. 包含前后混合时段的blob原样保留到审计Raw,派生值按可验证事件时间筛选;若源时间不足以筛选,采用已声明的host边界策略,不发明精确设备截止。
  4. 传输审计流可包含stop后关闭/丢包等控制事件,记录最终accepted序号;测量窗口截止与文件最后序号是两个不同概念。
  5. 多源共用一个绝对drain deadline,不给每个源串行叠加2秒。
  6. 延续最大2秒drain边界作为初始容错值;有明确source end则提前结束。静默只能说明quiescent,不宣称有终点证明。
  7. deadline到但尚有未处理尾部:显式partial/tail-incomplete,保存已完成边界;禁止为了≤2秒指标丢尾部并标complete。
  8. 物理服务未释放前不允许新Session复用同源;UI响应和设备释放分开记录。

10.3 封存伪代码

async def stop_session(request):
    accept_stop_once(request)
    freeze_measurement_cutoff(request)
    stop_periodic_scheduling()
    await close_sources_until_shared_deadline()
    # 最后一条closeout质量事件也进入有序writer
    boundary = await writer.barrier_after_final_control_record()
    assert boundary.raw_seq == boundary.metric_consumed_raw_seq
    assert boundary.raw_seq == boundary.report_consumed_raw_seq
    result = await writer.seal_live(boundary)
    emit_report_sealed(result)  # 采集端封存;产品端仍须同步保存

禁止等所有历史UI绘图渲染完再执行barrier;UI只接收最新快照。禁止只设置stop flag便显示“报告已保存”。

11. 持久化、共同水位与完整性

11.1 检查点顺序

每1秒或新增4MiB待提交数据触发一次检查点;两者取先到,但最小间隔250ms、最多一个checkpoint在途,合并积压请求。

冻结writer边界S
→ Raw/metric/frame/附件索引明细flush + fsync
→ 分析数据库提交到S(事务持久化)
→ 写小型checkpoint manifest临时文件并fsync
→ 原子rename为report-checkpoint.json
→ 同步父目录元数据
→ 宣布S为共同durable边界

sqlite采用WAL、synchronous=FULL;检查点预算在采集中测量,避免stop时强制全量VACUUM/REINDEX/TRUNCATE。DB仅作为中间索引与恢复状态,最终可读报告不依赖把整个DB导出成JSON。

WAL增长也要有界:不保持长寿命读事务,采集中按不超过4MiB未checkpoint日志的初始预算执行增量checkpoint;记录其耗时与阻塞。最后连接close可能触发checkpoint,因此D1/D6基准必须覆盖commit、close和重开全部时间,禁止把大WAL留到stop才处理。若平台API和数据库行为导致该预算持续超限,门禁失败;不要用隐藏未完成DB关闭来假装完整持久化。

若SQLite在崩溃时领先checkpoint文件,恢复时不得给分析状态倒填旧seq。首版恢复策略:校验Raw连续前缀并在新恢复目录全量重建派生状态,保留原会话;恢复速度不属于正常stop目标。以后再单独开发安全的增量恢复优化。

11.2 在线快速封存与独立审计区分

11.3 最终原子清单

每个最终报告文件先写临时文件、fsync并rename。最后发布report-seal.json及兼容foundation-delivery.json,列出全部必需文件哈希和最终边界。清单不含自身哈希;其SHA-256由上层记录,避免递归依赖。

读者只接受最后seal所引用的一代完整文件;途中的新旧文件混合不构成成功报告。任何一步失败都保留上一完整checkpoint和原始数据。

11.4 产品层与采集层双重封存

采集端report_sealed不能直接使界面saved:

  1. 产品层消费到该事件之前全部wire事件,并写入事件/CSV/附件索引。
  2. 产品层核对sessionID、sequence、采集seal哈希与必需文件。
  3. 产品层flush并原子保存顶层manifest,记录coreSealSHA256与最后wire序号。
  4. 第3步成功后才发出session_saved并允许用户把它当保存完成。

任一层失败都进入保存失败或partial。产品层禁止在这一步重新读取全部明细算统计;仅处理自己的有界尾部和清单。

12. 协议与兼容升级

沿用现有NDJSON外层协议,通过hello新增能力all-metric-plan.v1、online-report-seal.v1。字段扩展使用严格schemaVersion,不擅自破坏旧客户端。

新增/扩展事件:

{
  "kind": "app_ready",
  "requestID": "REQUEST_A",
  "deviceGeneration": 1,
  "appGeneration": 2,
  "capturePlanHash": "HASH",
  "capabilityRevision": 1,
  "requestedMetricCount": 64,
  "preparedMetricCount": 0,
  "coverage": []
}

以上count=0仅用于结构示例;真实app_ready必须带64项coverage并满足计数校验,禁止复制示例伪造准备成功。

{
  "kind": "report_sealed",
  "sessionID": "SESSION_A",
  "finalRawSequence": 100,
  "reportConsumedRawSequence": 100,
  "sealSHA256": "HASH",
  "integrityMode": "writer-seal.v1",
  "dataCompleteness": "complete",
  "relativeReportPath": "report-final.json"
}

13. 有界队列、线程与执行效率

13.1 固定初始预算(必须基准验证)

项目 初始设置 实现要求
DTX owner 1 共享服务去重;避免并发争抢tap
准备 单源3s、总12s 绝对deadline;普通串行,确实独立操作最多2路
主分析writer 1线程 顺序状态唯一所有者,不在主线程fsync
Raw待处理队列 64MiB字节预算 复用既有预算,不额外再排一份64MiB复制队列
UI事件批次 每250ms最多1次 coalesce快照,不丢弃持久化明细
精确索引缓存 16MiB初始 按字节计量,磁盘状态增长独立记录
DB提交 ≤256分析记录或250ms 事务批量更新,禁止一条数据一次fsync
durable checkpoint 1s或4MiB,最小250ms间隔 单在途、请求合并
设备尾部drain 全源共享≤2s初始deadline 超时明确partial,禁止每源叠加
停止失败兜底 保留现有约10s强制收口机制 不把此上限当正常目标;强制后partial

持续处理lag超过500ms发布诊断;超过队列上限必须记录真实loss、降级质量。禁止偷偷降低采样率或关源。工程调优先批量操作、去复制、去重复decode和索引缓存;持续吞吐未过门禁时不开默认新路径。

13.2 正常条件下验收目标

14. 开发任务卡(按顺序执行,禁止跳步)

每张任务卡的提交范围只包含本卡产品、匹配测试、合同文档与验证记录。任务卡通过不等于整体交付完成。

D0:冻结基线与公式账本,预计1–2工作日

修改:只新增internal基线记录与基准脚本,实施前先保留原代码基线。

操作:

  1. 记录HEAD、运行时版本、主机/磁盘条件、构建哈希;匿名化设备与目标。
  2. 导出64项请求ID、源族/adapter、现有quality和公式版本;断言每个Registry ID恰有一个owner。
  3. 对9个收尾阶段加入基线计时(新增审计字段,不改变计算)。
  4. 记录preparation request、provider创建/关闭、app bind计数,复现+重复准备。
  5. 形成固定原始输入与旧报告golden;所有失败样本保留。

通过:可重复复现两个现象;旧结果与输入哈希绑定;本方案附录的公式来源检查齐全。

D1:增量统计与精确索引原型,预计3–5工作日

新增:streaming_stats.py、exact_order_index.py与独立测试/benchmark。

操作:先实现RunningStats,再实现AVL持久化缓存及五类查询;与排序参考逐步对照;运行100万以上有效帧数据。

通过:所有整数/quality/边界一致,浮点容差满足;100万/500万distinct场景无内存线性增长;吞吐/空间开销门禁通过。未通过不接产品。

D2:采集计划与显示配置彻底分离,预计1–2工作日

修改:CapturePlan.swift、CollectorReadiness.swift、PerfowlWorkbenchView.swift、PerformanceSessionController.swift、CollectorSupervisor.swift、capture_policy.py、CLI协议与对应测试。

操作:所有准备API移除DisplayPreferences参数;添加新计划/能力协商;测试启动与截图默认启用;历史decode兼容。

通过:待机/采集中各增删图表100次,prepare/provider重启计数均不变;隐藏CPU/FPS/Startup/Screenshot后底层仍保存对应事实或明确缺失原因。

D3:全源准备与应用重绑定,预计2–3工作日

修改:source_preparation.py、prewarm命令循环、控制器token处理和设备层缓存。

操作:应用generation隔离;同设备A→B→A复用owner;按能力构造64项账本;USB电源分项门禁;目标绑定晚到回包丢弃。

通过:切应用不串目标;取消期间不泄漏旧任务;单源失败不阻断其他适用源;重复app请求幂等。

D4:统一在线报告writer,预计2–3工作日

新增/修改:online_report.py、session_writer.py、metric_engine.py、report_engine.py、self_calibration.py。

操作:Raw与Metric一次派生;增加共同consumedRawSequence;普通统计、network join、quality在线化;失败poison;小型快照。

通过:在线与旧离线报告字段匹配;空Raw/no-output事件正确推进;模拟写失败后无成功seal;报告快照不包含整场数组。

D5:帧、启动与映射报告增量化,预计3–4工作日

修改:frame_derivation.py、launch_trace.py、launch_mapping.py、startup_timing.py、realtime.py。

操作:decoder只consume一次;启动早期/正式consumer顺序交接;帧事件追加;精确索引接入;启动结果冻结且质量仍可撤销;兼容报告投影。

通过:分块、gap、warmup、source-loss、晚到终点、重复span、不同tap切换全部回归;停止路径禁止重decode启动/帧历史。

D6:持久化检查点与快速封存,预计3–4工作日

新增/修改:live_seal.py、persistence.py、checkpoint_policy.py、delivery.py、CLI。

操作:实现共同durable边界、原子最后清单、writer-owned seal、恢复隔离;新正常路径绕过offline finalizer。

通过:用spy拦截完整Raw/Metric读取和offline derive,正常stop调用次数0;每一步失败注入无假完成;原独立审计仍能发现篡改。

D7:产品层保存、截图及界面闭环,预计2–3工作日

修改:PerformanceSessionController.swift、PerformanceSessionStore.swift、CollectorNDJSONCodec/模型、SessionReview/Exporter及工作台。

操作:双层seal;截图默认采集;显示选择纯UI;旧会话reader;可见准备覆盖;保存状态只在durable后切换。

通过:UI不等待9阶段滚动;关闭后重新打开报告一致;新增图表有历史数据;stop→马上退出应用的已保存会话仍可恢复读取。

D8:性能、异常、真机和交付,预计3–5工作日+外部门禁时间

操作:完成第15–17章矩阵;匿名输出基线对比与统计;打测试包;按仓库发布规则分别验收签名/公证/安装;窄提交、双远程推送和通知。

通过:所有必需功能、性能与数值门禁有证据;未满足项明确partial/blocked。不把编译成功等同真机通过。

单名开发者粗估20–31工作日,不含设备通道恢复、公证凭据和其他外部等待;实际以D0/D1结果修正。这是调度估计,不是完成日期承诺。优先做D1可尽早暴露精确统计成本,避免先改全部UI后发现停止目标仍未解决。

15. 必须新增的自动化测试清单

所有测试必须断言实际调用/文件/序号,不仅检查提示字符串。建议名称可直接沿用。

准备与显示

增量统计与顺序索引

流式完整性

崩溃与磁盘故障

在以下位置逐一终止子进程/注入错误:Raw写后、Metric写后、frame写后、DB事务前/后、checkpoint临时写后、rename前/后、core seal后、product seal前/后。

每个失败点验证:原文件保留;无虚假saved;恢复目录独立;可恢复前缀连续;重复恢复不污染原文件;旧请求重发无双重计数。额外测试磁盘满、只读目录、fsync失败、路径替换、文件截断、同长度内容篡改的独立审计。

16. 性能基准怎么跑、怎么判断

16.1 数据规模

16.2 方法

  1. 固定baseline与candidate构建,记录哈希与环境;使用同一输入。
  2. 前后交替ABBA运行,暖缓存与冷启动分组;不声称清空OS缓存,实际条件写明。
  3. 每组至少20轮报告中位数与P95;P99至少100轮才进入正式门禁,少于100轮只给max及样本数。
  4. nearest-rank:排序后P95取ceil(0.95*n),P99同理。失败不删除,单列失败率并影响验收。
  5. 跑到报告文件durable为止,另记源释放、UI反馈、尾部处理、索引查询、产品封存持续时间。
  6. 记录CPU seconds/平均峰值CPU、RSS峰值、DB/Raw/Metric/PNG增量字节、读写吞吐、队列高水位与p95 lag。
  7. cache命中导致OS block I/O为0时,仍统计应用层逻辑读取量,不宣传磁盘零开销。
  8. 所有数字输出JSON + Markdown表;版本、数据输入哈希和运行命令可复现。

16.3 两个必须成立的不变量

相同Raw与公式合同 → online与offline值/quality/计数一致
相同设备/应用/采集计划 → display变化不会改变prepare/source生命周期

若切断某指标采集后才能达到性能目标,则方案未达成用户要求。

17. 真机验收脚本(执行者逐项打勾)

目标设备/应用按用户指定,本地记录真实身份,公开材料统一“设备A/目标应用A”。

  1. 新包独立安装运行,记录可执行文件哈希,确认不是旧进程。
  2. 选择设备,等待设备ready;选择应用,只出现一次全源准备周期。
  3. 在准备后等待15秒,增删显示图表10次:连接计数/准备请求均不变。
  4. 保持默认显示开始冷启动;启动阶段完成即发布;观察普通曲线继续。
  5. 采集中新增启动、GPU、能耗和截图显示,确认新增图表读取已有结果;Raw中没有再次prepare。
  6. 隐藏全部图表继续30秒:Raw/Metric持续增长,截图持续追加。
  7. 点击停止并录像/记录时间:界面响应、设备释放、报告保存分别计时。
  8. 退出再打开,核验完整覆盖、原始附件、最终报告和seal;核对隐藏指标数据仍在。
  9. 重复第二次冷启动,分别列出9阶段值/quality,禁止只报Total或“6/6核心”代替全部阶段账本。
  10. 进行至少1次1小时全量采集和1次4小时长稳;短两轮启动成功不关闭长稳门禁。
  11. USB与可用无线/无外供电条件分组;缺少相应条件明确未测。
  12. 收口异常组:停止时断连、目标退出、晚到数据、磁盘压力。存在尾部不完整时显示partial。

设备通道未就绪时可以继续E1/E2工作,但E4保持未完成;禁止重复使用旧版本两轮数字作为本批成功证据。

18. 回滚、版本与发布

19. 文档联动与交付清单

公式或quality变更先修订Canonical知识页,追加版本历史,再修改代码;旧devlog和归档验收数字保持不变。进度更新到站点时间线、八阶段表与开放门禁表。

执行者最终提交以下材料:

20. 给执行者的禁止简化清单

  1. 只把“所选指标”改成“全部指标”。
  2. 只删除UI task key,仍从显示列表发送startup/截图开关。
  3. 只创建报告模板,停止仍调用原9阶段finalizer。
  4. 把原finalizer放后台,UI先显示报告已完成。
  5. 保留全历史数组在内存,称其为流式计算。
  6. 用SQL OFFSET或全表聚合做中位数查询,声称对数时间。
  7. 用近似分位数替代精确统计却沿用旧算法版本。
  8. stop时丢掉队列尾部以满足耗时指标,且不记partial。
  9. 跳过fsync或产品层保存确认,提前saved。
  10. 禁用原始数据审计,或把writer-seal说成独立磁盘全量校验。
  11. 把启动9阶段中条件性缺失写为0/通过。
  12. 用过去真机测试和旧包证明新方案已完成。

21. 执行者可直接复制的工作指令

从D0开始,按本任务书开发。先验证基线,再写当前任务卡测试,再实现最小范围。显示选择与采集计划严格分离;默认覆盖全部Registry定义和截图附件;所有质量、时钟、归因和缺失语义保持可审计。正常停止路径只处理有界尾部和最终封存,禁止全历史回读、排序和重新派生。每个任务卡提交必须给出测试输出与差异清单;不得修改无关工作区文件。遇到任务书没有覆盖的语义冲突,记录具体冲突及最小决策问题,不自行降低精度、减少采集或伪装保存完成。D1精确索引、完整性合同和D8真机性能未过门禁时,整体保持partial。

附录A:64项指标到源族的基线账本

下面由当前固定版本的源合同生成。列表只声明请求覆盖与源归属;实际可采集状态以准备时探测与运行质量为准。执行者必须从所列函数机械提取每个字段的版本与golden测试,不把源族名称当数值能力证明。

# metricID 源族 Adapter归属 公式定位
1 ios.process.cpu.non_normalized sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
2 ios.device.cpu.total sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
3 ios.device.cpu.total.normalized sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
4 ios.device.cpu.core_usage sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
5 ios.process.memory.physical_footprint sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
6 ios.process.memory.resident sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
7 ios.process.memory.xcode_gauge sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
8 ios.process.memory.real sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
9 ios.process.memory.virtual sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
10 ios.device.memory.available sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
11 ios.process.thread.count sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
12 ios.process.scheduler.context_switch_rate sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
13 ios.process.scheduler.interrupt_wakeup_rate sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
14 ios.process.disk.read_rate sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
15 ios.process.disk.write_rate sysmontap pmd3.sysmontap.raw 计算沿用metric_engine.py;CPU/内存/调度/磁盘Canonical页
16 ios.device.network.send_rate device-network pmd3.device-network.raw metric_engine.py;network-send-recv.md
17 ios.device.network.receive_rate device-network pmd3.device-network.raw metric_engine.py;network-send-recv.md
18 ios.process.network.send_rate process-network perfowl.process-network.raw metric_engine.py;network-send-recv.md
19 ios.process.network.receive_rate process-network perfowl.process-network.raw metric_engine.py;network-send-recv.md
20 ios.foreground.fps graphics pmd3.graphics.raw metric_engine.py;frame-performance.md与GPU页
21 ios.foreground.gpu.renderer_utilization graphics pmd3.graphics.raw metric_engine.py;frame-performance.md与GPU页
22 ios.foreground.gpu.tiler_utilization graphics pmd3.graphics.raw metric_engine.py;frame-performance.md与GPU页
23 ios.foreground.gpu.device_utilization graphics pmd3.graphics.raw metric_engine.py;frame-performance.md与GPU页
24 ios.device.gpu.counter.source_native gpu-counter perfowl.gpu-counter.raw metric_engine.py;gpu-counter.md
25 ios.frame.time core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
26 ios.frame.fps.average core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
27 ios.frame.fps.at_least_18_percent core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
28 ios.frame.fps.at_least_25_percent core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
29 ios.frame.ftime_ge_100_percent core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
30 ios.frame.ftime_positive_delta_gt_100_per_hour core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
31 ios.frame.drop_per_hour core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
32 ios.frame.med_range_percent core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
33 ios.frame.one_percent_low_fps core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
34 ios.frame.small_jank_per_10min core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
35 ios.frame.jank_per_10min core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
36 ios.frame.big_jank_per_10min core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
37 ios.frame.stutter_percent core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
38 ios.frame.tiny_jank_per_10min core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
39 ios.frame.smooth core-profile-frame pmd3.core-profile.raw<br>perfowl.frame-pipeline frame_derivation.py;frame-performance.md
40 ios.process.energy.total_cost energy pmd3.energy.raw metric_engine.py;energy-battery-thermal.md
41 ios.process.energy.cpu_cost energy pmd3.energy.raw metric_engine.py;energy-battery-thermal.md
42 ios.process.energy.gpu_cost energy pmd3.energy.raw metric_engine.py;energy-battery-thermal.md
43 ios.process.energy.network_cost energy pmd3.energy.raw metric_engine.py;energy-battery-thermal.md
44 ios.process.energy.location_cost energy pmd3.energy.raw metric_engine.py;energy-battery-thermal.md
45 ios.process.energy.display_cost energy pmd3.energy.raw metric_engine.py;energy-battery-thermal.md
46 ios.process.energy.overhead energy pmd3.energy.raw metric_engine.py;energy-battery-thermal.md
47 ios.device.battery.current device-conditions pmd3.device-condition.raw metric_engine.py;energy-battery-thermal.md
48 ios.device.battery.voltage device-conditions pmd3.device-condition.raw metric_engine.py;energy-battery-thermal.md
49 ios.device.battery.power device-conditions pmd3.device-condition.raw metric_engine.py;energy-battery-thermal.md
50 ios.device.battery.temperature_raw device-conditions pmd3.device-condition.raw metric_engine.py;energy-battery-thermal.md
51 ios.device.battery.temperature_celsius device-conditions pmd3.device-condition.raw metric_engine.py;energy-battery-thermal.md
52 ios.device.thermal.status device-conditions pmd3.device-condition.raw metric_engine.py;energy-battery-thermal.md
53 ios.process.startup.observed_running_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
54 ios.process.startup.foreground_ready_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
55 ios.process.startup.app_launch_total_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
56 ios.process.startup.system_interface_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
57 ios.process.startup.static_runtime_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
58 ios.process.startup.uikit_initialization_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
59 ios.process.startup.uikit_scene_creation_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
60 ios.process.startup.will_finish_launching_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
61 ios.process.startup.did_finish_launching_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
62 ios.process.startup.scene_will_connect_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
63 ios.process.startup.scene_will_enter_foreground_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md
64 ios.process.startup.initial_frame_rendering_ms startup perfowl.lifecycle.passive.raw<br>pmd3.core-profile.raw<br>perfowl.startup-control.raw startup_timing.py / launch_trace.py;startup-timing.md

附录B:文件级实施清单与验收依赖

文件 操作 必须删除/替换的职责 新职责 对应任务
MacApp/Sources/PerfowlCore/CapturePlan.swift 新建 无 请求计划/显示偏好分离、规范化编码、跨语言hash D2
MacApp/Sources/PerfowlCore/CollectorReadiness.swift 修改 显示集合参与准备匹配 设备/应用代次和计划匹配 D2/D3
MacApp/Sources/PerfowlApp/PerfowlWorkbenchView.swift 修改 +触发prepare;截图由显示决定 显示纯UI、准备覆盖和保存状态 D2/D7
MacApp/Sources/PerfowlCore/PerformanceSessionController.swift 修改 显示驱动startup;采集seal直接等于保存 独立测试模式、双层封存 D2/D7
MacApp/Sources/PerfowlCore/CollectorSupervisor.swift 修改 旧prepare_app单次隐式计划 新能力协商与严格协议编码 D2/D3
MacApp/Sources/PerfowlCore/PerformanceSessionStore.swift 修改 停止临时重算/扫描若有 产品层增量哈希、序号及durable确认 D7
MacApp/Sources/PerfowlCore/SessionReview.swift 修改 只读旧报告假设 新报告小型入口与旧reader D7
MacApp/Sources/PerfowlCore/SessionExporter.swift 修改 显示集合过滤完整导出若有 按采集账本导出全部记录 D7
MacApp/Sidecar/perfowl_collector_core_cli.py 修改 display决定launch_trace、停止直接调用offline finalize 全量计划/可取消准备、live seal D2/D3/D6
MacApp/Sidecar/perfowl_collector_core/capture_policy.py 扩展 startup/截图隐式UI门禁 全量请求、条件能力、源去重 D2/D3
MacApp/Sidecar/perfowl_collector_core/source_preparation.py 扩展 整体一刀切失效 分级缓存、generation、有界探测 D3
MacApp/Sidecar/perfowl_collector_core/session_writer.py 扩展 只推进Raw/Metric水位 统一Raw/Metric/报告/索引水位 D4
MacApp/Sidecar/perfowl_collector_core/online_report.py 新建 无 报告聚合主入口、checkpoint、snapshot D4
MacApp/Sidecar/perfowl_collector_core/streaming_stats.py 新建 无 稳定均值/方差、计数及join贡献 D1/D4
MacApp/Sidecar/perfowl_collector_core/exact_order_index.py 新建 停止全量排序 磁盘AVL与有界缓存 D1
MacApp/Sidecar/perfowl_collector_core/metric_engine.py 修改 第二次decoder派生 暴露一次派生的结果并继续原Metric写入 D4/D5
MacApp/Sidecar/perfowl_collector_core/realtime.py 修改 双路径重复分析风险、无共同stop边界 单次派生、共享deadline、最终barrier D5/D6
MacApp/Sidecar/perfowl_collector_core/frame_derivation.py 扩展 在线完成依赖offline全量samples 增量帧与原离线对照双入口 D5
MacApp/Sidecar/perfowl_collector_core/launch_trace.py 扩展 最终必须重扫历史 终点冻结与最终质量投影 D5
MacApp/Sidecar/perfowl_collector_core/launch_mapping.py 扩展 在线交付重扫Raw审计 在线计数/指纹状态投影 D5
MacApp/Sidecar/perfowl_collector_core/report_engine.py 扩展 正常stop从完整frames构造series 增量状态到原29字段投影 D4
MacApp/Sidecar/perfowl_collector_core/self_calibration.py 扩展 正常stop全历史检查 在线bucket投影 D4
MacApp/Sidecar/perfowl_collector_core/live_seal.py 新建 无 writer-owned封存合同 D6
MacApp/Sidecar/perfowl_collector_core/delivery.py 扩展 CLI正常stop调用offline入口 新live入口;旧独立审计保留 D6
MacApp/Sidecar/perfowl_collector_core/persistence.py 扩展 只文件级rename当完整多文件事务 文件同步、最后清单、目录同步结果 D6
MacApp/Sidecar/perfowl_collector_core/checkpoint_policy.py 扩展 新路径checkpoint默认关闭 单在途周期/字节阈值检查点 D6
MacApp/Sidecar/perfowl_collector_core/metric_snapshot.py 保留独立功能 正常stop无条件完整扫描 独立审计/旧会话读取 D6
MacApp/Sidecar/perfowl_collector_core/recording.py 保留并扩展 无需删除独立验证 writer哈希导出、恢复完整验证 D6

拟新增测试文件与模块一一对应:test_capture_plan.py、test_streaming_stats.py、test_exact_order_index.py、test_online_report.py、test_live_seal.py、test_stop_boundary.py、test_dual_seal.py;Swift侧CapturePlanTests.swift、AllMetricPreparationTests.swift、OnlineReportLifecycleTests.swift。基线已有test_capture_policy.py等测试只扩展,不覆盖删除。

附录C:开发者执行命令与输出位置

以下为批准实施后执行的命令示例,本方案阶段没有运行产品新测试或生成新产品包。现有测试数量会随新增测试变化,禁止硬编码旧通过数量。

cd /Users/itreenewbee/工作/代码/perfowl
git status --short
git rev-parse HEAD
MacApp/.sidecar-build/venv-py312/bin/python -m unittest discover \
  -s MacApp/Sidecar/tests -p 'test_*.py' -v

cd /Users/itreenewbee/工作/代码/perfowl/MacApp
PERFOWL_DISABLE_SWIFTPM_SANDBOX=1 \
CLANG_MODULE_CACHE_PATH=/private/tmp/perfowl-clang-module-cache \
SWIFTPM_MODULECACHE_OVERRIDE=/private/tmp/perfowl-swiftpm-module-cache \
swift test --disable-sandbox

cd /Users/itreenewbee/工作/代码/perfowl/site
npm run build

拟新增基准脚本:MacApp/scripts/benchmark-online-report.py;先实现参数与JSON输出,再进行性能验收。

--mode baseline|candidate
--input INPUT_SESSION
--output OUTPUT_JSON
--rounds 20
--replay-speed realtime|unlimited|burst
--stop-at-event SEQUENCE
--fault none|disk-full|fsync-fail|disconnect|delayed-tail

基准文件必含:构建哈希、输入SHA-256、公式合同、round index、成功/失败状态、错误原因、accepted/durable序号、Raw/Metric/frame/截图字节数、逻辑读取量、CPU时间、RSS峰值、索引大小、队列字节峰值、快照lag、stop各阶段时长及最终报告哈希。

附录D:停止完成判定的最小布尔表达式

saved =
  sourcesReachedDeclaredBoundary
  AND rawWriterHealthy
  AND metricWriterHealthy
  AND reportWriterHealthy
  AND rawSeq == metricConsumedRawSeq
  AND rawSeq == reportConsumedRawSeq
  AND requiredArtifactsDurable
  AND coreSealPublished
  AND productConsumedWireSeq >= coreSealWireSeq
  AND productArtifactsDurable
  AND productSealPublished

dataCompleteness=partial与saved=true可同时成立:表示已保存一份明确不完整的数据报告。只有上述持久化条件满足时才允许saved;尾部缺失单独呈现,不被保存成功遮蔽。

附录E:必须先通过的小型数值样例

这些样例是单元测试输入,不代表真机测量。

  1. FPS=[10,20,40,80]:median=30;[24,36]内0个;MedRange=0%。注意1000/median([100,50,25,12.5])≈26.6667,不等于30。
  2. frameTime=[10,10,10,1000]:n=4,k=1,1% Low=1FPS;合法长帧完整参与Smooth,禁止裁剪。
  3. value=[1,2,3]:mean=2,populationVariance=2/3,非1。
  4. value=[missing,0,10]且0合法:n=2,mean=5;missing不计入分母。
  5. phase有开始无结束:duration=null;下一场出现结束也不能补齐上一场。
  6. 源A最后timestamp=100、源B首timestamp=200:新epoch首点warmup,不生成100的frame interval。
  7. 同timestamp发送10、接收20,发送更新为15:pair最终35,不是65。
  8. Raw seq=100但Metric/Report最后只到99,即使最后一条无数值也拒绝成功seal。
  9. 先到旧应用A的app_ready,当前选择B:保持B状态;token不匹配事件不生效。
  10. 显示集合为空:请求指标仍64项,所有适用源继续记录;最终覆盖账本仍64行。

附录F:计划交接与整体进度

本任务书交付完成不代表产品改造完成。当前为E1代码核查与设计;D0–D8尚待批准后实施。已有旧版本测试和安装包仅为历史基线,本计划未生成新的测试包。

下一阶段:先批准任务书中的全量采集语义、精确索引与writer-seal完整性边界,再执行D0/D1;其余工作按任务卡推进。现有新包真机复验、即时性、完整报告性能、frame drain、全源丢包、原生UI以及正式发布门禁继续保持开放。