外观
启动阶段随机缺失·系统性消因、旁路源可行性与方向1落地
承接启动时间采集现状与根因记录。本轮以"一次只动一个变量、每步验证、累积 A/B 分组"的方法,把"重型 App 冷启动随机漏采个别启动阶段"的所有主机侧与旁路侧可行修法逐一证伪,最终确认随机缺失发生在设备侧 kdebug / 传输前链路、现有公共配置无法进一步区分或修复;随后按用户决定落地方向1(Total 端点法稳定 + 只展示真实捕获阶段)并清除所有已证伪的实验开关。原始详录见仓库
docs/startup/startup-capture-stability-investigation.md与docs/startup/sidechannel-feasibility.md。
1. 方法与诚实红线
- 方法:cumulative A/B(Wilson 95% CI)。A=基线 → B=Ready Barrier → C=startup-exclusive → D=receive/parse decouple → Step 4=CoreProfile action matrix(ta/bso)→ 旁路源。每步只改一个变量,前一步验证后再进下一步。
- 红线(全程不破):不补 0、不估算、不为"好看"隐藏真实 missing 的事实;所有值必须是真实内核事件时间戳差;绝不跨轮拼接阶段(只能整轮择优,r1 头+r2 尾=一次没发生过的启动=红线)。
2. 主机侧路线——全部 NEGATIVE
| 变量 | 假设 | 结果 |
|---|---|---|
| H2 Ready Barrier | 采集源"就绪"后再放行启动,可减少最早事件竞态 | 无显著改善;缺失仍随机 |
| C startup-exclusive | 启动窗口内延后次要 adapter,独占带宽给 CoreProfile | 无改善;且引入 seal 回归(target-pid-missing,live decoder 在封存前被重置),后用快照兜底修复——说明问题不在主机带宽 |
| D receive/parse decouple | 独立解码 worker + 队列,消除主机接收阻塞 | receiverBlockedMs/rawQueueDepthMax 均无瓶颈信号,缺失不变 |
| Step 4 CoreProfile action matrix | 调 ta(kperf action)/bso(bufferSizeOverride)解耦生命周期 kdebug 与高体量 baseline/perf 流 | 移除 baseline action 直接杀死 CoreProfile 流(0 chunks);可用配置组合均无法解耦,缺失不变 |
结论:在当前 CoreProfile capture mode 下,生命周期 kdebug 与高体量 baseline/perf 事件流无法通过已知公共配置解耦;随机缺失发生在 host consumer 之前,现有实验无法进一步区分"设备 kdebug buffer 溢出 / CoreProfile 内部导出批处理 / 设备→DTX 前链路"中的具体丢失点。lossFree=true 只覆盖主机队列,不证明整条链完整。
3. 单轮重建(P4.1)——INFEASIBLE
对完整 9/9 轮做 leave-one-boundary-out:能否只用本轮真实事件重建被移除的阶段边界?
- 过渡码是单事件双边界(一个 code 同时是上一阶段 END + 下一阶段 BEGIN),两侧同生共死,无法精确恢复;
- perf/mach 洪流不携带 signpost 身份,不能顶替;
- 唯一可辩护的界是"最近存活的生命周期邻居"区间宽度,实测 0.02–276 ms(生命周期事件稀疏)→ 不足以作为阶段值。
- (曾出现"假精度":第一版把被移除边界自身时间戳拿来算界=偷看,已纠正为仅用生命周期邻居。)
4. 旁路数据源——两条均关闭
目标:CoreProfile 丢阶段时,用独立于 kdebug 的旁路源补齐同一次启动(不跨轮、不造假)。
4.1 System Trace(Instruments)—— 不适合,关闭
底层同为 coreprofilesessiontap + kdebug/kperf(非独立,同一设备侧丢失不构成冗余);kperf 单一所有者不能与我方 tap 并发;另起一轮=跨轮拼接=红线;App Launch 模板需 get-task-allow,生产 App 拉不起。
4.2 os_trace_relay(com.apple.os_trace_relay)—— spike 证伪,关闭
统一日志活动流,独立传输、可并发、自报 LOSS、带 mach 时间戳——基础设施看似齐全。决定性 spike(探针 Pid=-1=全进程、PAYLOAD|INFO|DEBUG,冷启动百度网盘,25s,132624 条记录):
| 观察 | 数据 |
|---|---|
| 目标进程被捕获 | ✅ 目标进程共 53563 条统一日志消息(network/defaults/boringssl/UIKit-Touch) |
| 非日志类型能流出 | ✅ ACTIVITY_TRANSITION(0x0202) 1319 条 |
| SIGNPOST(0x0600) | ❌ 全流 0 条 |
| 9 阶段启动边界 signpost | ❌ 无一出现。关键词命中全为假阳性:FirstFrame=coremedia 视频首帧;didFinish=WebKit 子进程 didFinishLaunching;百度 UIKit 子系统 225 条全为 Touch/Gesture/BackgroundTask 运维日志 |
| mach_timestamp 域 | ❌ ≈2.4e17,非 24MHz mach ticks、非合理 ns uptime,无法与 CoreProfile 对齐 |
根因:UIKit 启动阶段 signpost 是 kdebug-only(为 Instruments 经 kdebug 发出,对应 CoreProfile 的 0x2B87 class),不进统一日志,故 relay 里根本没有。启动阶段身份只存在于那条随机丢失的 kdebug 通道,统一日志里没有独立冗余。
5. 总结论
主机配置(Ready Barrier / exclusive / decouple / Step4 ta·bso)、单轮重建(P4.1)、旁路源(System Trace / os_trace_relay)——全部走完,全部 NEGATIVE。守住红线的前提下,不存在"补齐单次缺失"的诚实路径。真正能做的两条:①Total 端点法稳定 + 只展示真实捕获阶段(方向1,本轮落地);②多轮整轮择优(可选增强,绝不跨轮拼接)。
6. 方向1 落地(本轮实现)
- Total 端点法:Total = System Interface BEGIN → Initial Frame END 的内核时间戳差。中间必需阶段缺失时报告降为
partial(reasonlifecycle-spans-incomplete),但 Total 照常发布、不被清零;仅当端点本身缺失时 Total 诚实判missing(单元测试testEndpointTotalRemainsCandidateWhenPhaseCoverageIsPartial锁定)。 - 只展示真实捕获阶段:展示层
visiblePhases改为只渲染有真实值的阶段;missing/ 无值partial/not-applicable一律不渲染空行、不补 0、不估算。隐藏行不等于隐藏事实——底部以coverageDisclosure如实披露"本轮捕获 X/9;N 个本轮未捕获(不展示、不补值);M 个为该 App 未触发的可选回调",且九阶段全账本保留在phases供导出/审计。 - 诚实诊断保留:设备丢事件标记(
deviceLostEventMarkerCount,内核TRACE_LOST_EVENTS)与startupObservability(按启动窗分窗计数、原始 marker 保留、argSemanticsVerified=false)保留并在工作台单独披露(与阶段覆盖正交:完整 9/9 仍可能有非零 marker,零 marker 只表示"未观测到丢失证据",不等于全链无损)。
7. 清理(代码洁净)
删除所有已证伪的实验开关,collector 恢复 Build52 干净基线:Ready Barrier(STARTUP_READY_BARRIER_*)、startup-exclusive、receive/parse decouple(decode worker/queue、receiverBlockedMs/rawQueueDepthMax)、Step 4F/4G env(PERFOWL_STARTUP_PERF_TA/TA_CODESET/BSO/BSOC)、CoreProfile config-audit、startupRx* 指标,以及对应 Swift 配置字段/开关 UI 与其测试;一次性调查脚本(结论已入本文与 docs/startup/)一并移除。保留诚实诊断(设备丢失标记 / startupObservability)与其单元测试。
验证:Sidecar 70 项启动相关单测通过;Swift 包编译通过、StartupTracePresentationTests 9/9 通过。
8. Total 端点精确重配对(Mode A/C)——普查 + 实现
方向1 保证了"Total 有就稳、缺就诚实缺",但不改变缺失率。进一步问:kdebug 数据里是否有更好的结束边界能把缺失的精确 Total 捞回来?先普查、再实现。
8.1 边界模型与失败分类
- START =
System Interface BEGIN(0x1F/07 code=13);Initial Frame BEGIN = 过渡 code=12;END(Total 终点) =0x31CA0006首帧 present。 - 现规则:
0x31CA0006只有 code=12 先开了 Initial Frame 跨度才算终点,否则当"后续帧"丢弃(每帧都发 0x31CA0006)。据此把 Total 缺失分为:Mode A(首帧 END 到了、但 code=12 丢了被丢弃,ignoredTerminalBeforeBeginCount>0)、Mode B(首帧 END 本身被设备丢了)、Mode C(END 在、START 丢了)、Global fail(整轮没归因到进程)。
8.2 离线普查(134 个含启动轨迹的会话)
| 分类 | 会话数 | 占比 | 精确可救 |
|---|---|---|---|
| TOTAL_OK | 100 | 74.6% | —(其中 26 个"Total 有、中间阶段缺") |
| MODE_A | 15 | 11.2% | ✅ 端点重配对 |
| MODE_C | 2 | 1.5% | ✅ 早期阶段替代 START |
| MODE_B | 7 | 5.2% | ❌ 无精确解 |
| GLOBAL_FAIL | 10 | 7.5% | ❌ 非端点问题 |
结论:精确重配对(Mode A+C=17 个)可把 Total 采集率 74.6% → 87.3%(+12.7pp),全部真内核时间戳、零估算。剩余 Mode B(5.2%)首帧 END 真丢无精确解。
8.3 实现(launch_trace.py)
- 留存被丢弃的首帧 END(machTicks/ns/tid),report 时若 Total 缺失且属正常启动(
reason=lifecycle-spans-incomplete,排除 global gate)则重配对。 - Mode A:以"最后一个存活生命周期边界"为锚点,取其后的被丢弃首帧 END。锚点后唯一候选 → 精确(
totalRepairReason=frame-end-repaired);多候选(闪屏/多帧)→ 取最早并标frame-end-repaired-ambiguous,不冒充精确(护栏,避开 233ms-vs-1206ms 坑)。 - Mode C:END 在、START 丢 → 用最早早期阶段(dyld/init,PHASES[0..2]) BEGIN 替代 START(误差亚毫秒~低毫秒);绝不用帧 BEGIN 或晚期阶段当 START。
- 诚实标注:重配对的 Total 用独立
totalQuality="repaired"(不占candidate),并带totalSource=coreprofile-endpoint-repaired+totalRepairReason;关键:实时"完成"信号只认干净candidate端点,重配对值不会让实时流误判完成、只在最终封存报告出现。Swift 侧totalIsRepaired/totalRepairDisplay在工作台如实披露"Total 由端点重配对得出"。 - Mode B 不救:无被丢弃首帧 END 时不替代任何值,Total 诚实 missing。
验证:新增 Mode A(精确)/Mode A(多帧歧义)/Mode C/Mode B/干净端点 5 项单测;Sidecar 全量 371/371、Swift 全量 140(3 跳过) 通过。
8.4 待办(E4)
本节为 E2(离线普查 + 单测)。待设备通道恢复后做 E4 真机复测:确认重配对救回的 Total 与 Apple System Trace ground-truth 一致、闪屏护栏在重型 App 上不误采。实测可行后,再把"启动各阶段如何被获取"一文补入端点重配对小节,正式收录知识库启动章节。