planP3/P4E4implemented · gate-open2026-09-20

Build 73 多指标并采稳定性统一修复方案

> 交付给开发 AI 的唯一执行方案。 > 基线为 Build 73 / a8b8f39735fec068af82effe0d52c29c82ccd206。 > 主 E4 Session:0a098c81-58de-4e20-a937-fb7092ded94c。 > 测试条件:约 303 秒、46 个 requested metrics、截图序列开启。 > > 当前状态:Build 74(P0-A / P0-B / P0-C / P0-D)implemented · gate-open — > E2 completed / E4 pending。 代码实施与 E2 门禁见文末 > §15 Implementation Update 与 > §16 Validation Update。 > Build 75(P1/P2:Display coverage ledger / Sysmontap compact raw / > Screenshot 1Hz)未启动,按本文 §0 的拆分必须基于已通过 E4 的 Build 74 > commit 再进行。

本批不是重新修改已通过纯 Display E4 的 R8-Fix-11,而是解决多个 Source 同时开启后的数据正确性、event-loop 竞争、停止与资源成本问题。

0. 交付拆分

禁止一次把七类问题全部混改。拆成两个可归因批次。

Build 74:P0 数据正确性与并采稳定性

只包含:

  1. Device Network source drop 修复;
  2. GPU Counter 协议/data-row 取证与修复;
  3. 多 Source event-loop starvation 定位与修复;
  4. GPU / Network source consumer closeout timeout 修复;
  5. source-specific diagnostics。

Build 75:P1/P2 账本、磁盘和截图

只有 Build 74 E4 通过后才进入:

  1. Display report-derived metric coverage 账本;
  2. Sysmontap production raw compact retention;
  3. Screenshot 1 Hz cadence。

这样可以保证真机结果能归因,不会把 Network、GPU、Display、磁盘和截图的变化混成一个无法分析的大包。

---

1. Build 73 全指标 E4 基线

主 Session:0a098c81-58de-4e20-a937-fb7092ded94c。

整体:

最终 seal 完整,但运行期存在真实 source loss 和 closeout timeout,不能因为 complete 就认为所有 Source 数据可靠。

1.1 Display under load

纯 Display Build 73:

全指标 Build 73:

FrameLifetime 本身仍持续:

结论:当前是多 Source 并采导致 event-loop 竞争和实时延迟,不是旧“FrameTime 采一点后永久停流”复发。

1.2 ActivityTrace 被其它 Source 拖慢

纯 Display:

全指标:

同一 parser 在打开其它 Source 后明显恶化,因此本批禁止继续调整 R8-Fix-11 parser fairness 常量。

---

2. P0-A:Device Network source drop

2.1 已确认事实

network.device.raw:

Build 73 的 Pmd3Subscription 所有 Source 共用 event_queue_size=2048;队列满时主动丢最旧事件并增加 dropped_event_count。

所以 10576 是 Perfowl Host 自己真实丢掉的 upstream event,不是设备端统计字段。

2.2 为什么影响准确性

Metric Engine 对 ConnectionUpdateEvent 的 tx_bytes / rx_bytes 做逐条累加,再按 elapsed 得出 device send/receive rate。

因此丢 ConnectionUpdateEvent 会少累计字节,曲线即使连续,也可能系统性低估设备网络流量。

2.3 禁止的伪修复

禁止:

2.4 推荐实现:DeviceNetworkBurstAccumulator

在通用 Raw/JSON/Writer 路径之前增加 source-specific accumulator。

数据流:

NetworkMonitor upstream → 极轻量 accumulator → 短窗口 aggregate → compact aggregate Raw → Metric Engine

第一版 aggregation window 建议 100ms。它只是 Host 聚合窗,不改变产品 1 秒 sample clock。

ConnectionUpdateEvent 每条只做 O(1) 工作:

Interface / ConnectionDetection 低频事件不参与 byte sum,可原样传递或 compact 为 diagnostics。

新增明确事件类型,例如 network.device.aggregate.raw,禁止伪装成一条普通 ConnectionUpdateEvent。

Aggregate payload 至少包含:

Metric Engine 同时兼容 legacy network.device.raw 和 aggregate raw。数学语义保持窗口内 Σbytes / elapsed,但 source contract 已改变,需要新的 formula/source version,并在知识库说明与旧公式的等价关系。

完整 per-event Network raw 只允许 debug 开关开启,生产默认关闭。

2.5 Network diagnostics

新增:

硬门禁:聚合输入 + 非 Update 输入 + malformed 必须与总输入对账;source drop 必须为 0。

---

3. P0-B:GPU Counter 协议/data-row

3.1 最新证据

本轮 gpu.counter.raw 共 1337 条:

1333 条 missing 全部是同一个 shape:

所以问题已经从“未知 GPU 协议很多”收敛为“一个稳定重复的两整数消息合同没有被理解”。

派生 GPU Counter 只有 70 个 value,并有约 143.76 秒空洞。

3.2 Configure 参数合同必须重新验证

当前 Build 73 调用形态:

公开 Instruments/DTX 实现存在不同形态:

外部线索来自公开项目 dkw72n/idb 的 instrument_service.py,仅作为 hypothesis,禁止直接照抄。

重点验证当前把 min-collection-interval 放进 counters 参数是否合同错误。

3.3 先做 decoder 前 raw capture

在 GPUCounterDecoder 之前增加 bounded diagnostics:

禁止无限写 1333 条重复诊断。

3.4 Configure A/B 只允许内部诊断模式

实现显式 variant:

不得 production runtime 自动试多个 variant,也不得一次 Session 内切换后择优。两个 variant 必须独立 Session。

3.5 GPU shipping 成功门禁

只有满足以下事实,才允许提升为 shipping:

如果两整数最终只是 heartbeat/control ack,应显式标 control,不产生 metric,也不记 missing。

如果两整数是 data row index/handle,必须证明它与后续 payload/auxiliary 的关联后才能解码。

禁止直接把两个 int 当 counter value。

---

4. P0-C:多 Source event-loop starvation

4.1 冻结 R8-Fix-11

不得修改:

纯 Display E4 已经证明这套组合成立。

4.2 当前最可疑同步工作:Sysmontap payload normalization

本 Session:

当前 _RawStreamAdapter.raw_events 在 asyncio event-loop 中同步执行 payload conversion;super()._payload 会递归 json_safe_raw。

也就是说约 100KB 的深层 plist 会先在 event-loop 线程同步转换,writer offload 发生在它之后。

这与全指标时 ActivityTrace row P95/Max 明显恶化方向一致。

4.3 必须先补 per-source stage timing

每个 source consumer 记录:

按 adapterID 分组。

同时记录 consumer 当前 stage:

Closeout timeout 必须把 adapterID + stage + stage duration 写入 diagnostics。

4.4 Sysmontap normalization worker

只有 stage timing 证明 json_safe_raw 是主要 owner 后才执行。

第一版只针对 pmd3.sysmontap.raw,不要把所有小 Source 都 to_thread。

推荐固定小型 executor,max_workers=1,保证同一 Sysmontap source 严格顺序。

约束:

4.5 修复顺序

严格按:

Network flood 收敛 → GPU unsupported flood 收敛 → 重测 heartbeat → 若仍失败,再根据 stage timing 优化 Sysmontap

禁止继续叠加全局每-event sleep(0)。当前 generic consume 已经在每条 Raw 后显式 yield,新的 yield 不能解决单次同步 step 内部 20~200ms 占用。

---

5. P0-D:closeout timeout

本轮:

两个超时任务:

writerDrainStatus=drained、ingressUndrainedAtSeal=0、dataCompleteness=complete,所以问题是 source consumer 没在 500ms 内退出,不是 writer 丢 final record。

5.1 禁止延长 500ms

SOURCE_CONSUMER_DRAIN_TIMEOUT 继续冻结为 500ms。现有代码已经明确 timeout value is not the bug。

5.2 正确修复

利用 §4 的 stage diagnostics,先证明 timeout 卡在哪。

Build 74 必须输出:

Network stop:

  1. 停止接受新 upstream;
  2. flush 当前 aggregate;
  3. terminal;
  4. consumer drain;
  5. 不留下 per-event backlog。

GPU stop:

stopCollectingCounters → flushRemainingData → terminal → consumer exits

必须检查当前 GPU __aexit__、adapter.stop、backend.close 顺序。如果 service stop 只有 backend.close 才发生,而 consumer-drain 在 backend.close 前等待,则增加 source-specific graceful stop hook,例如 request_stop_for_drain / wait_source_terminal,不要延长 timeout。

---

6. Build 74 E2

6.1 Device Network burst

构造至少 100,000 ConnectionUpdateEvent,随机 tx/rx bytes。

必须:

混入 Interface/Detection event 后 byte sum 仍必须一致。

6.2 Network replay equivalence

同一 deterministic fixture 分别经过 legacy per-event 和 aggregate path,最终 1s send/recv rate 只允许浮点舍入差。

6.3 GPU fixtures

至少覆盖:

Unknown 必须 explicit missing/control,不能猜值。

6.4 Multi-source synthetic load

同时模拟:

目标:

6.5 Closeout

Network/GPU 有 backlog 时 stop:

6.6 全量回归

必须跑:

---

7. Build 74 E4 矩阵

E4-A:纯 Display 回归

60–90 秒。

必须:

确保多指标修复不破坏 R8-Fix-11。

E4-B:全指标、不截图,5 分钟

这是主门禁。

Network:

GPU(若协议已完成):

若 GPU 协议仍未证明,只允许标记 diagnostic gate 未通过,不得伪称修复。

Event loop:

Display under load:

Closeout:

E4-C:全指标 + 截图,5 分钟

只有 E4-B 通过后再跑。

Build 74 暂不要求截图完美 1Hz,但开启截图后 heartbeat / Display 仍必须满足 E4-B。截图 cadence 的产品门槛放到 Build 75。

---

8. Build 75-A:Display coverage ledger

当前 coverage 错误标记:

但同 Session:

这是 coverage ledger bug,不是采集缺失。

正确设计:Realtime Display Projector 进入产品 snapshot 的 display metrics,由同一个 snapshot publication path 记 coverage。

至少记录:

禁止 final report 阶段看到有 FrameTime 就猜 sampleCount;真实发布多少次就记多少次。

Gate:Workbench/metrics.csv 中存在真实 value 时,coverage.sampleCount 必须 >0 且 valueQuality 不能是 missing。

---

9. Build 75-B:Sysmontap compact raw

本 Session:

FTI-003 CoreProfile raw 优化已生效,现在磁盘主因是 Sysmontap。

新增策略:

默认只保留:

  1. target PID process row;
  2. 请求指标需要的 SystemCPU/SystemAttributes;
  3. PerCPUUsage;
  4. VM free / enabled CPU 等公式依赖;
  5. timestamp/source-window/identity/provenance。

删除无关进程完整 row 和 CapturePlan 不需要的大字段。

字段白名单必须由 Metric Registry / Source family 依赖驱动,禁止手工随意删字段。

新增 diagnostics:

Compact raw replay 必须得到与 live 相同的 requested metrics。

Storage 目标:

禁止靠降低正式指标采样频率省空间。

---

10. Build 75-C:Screenshot cadence

本 Session:

图片质量和黑图问题未复发,当前只是并采压力下 1 秒 cadence 不稳。

新增 diagnostics:

调度改为 monotonic fixed-rate:T0+1s、T0+2s、T0+3s……

如果 capture 跨过下一个 tick,跳过过期 tick、记录 missedTick,禁止 burst 补拍和并发发多个 DTX screenshot request。

E4 目标:

若设备 screenshot service 本身达不到门槛,必须用 captureRTT 证明 source limit,不能伪装 1Hz。

---

11. Startup 边界

本 Session Initial Frame Rendering missing、UIKit Scene Creation partial、startup window 有一个 device lost marker。这属于既有 Startup lifecycle 概率缺失方向。

Build 74/75 不修改 Startup 获取机制,只要求不得比当前更差。

---

12. 禁止项

禁止:

---

13. 开发提交与审计

建议独立 commit:

  1. test/diagnostics: source-stage and subscription telemetry
  2. fix(network): lossless device-network aggregation
  3. fix(gpu): protocol capture/config contract + decoder evidence
  4. fix(realtime): isolate proven heavyweight normalization
  5. fix(closeout): source-specific graceful terminal
  6. fix(report): display coverage ledger
  7. perf(storage): compact sysmontap raw retention
  8. fix(screenshot): fixed-rate cadence diagnostics/scheduler
  9. docs/tests

Build 74 只包含 1–5;Build 75 包含 6–8,并基于已经通过 E4 的 Build 74 commit。

每个批次必须:

---

14. FTI-011 关闭门槛

必须同时满足:

产品原则不变:

> 先保证数据不丢、不伪造、可证明,再优化实时性和磁盘;不能为了让曲线“看起来连续”牺牲真实数据语义。

---

15. Implementation Update(2026-09-20)

范围:只做 §0 划分出的 Build 74(P0-A / P0-B / P0-C / P0-D)。§8–§10 的 Build 75(Display coverage ledger、Sysmontap compact raw、Screenshot 1Hz cadence)一行未动。

改动清单(MacApp/Sidecar,tracked 部分 4 文件 +612/−18):

文件变更
perfowl_collector_core/device_network_aggregation.py新增(421 行)P0-A 聚合器与 aggregate 流
perfowl_collector_core/gpu_counter_forensics.py新增(376 行)P0-B 前置 census、configure 契约、§3.5 门禁
perfowl_collector_core/source_stage_timing.py新增(280 行)P0-C per-source stage 计时与注册表
perfowl_collector_core/adapters.py+163:接入聚合流、stage 括号、request_stop_for_drain、network_diagnostics() / gpu_diagnostics()
perfowl_collector_core/pmd3_deep_services.py+168:PerfowlNetworkMonitor、GPUCounterStream 的 drain 终止与 census
perfowl_collector_core/realtime.py+266:consumer 侧 stage 括号、source-terminal pass、manifest 诊断接线
perfowl_collector_core/metric_engine.py+27/−6:device-network source contract v1→v2,legacy 形状保留
tests/test_build74_*.py新增 5 个套件共 49 项(见 §16)

另有规范合同与知识库的同步改动(§2.4 强制要求,详见 §15.5): MacApp/Sources/PerfowlCore/PerformanceModels.swift、 site/data/metric-contracts.json、site/knowledge/calculation/metric-contract-index.md、 site/knowledge/metrics/device-network-rate.md,以及本 devlog 与 site/devlog/index.md。

15.1 P0-A:无损 device-network 聚合

DeviceNetworkBurstAccumulator 插在 Raw/JSON/Writer 通用路径之前:上游 NetworkMonitor → O(1) 累加器 → 100ms 对齐窗 aggregate → compact Raw → Metric Engine。上游每条 ConnectionUpdateEvent 只做整数加(不分配、不额外 读表),窗口常量 DEVICE_NETWORK_AGGREGATE_WINDOW_NS = 100_000_000——它只是 host 聚合窗,产品 1 秒 sample clock 未变。

不是伪装成一条普通 ConnectionUpdateEvent,回放能区分"折叠后的字节窗"与 "源端单行";InterfaceDetectionEvent / ConnectionDetectionEvent 原样透传 (不参与 byte sum)。

networkInputEventCount == networkConnectionUpdateCount + networkPassthroughEventCount + networkAggregateMalformedCount (networkInputBalanced),以及 networkAggregateTxBytes/RxBytes == networkSourceTxBytes/RxBytes (networkBytesBalanced)。

Raw 只在显式 debug 开关 PERFOWL_DEVICE_NETWORK_PER_EVENT_RAW 下追加输出, 生产默认关闭。未扩大全局队列、未插值/补值、未降低采样频率(§12)。

networkPassthroughEventCount / networkAggregateMalformedCount / networkAggregateFlushCount / networkAggregateInputCount / networkAggregate{Tx,Rx}Bytes / networkSource{Tx,Rx}Bytes / networkMaxAggregateWindowMs / networkMeanAggregateWindowMs / networkPerEventRawEnabled,加上 transport 侧的 networkSourceDropCount / networkQueueHighWater(在 subscription 释放之前取快照,否则 stop 后就 再也测不到丢数)。

network.device.aggregate.raw(v2)同一个累加点,因此混合 Session 既不 会双计也不会漏窗;算术不变(窗口内 Σbytes ÷ 真实 elapsed), network-window-rate.v1 → network-window-rate.v2 是 source-contract 版本号变化而非数值重定义。但它不是"只差浮点舍入"(见 §15.6 偏差 1): 聚合行的 host 时间戳是窗尾而非逐事件到达时刻,实测同一 5 秒 fixture 下 字节总量完全守恒,而逐 1 秒窗 rate 差 ≤ 0.16%、累计差 0.05%(约合 边界上 1–2 条源事件的字节)。Build 73 及更早 Session 的 legacy 逐事件回放 继续支持。

15.2 P0-B:GPU Counter 协议取证(不猜值、不自动择优)

/ SAMPLE_CAPACITY = 64 / SELECTOR_CAPACITY = 16 / SHAPE_CAPACITY = 16 / TOP_PAIR_COUNT = 8。两整数消息按形状签名 TWO_INT_PAIR_SIGNATURE = "list:2:[int, int]" 计数(含 overflow 计数), 只记录形状、绝不当成 counter 值解出;dispatch / notification 路由分开 统计,因此"两整数"到底属于哪条路由是可回答的,而不是被合并掉。

current 复现 Build 73 的形态、reference 是公开文档形态; resolve_gpu_configure_variant() 对未知值 fail-closed(抛错),并且 不在 Session 内切换、不 runtime 试多个 variant 择优;若要 A/B,按 §3.4 走 独立 Session + PERFOWL_GPU_CONFIGURE_VARIANT,两次结果各自属于一个独立 Session record。

gpuDeviceInfoOk / gpuSchemaOk / gpuContinuousDataRows / gpuUnsupportedNotDominant / gpuTimestampMonotonic / gpuValuesDynamic / gpuNoLongDataGap / gpuStopFlushClean → gpuPromotable),阈值常量与门禁 放在一起(GPU_UNSUPPORTED_DOMINANCE_RATIO = 0.5、 GPU_MAX_DATA_GAP_MS = 30_000.0)以便读者看到判据而不只是结论。 门禁不提升任何东西:它只报告"某次 Session 是否具备提升所需的证据"。 新增测试 test_r_build_73_evidence_is_not_promotable 明确钉死 Build 73 的 证据不足以提升——即 GPU 指标在协议被证明之前只能是 candidate/unavailable, 不得伪成功。

15.3 P0-C:多 Source event-loop 归因(并冻结 R8-Fix-11)

source_stage_timing.py 为每个 source 记录两段而不仅是总量:

rawEnvelopeBuildActive, appendEnqueue, projectorConsumeActive, postProcessActive),加"当前处于哪一阶段" (waiting-source / normalizing / append / projector / post-process / stopping / done`)——这样一次 stall 能读到"卡在 append",而不是只能从 heartbeat 退化反推。

over-threshold 计数(1ms / 5ms / 20ms)对整个 Session 精确**;时钟回退的 样本被丢弃而不是记成负时长;stopping 与 done 可与真实工作区分; observe_source_wait() 只测"两个 item 之间"的等待,不会把自身 normalize 算进等待。

并有任务名桥 adapter_id_for_task_name() 让 closeout 超时能直接说出 "是哪个 adapter 卡在哪个阶段"。

快路径与普通路径各自补 projectorConsumeActive / postProcessActive。

保留且恒为 0、未恢复 wall-time fairness、未给 Pump 增加任何新调度 heuristic。 本次对多源竞争的处置是加观测,不是再改调度。

15.4 P0-D:source-specific graceful terminal

病因是停止序列而非窗口:Build 73 里 stream-pmd3.device-network.raw 与 stream-perfowl.gpu-counter.raw 卡在 service queue 上,而 service 只在 channel teardown 才停——teardown 在 consumer-drain 窗之后。

request_stop_for_drain():先 stop_monitoring() / stop_collecting_counters() 让上游停止产生新行,再用 shutdown_queue(events, immediate=False) 关队列。immediate=False 把停止 哨兵放在已有行之后,所以 consumer 先把 backlog 读干净、再遇到终止; immediate=True 会静默丢在途行,直接破坏 byte 守恒。

Pmd3Subscription 会把它当成 fatal,一次正常 Stop 会被记录成 source 失败。__aexit__ 只在"没有请求过 drain"时才再调一次 stop,避免重复;GPU flush_remaining_data() 保持恰好一次且幂等。

deadline min(SOURCE_TERMINAL_MAX_SECONDS = 0.5, heavy_remaining_seconds() * 0.3) 对每个 adapter 有界化,并记录 sourceTerminalStatus / sourceTerminalRequestedAdapterIDs / sourceTerminalFailedAdapterIDs / sourceTerminalDurationMs 与 closeout_ledger 的 source-terminal 阶段。CoreProfile 与 prestarted core 被 排除在外——它们有自己的 drain 契约。

超时路径新增 sourceStageTimingAtTimeout,逐条给出 taskName / adapterID / stage / stageDurationMs / itemCount,让"哪个 source 的哪个阶段没退出来"不再只能靠猜。

(逐 adapter 调 network_diagnostics() / gpu_diagnostics(),逐 adapter 容错,单个 adapter 抛错记为 <err>DiagnosticsError 而不拖垮整份 manifest)。

15.5 规范合同与知识库同步(§2.4 要求)

方案 §2.4 要求"需要新的 formula/source version,并在知识库说明与旧公式的等价 关系"。规范版本号的唯一事实源是 Swift Registry (MacApp/Sources/PerfowlCore/PerformanceModels.swift),知识门禁由它推导出 site/data/metric-contracts.json 快照并强制两者一致,因此三处必须同步改:

文件变更
MacApp/Sources/PerfowlCore/PerformanceModels.swift两条 device-network 指标的 definitionVersion network-interval.v1 → network-window-rate.v2;rawUnit → bytes-per-host-aggregate-window;normalization → sum host-aggregated 100ms windows / elapsed seconds。sourceKey 不变——被求和的物理量仍是 ConnectionUpdateEvent.{tx,rx}_bytes(聚合窗的 txBytes/rxBytes 就是它们的和),变的是聚合契约,所以由 definitionVersion 承载
site/data/metric-contracts.json由 python3 site/scripts/metric-knowledge-gate.py --write-snapshot 重新生成(不手改),仅两处 definitionVersion 变化
site/knowledge/calculation/metric-contract-index.md第 15/16 行同步版本与"100ms host 聚合窗"说明
site/knowledge/metrics/device-network-rate.md规范页:§2 合同表、§3 原理、§4 实现位置(新增 device_network_aggregation.py 与折叠点在通用队列之前)、§5 两种输入形状与对账恒等式、§9 聚合层自对账与 metric 级 quality 分离、§14 新增 v1↔v2 等价关系(含实测偏差)、§15 版本历史新增 v2 行

未改 site/retrospective/genesis-and-metric-provenance.md:该页 frontmatter 下方明确声明自己是"历史架构快照,不承担当前 Registry 合同职责",其 network-interval.v1 是历史事实,按"历史节点不改写"的纪律保留。

15.6 E2 期间发现并修复的本批真实回归

_underlying_prepared_stream() 只遍历 Perfowl 自己的几个流包装器;P0-A 新增的 DeviceNetworkAggregateStream 没被登记,于是 prepare_source_transports() 在 transport 预热阶段够不到底下的 pinned NetworkMonitor,抛 RuntimeError("pinned stream lacks transport-only connection contract")。 后果不是"少拿数据"而是覆盖账本会把 pmd3.device-network.raw 记成 failed source——一个把正确性缺陷伪装成"设备不支持"的形态。

它是被既有测试 test_source_preparation. test_pinned_real_adapter_factories_prepare_without_sampling 抓到的 (7 != 8 prepared,pmd3.device-network.raw 的 reason: RuntimeError)。 修复=把该包装器加入遍历链(stream._source),并新增 test_h_aggregate_wrapper_stays_transparent_to_transport_preparation 钉住 "预热能穿过聚合器、且穿过是只读的"。这条回归说明"新增一层包装器"必须同时 检查所有按类型遍历旧包装器的地方。

15.7 与方案的偏差(逐条,含理由)

偏差 1(§6.2 的数值口径,实测未达字面要求)。 方案 §6.2 写"最终 1s send/recv rate 只允许浮点舍入差"。实测做不到:整段 fixture 的字节总量完全 守恒(5 094 112 == 5 094 112),但逐 1 秒窗的 rate 并不 bit-identical—— legacy [1009833, 1014319, 1042184, 1012572] vs aggregate [1011425, 1012907, 1042497, 1014100],逐窗差最大 1 592 B/s(≈0.16%)、 累计差 2 021 B/s(≈0.05%)。按其量级,残差约等于边界上 1–2 条源事件的 字节(fixture 平均 ≈1 019 B/事件),不是系统性丢失,这正是字节总量仍然精确 相等的原因。

根因是聚合的host 时间戳语义变了:legacy 每条 Raw 带的是事件到达时刻, 聚合行带的是窗尾(这正是让边界对齐窗落在 1 秒窗同一侧的原因,见 §15.1); 两者在 1 秒 flush 边界上的归属可以差一条窗。要逐窗 bit-identical,就得让 100ms 聚合窗与 1 秒 sample clock 共享同一个锚点,而 §2.4 明确要求聚合窗"只是 host 聚合窗,不改变产品 1 秒 sample clock"——因此本批不朝那个方向改,而是:

(i) Metric Engine 的 v1→v2 注释从"只差浮点舍入"改成"不是 bit-identical, 并写出实测上界"; (ii) 测试断言写成实际成立的三个性质:字节累计守恒精确、两条路径 1 秒窗 数量相同、逐窗差 ≤ 一个聚合窗字节(由 fixture 派生,非手挑常数; 实测只用掉该上界的 1.3%); (iii) 测试类文档与本节都点明这是对方案 §6.2 字面的偏差,而不是"已按 §6.2 通过"。是否要求逐窗 bit-identical 属于需要重新决策的产品问题,留给独立验收 判断,本批不擅自改口径。

偏差 2(§6.5 未规定 backlog 规模)。 方案只要求"有 backlog 时 stop", 未给规模。本批按实测选 4 000(GPU)/ 20 000(Network),理由与数据见 §16.2 (二):20 000 的 GPU backlog 会占满冻结窗口的 0.53–0.81,属于把测量钉在阈值 边缘。窗口本身未放宽。

偏差 3(§4.4 明确未执行)。 方案 §4.4 的 Sysmontap normalization worker 是 条件项——"只有 stage timing 证明 json_safe_raw 是主要 owner 后才执行"。 本批只交付 §4.3 的 stage timing 本身(P0-C),没有把 Sysmontap 放到 to_thread。按 §4.5 的顺序,这一步要等真机重测 heartbeat 后由数据决定。

---

16. Validation Update(2026-09-20)

16.1 E2 门禁结果(§6 逐条)

§内容结果
§6.1Device Network burst(100,000 事件、混入 Interface/Detection、malformed)test_build74_device_network_aggregation A–H 通过:inputEventCount = aggregateInputCount = 100000、sourceDrop = 0、Σaggregate.tx/rx == Σinput.tx/rx 精确相等、malformed 计数显式、状态对任意洪水长度有界;尾部 aggregate 在 source terminal 恰好发出一次
§6.2Network replay equivalenceDeviceNetworkReplayEquivalenceTests:同一 deterministic fixture(5 000 事件 @1kHz,跨 5 个 1 秒窗)分别走 legacy per-event 与 aggregate 两条路径。字节累计守恒是精确的(legacy_tx == aggregate_tx、rx 同)、两条路径给出相同数量的 1 秒窗(4/4)、逐窗差有派生上界(一个聚合窗的字节,实测最大 1 592 B/s ≈ 0.16%,远未触及 121 623 的上界)、累计差 2 021 B/s ≈ 0.05%。注意:未达到方案 §6.2 字面的"只允许浮点舍入差",原因与实测见 §15.6 偏差 1
§6.3GPU fixtures(device-info / schema / 两整数 / notification+auxiliary / data row / unknown shape / stop-flush)test_build74_gpu_counter_forensics A–T 全过(20 项):形状分离、宽度不匹配与 schema 缺失一律 missing 而非部分解码、未知形状显式 missing 不猜值、census 有界且 overflow 计数、drain 终止幂等、flush 恰好一次
§6.4Multi-source synthetic loadtest_build74_multi_source_event_loop:source drop = 0、byte 守恒、heartbeat P95 < 100ms / Max < 250ms、display wake P95 < 20ms、无 task 增长、GPU census 存活、source terminal 真实执行(shutdown_immediate == [False])
§6.5Closeouttest_build74_closeout_source_terminal A–E 全过:窗口常量冻结校验(== 0.5)、有 backlog 时真实 drain_closeout_tasks 报 completed 且 timedOut = 0 / cancelled = 0、aggregate 尾部与 GPU flush 各恰好一次、subscription release 恰好一次
§6.6全量回归Python 827/827 OK(基线 778,本批 +49)、Swift 182 tests / 3 skipped / 0 failures(本批为 §2.4 要求同步了 Swift Registry 的 definitionVersion/rawUnit/normalization,套件数量与结果与基线持平)、知识门禁 [metric-knowledge-gate] PASS: 66 Registry contracts + VitePress 构建 PASS(sync-archive 249 files,build 46.97s)、git diff --check 干净

16.2 两条与主机负载相关的测量,以及为什么都不是本批回归

(一)既有的 test_pump_filter_active_fairness. test_g_rejected_flood_keeps_message_bound_fairness(登记在册的负载敏感 flake)。 判据是 reasons[message-limit] > total // 64 - 2(200000 // 64 - 2 = 3123)。 按"交错配对"验证(base a8b8f39 树 ↔ 本批树,同一时段的同一台机):

轮BASESHIP当时 load
13124 ✅3123 ❌10.9–11.3
23124 ✅3124 ✅10.2–10.9
33124 ✅3123 ❌9.9
B3121 ❌3124 ✅(load 10.04)10.0 / 59.5
C3102 ❌3102 ❌55.6 / 58.0
D3120 ❌3101 ❌51.8 / 45.8
E3124 ✅3112 ❌43.1–45.8

结论:两棵树在同一高负载轮次里给出几乎完全相同的读数(C 轮 3102 / 3102), 且两棵树都各自既通过又失败(B 轮 BASE 失败而 SHIP 在低负载下通过)。 active-time 是墙钟阈值:宿主停顿落在某个 64 消息 burst 内时,本该记在 message-limit 的那一次让出会被记到 active-time(观测到的 active-time 在各轮间从 0 摆到 43,而 message-limit + active-time 的总和 在 3125 附近浮动)。未放宽任何阈值,也不应把它改成本批判据。 同一时段复跑全量 Python(负载已升到 17.4)为 827/827 OK。

(二)本批新增 closeout 门禁的规模选择。 初版把 GPU backlog 定为 20 000, 实测真实 drain_closeout_tasks 耗时 265–405ms,即占冻结 500ms 窗口的 0.53–0.81;全量套件在 load ≈14 时它就翻成 cancelled。这属于本批自身的 测量设计缺陷(把机器测量钉在阈值边缘),通过改规模而不是改窗口修正:

GPU backlog实测 drain是否仍留下真实 backlog
1 0000.2–0.3ms否(已被 50ms 预热窗吸收)
2 0000.2–111ms3/4 次已被吸收 → 不再证明"Stop 时有 backlog"
4 00020–47ms是,未观测到被吸收;余量 10–25×
8 00090–132ms是,但余量仅 4–5×
20 000351–405ms是,但余量 0.5–0.8× ← 初版

因此 GPU 用 4 000、Network 保留 20 000——后者恰好是 P0-A 的证据:20 000 条 源端行的 network 场景 drain 只需 0.17–0.19ms,因为聚合已经在 subscription 之下把它们折成约 200 个窗,压力根本没到队列。余量复核:同一 4 000 档在 宿主 load 升到 ≈26 时实测 97.9ms(仍为窗口的 0.20,约 5× 余量), 而在 load ≈7 时是 15–47ms——余量随负载线性收缩,但不再贴边。同时把 P0-D 的策略证明 与窗口证明拆开:新增 test_e_gpu_terminal_semantics_do_not_depend_on_the_drain_window 在不施加 500ms 窗口的前提下证明同一终端的语义(backlog 完整送达、 dropped = 0、immediate = False、flush 恰好一次、流自行结束),这样 "终端的语义"不再依赖任何墙钟测量;窗口本身仍由 Test D 用未放宽的 0.5s 断言。另外修掉测试自身一处缺陷:Task.exception() 在任务被 cancel 时会抛 CancelledError,原先的写法会把"超时被取消"报成 ERROR + traceback,而不是 一条指明 drain 结果的断言失败——改为 _task_finished_cleanly()。

16.3 状态与遗留

不声明 validated;不构建 Build 74 真机包;不自跑 E4。

重点是——sourceConsumerTaskCount 全 completed / timedOut = 0、 closeoutStatus 不再 timed-out、networkSourceDropCount = 0 且 networkBytesBalanced = true、gpuPromotable 诚实取值(不允许为了好看而 提升)、sourceStageTiming 能指出多源竞争落在哪一层、 sourceStageTimingAtTimeout 为空(因为不应再有超时)。

真正回答"两整数是什么"要靠真机 raw capture(§3.3)与 §3.5 证据齐备。