外观
pymobiledevice3 + py-ios-device 双 Provider 基座决策草案
已确认的范围
用户已明确:go-ios 没有当前产品不可替代的能力,后续不进入 Perfowl 的候选架构、开发计划和发布包。Perfowl 底层收敛为两类 Provider:
- pymobiledevice3:连接基座、Developer service 路由、设备与应用目录、基础性能采集;
- py-ios-device 深度 Provider:仅补充 pymobiledevice3 当前无法稳定提供或 Perfowl 尚未完成解析的深度原始数据;
- Perfowl Metric Engine:唯一负责指标公式、单位、scope、质量与版本;
- xctrace:按需校准与诊断,不是实时主链。
本页是 E1 架构草案。产品代码需要用户明确批准后再修改。
决策:不把整个 py-ios-device 变成产品基座
不接入它的设备发现、应用安装、证书、位置、XCTest、网络模拟、CLI 输出和最终指标计算。只允许一个白名单 Deep Collector Provider 调用以下原始能力:
| 深度能力 | 只承接什么 | 不承接什么 |
|---|---|---|
| CoreProfile | 原始 KDebug buffer、事件码、device mach timestamp、丢包与解析错误 | py-ios-device 输出的 FPS / Jank / BigJank / Stutter |
| Process NetworkStatistics | 指定 PID 的原始 attributes / counters | 未校准的速率、范围推断和缺值 0 |
| Energy debug gauge | PID、原始字段、采样请求与回包 | 未确认单位的 Energy 分值 |
| GPU Counter | 设备返回的 counter schema、原始 counter stream、时间基 | 固定机型 counter 名称和预计算百分比 |
| App Launch Lifecycle | 原始 lifecycle / CoreProfile 事件、时间基和线程 / 进程事实 | 已格式化的阶段耗时和总启动时间 |
基础 CPU、内存、线程、调度、磁盘、Graphics、设备级 Network 和 Screenshot 继续由 pymobiledevice3 Provider 负责,不因启用深度 Provider 自动切源。
“不整体接入”在工程上的准确含义
架构上不允许 Mac App、Session Schema 和 UI 依赖 py-ios-device 的类、CLI 或数据格式。所有调用都封装在独立的 PyIOSDeviceDeepCollectorProvider 后面。
但验证 Spike 阶段不建议从 GPL 仓库直接复制若干 .py 文件:其 InstrumentServer、DTX 编解码、RemoteLockdown、KPerf、GPU decoder 和 lifecycle parser 存在内部依赖。首个实验可以固定完整 Python 包版本并在独立研究 Sidecar中只暴露白名单命令;这只是隔离的实验依赖,不代表整个库成为产品架构。
生产阶段有两个待批准选项:
- 保留固定版本的 py-ios-device 作为 Deep Sidecar 依赖,建立 SBOM / NOTICE / GPL 交付方案;
- 基于原始协议证据和自有 Fixture 建立 Perfowl 自己的深度 Adapter,不直接复制库代码。
独立进程边界不会自动消除 GPL-3.0 评估,正式分发仍受现有法务门禁约束。
连接与采集关系
text
Mac App
└── Connection Broker(pymobiledevice3)
├── iOS 15/16: USB Lockdown provider
└── iOS 17+: RSD TunnelLease
│
├── Pmd3BaseCollector
│ └── 基础 raw events
│
└── PyIOSDeviceDeepCollector(按需启动)
└── 深度 raw events
两路 raw events → Perfowl Event Envelope → Metric Engine → Session / UI / ReportConnection Broker 交付的是 TunnelLease,不是把 pymobiledevice3 的 Python对象跨进程传递。租约包含匿名设备键、iOS 版本、route、RSD host / port、可选 local proxy、Developer image 状态和 lease generation。
- iOS 15/16:Deep Collector 可走已配对 USB Lockdown;
- iOS 17.4+:优先使用 macOS native / 可共享 RSD;
- iOS 17.0–17.3.1:使用 pymobiledevice3 persistent tunneld;
- pymobiledevice3 的临时进程内 userspace tunnel 不应交给另一个进程假装可共享。
DTX 所有权与并发门禁
py-ios-device 不能读取 pymobiledevice3 已打开的同一个 DTX socket。每个 socket 必须只有一个 reader。
第一阶段采用最保守方式:
- 普通 Session:只启动 Pmd3BaseCollector;
- Deep Spike:Deep Collector 独占深度 DTX Session,只验证 raw 数据;
- 不在第一阶段让两个库同时持续读取同一设备的多个 Instruments session。
如果产品最终要求基础曲线和深度指标同时实时展示,必须先做 DTX-CONCURRENCY-1:验证同一设备两个独立 DTX connection 的建链成功率、事件丢失、串流互扰、停止顺序和 30 分钟稳定性。通过后才能并行;未通过则由一个 Deep Collector 在深度模式下统一拥有所需 DVT channels。
统一原始事件契约
Deep Provider 不输出最终指标,只输出类似以下事实:
text
eventType
service / channel / selector
provider / providerVersion / providerCommit
deviceMachTimestamp / machNumer / machDenom
hostReceiveMonotonicNs / wallTimestamp
targetPID / targetGeneration / identityStatus
rawPayload / rawPayloadHash
decodeStatus / droppedEventCount / reconnectGeneration之后由 Perfowl 完成:
- mach time → 纳秒转换;
- 相邻帧 FrameTime;
- FPS 窗口;
- SmallJank / Jank / BigJank / Stutter;
- counter 差分与 bytes-per-second;
- Energy / GPU 字段单位映射;
- missing / warmup / reset / candidate / valid 质量语义。
每个公式仍需在进入 Registry、UI 和报告前单独入档。
推荐实施批次
| 批次 | 内容 | 结果边界 |
|---|---|---|
| D0 | 冻结 py-ios-device 版本、模块依赖、License 与白名单 | 只有 E1;不打包产品 |
| D1 | DeepCollectorProvider 协议 + fake fixture | 不连接真机,不改变默认 Collector |
| D2 | CoreProfile raw Spike | 只保存 raw,不显示 Jank |
| D3 | Process Network / Energy raw Spike | scope / unit 未校准前保持 candidate |
| D4 | GPU schema / counter raw Spike | 未知 schema fail-closed |
| D5 | Lifecycle raw Spike | 被动 / 受控启动分开 |
| D6 | DTX 双连接并发验证 | 决定基础与深度能否同 Session 并行 |
| D7 | 公式、知识页、同场校准与灰度发布 | E4 后逐指标开放 |
下一批准边界
建议只批准 D0 + D1:建立白名单和 Provider / Fixture 契约,不接真机、不集成最终指标、不改变现有 pymobiledevice3 默认链路。完成后再批准 D2 CoreProfile raw Spike。