外观
CoreProfile Presented Frame 降源吞吐优化与 C1 Exact Filter 实验方案
执行状态:C1 已完成且判定
C1-THROUGHPUT-NOT-IMPROVED;C2ta/tkAction Contract 审计已完成。高吞吐治理历史与证伪路线冻结在第 6 节,审计结论与后续路线冻结在第 7 节。
1. 实验 1 基线与背景问题
在 Build 61 落地 Startup → Frame-only 串行 handoff(rotate-to-frame-only)后,通过受控真机 Session 8ddcdd42-864e-48f3-b03f-4b8826a9bb60 建立了第一份客观 Event Census 基线:
text
Collector duration:
353.1s ≈ 5分53秒
CoreProfile total:
10.34 GiB
Epoch 2 total:
10.23 GiB
平均 CoreProfile 吞吐:
29.99 MiB/s
kd_buf totalRecords:
171,561,893 条
uniqueDebugIDCount:
179 种
0x3180 subclass 记录数:
727,867 条(占全部 0.4243%)
Presented Frame (0x31800318):
3,981 条(占全部 0.00232%,占 0x3180 仅 0.5469%)
bytes / Presented Frame:
约 2.66 MiB / 帧当前 Frame-only 的生效配置为:
csd = 0(调用栈深度为 0,已排除栈展开开销)kdf2 = [830472192](即0x31800000)
核心结论与技术矛盾
- Subclass Filter 未起到预期拦截效果: 虽然配置了
kdf2=[0x31800000],但内核驱动输出了海量与0x3180完全无关的事件(例如0x25000001,0x25000002,0x25020010,0x25010014,0x01400121,0x01400122,0x25010004等)。 - 连接稳定性威胁: 高达近 30 MiB/s 的无用内核事件洪流对宿主 CPU/USB 通道形成巨大压力,并在会话末期引发共享 DTX 通道断开:
firstFatalException: ConnectionTerminatedError: Connection closed (source: stream-target-monitor)。 - 降源迫切性: 若能将设备侧 filter 进一步收敛,从
subclass级收敛为精确呈现帧 DebugID,理论上可过滤掉 99.5% 以上的不相关事件。
2. C1 Exact Filter 方案设计(严格单变量)
2.1 变更范围与单变量原则
本阶段仅在 Frame-only 阶段(Startup → Frame-only handoff 后的 Epoch 2) 允许将 kdf2=[0x31800000] 切换为 kdf2=[0x31800318]。
- 生产默认绝对不变:当未设置环境变量或显式指定时,生产默认严格保持
subclass(0x31800000); - 受控实验环境变量:
PERFOWL_FRAME_FILTER_EXPERIMENT=exact-debug-id; - 严格 Fail-Closed:若环境变量设置为任何非
subclass/exact-debug-id的值(如candidate-2、拼写错误),立即抛出ValueError终止,严禁静默回退; - Startup Epoch 1 绝对隔离:冷启动阶段
launch_trace=True时,无论环境变量为何,filter 始终强制锁定为PRESENTED_FRAME_FILTER(0x31800000),绝不污染启动生命周期探测。
2.2 遥测与对比字段
在 core-profile-census.json 与 manifest.json 中完整持久化以下字段,确保真机验证时事实可溯:
json
{
"frameFilterExperiment": "exact-debug-id",
"requestedFrameFilter": "0x31800318",
"effectiveFrameFilter": ["0x31800318"],
"filters": [830472984],
"firstFatalSource": null,
"firstFatalException": null
}3. 单元测试验收(Test 1 ~ Test 8)
在 MacApp/Sidecar/tests/test_frame_census_and_fatal_diagnostics.py 中落地 8 项专项测试:
- Test 1 (
test_01_frame_filter_experiment_resolution_default):默认或空环境变量返回subclass; - Test 2 (
test_02_frame_filter_experiment_resolution_exact):exact-debug-id大小写与空格兼容解析; - Test 3 (
test_03_frame_filter_experiment_resolution_invalid_fail_closed):非法值抛出ValueErrorfail-closed; - Test 4 (
test_04_frame_core_profile_filter_values):subclass→0x31800000,exact-debug-id→0x31800318; - Test 5 (
test_05_core_profile_filters_startup_isolation):Startup 阶段强制锁定0x31800000,不被实验污染; - Test 6 (
test_06_core_profile_filters_frame_only_selection):Frame-only 阶段精确响应subclass与exact-debug-id; - Test 7 (
test_07_extract_effective_frame_filters_helper):effectiveConfig.tc[0].kdf2十六进制字符串转换与防御解析; - Test 8 (
test_08_session_c1_subclass_and_exact_end_to_end):端到端会话流下 filter 配置传递、manifest 与 census 持久化核验,以及非法变量 fail-closed 拦截。
全部 454 项 Sidecar Python 测试与 167 项 MacApp Swift 测试均 100% 通过。
4. 强制执行门禁:当前阶段停止指令
本方案严格遵守门禁规则:
- 禁止自行推演或宣布 C1 成功;
- 禁止一次性实现 candidate-2 或其他猜测性 filter;
- 必须构建包含 C1 实验能力的交付包,并绝对停止等待用户 5 分钟真机验证。
5. 2026-09-17 Implementation Update · Launch Injection 架构修复
5.1 Attempt #1 判定与根因分析
真机测试 Session 4399abb7-34b5-4cde-bb48-2d3d47160eba 记录显示:
frameFilterExperiment:subclassrequestedFrameFilter:0x31800000effectiveFrameFilter:["0x31800000"]
判定结论:Attempt #1 为 INVALID(未激活)。 根因:macOS 下从 Finder/Dock 双击启动 App 时,进程属于 launchd 托管,不会继承终端中的 export 环境变量;而 CollectorSupervisor 直接复制宿主环境,导致子进程未能感知终端设置的 PERFOWL_FRAME_FILTER_EXPERIMENT=exact-debug-id。
5.2 显式注入与防污染安全链路
为彻底脱离终端环境变量依赖,建立强类型端到端控制管道:
- 类型定义与配置载体:
CollectorSupervisor.swift定义public enum FrameFilterExperiment: String, Sendable, Codable, Equatable { case subclass, exactDebugID = "exact-debug-id" };CollectorLaunchConfiguration扩展可选属性public let frameFilterExperiment: FrameFilterExperiment?。
- 防污染安全环境构造:
- 提取纯函数
CollectorSupervisor.environment(for:base:):- 若
configuration.frameFilterExperiment != nil,显式写入PERFOWL_FRAME_FILTER_EXPERIMENT; - 若为
nil,显式执行environment.removeValue(forKey: "PERFOWL_FRAME_FILTER_EXPERIMENT"),彻底清理宿主环境污染,保障生产默认。
- 若
- 提取纯函数
- C1 实验构建注入:
PerformanceSessionController增加@Published public var frameFilterExperiment: FrameFilterExperiment? = .exactDebugID;- 在
start(plan:)与prewarm中均通过CollectorLaunchConfiguration显式下发。
- 早期诊断与 UI 反馈:
- Sidecar CLI 在
hello发出后立即 emitframe_filter_experiment_configured; stream_candidate_session在provider租约建立后立即 emit 同款诊断;- Swift 端消费事件并实时展示
帧耗时实验配置:\(exp) (\(req)),在 Epoch 2 开始前即可肉眼确认配置生效。
- Sidecar CLI 在
5.3 验证结果与 20 秒真机预检门禁
- 单元测试:
- Swift
DeviceConnectionServiceTests.swift补充 4 项配置注入与清理单测,171/171 项全绿; - Python
test_perfowl_realtime.py补充早期 wire event 断言,454/454 项全绿。
- Swift
- 真机预检要求:
- 用户先运行 10~20 秒短 Session,核验
manifest.json与core-profile-census.json中:frameFilterExperiment == "exact-debug-id"requestedFrameFilter == "0x31800318"effectiveFrameFilter == ["0x31800318"]
- 确认三项全部生效后,再执行 5 分钟正式验证。
- 用户先运行 10~20 秒短 Session,核验
6. 高吞吐治理尝试与经验账本(长期保留)
本节不是临时实验记录,而是 CoreProfile / DTX 长稳优化的经验基线。后续任何 AI 或开发者在提出新的降吞吐方案前,必须先对照本节,禁止重复已证伪方向。事实与推测严格分开:只有真机 Session、effective config、Census 或历史 E4 证据支持的内容才记为“已确认”。
6.1 问题画像:FrameTime 的有效信息密度极低
Build 61 后,FrameTime 已能在 Startup → Frame-only 串行 handoff 后持续采集,但长测出现新的主矛盾:为了得到极少量 Presented Frame (0x31800318) 时间戳,CoreProfile 通过 DTX 搬运了数 GiB 的 kd_buf 数据。
实验 1 基线 Session 8ddcdd42-864e-48f3-b03f-4b8826a9bb60:
| 指标 | 结果 |
|---|---|
| Collector duration | 353.1 s |
| Epoch 2 数据量 | 10.23 GiB |
| 平均吞吐 | 29.99 MiB/s |
| kd_buf 总记录 | 171,561,893 |
0x3180 subclass | 727,867(0.4243%) |
0x31800318 Presented Frame | 3,981(0.00232%) |
| bytes / Presented Frame | 约 2.66 MiB |
| first fatal | stream-target-monitor → ConnectionTerminatedError: Connection closed |
这证明高吞吐主要发生在 device → Instruments/CoreProfile → DTX → Mac 的源传输链路,而不是产品 JSON、磁盘 Raw retention 或 FrameTime 后处理造成的膨胀。
6.2 尝试 A:仅减少宿主侧 Raw retention —— 不能解决根因
历史 Raw Retention 优化可以显著减少磁盘占用,但不会减少 iPhone 已经通过 DTX 发送到 Mac 的字节数。因此以下方案只属于“存储优化”,不能作为本问题的降源修复:
derivedOnly/not-retained;- Host 收到 chunk 后丢弃;
- 更快的 JSON writer / batch writer;
- 压缩、checkpoint、减少分析状态落盘。
经验结论: 判断降吞吐是否有效,必须看 coreProfileObservedByteCount / Epoch MiB/s,不能看 Session 目录大小。
6.3 尝试 B:callstackDepth 128 → 0 —— 排除调用栈,但未降低主吞吐
早期 Frame-only restart 曾漏传 callstackDepth,导致 pymobiledevice3 使用默认 csd=128。随后修正为:
text
callstackDepth = 0真机 B 组 Session 6f4a5fd5-5ba5-4961-af95-6c70463b26b5 确认 Epoch 2 csd=0,但仍产生约 16.44 GiB Epoch 2 数据,平均约 33 MiB/s,并最终失败。
结论: csd=128 不是高吞吐主放大器;csd=0 应继续保留,但不能再把“关栈”作为本问题的主要优化方向。
6.4 尝试 C:Event Census —— 找到真正的信息密度问题
为避免继续猜测,增加了只读 Event Census。实验 1 直接证明:
0x31800318只占全部 kd_buf 的约0.00232%;- 大量主导事件甚至不属于
0x3180; - Top debugID 长期稳定集中在
0x25xxxxxx与0x0140xxxx。
典型高频项:
text
0x25000001
0x25000002
0x25020010
0x25010014
0x25010004
0x01400121
0x01400122
0x01400080
0x01400008
0x01400060同时,171,561,893 × 64 B 与 Epoch 2 观测字节量基本相符,进一步证明巨量传输就是 kd_buf 本身,而非外围格式开销。
经验结论: Event Census 是此类私有 Instruments 配置实验的必要门禁;任何“减少了某类事件”的结论必须用完整 debugID 分布验证。
6.5 尝试 D:C1 Exact kdf2=0x31800318 —— 配置有效,但主噪声仍存在
C1 的目标是把 Frame-only 的:
text
kdf2 = [0x31800000]收窄到:
text
kdf2 = [0x31800318]第一次 Attempt 4399abb7-34b5-4cde-bb48-2d3d47160eba 因 macOS GUI App 未继承终端环境变量,实际仍是 subclass,判定为 INVALID / NOT ACTIVATED。这次失败推动了 CollectorLaunchConfiguration → CollectorSupervisor → Collector Core 的显式实验配置注入链路,并增加 requested/effective 早期门禁。
真正有效的 C1 长测 Session:
text
e134a667-7e70-40a4-82df-3cb4c9582545设备实际确认:
text
frameFilterExperiment = exact-debug-id
requestedFrameFilter = 0x31800318
effectiveFrameFilter = [0x31800318]
csd = 0最终结果:
| 指标 | C1 |
|---|---|
| Collector duration | 192.1 s |
| Epoch 2 duration | 178.1 s |
| Epoch 2 数据量 | 约 3.34 GiB |
| Epoch 2 平均吞吐 | 18.76 MiB/s |
| kd_buf 总记录 | 54,545,945 |
| Presented Frame | 1,546 |
| 有效 FrameTime | 1,545 |
| Presented Frame 占全部 | 约 0.0028% |
| droppedEventHighWater | 0 |
| first fatal | stream-target-monitor → ConnectionTerminatedError: Connection closed |
相对 29.99 MiB/s 基线仅下降约 37%,远低于 ≥70% 的最低目标;而 0x25xxxxxx / 0x0140xxxx 仍是 Top debugID。
已确认结论:
- Instruments Server 接受并保留 exact
0x31800318; - Presented Frame 与 FrameTime 在 exact filter 下仍然真实连续;
kdf2精确化能降低一部分流量,但不是控制主要0x25 / 0x0140洪流的配置面;- C1 判定为
C1-THROUGHPUT-NOT-IMPROVED,不再继续枚举其他 debugID。
6.6 尝试 E:历史 ta 降噪 —— 两个最直接候选均已被 E4 证伪
项目历史文档 p2-startup-kdebug-only-trigger-v1-plan.md 已做过同类 CoreProfile trigger 降噪真机实验,必须作为当前 C2 的前置证据复用。
历史默认主 trace:
text
tk = 3
ta = [[3], [0], [2], [1,1,0]]当时设备窗口同样被:
text
0x0140
0x2500–0x2502大量占据。
候选 E1:ta=[[3]]
真机结果:仍产生大量 0x0140/0x2500/0x2501,且 Startup 只形成 6 个阶段 Span。
判定:NEGATIVE。禁止重复。
候选 E2:完全省略 ta
真机总记录下降到 150,749 条,但 0x31CA 仍有 113,504 条,Startup 同样只形成 6 个 Span。
判定:NEGATIVE。禁止重复。
注意:这两次历史实验针对 Startup 语义,不等于已经证明 Frame-only Presented Frame 在所有 ta 组合下都无解;但它已经足以证明“简单认为 ta 某项就是 KPerf 噪声开关”是不可靠的,后续不得再次盲猜 ta=[[3]] 或 ta=[]/omit。
6.7 自动停止现象的当前证据边界
多轮长测出现不同底层表现:
- 有的 Session 在 macOS TCP 层观察到 peer RST;
- 有的 Session TCP 正常收尾,但上层先出现
QueueShutDown; - C1 有效长测中 first fatal 仍是
stream-target-monitor首先观察到共享 DTXConnection closed。
因此当前可以确认的是:
数分钟、数 GiB 级持续 CoreProfile 流量与共享 DTX 会话失效高度相关,并已反复复现;但不同 Session 的最底层关闭机制并不完全一致,不能统一描述成“TCP RST 根因”。
Transport Recovery 属于后续保险机制,不能代替降源;在降源实验完成前禁止通过自动重连掩盖 source load 问题。
6.8 已冻结的优化原则
后续 CoreProfile 高吞吐治理必须遵守:
- 先 Source Census,再修改私有配置。
- requested config 与 effective config 双记录,设备 effective 为真值。
- 一次只验证一个最小假设,短测通过后才长测。
- 评价指标是 device→Mac 实际观测 MiB/s,而不是磁盘大小。
- 必须同时保护 FrameTime 真实性:不允许
1000/FPS、旧值 replay 或跨 epoch interval。 - 已证伪方向禁止重跑:Host retention、仅 csd、继续猜 kdf2 debugID、
ta=[[3]]、省略ta。 - 如果
ta/tk合同最终证明无法把 Presented Frame 与海量 KPerf 伴生流拆开,应明确判定 CoreProfileSessionTap 不适合作为长时间 FrameTime source,并切换数据源研究,而不是无限枚举私有配置。
6.9 当前下一步
C1 已完成并关闭。下一阶段只做 C2 CoreProfile Action Contract Audit:
text
重点:tc.ta / tc.tk / 主 trace 与 sampling trigger 的真实协议语义
目标:找到一个有源码/历史真机/Apple Instruments 静态证据支持、且尚未被历史 E4 证伪的最小单变量候选在审计完成前,不新增真机穷举候选。
7. C2 CoreProfile Action Contract 审计结论
审计目标不是为完成实验数量而强行找一个新的
ta/tk组合,而是判断tc.ta / tc.tk是否存在一个有协议证据、尚未被历史 E4 证伪、并能保持 Presented Frame 真实性的最小单变量候选。审计来源包括:当前 pymobiledevice3 11.3.1、Perfowl 现有适配层、历史 Startup E4、当前 XcodeDVTInstrumentsFoundation私有框架静态符号/反汇编,以及 C1 真机 Census。
7.1 pymobiledevice3 当前公开合同非常有限
CoreProfileSessionTap 的默认主 trace 配置为:
text
tc[0] = {
csd: 128,
kdf2: filters,
ta: [[3], [0], [2], [1,1,0]],
tk: 3,
uuid: ...
}
rp = 100
bm = 0上游源码对 ta 只注释为 Actions,对 tk 只注释为 Kind,没有公开枚举或 action 数字语义。因此:
- 不能从 pymobiledevice3 本身证明
[3] / [0] / [2] / [1,1,0]分别是什么 sampler; - 不能仅凭
tk=3的数字值自行命名 trigger 类型; - 任何直接删除某个 action 的方案若无额外证据,均属于猜测性私有协议枚举。
7.2 Xcode 私有框架确认:CoreProfile KDebug 路径属于 KPerf 配置体系
当前本机 Xcode 的 DVTInstrumentsFoundation 静态证据明确包含:
text
DTKPKperfConfiguration
DTKPTriggerKDebug
DTKPTriggerTime
DTKPTriggerPMI
createTriggerKDebug:
createTriggerTime:
createTriggerPMI:
setKdebugFilter:
createKperfFilter
kperf_buffer_create_live
kperf_buffer_set_config
kperf_logging_start
kperf_sample_on
com.apple.instruments.server.services.coreprofilesessiontap同时存在错误/诊断字符串:
text
Unsupported trigger kind: %u
Error allocating a Kdebug trigger action
Error allocating a PMI trigger action
Error allocating a Time trigger action
A kdebug trigger already exists. DTKPKperfConfiguration doesn't support creation of multiple kdebug triggers.这把此前的“可能”提升为较强静态证据:
coreprofilesessiontap的 KDebug 采集并不是独立于 KPerf 的纯 typefilter socket;Apple Instruments 内部使用DTKPKperfConfiguration管理 KDebug/Time/PMI trigger,并通过 kperf live buffer/logging 基础设施交付数据。
因此 C1 中即使 kdf2 精确到 0x31800318,仍然收到大量 0x25xxxxxx / 0x0140xxxx,与该内部架构是自洽的。
7.3 trigger kind 的内部枚举映射与 wire tk 边界
对当前 Xcode 私有框架的三个 triggerKind 实现做最小反汇编,得到:
text
-[DTKPTriggerTime triggerKind] -> 0
-[DTKPTriggerPMI triggerKind] -> 1
-[DTKPTriggerKDebug triggerKind] -> 2而 CoreProfile wire config 使用:
text
sampling trigger: tk = 1
main trace: tk = 3二者不能直接按同一个数字枚举解释。当前最合理的协议解释是 wire 值可能采用 internalKind + 1 编码:
text
wire tk=1 -> internal Time(0)
wire tk=2 -> internal PMI(1)
wire tk=3 -> internal KDebug(2)该映射与现有使用形态高度吻合:sampling trigger 带 si 周期字段,而主 trace 带 kdf2 KDebug filter。
但目前尚未取得 Instruments Server 对 wire dictionary 的 parser 反汇编来直接证明 +1 映射,因此正式置信度标记为:
text
LIKELY,非 CONFIRMED关键工程结论不依赖该最后一步完全确认:tk=3 不应被当作可随意降低的“采样等级”参数。 改成 1/2 很可能是在切换 trigger 类型,而不是在降低同一种 KDebug trigger 的噪声;这会破坏 Presented Frame source contract,因此不构成安全 C2 候选。
7.4 ta 的证据边界
现有证据只能确认 ta 是 trigger actions 配置的一部分,不能把具体数组项安全映射到公开 action 名称。
已知两组结构:
text
main KDebug trace:
ta = [[3], [0], [2], [1,1,0]]
5ms / 50ms sampling auxiliary:
ta = [[0], [2], [1,1,0]]唯一结构差异是主 trace 多了 [3],但这不能证明 [3] 就是 KDebug-only action 或 perf-noise action。历史 E4 已直接否定这种简单推断:
text
ta=[[3]]
→ 仍大量 0x0140 / 0x2500 / 0x2501
→ NEGATIVE
完全省略 ta
→ 记录总量下降,但仍有大量非业务记录且 Startup 语义残缺
→ NEGATIVE所以当前没有任何一个单独 ta[i] 具备足够证据支持“删除它即可关闭 0x25/0x0140 洪流,同时保留 Presented Frame”。
7.5 高频 debugID 的语义进一步支持 KPerf-backed 判断
历史 Perfowl 解码与公开 py-ios-device 输出已经确认若干代表项:
text
0x25010004 = PERF_THD_Data
per-thread info / PID-TID attribution
0x25010014 = PERF_THD_CSwitch
perf thread context-switch 类记录
0x01400080 = MACH_DISPATCH
DBG_MACH / scheduler 类记录C1 exact-filter Census 中这些类别仍持续高频出现,而它们并不属于 0x3180。这说明主导字节量不是 Presented Frame subclass 本身,而是 KPerf/KDebug trigger 运行时伴随产生/交付的 perf-thread、Mach scheduler/info 数据。
此外,历史 Startup 审计已经独立观察到:0x07 / 0x25 等 class 不在业务 kdf2 集合里仍会大量进入 CoreProfile,且此前已把“只收窄 lifecycle kdf2 可以解决压力”判为边际收益有限。C1 现在用 Frame-only exact debugID 再次验证了同一事实。
7.6 Action Contract 状态表
| 配置 | 当前值 | 审计结论 | 置信度 | 是否值得继续真机枚举 |
|---|---|---|---|---|
csd | 0 | callstack depth;已排除主放大器 | CONFIRMED | 否 |
kdf2 | [0x31800318] | KDebug filter;C1 已证可 exact,但不能滤掉主伴生流 | CONFIRMED | 否 |
tk | 3 | wire trigger kind;高度吻合 KDebug trigger,改变值可能切换 Time/PMI/KDebug 类型 | LIKELY | 否,除非获得 parser 级证据 |
ta[0] | [3] | action;具体 sampler 名称未知;单独保留已 E4 失败 | UNKNOWN/NEGATIVE | 否 |
ta[1] | [0] | action;具体语义未公开 | UNKNOWN | 无证据,不跑 |
ta[2] | [2] | action;具体语义未公开 | UNKNOWN | 无证据,不跑 |
ta[3] | [1,1,0] | action;具体语义未公开 | UNKNOWN | 无证据,不跑 |
rp | 100 | recording priority | CONFIRMED(上游注释) | 否 |
bm | 0 | buffer mode | CONFIRMED(历史实验) | 只影响缓冲策略,不作为降源候选 |
7.7 C2 审计最终判定:当前不存在值得执行的 ta/tk 单变量真机候选
本轮审计没有找到满足以下全部条件的候选:
text
有协议/静态证据
AND 尚未被历史 E4 证伪
AND 只改一个最小变量
AND 能合理预期降低 device→DTX 流量
AND 不改变 Presented Frame 的真实来源语义因此不能为了“继续 C2”而制造一个猜测性的 candidate-1。
正式判定:
text
C2-ACTION-CONTRACT-AUDIT: COMPLETE
C2-TA/TK-CANDIDATE: NO-EVIDENCE-BACKED-CANDIDATE7.8 对 CoreProfileSessionTap 作为长期 FrameTime source 的阶段性结论
证据链现在为:
text
Host Raw retention
NEGATIVE(不降 device→Mac 流量)
csd 128 → 0
NEGATIVE(仍 20~33 MiB/s)
subclass kdf2 → exact 0x31800318
PARTIAL only(29.99 → 18.76 MiB/s,仍数 GiB/数分钟)
ta=[[3]]
NEGATIVE(历史 E4)
omit ta
NEGATIVE(历史 E4)
改变 tk
NOT SAFE:当前证据表明它是 trigger-kind 选择,而非同类 trigger 的轻量级旋钮因此当前最有根据的阶段性判断是:
在已知 CoreProfileSessionTap /
DTKPKperfConfiguration合同下,没有找到能够把 Presented Frame 与 KPerf-backed 大流量伴生记录诚实拆开的配置。继续穷举ta/tk的收益已经低于风险。
这不是声称 Apple 内部绝对不存在未公开配置,而是说明现有代码、历史真机和当前 Xcode 静态证据不足以支持下一次真机私有枚举。
7.9 下一步路线冻结
下一阶段停止 ta/tk/kdf2 穷举,转为两个独立方向:
- Frame timestamp 替代数据源研究(优先)
- 搜索能够直接产出 display / compositor / presented-frame timestamp 的 Instruments/DVT source;
- 目标是不占用 CoreProfile KPerf heavy stream;
- 仍要求真实时间戳,不接受
1000/FPS。
- Transport Recovery(次级保险,单独立项)
- 解决任意 DTX source Connection closed 时不应让整个 Session 无条件死亡的问题;
- 必须记录 honest gap / sourceEpoch / reconnectGeneration;
- 不得用重连掩盖高吞吐根因。
在找到新的 Frame timestamp source 前,当前 exact-debug-id 仅保留为实验事实,不提升为“吞吐修复完成”的生产结论。
8. FrameTime 替代源 POC 阶段记录
8.1 Stage 1 · Animation Hitches Ground Truth 环境预检(2026-09-17)
目的:先使用 Apple xctrace 的 Animation Hitches 模板在真机上建立 Frame Lifetimes / Built-In Display / VSYNC Ground Truth,再据此反推可替代长期 CoreProfileSessionTap 的轻量 DVT source。
公开/本机静态依据:
- Apple Animation Hitches 明确包含
Frame Lifetimes与Built-In Display轨道;Built-In Display 展示真正出现在显示器上的帧与 VSYNC。 - 当前 Xcode 的 instrument inventory 包含
Display、Frame Lifetimes、Hitches。 - 当前 Xcode
DVTInstrumentsFoundation中存在独立服务名:com.apple.instruments.server.services.hitches.recordingcom.apple.instruments.server.services.graphics.coreanimationcom.apple.instruments.server.services.graphics.coreanimation.immediatecom.apple.instruments.server.services.graphics.coreanimation.deferred
- py-ios-device 的
display已源码确认仍调用coreprofilesessiontap并从0x31800318计算帧间隔,因此不是替代源,禁止把它重新包装成新方案。
环境预检方法:
text
xcrun xctrace list devices
pymobiledevice3 usbmux list
检查 Perfowl prewarm / tunnel 进程结果:
pymobiledevice3 usbmux list能看到 USB 真机,Perfowl prewarm Collector 也已连接其中一台 iOS 17 真机;- 但本轮
xcrun xctrace list devices将全部 iPhone 列为Devices Offline,因此当前时刻无法可靠启动真机Animation Hitchestrace; - 该结果属于 E3 环境阻塞,不能据此否定 Animation Hitches 数据源本身。
阶段经验:
xctrace与 Perfowl/pymobiledevice3 的“设备可达”不是同一状态,不能用其中一方在线推断另一方必然可录制;- Ground Truth 路线必须把
xctrace device online作为独立前置门禁; - 在该门禁恢复前,不等待环境,继续通过本机 Xcode 私有框架 + DVT 动态服务探测完成协议 POC;
- 后续一旦 xctrace 真机恢复在线,再补 20~30 秒 trace 做 E4 对照,不需要重做前面的协议调查。
Stage 1 状态:
text
ANIMATION-HITCHES-GROUND-TRUTH:
BLOCKED-BY-XCTRACE-DEVICE-OFFLINE
ALTERNATIVE-SOURCE-POC:
CONTINUE下一步:直接审计并探测 hitches.recording / graphics.coreanimation 的 channel、selector、启动配置与回包形态;若能以独立 DVT service 低流量得到 display/presentation timestamp,则进入最小 Adapter POC。
8.2 Stage 2 · HitchesTap 协议与实时性审计(2026-09-17)
目的:判断 com.apple.instruments.server.services.hitches.recording 是否能直接作为 Perfowl 的实时逐帧 FrameTime source,而不是仅作为停止录制后的离线分析源。
本机 Xcode 静态证据:
- Instruments 自带独立插件
HitchesTap.framework,并存在Hitches.instrdst、Animation Hitches.tracetemplate与coreanimation-frame-lifetime-modeler.dtac; HitchesTap.framework直接链接SignpostSupport.framework,并显式引用:SignpostFrameLifetimeIntervalSignpostFrameOverrunIntervalSignpostUpdateSequenceIntervalhandleSignpostInterval:/handleSignpostIntervalBegin:
XRHitchesTapAgent的自定义执行路径明确包含:parseKTraceFileprocessKTraceFile(...)executeStop(on:)
processKTraceFile(...)的 Swift 符号签名直接返回SignpostFrameLifetimeInterval集合;XRHitchesTapAgent未发现对应的 Hitches 专用实时executeStart数据处理实现。- Hitches/CoreAnimation schema 明确存在真实逐帧模型字段:
start、duration、display-id、lifetime-id (Frame #)、swap-id、frame-seed、hitch-duration、acceptable-latency、render-start、render-duration等;这些字段非常适合作为 FrameTime 真值对照。
动态环境旁证:
- USB Lockdown 能识别当前 iOS 17 真机,但直接通过裸 Lockdown 构造
DVTSecureSocketProxyService返回InvalidService; - Perfowl 当前 prewarm 则能通过其 iOS 17+ modern RSD/native route 建立 DVT,因此“裸 Lockdown DVT 探测失败”只说明路由不匹配,不能用于判定
hitches.recording服务不存在。
阶段判定:
text
HITCHES-FRAME-LIFETIME-TRUTH:
STRONG-CANDIDATE
HITCHES-RECORDING-AS-REALTIME-FRAMETIME-SOURCE:
NOT-SELECTED / POST-STOP-KTRACE-PROCESSING当前证据表明 Animation Hitches 的 Frame Lifetime 数据具有很强的逐帧真值价值,但 Hitches 特有处理主要发生在停止录制后的 ktrace/signpost 解析阶段。它适合作为后续 xctrace E4 Ground Truth Teacher,不适合直接承担 Perfowl 约 1 Hz UI 更新背后的实时 FrameTime 流。
阶段经验与冻结边界:
- “独立 DVT service 名”不等于“实时 callback source”;必须继续区分设备端录制、bulk/file spool 与实时 channel;
- Frame Lifetime schema 已证明 Apple 内部存在我们需要的
display/frame lifetime真值,因此替代源研究方向成立; hitches.recording不再作为第一实时 Adapter 候选,避免把离线 trace parser 错包装成实时源;- 后续 xctrace 恢复在线后,只需要用 Animation Hitches 做 20~30 秒真值对照,不需要重新证明 schema;
- Stage 3 转向
com.apple.instruments.server.services.graphics.coreanimation(.immediate),重点验证它是否是轻量实时 DTTap callback,是否能产出 frame/display/presentation 级事件。
下一步:使用 Perfowl 自己的 iOS 17+ modern provider route 做 graphics.coreanimation.immediate 最小动态 channel POC;先只验证 channel 与已知 selector,不修改产品默认采集链路。
8.3 Stage 3A · CoreAnimation Frame-Info 真值发现与 DVT Channel 存在性真机探测(2026-09-17)
目的:在不启动任何新采集任务、不发送 setConfig:/start 的前提下,先回答两个问题:Apple Frame Lifetime 的 iOS 原始事件是否直接携带 presentation 时间;graphics.coreanimation(.immediate/.deferred) 是否确实是当前真机 Instruments Server 注册的可打开 DVT channel。
静态真值发现(E1):当前 Xcode coreanimation-frame-lifetime-modeler.dtac 的 iOS 规则 frame-info-event 明确消费 CoreAnimation KDebug subclass=0xca, code=0x0b, function=0,其原始参数定义为:
text
arg1 = swapID
arg2 = earliest-time
arg3 = presentation-time
arg4 = requested-timeApple modeler 使用设备 mach timebase 将 earliest-time / presentation-time / requested-time 转成 trace-relative 时间,并生成 coreanimation-lifetime-interval。因此新的目标不再只是寻找另一个“Presented Frame tick”,而是优先寻找能够直接获得 frame-info.presentation-time 的轻量实时源;它比当前 0x31800318 相邻时间戳做差更接近 Apple 自己的 Frame Lifetime 真值模型。
对应 binding 还明确依赖:
text
kdebug codes = 0x31,0xca
display-surface-swap这同时说明 Frame Lifetimes 最终仍建立在 CoreAnimation/KDebug 原始事件上;后续必须验证新的 DVT service 是否已经在设备端做了筛选/模型化,不能仅凭 service 名字假设其天然低流量。
真机最小探测(E4):使用 Perfowl 现有 iOS 17+ 路由:
text
USB 真机 A
→ open_service_provider(use_native=true)
→ tunneld-native-rsd
→ DvtProvider
→ 仅 open_channel本阶段没有调用 setConfig:、start、startSamplingAtTimeInterval:,打开后立即由 DVT owner 统一关闭连接,避免产生新的采集压力。
真机返回:
text
com.apple.instruments.server.services.graphics.coreanimation.immediate OPEN
com.apple.instruments.server.services.graphics.coreanimation.deferred OPEN
com.apple.instruments.server.services.graphics.coreanimation OPEN
com.apple.instruments.server.services.hitches.recording OPEN阶段结论:
text
COREANIMATION-FRAME-INFO-PRESENTATION-TIME:
CONFIRMED-IN-XCODE-MODELER (E1)
COREANIMATION-DVT-CHANNELS-REGISTERED-ON-IOS17:
PASS (E4)
REALTIME-PER-FRAME-PAYLOAD:
NOT-YET-PROVEN阶段经验:
graphics.coreanimation(.immediate/.deferred)与hitches.recording已由真机证明是实际注册服务,不再只是 Xcode 静态字符串;- Apple Frame Lifetime 的 iOS 原始
frame-info本身直接包含 presentation time,这是当前替代源调查中比0x31800318更强的真值线索; - 仅能打开 channel 仍不能证明它会实时输出逐帧消息,也不能证明低带宽;下一阶段必须分别验证 selector、消息形态与真实 bytes/s;
- 为避免再次制造高流量,Stage 3B 必须从
immediate单一路径开始,短时、单 channel、先收首包/超时即停,禁止同时启动多个 CoreAnimation service。
下一步:反查 graphics.coreanimation.immediate 的 selector/config 合同,然后只在真机 A 上启动一个 5~10 秒最小流,记录首包 latency、消息数、总字节、payload 结构以及是否直接包含 presentation/swap/frame 字段;完成后先写阶段记录,再决定是否进入 Adapter POC。
8.4 Stage 3B · graphics.coreanimation.immediate 5 秒实时流真机验证(2026-09-17)
目的:验证 graphics.coreanimation.immediate 是否是可实时使用的低流量 source,并确认它是否直接提供逐帧 presentation/swap/frame timestamp。
方法(E4):
text
真机 A / iOS 17
→ tunneld-native-rsd
→ DvtProvider
→ open_channel(graphics.coreanimation.immediate)
→ startSamplingAtTimeInterval:(0.0)
→ 单 channel 收取 5 秒
→ stopSampling本轮没有打开 CoreProfile,也没有同时启动 .deferred / Hitches;探针同时监听 DTX dispatch、notification 与 raw data,并对 payload 做最小计数。
真机结果:
text
startSamplingAtTimeInterval: ACCEPTED
startError none
elapsed 5.007 s
notification 5
DTX dispatch 0
raw data 0
approx payload bytes 440 B样本形态稳定为约 1 Hz 的轻量字典:
text
{
IOGLBundleName: Built-In,
FramesPerSecond: 19~20,
XRVideoCardRunTimeStamp: ...
}本轮没有观察到 presentation-time、swap-id、frame-seed、逐帧 timestamp 或等价 per-frame payload。
阶段判定:
text
COREANIMATION-IMMEDIATE-REALTIME:
PASS (E4)
COREANIMATION-IMMEDIATE-BANDWIDTH:
VERY-LOW / ~5 small notifications in 5s
COREANIMATION-IMMEDIATE-AS-FRAMETIME-SOURCE:
NEGATIVE — AGGREGATED FPS ONLY经验总结:
.immediate的 channel 与 Graphics 风格 selector 已由真机确认,可作为低开销 CoreAnimation/FPS source;startSamplingAtTimeInterval: 0.0并不意味着逐帧 callback,在当前真机上实际仍约 1 Hz 汇总;XRVideoCardRunTimeStamp是采样运行时间戳,不能因为名字里有TimeStamp就当作每帧 presentation time;- “低带宽”与“逐帧真实性”必须分别验收;本阶段只满足前者,不满足 FrameTime 合同;
- 不把
.immediate包装成 FrameTime fallback,也不使用1000/FPS伪造 FrameTime。
下一步:先验证 .deferred 是否只是同一聚合源的延迟展示版本;若同样只有 FPS 汇总,则停止在 graphics.coreanimation 家族继续猜 selector,转向 Apple Frame Lifetime modeler 已确认的 0x31/0xca frame-info 原始事件,寻找能够只订阅该 CoreAnimation KDebug class/subclass 的轻量 recording/tap source。
8.5 Stage 3C · graphics.coreanimation.deferred 对照真机验证(2026-09-17)
目的:确认 .deferred 是否隐藏了 .immediate 没有暴露的逐帧数据,避免过早放弃 CoreAnimation 专用服务。
方法(E4):保持与 Stage 3B 相同条件,只把单一 channel 改为:
text
com.apple.instruments.server.services.graphics.coreanimation.deferred仍使用 startSamplingAtTimeInterval: 0.0,只采 5 秒,并监听 dispatch / notification / raw data。
结果:
text
startSamplingAtTimeInterval: ACCEPTED
elapsed 5.005 s
notification 5
DTX dispatch 0
raw data 0
approx payload bytes 440 Bpayload 与 .immediate 同构:
text
IOGLBundleName = Built-In
FramesPerSecond = 19(首样本 0)
XRVideoCardRunTimeStamp = 约 1 秒递增同样未出现 per-frame presentation-time / swap-id / frame-seed。
阶段判定:
text
COREANIMATION-DEFERRED-REALTIME:
PASS (E4)
COREANIMATION-DEFERRED-AS-FRAMETIME-SOURCE:
NEGATIVE — SAME AGGREGATED FPS CONTRACT
GRAPHICS.COREANIMATION-SAMPLING-FAMILY-AS-FRAMETIME:
CLOSED / DO-NOT-ENUMERATE-FURTHER经验总结:
.immediate/.deferred在当前真机上的实时 sampling contract 均为低带宽 FPS 汇总,并非逐帧事件;- 二者名字里的 immediate/deferred 不应被解释为“逐帧立即/延迟事件”,至少
startSamplingAtTimeInterval:路径不是; - 已有两次 E4 足以停止继续对
graphics.coreanimation猜其他 interval 值或把采样频率强行调高; - 下一阶段只研究 Apple modeler 明确依赖的
CoreAnimation frame-info原始事件,以及有没有比CoreProfileSessionTap更轻的 KDebug/ktrace delivery source。
下一步:确认 frame-info 的完整 DebugID、Apple Instrument 对该输入表的采集配置,以及 Xcode 是否存在专门的 KDebug recording/tap 服务可以只采 0x31/0xca,再决定是否值得做下一次 5~10 秒真机流量实验。
8.6 Stage 4 · Frame Lifetime 原始输入与轻量 KTrace delivery 审计(2026-09-17)
目的:在 graphics.coreanimation 采样家族已由 E4 证伪为 1 Hz FPS 汇总后,继续追 Apple Frame Lifetimes 真值模型的真实输入,并判断是否存在一个独立于 CoreProfileSessionTap heavy KPerf 流的实时 KDebug/KTrace delivery source。
Frame Lifetime 原始事件合同(E1,当前 Xcode 静态证据):
coreanimation-frame-lifetime-modeler.dtac 的 iOS frame-info-event 明确消费:
text
KDebug:
class/subclass binding = 0x31 / 0xca
subclass = 0xca
code = 0x0b
function = 0
arg1 = swapID
arg2 = earliest-time
arg3 = presentation-time
arg4 = requested-timeApple modeler 使用设备 mach timebase 将 earliest-time / presentation-time / requested-time 转成 trace-relative 时间,并直接形成 coreanimation-lifetime-interval。因此 arg3 presentation-time 是目前找到的最强逐帧真值候选。按标准 KDebug 编码推导,该 frame-info DebugID 可能为 0x31CA002C,但在当前阶段只标记为 DERIVED / NOT-YET-E4-CONFIRMED,不得当作真机已确认常量。
Frame Lifetime modeler 的完整输入还包括:
text
kdebug 0x31/0xd1
kdebug 0x31/0xd0
kdebug 0x31/0xca
display-surface-swap
time-info
tick这说明 Apple 的完整 Frame Lifetime 是多个输入表模型化的结果;若 Perfowl 仅需要 FrameTime = presentation[n] - presentation[n-1],理论上可以只寻找 frame-info.presentation-time 的 delivery,不必复制完整 Hitches/Frame Lifetime 分析栈。
通用 KTrace Tap 审计(E1):
当前 DVTInstrumentsFoundation 中确认存在独立内部类型:
text
DTKTraceTapConfig
DTKTraceTapTriggerConfig
DTKTraceTapServiceDelegate
DTKPKDebugCodeSet
DTKPKDebugCodes并存在以下能力/selector:
text
addCodes:
setKdebugFilter:
createTriggerKDebug:
filteredToPid
collectionInterval
startSampling
startKtraceSession:useExisting:outputFileURL:这证明 Apple Instruments 内部确实支持“按 KDebug code set + PID/filter 配置”的通用 KTrace Tap,而不仅是 CoreProfile 的固定配置。
但对当前 Xcode 全部 DVT service 字符串审计后,没有发现独立的 com.apple.instruments.server.services.ktracetap / ...kdebugtap channel。设备可见的相关服务仍主要是:
text
activitytracetap(.immediate/.deferred/.windowed)
coreprofilesessiontap(.immediate/.deferred/.windowed)
hitches.recording
processortrace因此:
text
GENERIC-DTKTRACETAP-INTERNAL-CAPABILITY:
CONFIRMED (E1)
GENERIC-DTKTRACETAP-AS-INDEPENDENT-DVT-CHANNEL:
NOT-FOUNDactivitytracetap 不能因为名字带 trace 就当成 raw KDebug source。其 DTActivityTraceTapConfig 公开字段集中于 predicate、logs、signposts、HTTP archive、subsystem/category 与 target PID,属于统一日志/activity/signpost 路线;当前没有证据支持它直接输出 frame-info KDebug,因此不作为下一次真机 FrameTime candidate。
display-surface-swap 路线审计(E1):
该表由 GPU tracing package 定义,字段非常接近真实显示链:
text
timestamp
swap-id
surface-id
framebuffer-index
desired-presentation-time
generation-time
hid-time但其 producer com.apple.metalsystrace-surface-swap 的 binding 明确依赖:
text
device-display-info
kdebug codes "0x85,0x20" "0x31,0x80"
time-info其中 0x31/0x80 正是当前 CoreProfile Presented Frame 路线涉及的 CoreAnimation KDebug 类别。也就是说 display-surface-swap 本身是很好的显示真值表,但若仍通过 CoreProfileSessionTap 获取底层 KDebug,不会自动解决 device→DTX 高吞吐问题。
正式判定:
text
DISPLAY-SURFACE-SWAP-AS-GROUND-TRUTH:
STRONG-CANDIDATE
DISPLAY-SURFACE-SWAP-VIA-COREPROFILE-AS-LOW-BANDWIDTH-SOURCE:
NEGATIVE / DO-NOT-REPEAT当前动态环境:
xcrun xctrace list devices当前仍把物理 iPhone 列为 Offline,因此 Animation Hitches Ground Truth 录制继续受阻;- 但
python3 -m pymobiledevice3 usbmux list能看到 USB iOS 17 真机小红茂,因此 Perfowl modern RSD/DVT 路线仍可继续做最小 channel POC; - 系统 Python 与 Perfowl 冻结的 pymobiledevice3 版本不同,后续动态 POC 必须使用
MacApp/.sidecar-build/venv-py312/ Perfowl bunded runtime,禁止用系统 pymobiledevice3 API 形态做结论。
Stage 4 经验总结:
- Apple 已明确给出真实
presentation-time原始字段,FrameTime 真值方向成立; - “Apple 内部有 DTKTraceTap”不等于“设备暴露独立 ktracetap channel”,两者必须分开;
activitytracetap是 Activity/Log/Signpost contract,当前不应拿来盲试 KDebug;display-surface-swap很接近屏幕真实 swap,但它的输入仍依赖 KDebug;不要把模型化表误认为天然低带宽 source;- 下一阶段不再枚举
graphics.coreanimationselector,也不把display-surface-swap重新塞回 CoreProfile; - 剩余值得验证的方向收敛为:设备端 KTrace file/recording 路径是否能把高流量留在设备端并只批量传结果,以及是否存在可增量/短窗读取的 display-frame 数据;否则零侵入实时 FrameTime 替代源在当前 DVT 暴露面上可能不存在。
Stage 4 状态:
text
FRAME-INFO-PRESENTATION-TIME-CONTRACT:
CONFIRMED (E1)
GENERIC-KTRACE-INTERNAL-FILTERING:
CONFIRMED (E1)
INDEPENDENT-REALTIME-KDEBUG-DVT-CHANNEL:
NOT-FOUND
NEXT:
DEVICE-SIDE-KTRACE-RECORDING / BATCH-DELIVERY POC8.7 Stage 5 · Hitches/KTrace 设备端录制与实时性 POC(2026-09-17)
目的:验证 Apple hitches.recording / KTrace recording provider 是否可以把高频 KDebug 留在设备端,仅以低带宽方式回传逐帧结果;如果只能 Stop 后整包处理,则只保留为离线 Ground Truth,不进入实时 FrameTime 候选。
静态合同(E1):
当前 Xcode HitchesTap.framework 明确包含:
text
ktraceFileURL
parseKTraceFile:
processFileWithPath:startDate:endDate:errorsOut:
executeStopOnItinerary:DVTInstrumentsFoundation 同时存在:
text
com.apple.dt.ktrace_recording.provider
DVTKTraceSessionConfiguration
DTKTraceSessionCreator
startKtraceSession:useExisting:outputFileURL:
stopKtraceSessionWithError:这说明 Animation Hitches 的底层确实支持设备/trace 级 KTrace file recording,而不是必须将全部 raw KDebug 持续实时发送给 Host。
2 秒真机动态探针(E4):
text
真机 A / iOS 17
→ Perfowl tunneld-native-rsd
→ DvtProvider
→ open_channel(com.apple.instruments.server.services.hitches.recording)
→ setConfig({})
→ start
→ bounded receive 2s
→ stop结果:
text
channel: OPEN
setConfig: accepted
start: accepted
2s running messages: 0
2s running payload bytes: 0
stop: accepted本轮未开启 CoreProfile,也没有观察到实时逐帧 callback。关闭 DTX 时出现的 channel cancellation 日志属于连接 teardown,不作为 Hitches source failure 证据。
Animation Hitches 模板 recording-mode 真值(E1):
对当前 Xcode Animation Hitches.tracetemplate 的 XRRecordingOptions 解档后得到:
text
recordingMode = 2
supportsDeferredMode = true
supportsImmediateMode = false
supportsWindowedMode = false这条证据比服务名/方法名更强:当前 Apple Animation Hitches 模板本身明确不支持 Immediate/Windowed recording,只支持 Deferred。
因此正式判定:
text
HITCHES/KTRACE-FILE-AS-LOW-DTX-RECORDING:
SUPPORTED / STRONG (E1 + bounded E4)
HITCHES.RECORDING-AS-REALTIME-FRAMETIME-SOURCE:
NEGATIVE
ANIMATION-HITCHES-AS-GROUND-TRUTH/OFFLINE-REPORT:
KEEP经验总结:
- 把 KDebug 留在设备端/trace 文件里可以避免“实时 raw KDebug 全量灌入 DTX”的架构形态,但它解决的是传输方式,不自动提供实时 UI;
hitches.recordingchannel 能打开并接受 start/stop,不代表它会实时回调;本轮 2 秒为 0 message / 0 B,与 Host 侧 Stop 后ktraceFileURL解析合同一致;- 当前 Xcode 模板明确
supportsImmediateMode=false,因此不应继续尝试通过猜 selector/config 强行把 Hitches 变成实时 tap; - Hitches 仍是非常有价值的最终报告/真值老师,可用于校准 Perfowl 其他实时 source;
- 后续若产品允许“实时曲线无 FrameTime、Stop 后补齐逐帧 FrameTime 报告”,设备端 deferred KTrace 可以作为一个产品降级方案,但它不是当前目标的实时替代源。
Stage 5 状态:
text
DEVICE-SIDE-DEFERRED-KTRACE:
VIABLE FOR OFFLINE / POST-STOP
REALTIME FRAME CALLBACK:
NOT AVAILABLE VIA HITCHES TEMPLATE
NEXT:
SEARCH NON-KDEBUG REALTIME DISPLAY/PRESENTATION SOURCE8.8 Stage 6A · coreprofilesessiontap.deferred 低传输假设真机证伪(2026-09-17)
目的:验证 CoreProfile 的 .deferred service 是否能够把 exact Presented Frame KDebug 保留在设备端,在运行期间避免 raw KDebug 经 DTX 持续回传,并于 Stop 后再批量交付。若成立,可作为“实时 UI 暂无 FrameTime、Stop 后补齐真实逐帧数据”的降流量候选。
方法(E4):
text
真机 A
→ Perfowl iOS 17+ modern native RSD route
→ com.apple.instruments.server.services.coreprofilesessiontap.deferred
→ csd = 0
→ kdf2 = [0x31800318]
→ ta = [[3],[0],[2],[1,1,0]]
→ tk = 3
→ start
→ bounded receive 2s
→ stop
→ bounded post-stop receive 2s本轮仅验证 delivery 行为,不接入产品 Session,也不与其他 CoreProfile tap 并发。
真机结果:
text
运行中 2 秒:
message count = 9
DTX payload = 11,881,321 B ≈ 11.33 MiB
其中 data = 6
notification = 3
Stop 后 2 秒:
message count = 10
DTX payload = 16,970,540 B ≈ 16.18 MiB
其中 data = 6
notification = 4运行期已经直接收到 MiB 级 binary KDebug/stackshot 数据,因此 .deferred 并不等价于“设备端只录文件、运行中零 DTX raw”。Stop 后还会继续 drain 大块 binary 数据。
阶段判定:
text
COREPROFILE-DEFERRED-AS-LOW-DTX-FRAMETIME:
NEGATIVE (E4)
COREPROFILE-DEFERRED-AS-DEVICE-SIDE-SPOOL:
FALSE FOR THIS CONTRACT经验总结:
- DVT service 名称中的
.deferred不能解释成“整段采集延迟到 Stop 后才传输”;真实 wire 行为必须用运行期 byte census 验证; - 即使
kdf2已精确为0x31800318且csd=0,.deferred仍在 2 秒内向 Host 推送约 11.3 MiB 数据,未改变 CoreProfile heavy-stream 本质; hitches.recording的设备端 KTrace/file recording 与coreprofilesessiontap.deferred是两种不同合同,不能因为都叫 deferred 而混为一谈;- 后续禁止把 CoreProfile
.deferred作为高吞吐治理方案重新测试; .windowed若没有新的协议证据证明服务端会先模型化/过滤 Presented Frame,不再仅凭名字继续枚举,避免重复制造 KDebug 洪流。
下一步:继续验证非 CoreProfile 实时源:GPU/Metal presentation-info 是否仅来自 Stop 后 KTrace importer;同时评估 QuickTime/Valeria 屏幕流能否满足 Display FrameTime 真值与 120Hz 合同。若两者均失败,则实时、零侵入、低 DTX 的公开/可见 DVT source 当前可能不存在,产品候选将收敛为 Hitches deferred KTrace 的 Stop 后真实 FrameTime。
8.9 Stage 6B · GPU/Metal presentation-info 实时源审计(2026-09-17)
目的:判断 Instruments GPU/Metal 路径中的 frame presentation 信息是否来自一个独立低带宽实时 DVT source,还是 Stop 后从 KTrace/AGX trace 导入生成。
静态证据(E1):当前 Xcode GPUPlugin 同时包含:
text
com.apple.instruments.server.services.gpu
com.apple.instruments.server.services.gpu.counters.multisources
com.apple.instruments.server.services.gpu.counters.aps
requestDeviceGPUInfo
configureCounters:counterProfile:interval:windowLimit:tracingPID:
startCollectingCounters
stopCollectingCounters
flushRemainingDataPerfowl 当前也已实现同一 gpu counter control surface;其合同是 device-specific GPU counter schema/rows,不是 display presentation event。
GPUPlugin 虽然包含真正的 frame presentation 字段/接口:
text
frames.cpuTimestamps
frames.gpuTimestamps
frames.drawableTimestamps
frames.desiredPresentTimestamps
frames.desiredPresentDelays
frames.presentFlags
frames.drawableIOSurfaceIDs
_agxps_trace_frame_set_presentation_info但这些符号与以下 Stop/import 路径同时出现:
text
_loadKTrace
containerFromKtraceFile:
initWithkTraceFile:
_importTrace:itinerary:
executeStopOnItinerary:
recordingStopped:
com.apple.ktrace
AGXTraceConfigurationGPUPlugin 还直接链接 ktraceDT.framework。当前没有发现一个独立实时 selector 能把 frame_set_presentation_info 作为低带宽 DTX callback 输出;现有实时 remote service surface 是 GPU counters,而 frame presentation 容器属于 trace/import 模型。
阶段判定:
text
GPU-COUNTER-SERVICE-AS-FRAMETIME-SOURCE:
NEGATIVE
GPU-PRESENTATION-INFO-EXISTS:
CONFIRMED IN XCODE TRACE MODEL (E1)
GPU-PRESENTATION-INFO-AS-INDEPENDENT-REALTIME-DTX:
NOT FOUND经验总结:
- “GPU trace 中存在 presentation timestamp”不等于“GPU counter DVT service 实时返回 presentation timestamp”;Counter 与 Trace/Importer 必须分开理解;
- 当前
com.apple.instruments.server.services.gpu的已知实时合同仍是 counters,不能通过猜 profile 把它宣称成 FrameTime source; - frame presentation info 对 Ground Truth 很有价值,但目前证据指向 KTrace/AGX trace 的 Stop/import 路径,与 Hitches 的结论一致;
- 未出现新的 remote selector/实时 payload 证据前,不做 GPU profile/selector 穷举真机测试。
8.10 Stage 6C · QuickTime/Valeria/CoreMediaIO 屏幕流候选审计(2026-09-17)
目的:判断零侵入 USB 屏幕视频流的 sample PTS 是否能直接替代 Display FrameTime。
本机探针(E3):通过 CoreMediaIO 设置 kCMIOHardwarePropertyAllowScreenCaptureDevices=1 成功,但当前 AVCapture 枚举只看到 Mac 内建摄像头,没有暴露连接 iPhone 的屏幕 capture device,因此本机暂时无法做 sample PTS 真机校准。
公开协议/实现证据(E1):QuickTime/Valeria 类 USB mirroring 路线公开实现描述为 HEVC 屏幕视频流,典型能力上限约 60 FPS。即使能拿到媒体 sample PTS,它描述的也是编码/传输视频帧时间线,尚无证据等价于 Core Animation presentation-time / Built-In Display frame truth;对 120Hz/ProMotion 动态刷新率尤其不足。
阶段判定:
text
USB-SCREEN-VIDEO-AS-CANONICAL-DISPLAY-FRAMETIME:
NEGATIVE
USB-SCREEN-VIDEO-AS-VISUAL-AUXILIARY:
KEEP AS NON-FRAMETIME TOOL经验总结:
- 不能因为视频帧有 PTS 就把它当作屏幕真实新画面 presentation timestamp;必须区分 compositor/display truth 与 encoder delivery time;
- 60 FPS 上限无法完整表达 80/120Hz ProMotion FrameTime;
- 该路线可以继续服务截图/视频/人工视觉校验,但不得进入 Perfowl canonical FrameTime。
Stage 6 总结:实时零侵入候选进一步收敛。graphics.coreanimation 只有 1Hz FPS 汇总,GPU presentation 依赖 trace/import,QuickTime/Valeria 不满足 Display FrameTime/120Hz 真值,CoreProfile deferred 仍然重流量。当前唯一保留真实 Display FrameTime 且能够避免长期 raw KDebug 经 DTX 洪流的架构方向,是 Animation Hitches / device-side KTrace 的 deferred recording + Stop 后 Frame Lifetime 解析。下一阶段必须把这条路线从“静态/控制面可行”推进到“端到端取回并解析 presentation-time 的 POC”。
8.11 Stage 7A · Animation Hitches 全模板真机体积基线与最小 Frame Lifetimes Instrument 可行性(2026-09-17)
目的:先建立 Apple 官方 Animation Hitches Ground Truth 的真实录制成本,再验证是否能脱离整套模板,仅使用 Frame Lifetimes Instrument 收窄输入集合,为后续真机低体积 POC 建立单变量方案。
A. 20 秒真机 Animation Hitches 全模板基线(E4)
当前 xctrace 曾短暂识别真机 00008140-0019786C3C82801C / iOS 18.2.1,对目标进程 netdisk_iPhone 执行:
text
xctrace record
--template "Animation Hitches"
--attach netdisk_iPhone
--time-limit 20s录制本身达到 20 秒时间上限,但 xctrace 在 120 秒执行预算内没有完成最终 trace 封装;落盘证据为:
text
.trace 总量 ≈ 3.3 GiB
trace-data.atrc ≈ 3.3 GiB
临时 instruments*.ktrace ≈ 3.3 GiB
xctrace finalize > 120s / 未完成
不完整 trace export --toc Document Missing Template Error判定:
text
ANIMATION-HITCHES-FULL-TEMPLATE-AS-PRODUCTION-FRAMETIME:
NEGATIVE — FAR TOO HEAVY
ANIMATION-HITCHES-FULL-TEMPLATE-AS-GROUND-TRUTH:
KEEP / SHORT CONTROLLED RUNS ONLY该结果说明“把整套 Animation Hitches 直接嵌入 Perfowl”不可接受;但这并不能证明 Frame Lifetimes 单 Instrument 同样重,因为完整模板同时携带 CPU、render、GPU、display、hitch 等多组输入。
B. Frame Lifetimes 单 Instrument 能否独立运行(E3 Host Smoke)
xctrace list instruments 明确列出独立:
text
Frame Lifetimes使用 Blank template,仅添加该 Instrument:
text
xctrace record
--instrument "Frame Lifetimes"
--all-processes
--time-limit 1sHost smoke 正常完成并可导出,结果:
text
trace 总量 ≈ 9.2 MiB
其中 form.template ≈ 8.0 MiB(固定模板资产)
record/finalize PASS
export --toc PASSTOC 证明最小 Frame Lifetimes Instrument 的输入被明显收敛为:
text
coreanimation-lifetime-interval
device-display-info
time-info
tick
display-surface-swap
kdebug codes:
0x07,0x00 + 0x1f,0x05
0x31,0xd0
0x85,0x20 + 0x31,0x80
0x31,0xd1
0x31,0xca这与前面从 Apple modeler 静态审计得到的 Frame Lifetime 依赖关系一致,说明 Frame Lifetimes 可以脱离完整 Animation Hitches 模板独立配置,且不会自动引入所有 Hitches 分析轨道。
阶段经验:
- 不能用完整
Animation Hitches的 3.3 GiB/20s 直接否定Frame Lifetimes最小录制;必须区分 Template 与单 Instrument; xctrace --instrument "Frame Lifetimes"会自动生成 Blank template + 必需 table/provider 输入,是目前最值得做真机体积验证的 Apple 官方最小配置;- Host 1 秒 9.2 MiB 主要包含约 8 MiB 固定
form.template,不能直接换算真机采集带宽;真正门禁仍是真机trace-data/ktrace增量; - 最小 Instrument 仍需要多个 KDebug code set,因此它是否比 CoreProfile 低流量不能凭配置数量判断,必须做 2~10 秒真机 byte census;
- 当前
xctrace对物理设备状态会在 Online/Offline 间漂移,2 秒真机复测曾因No devices found matching未启动;这属于环境门禁,不计为候选失败。
Stage 7A 状态:
text
FULL-ANIMATION-HITCHES:
NEGATIVE FOR PRODUCTION
STANDALONE-FRAME-LIFETIMES-INSTRUMENT:
CONFIGURATION PATH CONFIRMED
TRUE-DEVICE BANDWIDTH:
PENDING DEVICE ONLINE下一步:真机重新 Online 后,只执行 Blank + Frame Lifetimes 2 秒短录制,记录 trace-data/ktrace 实际体积、finalize 时间,并导出 coreanimation-lifetime-interval / display-surface-swap;若体积相对完整 Hitches 数量级下降且 presentation 时间可导出,再进入 10~20 秒验证。与此同时继续审计 Xcode KTrace provider contract,避免依赖设备状态等待。
8.12 Stage 7B · 设备可达性门禁复核(2026-09-17)
目的:在执行 standalone Frame Lifetimes 真机 2 秒 byte census 前,区分“设备物理/协议在线”与“Xcode Instruments 可录制”两个独立条件,避免把环境门禁误判成数据源失败。
本轮设备 小红茂 / iOS 17.0 / UDID 00008120-000A038A0232201E 的观测为:
text
pymobiledevice3 usbmux list:
PASS
ConnectionType = USB
HostAttached = true
TrustedHostAttached = true
xcrun devicectl list devices:
PASS
CoreDevice = D3CB1D28-5CD8-55C0-B858-506868E0C0A0
state = available (paired)
xcrun xctrace list devices:
NOT LISTED AS ONLINE
xctrace --device <UDID>:
No devices found matching
xctrace --device <CoreDevice UUID>:
No devices found matching
xctrace --device 小红茂:
No devices found matching因此这轮没有真正启动 Frame Lifetimes recording,也没有产生可用于吞吐判断的 trace 数据。
阶段判定:
text
PHYSICAL/USBMUX REACHABILITY:
PASS
COREDEVICE PAIRING/AVAILABILITY:
PASS
XCTRACE-INSTRUMENTS RECORDABILITY:
FAIL / ENVIRONMENT GATE
FRAME-LIFETIMES-CANDIDATE VERDICT:
UNCHANGED / NOT TESTED经验总结:
usbmux/lockdown在线、devicectl available (paired)与xctrace可录制是三个不同层级,不能互相替代;- 对 FrameTime 替代源实验,只有
xctrace list devices或实际xctrace record成功进入 recording 才算通过 Apple Instruments 设备门禁; No devices found matching发生在 recording 创建之前,不得计入候选数据源失败、带宽失败或兼容性失败;- 后续真机 byte census 必须保持实验变量不变:仍然只使用
Blank + Frame Lifetimes,不能因为设备发现问题切回完整Animation Hitches; - 下一次应优先使用已经被
xctrace明确列为 Online 的物理设备;设备进入 Online 后直接重跑同一 2 秒命令,避免引入新的 Instrument/配置变量。
下一门禁:等待/选择一台 xctrace Online 的真机,执行 standalone Frame Lifetimes 2 秒录制;只有录制真正完成后才评估 trace-data/ktrace 增量、finalize 时间和 presentation-time / display-surface-swap 可导出性。
8.13 Stage 7C · 替代源 POC 重启与静态协议收敛(2026-09-17)
目的:在不重复已证伪 CoreProfile 路线的前提下,重新确认 FrameTime 替代源候选、验证 Apple Frame Lifetimes/Display 真值语义,并把设备不可录制与候选失败严格分离。
本阶段先复核开源实现与当前 Xcode:
py-ios-device 2.4.25的高级display/Jank 实现最终仍调用com.apple.instruments.server.services.coreprofilesessiontap,并从0x31800318Presented Frame 时间戳计算逐帧间隔。因此它不是新的数据源,继续沿用会重新进入当前已证伪的 CoreProfile heavy-stream 路线;- 当前 Xcode
DVTInstrumentsFoundation明确包含独立服务名com.apple.instruments.server.services.hitches.recording,同时存在com.apple.instruments.server.services.graphics.coreanimation(.immediate/.deferred); - Apple Animation Hitches 的 Built-In Display 轨道语义明确是“实际出现在显示器上的帧 + 期间 VSYNC”,Frame Lifetimes 描述帧从事件到显示的完整生命周期,因此它们满足 canonical Display FrameTime 的语义要求;
hitches.recording在二进制字符串布局中靠近通用DTTapMessageHandler/DTTapRecordingInfo_Timestamp,与coreprofilesessiontap的 KPerf config surface 不同,值得继续逆向其 recording contract,但“服务名不同”本身不能证明底层完全不使用 KTrace/KPerf;- 本机
xctrace list devices当前没有任何物理设备处于 Online,之前用于 Perfowl 的设备均出现在Devices Offline,因此本轮不能诚实执行新的真机 Frame Lifetimes byte census。
阶段判定:
text
PY-IOS-DEVICE-DISPLAY-AS-ALTERNATIVE-SOURCE:
NEGATIVE / SAME COREPROFILE PATH
APPLE-FRAME-LIFETIMES-DISPLAY-SEMANTICS:
CONFIRMED AS GROUND-TRUTH CANDIDATE
HITCHES.RECORDING-INDEPENDENT-SERVICE-SURFACE:
CONFIRMED (STATIC)
TRUE-DEVICE STANDALONE FRAME-LIFETIMES POC:
BLOCKED BY XCTRACE DEVICE-OFFLINE GATE
CANDIDATE VERDICT:
UNCHANGED / KEEP P0阶段经验总结:
- 开源项目宣称“advanced FPS / display / jank”不能直接视为新源,必须追到底层 channel/service;
py-ios-device display已证明只是 CoreProfile 的另一层算法包装; - Apple
Built-In Display + VSYNC是当前零侵入 Display FrameTime 的最佳真值语义,后续候选必须能对齐这一时间线; hitches.recording与Frame Lifetimes候选目前最值得继续,但必须通过真机 byte census 证明其传输/落盘成本,而不能根据 service 名称推断低开销;- 设备处于
xctrace Devices Offline时不得把xctrace record未启动算作候选失败; - 后续继续静态逆向 selector/config/schema;一旦出现
xctraceOnline 真机,优先重跑既定的Blank + Frame Lifetimes + 2s单变量实验,不切回完整 Animation Hitches。
下一门禁:优先定位 hitches.recording / Frame Lifetimes 的 selector、config 与 payload schema;若能直接通过 Perfowl 现有 DVT provider 建立该独立 channel,则先做 2 秒 direct-DVT POC。否则等待任一物理设备在 xctrace 中 Online 后执行 standalone Frame Lifetimes 2 秒录制并导出 coreanimation-lifetime-interval / display-surface-swap。
8.14 Stage 7D · Frame Lifetime 原始事件合同与 hitches.recording 真机握手探针(2026-09-17)
目的:确认 Apple Frame Lifetimes 在 iOS 上真正依赖的原始事件语义,并绕过 xctrace 设备发现,通过现有 RSD/DVT 连接直接验证 hitches.recording 是否为真实设备服务、通用 Tap 协议是否适用。
A. Xcode Frame Lifetime Modeler 原始合同(E1)
当前 Xcode 的 coreanimation-frame-lifetime-modeler.dtac 明确给出 iOS 原始事件:
text
CoreAnimation class/subclass:
0x31 / 0xCA
frame-info event:
subclass = 0xCA
code = 0x0B
function = 0
arg1 = swapID
arg2 = earliest-time
arg3 = presentation-time
arg4 = requested-timeModeler 随后使用设备 timebase 把 earliest-time / presentation-time / requested-time 转换为 trace-relative 时间,并产出 coreanimation-lifetime-interval。这证明 Apple Frame Lifetimes 在 iOS 上存在单事件直接携带 presentation timestamp 的合同,不需要像当前 Perfowl 0x31800318 Presented Frame 路线那样仅靠相邻事件时间差推导。
同一 modeler 的输入绑定为:
text
kdebug 0x31/0xCA
kdebug 0x31/0xD0
kdebug 0x31/0xD1
display-surface-swap
time-info
tickHitches schema 同时确认存在:
text
expected-presentation-time
presentation-time
swap-id
frame-seed
refresh-interval因此 presentation-time 是当前替代源 POC 的 canonical 目标字段。
B. 绕过 xctrace Offline 门禁的真机 DVT 探针(E4)
虽然 xctrace list devices 当前仍把物理设备列为 Offline,但:
text
pymobiledevice3 usbmux:
小红茂 / iOS 17.0 / 00008120-000A038A0232201E
PASS
existing tunneld RSD:
fdef:2c2a:3660::1:52953
PASS使用 Perfowl 打包的 pymobiledevice3 11.3.1 x86_64 runtime,通过:
text
developer dvt shell --rsd fdef:2c2a:3660::1 52953成功连接 DvtProvider / DTXConnection,随后直接创建:
text
com.apple.instruments.server.services.hitches.recording结果:
text
DTX channel open:
PASS
returned service:
DTXDynamicService这把 hitches.recording 从“Xcode 静态字符串”提升为“iOS 17 真机 Instruments Server 实际可打开的 DVT service”。
C. 通用 Tap 协议与 Wire Config(E1/E3)
pymobiledevice3 11.3.1 的 TapService 合同确认:
text
setConfig:
start
<data / plist callbacks>
stop同时通过 macOS 动态加载 DVTInstrumentsFoundation 并实例化 Apple 自己的 DTTapConfig,确认:
text
Default DTTapConfig plist:
{"bm": 0}
spoolToDiskWhenPossible = true:
"s2d" = 1
bufferMode = 1:
"bm" = 1D. 2 秒最小真机 Start 探针结果(E4)
首个受控 config:
text
{"bm": 1, "s2d": 1}执行:
text
open hitches.recording as TapService
setConfig:
start
wait for first ack/data
2s
stop实际结果:
text
channel open PASS
setConfig send PASS
start send PASS
first ack/data NONE
queue after 2s 0
DTX control notification _channelCanceled: -1
pymobiledevice3 warning cancellation for unknown channel -1
stop send PASS
post-stop payload NONE阶段判定:
text
HITCHES.RECORDING TRUE-DEVICE SERVICE AVAILABILITY:
PASS
GENERIC TAP SELECTOR CONTRACT:
CONFIRMED
APPLE FRAME-LIFETIME PRESENTATION-TIME SEMANTICS:
CONFIRMED
MINIMAL {bm:1,s2d:1} CONFIG HANDSHAKE:
FAIL / INCOMPLETE CONFIG
HITCHES.RECORDING SOURCE VERDICT:
NOT NEGATIVE — CONFIG CONTRACT NOT YET RECONSTRUCTED阶段经验总结:
channel open、setConfig/start可发送、以及真正进入 recording/收到 ack 是三个独立门禁;不能把 channel 可创建直接等同于配置正确;_channelCanceled:-1出现在start后且没有任何业务 payload,更符合“服务端拒绝/缺少必要 recording config”的形态;目前没有证据证明数据源本身不可用;{bm:1,s2d:1}只是由 AppleDTTapConfigwire key 推导出的最小实验,已经失败,后续禁止原样重复;- 下一步不能继续盲猜
bm/s2d组合,必须从 Xcode trace template / Instruments 对象图提取实际DTTapConfig或对应 recording 参数; - iOS
0x31/0xCA/0x0B的arg3=presentation-time是本阶段最重要的新协议事实;后续任何低带宽候选只要能稳定获得该事件或其模型化结果,就有机会替代当前 CoreProfile Presented Frame 路线。
下一门禁:从 Animation Hitches.tracetemplate / Instruments 私有对象中恢复真实 hitches.recording config;优先验证 Apple 默认/模板实际配置,而不是枚举随机字段。只有出现真实 ack/recording payload 后才进入 byte census 与 presentation-time 解码。
8.15 Stage 7E · Xcode Deferred Mode 复原与 bm=2 单变量真机复测(2026-09-17)
目的:根据 Xcode 模板实际 recording option,而不是猜测,验证 Stage 7D 的失败是否仅由错误 bufferMode 引起。
从 Animation Hitches.tracetemplate 的 NSKeyedArchive 对象图恢复出 XRRecordingOptions:
text
recordingMode = 2
supportsDeferredMode = true
supportsImmediateMode = false
supportsWindowedMode = false
windowLimit = 0结合 Apple DTTapConfig.plist 已确认 bufferMode -> bm,因此本轮严格单变量:
text
Stage 7D: {bm:1, s2d:1}
Stage 7E: {bm:2, s2d:1}
^ only change真机执行仍使用同一 iOS 17 设备、同一 RSD、同一 hitches.recording channel 与标准 Tap setConfig → start → stop 路径。
实际结果:
text
channel open PASS
setConfig bm=2,s2d=1 PASS (send)
start PASS (send)
first ack/data NONE
queue after 2s 0
DTX control notification _channelCanceled: -1
stop PASS (send)
post-stop payload NONE阶段判定:
text
XCODE HITCHES RECORDING MODE:
DEFERRED / MODE 2 CONFIRMED
BM=2 AS SOLE FIX FOR HANDSHAKE:
NEGATIVE
HITCHES.RECORDING SOURCE VERDICT:
STILL OPEN — CONTROL CONTRACT / REQUIRED CONFIG INCOMPLETE阶段经验总结:
- Xcode 模板明确要求 Hitches Deferred mode,
recordingMode=2是证据支持的值,不再把 mode 枚举作为未知变量; bm=2仍在start后产生同样_channelCanceled:-1,因此 Stage 7D 失败不是单纯因为bm=1;- 后续禁止继续枚举
bm=0/1/2/...,也禁止随机叠加 DTTapConfig 字段; - 下一步必须确认
hitches.recording的服务端注册类/selector 以及 Xcode 是否通过额外 provider/file-recording control surface 启动它; hitches.recordingchannel 在真机可打开仍是有效证据,但“标准 Tap selector 与正确控制合同相同”尚未证明。
下一门禁:逆向 hitches.recording 服务注册与消息处理类,确认实际 selector;只有 selector/control contract 确认后再执行下一次真机 start 探针。
8.16 Stage 7F · hitches.recording Delegate 归属、ActivityTrace 默认合同与握手收口(2026-09-17)
目的:确认 hitches.recording 的真实服务端 delegate、是否属于 KTrace 主源,并验证 Stage 7E 是否只是缺少 DTActivityTraceTapConfig 默认字段。
A. 服务端注册归属(E1)
通过 DVTInstrumentsFoundation 的 literal xref 与 DTTapService registerCapabilities:forDelegateClass:forConnection: 调用链确认:
text
com.apple.instruments.server.services.hitches.recording
→ DTActivityTraceTapServiceDelegate
→ DTTapService不是 DTKTraceTapServiceDelegate。
DTActivityTraceTapServiceDelegate 的实现进一步确认:
text
createConfigWithPlist:
→ [[DTActivityTraceTapConfig alloc] initWithPlist:]
validateConfig:
→ always true因此 hitches.recording 更接近 ActivityTrace / os_log / signpost 辅助 recording,而不是 Frame Lifetimes 的 KDebug/KTrace presentation 主源。
B. ActivityTrace 子类真实默认 wire plist(E3)
通过 Apple DTActivityTraceTapConfig 动态实例化:
text
DEFAULT:
{
bm = 0;
predicate = "";
}并确认常见字段 wire key:
text
onlySignposts
signpostsAndLogs
trackExpiredPIDs
includeWallTime
includeSenderInfo
includeEmitLocation
s2d
bm
predicateStage 7D/7E 的配置确实遗漏了子类默认的 predicate=""。
C. 单变量补全 predicate 的真机复测(E4)
保持:
text
bm=2
s2d=1只新增:
text
predicate=""结果:
text
channel open PASS
setConfig PASS (send)
start PASS (send)
first ack/data NONE
queue 0
DTX control notification _channelCanceled:-1与前两轮完全一致。
D. KTrace capability 边界(E1)
动态调用 Apple DTDeviceKTraceSupport.capabilities 得到当前 Xcode 真实 KTrace Tap 能力:
text
coreprofilesessiontap
coreprofilesessiontap.config
coreprofilesessiontap.deferred
coreprofilesessiontap.immediate
coreprofilesessiontap.multipleTimeTriggers
coreprofilesessiontap.pmc
coreprofilesessiontap.pmi
coreprofilesessiontap.windowed没有第二个独立的通用 KTrace DVT channel。
阶段判定:
text
HITCHES.RECORDING AS FRAME-LIFETIME KTRACE PRIMARY SOURCE:
NEGATIVE
HITCHES.RECORDING DIRECT STANDARD-TAP HANDSHAKE:
NEGATIVE ON CURRENT CONTRACT
ACTIVITYTRACE MISSING-PREDICATE HYPOTHESIS:
NEGATIVE
INDEPENDENT GENERIC KTRACE DVT SOURCE OUTSIDE COREPROFILE:
NOT EXPOSED BY DTDeviceKTraceSupport阶段经验总结:
hitches.recording名称容易误导;真实 delegate 是 ActivityTrace,不是 Frame Lifetimes 的 KTrace 主输入;- Hitches 产品由多源拼装,不能把某个叫
hitches.*的服务直接当成逐帧 Display source; DTDeviceKTraceSupport的公开 capability 已收敛到 CoreProfile 家族,继续猜不存在的ktracetapchannel 没有价值;hitches.recording连续三次基于证据补全配置仍在 start 后被取消,本轮停止继续枚举其配置;- 下一条值得验证的低带宽实时方向只剩:通用 ActivityTrace 中是否存在可启用的 CoreAnimation frame-lifetime signpost。若没有,则 Apple Frame Lifetimes 的真实 presentation-time 在当前 DVT 暴露面上仍只能通过 KDebug/KTrace/CoreProfile 或 Stop 后 trace modeler 获得。
下一门禁:使用已知可工作的 activitytracetap 通用服务,仅采 signpost,检查是否存在 SignpostFrameLifetimeInterval/CoreAnimation instrumentation 所需事件;必须先做 2~5 秒低带宽 census,再决定是否进入解码器 POC。
8.17 Stage 7G · ActivityTrace / CoreAnimation FrameLifetime 低带宽实时替代源 POC(2026-09-17)
目的:验证通用 activitytracetap 的 signpost 流中是否直接存在可用于 canonical FrameTime 的 CoreAnimation FrameLifetime 事件,并同时验收逐帧真实性、时间戳链一致性和 DTX 带宽。
A. 真机低带宽配置(E4)
设备:
text
小红茂 / iOS 17.0
RSD: fdef:2c2a:3660::1:52953数据源:
text
com.apple.instruments.server.services.activitytracetap使用 pymobiledevice3 11.3.1 ActivityTraceTap 已验证的配置结构,并收窄到 only-signposts:
text
bm = 0
onlySignposts = 1
signpostsAndLogs = 0
predicate = ""
targetPID = -3 # all processes; compositor event 属于系统进程
trackExpiredPIDs = 1
trackPidToExecNameMapping = true
ur = 500本轮没有启动 CoreProfileSessionTap。
B. 真实逐帧事件发现(E4)
真机 ActivityTrace stream 稳定出现:
text
subsystem = com.apple.coreanimation
category = IOMFBServer
name = FrameLifetime
eventType = Event单条事件的 format contract:
text
ID is swap ID.
%{signpost.description:begin_time}llu
frame_seed=%#x
refresh_interval=%llu
buffer_count=%hhu
%{signpost.description:end_time}llu
prev_frame=%llu
skip_request=%hhu
display_id=%u
surface_id=%#x实际 typed payload 中可稳定解析:
text
swapID / begin_time
frame_seed
refresh_interval
buffer_count
end_time
prev_frame
skip_request
display_id
surface_id这与 Xcode HitchesTap 的 SignpostFrameLifetimeInterval / Frame Lifetimes 模型语义吻合,并且 refresh_interval、display/surface 信息直接来自 CoreAnimation compositor 路径。
C. 5 秒 Byte Census(E4)
同一配置采 5 秒:
text
elapsed 5.001 s
DTX messages 20
data 9
plist 11
raw data bytes 193,136 B
approx total payload 193,830 B
throughput 0.03696 MiB/s
all decoded signpost rows 481
FrameLifetime rows 90
observed frame rows/sec ~18.0
bytes / FrameLifetime ~2,154 B当前设备画面不是满 60 FPS 动画,因此 FrameLifetime 只在真实新帧出现时产生;不能用 rows/sec 直接等价刷新率。
与 C1 CoreProfile baseline 对比:
text
CoreProfile exact Presented Frame:
18.76 MiB/s
ActivityTrace FrameLifetime:
0.03696 MiB/s
带宽降低:
≈ 507x
≈ 99.80%即使考虑未计入的 DTX framing overhead,仍与 CoreProfile 相差两个以上数量级。
D. FrameTime 时间链验证(E4)
5 秒样本:
text
refresh_interval:
min/p50/p95/max ≈ 16.666 ms
FrameLifetime begin→end:
p50 ≈ 16.550 ms
successive end_time interval:
p50 ≈ 49.926 ms随后独立 3 秒一致性复测:
text
FrameLifetime rows 60
adjacent pairs 59
prev_frame == previous end_time 59
mismatch 0
internal display IDs [1]
surface IDs observed 2两种计算方式完全一致:
text
self-contained:
(end_time - prev_frame) * mach_timebase
chain-derived:
end_time[n] - end_time[n-1]
median = 49.926 ms
min = 49.909 ms
max = 49.945 ms设备 timebase 本轮为:
text
numer = 125
denom = 3因此候选 canonical 公式为:
text
FrameTimeMs =
(end_time - prev_frame)
* numer / denom
/ 1_000_000但产品实现不得硬编码 125/3,必须继续从设备 machTimeInfo / 当前 DVT timebase 获取。
E. Attribution / source scope
FrameLifetime 事件由系统 compositor 进程发出,不是目标 App PID 事件。因此该 source 应明确标记:
text
scope = foreground-device / display产品归因规则应与现有 Graphics FPS 一致:
text
targetForeground == true
→ 允许发布 FrameTime
targetForeground == false
→ FrameTime = nil / not-foreground不能声称事件本身是 process-scoped。
阶段判定:
text
ACTIVITYTRACE COREANIMATION FRAMELIFETIME SOURCE:
PASS (E4)
REAL PER-FRAME TIMESTAMP:
PASS
REALTIME DELIVERY:
PASS
NO COREPROFILE REQUIRED:
PASS
LOW-DTX-THROUGHPUT:
PASS (~0.037 MiB/s)
PREV_FRAME CHAIN CONSISTENCY:
PASS (59/59, 0 mismatch)
FRAMETIME ALTERNATIVE SOURCE POC:
SUCCESS阶段经验总结:
- FrameTime 替代源已经找到:不是
hitches.recording,也不是另一个 KTrace channel,而是通用activitytracetap中 CoreAnimation 自己发出的IOMFBServer / FrameLifetimesignpost; - 该事件是 Apple Hitches/Frame Lifetime 体系的一部分,直接携带
begin/end/prev_frame/refresh_interval/display/surface,比1000/FPS或视频 PTS 更接近 Display truth; - 真机 POC 证明不需要持续 CoreProfile heavy stream,DTX payload 从 18.76 MiB/s 降至约 0.037 MiB/s;
prev_frame在连续 59 个 pair 中 100% 精确等于上一条end_time,因此可用事件内end_time-prev_frame直接计算 FrameTime,同时保留跨 sourceEpoch/rebind 首帧 warmup 门禁;- 首条 FrameLifetime 不得无条件使用其
prev_frame,因为它可能指向采集开始前的帧;正式实现应在每个sourceEpoch/reconnectGeneration/foreground transition后先建立一帧锚点,再发布后续 FrameTime; skip_request != 0、display 切换、锁屏/后台、多显示器、ProMotion 80/120Hz、iOS 版本差异仍需验证;当前 POC 只证明 iOS 17 当前设备可用;- all-signposts 5 秒约 194 KB 已足够低,初版不需要为了节省几十 KB/s 冒险研究私有 subsystem/category filter;后续可作为二级优化;
- Stage 6 “只剩 Stop 后 Hitches/KTrace” 的阶段结论被本阶段正式 supersede:现在已经有零侵入、实时、低带宽的真机 FrameTime 候选。
下一门禁(产品化前):实现独立实验 Adapter ActivityTraceFrameLifetime,不修改正式 FrameTime 默认源;至少完成 iOS 17 当前设备 5~10 分钟长测、前后台切换、60/120Hz/动态刷新率、与现有 CoreProfile 短窗同场误差对照。通过后再切换生产 FrameTime source,并删除长期 CoreProfile continuation。