Skip to content

Build52 启动流初始化保留与独立结果发布 ​

用户已明确批准直接修复,目标为设备 A / 目标 App A 两次真机启动阶段正常,并在完成后通知。

已确认问题 ​

最新会话启动段 46 chunks / 189,665,822 bytes 中无 RAW v2 header,解码记录为 0;预热 ring 淘汰 61 chunks / 213,783,884 bytes。普通 sample 50 条,launch_trace 0 条。1,053,437,322-byte 会话停止后在 frames 阶段触及宿主 10s 预算,最终启动报告也未发布。既有 19/20 Total 不是九阶段完整率或延迟启动路径证明。

修复合同与资源门禁 ​

  1. 同一 tap 的初始化 header / 线程表及定位所需原始块独立保留,不参与 32 MiB 滚动淘汰。初始化保留上限沿用 decoder 的 MAX_RAW_V2_PREFIX_BYTES;超限明确失败,不生成伪 header。
  2. 原始二进制流累计偏移随块记录。淘汰前后不拼出伪造 kd_buf:第一个保留块按原流 record offset 重新对齐,旧 partial record 丢弃;gap 显式标记且不跨 gap 差分帧时间。采集开始后的丢包仍使启动值 missing。
    • 点击激活时生产链输出集合固定为 bootstrap ∪ postActivationLive,不回放 prelaunchRingTail。ring tail 全部早于唯一 launch request、当时目标进程尚不存在;它只承担预热期间的有界诊断缓存,不属于目标启动窗口。
    • firstLiveOffset > bootstrapEndOffset 时由真实 source offset 形成可审计 gap;解码从下一个 64-byte 记录边界继续,不能把 gap 计作启动后 live queue loss,也不能把 gap 两端拼接。
  3. 就绪由真实结构解码准备状态确认,而非仅有非空回包;同一 tap epoch 绑定,初始化失败明确回传。
  4. 启动请求后 2.0 秒主动结束启动 tap,并在有界 drain 后立即发布阶段或明确缺失诊断并持久化;最终启动结果不等待整场帧计算。不同 tap header / frame 源独立,不跨流拼接。
  5. 阶段数学公式、单位、固定 DebugID、5ms reorder 不变;未观测条件回调不补零,不把仅 Total 成功当九阶段全量通过。

关键路径 O(N) 增量扫描;初始化前缀只解析到就绪即停止,内存上限为初始化预算 + 32MiB ring + 64MiB live queue;点击路径仅常数次集合切换,不做整个历史回放,激活后需写盘/解码的预热输入由 32MiB ring 上限降为初始化前缀上限。普通样本继续异步处理,单 owner / 单活跃 tap。初始化 5s、启动后 2.0s 主动 flush、drain 最长 4s/750ms 静默短路、restart 5s;新增状态不创建轮询任务。

停止期 hash-bound 启动重算采用与实时链相同的冻结 DebugID 选择扫描:对每个完整 64-byte kd_buf 块,以 C 层字节搜索定位 LIVE_LIFECYCLE_DEBUG_IDS ∪ LIVE_ATTRIBUTION_DEBUG_IDS ∪ PRESENTED_FRAME_DEBUG_ID,仅接受 debugID 字段位于 recordOffset + 48 且 recordOffset mod 64 = 0 的命中。decodedRecordCount 仍按完整记录总数累计,阶段/Total 结果与逐条 exhaustive 语义等价;避免 Python 对数百万无关记录逐条解包。最终结果仍从已校验 Raw 重算,不以实时缓存替代。

同批收紧停止期 Frame 报告扫描:RAW v2 header、timebase、绝对 64-byte 对齐和尾部余数门禁保持不变;完整记录数按窗口字节数直接计算,仅用精确 PRESENTED_FRAME_DEBUG_ID 的 4-byte needle 搜索候选,再验证其字段偏移为 eventOffset + n×64 + 48。命中记录仍执行原 Mach tick 整数换算,未命中记录计入 rejected;该优化不改变 frame event、Jank/Smooth 公式或 hash 产物,只移除对数百万无关记录的 Python 逐条解包。

启动 tap 的过滤集合固定为 LAUNCH_KDEBUG_FILTERS,不并入高频 PRESENTED_FRAME_FILTER,并保持已校准的 bufferMode=0。Initial Frame Rendering 的终点继续来自已冻结生命周期 0x31CA0006,不是 presented-frame 事件;因此移除高频帧过滤不改变九阶段或 Total 公式。启动源完成排空并关闭后,串行启动 frame-only tap,过滤集合固定为 {PRESENTED_FRAME_FILTER};任意时刻仍只有一个 kperf tap。逐帧窗口明确从续接 tap 首个可解码帧开始,启动窗口内 Frame Time 保持 missing,不用其他信号补齐。实验性 bufferMode=1 在相同 30 秒预热 smoke 中 Total 缺失,不进入实现。

验证 ​

  • 回归:header 淘汰、分块 header、非 64-byte 边界、预热等待后启动、非法前缀、无启动终点、源切换、取消竞态。
  • 真机两轮:准备后分别等待,验证启动阶段有真实来源、Total、实时展示、普通曲线与停止报告;失败仍在记录中保留,目标未达成不标 complete。
  • 真实规模回放记录 wall/CPU/RSS/I/O 与基线;1GiB 会话独立启动结果不受完整帧收尾阻塞。

进行中的验证补充 ​

启动 closure 的直接 report 也必须应用 live queue loss 全局门禁,禁止实时显示 candidate 而最终报告才撤销。30s 预热烟测发现 9 个 live queue drop,该轮不计成功。已增加直接 closure 失效回归;失败轮保留为证据。最终报告已提前到 frames 之前发布,frame 超时不阻断已生成启动报告。

根因与最终实现 ​

缺失并非界面把合法数值隐藏,而是启动 tap 的 RAW v2 初始化结构被 32 MiB 预热 ring 淘汰;激活后仅回放 ring 尾部时,decoder 收不到 header/线程表,因而 0 record、0 阶段。与此同时,点击后继续回放启动前高频历史会放大 live queue 压力,并把启动这个瞬时窗口拖到停止收尾。

Build52 新增独立 bootstrap store,保留 RAW v2 header、线程表和定位所需元数据;生产激活只回放 bootstrap + postActivationLive,原始 byte offset 与 64-byte gap 对齐继续可审计。启动 tap 仅订阅生命周期过滤集合,2.0 s 后主动 flush;tap 关闭后才串行启动 frame-only tap。阶段 revision 在采集中持续发布,闭合 Total 后写入 launch-trace-live.json,不等待用户停止。停止重算的 Launch/Frame 均改成精确 DebugID 选择扫描,Frame 采用分块流式读取,不拼接整份 trace。

E4 两轮最终包内 Sidecar 验收 ​

设备 A / 目标 App A,Build52 包内 Collector 0.18.2,准备完成后等待 15 s 再启动。两轮都在用户停止前自动发布完整启动端点,随后普通性能样本继续到达。

项目第 1 轮第 2 轮
Total1814.452 ms1812.444 ms
有值核心阶段6/66/6
launch request → 完整启动结果4249.710 ms4266.617 ms
完整结果后继续到达的 wire sample1616
Session 普通 sample2121
frame continuation event308306
启动 live queue drop00
stop → stopped7672.689 ms7813.215 ms

六个稳定有值的核心阶段为 System Interface Initialization、Static Runtime Initialization、UIKit Initialization、UIKit Scene Creation、didFinishLaunchingWithOptions() 和 Initial Frame Rendering。willFinishLaunchingWithOptions()、sceneWillConnectTo()、sceneWillEnterForeground() 在该目标中未观测,保持真实 missing,不补 0。最终报告事件每轮仅 1 个;采集中间 revision 仍按完成进度正常递增。

失败尝试不从证据中删除:acceptance2 缺 didFinish;smoke9 的实验性 bufferMode=1 缺 Total,已回退并固定 bufferMode=0;更早 smoke5 出现 9 次启动 live queue drop,按 source-buffer-loss 将启动值全部置 missing。这些失败证明当前结论只覆盖最终相同配置的连续两轮,不外推成所有历史尝试 100% 稳定。

回归、效率与资源证据 ​

  • Python 314/314;Swift 130 项执行、127 通过、3 项显式资源 benchmark 跳过、0 失败。
  • 两份最终真机 Raw 分别为 674,966,570 与 668,502,754 bytes。完整重算 wall 5.162/4.987 s,CPU 5.115/4.943 s,进程峰值 RSS 877,916,160/832,090,112 bytes;文件系统 block I/O 计数因缓存命中为 0,不据此宣称物理磁盘零读取。
  • 冻结产物复算后 Launch spans/report 与 Frame events/manifest/report 的 SHA-256 两轮全部逐字节一致。单阶段 launch 重算 1.335/1.348 s,frame 流式重算 2.041/1.852 s;完整报告仍未达到长期 ≤2 s 目标。
  • 相对 Build51,完整启动结果延迟从 P95 5.343 s 收敛到 4.250/4.267 s;早期失败 smoke 的 stop→stopped 约 11.986 s,最终两轮为 7.673/7.813 s。该提升不等于 1 s 即时性或 2 s 完整报告门禁已关闭。

Build52 测试包 ​

已重新冻结 Sidecar、构建 1.0.0 (52) x86_64 App,并对 322 个 Mach-O 内嵌代码及 App 使用 Developer ID Application + hardened runtime 签名;DMG 容器也使用 Developer ID Application 签名。codesign --verify --deep --strict、Collector Core ready=true、64 Registry 合同、pymobiledevice3 11.3.1、独立 ZIP 解包/启动和独立 DMG 挂载均通过。

  • ZIP:MacApp/dist/Perfowl-internal-test-Build52-x86_64.zip,108,525,185 bytes,SHA-256 26b015823bf7e5a0b449c7a66f681e63a2f98354cc23fd5dc6b5519e58271b59;
  • DMG:MacApp/dist/Perfowl-internal-test-Build52-x86_64.dmg,140,464,140 bytes,SHA-256 e18ca70067a80396a29a7eb7ba12d97bfa508a9d9081eb44f117e3260aef8ef2。

本机没有 notarytool Keychain profile,提交公证命令返回“profile not found”;因此未 notarize/staple,spctl 如实为 unnotarized Developer ID rejected。GPL-3.0 法务结论也未建立,测试包只用于内部测试,不能标为正式外部分发完成。

2026-09-13 update:用户复测四阶段与启动完成边界保护 ​

用户使用上一版 Build52 测试包复测时,最终报告只有前四个阶段有值,didFinishLaunchingWithOptions() 与 Initial Frame Rendering 等后续阶段为 missing。该会话的 startupCoreProfileRotationStatus=startup-source-drained,停止前没有启动源丢包(liveQueueDropCount=0),但启动 tap 在固定 launch request + 2.0s 主动 flush 后以 750 ms 静默短路;目标前台观测发生在约 2.27 s,晚到的生命周期终点没有进入启动 tap。因而这是启动源过早关闭,不是 UI 隐藏数值,也不是普通全源队列丢失。

已在下一版源码加入有界完成等待:2.0 s 主动 flush 到期后,若启动 trace 尚未同时具备 Total 与必需阶段,则继续保持同一 startup tap 最多 4.0 s;期间仍按 revision 即时发布已闭合阶段。等待与用户 stop 竞速,stop 先到时立即退出;超时仍 fail-closed,保留真实 partial/missing,不补零、不跨 startup/frame gap 拼接。最终 manifest 记录 startupCoreProfileCompletionGraceSeconds,便于审计实际边界。新增回归断言验证终点 chunk 到达后才请求停止;Python 全量 315/315、Swift 130 项(127 通过、3 显式跳过、0 失败)通过。当前设备在本轮复验前发生 CoreProfile prelaunch readiness 超时,新的包内 E4 连续两轮尚待重新建立;因此本 update 只记为 implemented · partial,不把源码回归提升为新的真机通过结论。

结论与开放门禁 ​

本批用户目标中的“最终包内 Sidecar 连续两轮,六个核心启动阶段、Total、自动展示及后续性能采集均正常”已达到 E4 命令级证据;启动缺失的 header 淘汰根因和停止前不展示问题已修复。

整体交付仍为 partial:两轮 Session 因 frame continuation stop drain timeout 和普通全源 drop high-water(36/53,主要来自 device-network)保持 partial;自动展示约 4.25 s,未达到历史 ≤1 s 目标;完整报告约 5 s,未达到 ≤2 s;原生 GUI 像素级展示未录证;正式发布仍受 Notary/stapling 与 GPL-3.0 法务结论约束。启动源自身两轮 liveQueueDropCount=0,不得与普通全源 drop 混为一谈。

2026-09-13 update:身份核验期间启动源提前消费修复 ​

后续真机复测发现一次回归:9 个阶段全部 missing。对失败 Session 的 core-profile.source-loss.raw 复核显示,目标身份核验约 1 秒期间,预热 CoreProfile 已继续产出,而正式 consume() 尚未启动;64 MiB live queue 丢失 1 个块(约 12.6 MiB),fail-closed 语义因此正确地将 Total 与全部阶段置为 missing。该现象不是 UI 隐藏合法值。

本次修复在激活同一 tap 后立即启动有界早期 drain:bootstrap 先写入同一个 Raw writer 并解码,身份核验期间持续排空 live queue;身份确认后通过可中断 Queue handoff 将未消费块交给常规 startup/frame consumer,且所有异步写入共用一个 writer lock,避免 launch envelope 与早期 Raw 并发写入。新增 events_until() 交接回归测试;不改变 source-loss fail-closed 规则,也不扩大三类条件回调的映射。

修复包内 Collector 在当前 iPhone15 / 目标 App A 上连续两次受控冷启动复测:

项目live-fix4live-fix5
Total1845.593 ms1812.319 ms
核心阶段有值6/66/6
首个完整启动快照5431.564 ms5216.357 ms
启动 liveQueueDropCount00
目标归因记录2324
普通 sample1616

willFinishLaunchingWithOptions()、sceneWillConnectTo()、sceneWillEnterForeground() 两轮均未从该目标观测到,保持真实 missing;两轮整体 Session 仍因普通全源 drop 与 frame drain timeout 标记 partial。一次普通受控启动未纳入成功轮:目标当时已运行,无法形成完整启动边界,已单独保留为反例。

2026-09-13 package refresh ​

修复后的当前 Build52 内部测试包已重新冻结并复核:

  • ZIP:MacApp/dist/Perfowl-internal-test-Build52-x86_64.zip,107,609,825 bytes,SHA-256 8b9c35adad739204df086d86bf562a8a5e95376bf3a3961017f9f552c5a71d20;
  • DMG:MacApp/dist/Perfowl-internal-test-Build52-x86_64.dmg,140,192,014 bytes,SHA-256 e67ecdc6dd843c73442905f8ba14e4fab054acaf57eec1dfbae3cf1ff25d5d38。

App、ZIP 独立解包和 DMG 挂载后的 codesign --verify --deep --strict 均通过;当前仍无 notarytool Keychain profile,尚未 notarize/staple,spctl 保持如实的 unnotarized Developer ID 结果。GPL-3.0 法务门禁仍开放,包的交付状态为 partial,仅用于内部测试。

2026-09-14 update:官方 xctrace 阶段结果展示兼容 ​

对上一版包的另一条缺失路径完成了 E2 复核:官方 xctrace 解析器已经得到 phaseTotalsMs 与 spans,但没有写入 Swift 工作台读取的 phaseResults,且 Span 没有 startOffsetMs。因此报告在 JSON 中有值,九行时间轴仍全部显示 missing;这不是采集源丢包,也不是把合法值过滤掉。

本次修复让 xctrace 适配器直接输出完整 CollectorLaunchTrace 形状:九个阶段均有 phaseResults、metricID、spanCount、quality/reason,Span 带相对启动偏移,Total/阶段覆盖与 requiredMissingPhases 同步。缺少必需阶段时保留已经观测到的阶段值和端点 Total,缺少条件回调时标记 not-applicable;结构或来源失败仍全局 fail-closed。Swift 展示层同时增加旧报告回退,读取没有 phaseResults 的历史 phaseTotalsMs,避免已落盘会话继续整页 missing。

回归结果:Python Sidecar 316/316,Swift 131 项(128 通过、3 显式资源基准跳过、0 失败);站点指标门禁与 npm run build 通过。当前 iPhone 15 的 CoreDevice tunnel 仍为 unavailable,本轮没有新的设备 E4 数字;既有两轮 6/6 核心阶段与 Total 继续作为历史证据,不升级为当前复验。

当前包已重新冻结并完成独立验证:ZIP MacApp/dist/Perfowl-internal-test-Build52-x86_64.zip(108,573,669 bytes,SHA-256 5dff47c5a2e977bed98c7c1fa39211faff9f5334af4b2d8b4a68ebe36f14a4dc);DMG MacApp/dist/Perfowl-internal-test-Build52-x86_64.dmg(141,852,701 bytes,SHA-256 b0f8079c58aeebb01d981ed656021ef68c2d67232ef48669560f8f52e2348f33)。App、ZIP 解包、DMG 挂载后的严格验签和 Collector Core ready=true 探针均通过;notarization/stapling、GPL-3.0 法务与 iPhone 15 两轮 E4 仍开放,整体状态保持 partial。

2026-09-14 update:签名完成后的交付包刷新 ​

Developer ID 签名完成后重新生成交付容器,并从独立解包与挂载路径复验:ZIP 与 DMG 均可验证,包内 App 严格签名通过,Collector Core 探针均返回 ready=true、9 项能力、64 项 Registry 合同有效。新容器哈希如下(替换同名旧包):

  • ZIP:MacApp/dist/Perfowl-internal-test-Build52-x86_64.zip,108,573,658 bytes,SHA-256 1d3129266ea2cfe699889d611364508236492d0d73ce47cadfcdc0e638189ebd;
  • DMG:MacApp/dist/Perfowl-internal-test-Build52-x86_64.dmg,136,428,943 bytes,SHA-256 85373c8d04555ecd6f1c235ebc01183fed9ac7c2e2ff4471de002abdad386fe4。

本次复验还通过 hdiutil verify、ZIP 完整性测试、DMG 挂载后独立 codesign --verify --deep --strict 与两条独立 Core probe。签名主体仍为 Developer ID Application;notarization/stapling、GPL-3.0 法务与 iPhone 15 两轮 E4 仍开放,交付状态保持 partial。

Perfowl · Performance Observer