Skip to content

知识库总览与全指标采集溯源

本文档是 Perfowl 客户端与采集基座(Collector Core)的知识库总览与架构溯源总纲。它剥离了日常研发中的琐碎调试复盘,全景复原产品从 0 到 1 的工程演进脉络、底层技术选型抉择,以及当前 64 项性能指标的真实采集服务、命令路径、数学计算模型与文档直达索引。


一、 核心设计哲学:为什么必须是“纯粹的无侵入”?

移动端性能评测行业长期存在两类技术路线:

  1. 侵入式 SDK 路线:需在 App 内集成 Probe/SDK,甚至依赖 Hook、方法混淆或重签名。这种方式不仅改变了 App 的原始二进制体积与链接行为,还会引入不可忽视的 “观察者效应(Observer Effect)” —— 探针本身抢占 CPU 与内存,导致测得的并非用户真实体验。
  2. 外部黑盒轮询路线:不集成 SDK,但为了获取高帧率曲线,采用高频截图(如 60fps 截图 OCR 差分)伪装逐帧真值,造成 USB 负载飙升、设备发热严重,且无法归因后台真实消耗。

Perfowl 的立项基石是“对齐 PerfDog 的非侵入式可信体验”:

  • 零侵入红线:不改源码、不重签、不重打包、不注入动态库、不越狱、不依赖 UIAutomation/WDA 运行底座。
  • 系统服务只读采集:全部数据均通过宿主机与 iOS 设备的 USB / Wi-Fi 建立的安全通道,接入 Apple 系统底层的开发者支持守护进程(DVT Services、Lockdown Relay、CoreProfile Kernel Trace),保证目标 App 处于 100% 原始运行环境。
  • 本地 Session 优先:采集到的每一条 Raw 事件(Sysmontap、Graphics、CoreProfile kd_buf、Diagnostics)均采用 Append-only 的不可变 JSONL 格式完整落盘,支持脱机重放与离线重算,绝不强制依赖云端网络。

二、 采集基座的技术抉择:DTX 独占内核与 Clean Adapter 模式

在开源生态中,与 iOS 底层协议交互的知名库有两个:pymobiledevice3py-ios-device。在从 0 到 1 的开发过程中,Perfowl 经历了深刻的协议调研与架构裁决:

                    ┌────────────────────────────────────────────────────────┐
                    │                   MacApp (SwiftUI)                     │
                    └───────────────────────────┬────────────────────────────┘
                                                │ JSON-RPC / Subprocess
                    ┌───────────────────────────▼────────────────────────────┐
                    │          Perfowl Collector Core (Python 3.12)          │
                    │                                                        │
                    │   ┌────────────────────────────────────────────────┐   │
                    │   │        pmd3_deep_services.py (自研适配层)        │   │
                    │   │  - ProcessNetworkStatistics (PID 网络吞吐)      │   │
                    │   │  - GPUCounterControlService (硬件 GPU Counter)  │   │
                    │   │  - CoreProfileSessionTap Multi-Config (采样触发)│   │
                    │   └───────────────────────┬────────────────────────┘   │
                    │                           │                            │
                    │   ┌───────────────────────▼────────────────────────┐   │
                    │   │   pymobiledevice3 (11.3.1 锁定版本)             │   │
                    │   │   ★ 唯一运行时内核 (Sole DTX Transport Owner)  │   │
                    │   └───────────────────────┬────────────────────────┘   │
                    └───────────────────────────┼────────────────────────────┘
                                                │ USBMux / RSD Tunnel
                    ┌───────────────────────────▼────────────────────────────┐
                    │          iOS Device (DVT / Lockdown Services)          │
                    └────────────────────────────────────────────────────────┘

1. 为什么不能同时混用两个开源库?

  • DTX 连接互斥:iOS 设备的 Instruments 守护进程(com.apple.instruments.server.services)基于独占式的 DTX 消息分发体系。若在同一个测试会话中,让两个不同的 Python 进程或两个不同的传输层并发抢占 DVT 端口,会导致会话抢占中断、DTX 序列号错乱,甚至触发 iOS 端守护进程崩溃重启。
  • 时钟源分裂:不同库在事件分发、线程等待和时间戳打标上存在毫秒级甚至秒级偏差,无法满足 FPS、CPU 与网络指标在同一微秒级时间轴上的强对齐。

2. 架构最终确立:独占内核 + Clean Adapter 模式

Perfowl 确立了铁律规范(定义于 pins.pyadapters.py):

  • pymobiledevice3 (v11.3.1):被裁定为唯一的物理通信与 DTX 传输内核(Kernel)。负责处理 USBMuxd 握手、iOS 17+ RemoteXPC / RSD 隧道解包、通道复用与基础 DVT 服务生命周期。
  • py-ios-device (v2.4.26):被定性为纯研究参考源(Research Reference Only)。它在逆向研究 Apple 内部未公开 selector(如 NetworkStatisticsGPU Counter 的配置参数)时提供了巨大价值,但它在运行时绝不被导入、绝不打包进分发产物、绝不碰触 DTX Socket
  • Perfowl 自研 Clean Adapter 层: 在 pmd3_deep_services.py 中,Perfowl 继承 pymobiledevice3.dtx_service.DtxService,纯手工实现了对未公开 DVT 服务的封装。这样既复用了 pymobiledevice3 健壮的单一连接,又安全获得了高阶深层指标能力。

三、 64 项指标全景采集谱系与直达索引

在当前产品基座中,静态 Registry 共包含 64 项指标。下表汇总了所有指标的真实采集服务、底层命令/类方法、实现归属与计算公式,并附有对应知识库单页的直达链接:

指标大类指标名称 (点击直达)metricID作用域 (Scope)系统服务标识 (Service)底层命令 / 方法 / 协议实现层归属单位与计算简式
计算APPCpuios.process.cpu.non_normalizedprocesscom.apple.instruments.server.services.sysmontapSysmontap.create(interval=500)PMD3 Native%;Processes[target_pid].cpuUsage
计算TotalCPU (Raw)ios.device.cpu.totaldevice同上 (sysmontap)同上PMD3 Native%;SystemCPUUsage.CPU_TotalLoad
计算TotalCPU (Norm)ios.device.cpu.total.normalizeddevice同上 (sysmontap)同上Perfowl Engine%;CPU_TotalLoad / EnabledCPUs (有效核数)
计算CPU Core Usageios.device.cpu.core_usagedevice同上 (sysmontap)同上PMD3 Native%;PerCPUUsage[*].CPU_TotalLoad 数组
调度Thread Countios.process.thread.countprocess同上 (sysmontap)同上PMD3 Nativecount;Processes[target_pid].threadCount
调度CSwitchios.process.scheduler.context_switch_rateprocess同上 (sysmontap)同上Perfowl Enginecount/s;Δ(ctxSwitch) / Δt (同代差分)
调度Wakeupsios.process.scheduler.interrupt_wakeup_rateprocess同上 (sysmontap)同上Perfowl Enginecount/s;Δ(intWakeups) / Δt
内存Memory (Footprint)ios.process.memory.physical_footprintprocess同上 (sysmontap)同上PMD3 NativeBytes;Processes[target_pid].physFootprint
内存Resident Memoryios.process.memory.residentprocess同上 (sysmontap)同上PMD3 NativeBytes;Processes[target_pid].memResidentSize
内存XcodeMemoryios.process.memory.xcode_gaugeprocess同上 (sysmontap)同上Perfowl EngineBytes;映射为 physFootprint (对齐 Xcode)
内存RealMemoryios.process.memory.realprocess同上 (sysmontap)同上Perfowl EngineBytes;取 memResidentSize
内存VirtualMemoryios.process.memory.virtualprocess同上 (sysmontap)同上PMD3 NativeBytes;Processes[target_pid].memVirtualSize
内存AvailableMemoryios.device.memory.availabledevice同上 (sysmontap)同上Perfowl EngineBytes;vmFreeCount * 16384 (ARM64 16KB)
存储ReadDiskBytesios.process.disk.read_rateprocess同上 (sysmontap)同上Perfowl EngineByte/s;Δ(diskBytesRead) / Δt
存储WriteDiskBytesios.process.disk.write_rateprocess同上 (sysmontap)同上Perfowl EngineByte/s;Δ(diskBytesWritten) / Δt
网络Send (Device)ios.device.network.send_ratedevicecom.apple.instruments.server.services.networkingNetworkMonitor(provider)PMD3 NativeByte/s;ConnectionUpdateEvent.tx_bytes 平滑
网络Recv (Device)ios.device.network.receive_ratedevice同上 (networking)同上PMD3 NativeByte/s;ConnectionUpdateEvent.rx_bytes 平滑
网络Send (Process)ios.process.network.send_rateprocesscom.apple.xcode.debug-gauge-data-providers.NetworkStatisticsProcessNetworkStatistics.sampleAttributes:forPIDs:Perfowl Adapter (pmd3 DTX)Byte/s;基于 Cocoa 时钟 Δ(tx.bytes) / Δt
网络Recv (Process)ios.process.network.receive_rateprocess同上 (NetworkStatistics)同上Perfowl Adapter (pmd3 DTX)Byte/s;基于 Cocoa 时钟 Δ(rx.bytes) / Δt
渲染Foreground FPSios.foreground.fpsforegroundcom.apple.instruments.server.services.graphics.openglGraphics(provider) 轮询PMD3 Nativefps;CoreAnimationFramesPerSecond (秒级)
渲染GPU Renderios.foreground.gpu.renderer_utilizationforeground同上 (graphics.opengl)同上PMD3 Native%;Renderer Utilization %
渲染GPU Tilerios.foreground.gpu.tiler_utilizationforeground同上 (graphics.opengl)同上PMD3 Native%;Tiler Utilization %
渲染GPU Deviceios.foreground.gpu.device_utilizationforeground同上 (graphics.opengl)同上PMD3 Native%;Device Utilization %
逐帧Frame Timeios.frame.timeforegroundcom.apple.instruments.server.services.coreprofilesessiontapRAW v2 kd_buf stream (DebugID 0x31800318)Perfowl Custom Decoderms;(t_n - t_{n-1}) 精确到纳秒换算
逐帧Average FPSios.frame.fps.averageforeground同上 (coreprofilesessiontap)同上Perfowl Pipelinefps;1000 / mean(FrameTime)
逐帧1% Low FPSios.frame.one_percent_low_fpsforeground同上 (coreprofilesessiontap)同上Perfowl Pipelinefps;1000 / mean(最差 1% FrameTime)
卡顿SmallJankios.frame.small_jank_per_10minforeground同上 (coreprofilesessiontap)同上Perfowl Pipelinecount/10min;FT > 2*B 且 FT > 41.66ms
卡顿Jankios.frame.jank_per_10minforeground同上 (coreprofilesessiontap)同上Perfowl Pipelinecount/10min;FT > 2*B 且 FT > 83.33ms
卡顿BigJankios.frame.big_jank_per_10minforeground同上 (coreprofilesessiontap)同上Perfowl Pipelinecount/10min;FT > 2*B 且 FT > 125ms
卡顿Stutter %ios.frame.stutter_percentforeground同上 (coreprofilesessiontap)同上Perfowl Pipeline%;∑(FT - B) / TotalDuration * 100
逐帧Drop / Hourios.frame.drop_per_hourforeground同上 (coreprofilesessiontap)同上Perfowl Pipelinecount/h;连续帧率跌幅大于 8fps 频次折算
逐帧FT >= 100msios.frame.ftime_ge_100_percentforeground同上 (coreprofilesessiontap)同上Perfowl Pipeline%;耗时超过 100ms 帧数占比
逐帧80%~120% 稳态比ios.frame.med_range_percentforeground同上 (coreprofilesessiontap)同上Perfowl Pipeline%;落在中位数 FPS ±20% 区间帧数比例
能耗Total Energyios.process.energy.total_costprocesscom.apple.xcode.debug-gauge-data-providers.EnergyEnergyMonitor(provider, [pid])PMD3 Nativescore;energy.cost (负值哨兵过滤)
能耗CPU Energyios.process.energy.cpu_costprocess同上 (Energy)同上PMD3 Nativescore;energy.cpu.cost
能耗GPU Energyios.process.energy.gpu_costprocess同上 (Energy)同上PMD3 Nativescore;energy.gpu.cost
能耗Network Energyios.process.energy.network_costprocess同上 (Energy)同上PMD3 Nativescore;energy.networking.cost
能耗Location Energyios.process.energy.location_costprocess同上 (Energy)同上PMD3 Nativescore;energy.location.cost
能耗Display Energyios.process.energy.display_costprocess同上 (Energy)同上PMD3 Nativescore;energy.display.cost
能耗Energy Overheadios.process.energy.overheadprocess同上 (Energy)同上PMD3 Nativescore;energy.overhead
电池Battery Currentios.device.battery.currentdevicecom.apple.mobile.diagnostics_relayDiagnosticsService.get_battery() (独立通道)PMD3 LockdownmA;InstantAmperage (仅纯放电模式输出)
电池Battery Voltageios.device.battery.voltagedevice同上 (diagnostics_relay)同上PMD3 LockdownmV;Voltage (仅纯放电模式输出)
电池Battery Powerios.device.battery.powerdevice同上 (diagnostics_relay)同上Perfowl EnginemW;|InstantAmperage| * Voltage / 1000
温度Battery Temp Rawios.device.battery.temperature_rawdevice同上 (diagnostics_relay)同上PMD3 Lockdownsource-native;Temperature (原生整数)
温度Battery Temp ℃ios.device.battery.temperature_celsiusdevice同上 (diagnostics_relay)同上Perfowl Engine℃;Temperature / 100
温度ThermalStatusios.device.thermal.statusdevice同上 (diagnostics_relay)同上PMD3 Lockdownenum (0~3 级热状态);非 0~3 保持 missing
启动Observed Runningios.process.startup.observed_running_msprocessprocesscontrol + deviceinfo受控拉起 + runningProcesses 轮询PMD3 Nativems;t(首次观察到运行) - t(拉起命令)
启动Foreground Readyios.process.startup.foreground_ready_msprocess同上 (deviceinfo)进程前台状态监听PMD3 Nativems;t(前台状态就绪) - t(拉起命令)
启动App Launch Totalios.process.startup.app_launch_total_msprocesscoreprofilesessiontap (KDebug)Mach Absolute 时钟桥锚点Perfowl Custom Decoderms;t(首帧呈现) - t(Mach 启动零点)
启动9 类启动分阶段ios.process.startup.*_ms (共 9 项)process同上 (coreprofilesessiontap)KDebug 类别过滤 (0x1F07, 0x2BDC, 0x2B87, 0x31CA)Perfowl Custom Decoderms;分段 Span 耗时(系统接口/运行时/UIKit/Scene/回调/首帧)
GPUGPU Counter Nativeios.device.gpu.counter.source_nativedevicecom.apple.instruments.server.services.gpuGPUCounterControlService.configureCountersPerfowl Adapter (pmd3 DTX)count;动态硬件 Counter 解码与差分
待定TinyJank / Smoothios.frame.tiny_jank_per_10min / smoothforeground暂无业界公认标准算法保持 Unsupportednull;缺可信定义坚决不伪造

四、 核心数理模型与边界计算细节

1. 逐帧卡顿判定与卡顿率(Jank & Stutter)算法模型

frame_derivation.py 中,Perfowl 拒绝用一秒一次的 Graphics 曲线猜测卡顿,而是通过直接监听内核层 CoreProfile RAW v2 的每一笔呈现事件(DebugID 0x31800318)精确计算 FrameTime:

  • 动态平滑基线 ($B$):实时取前 3 帧的算术平均值作为基准帧耗时: $$B_n = \frac{FT_{n-1} + FT_{n-2} + FT_{n-3}}{3}$$
  • 动态阈值 ($D$):设定为 $D_n = 2 \times B_n$(即当前帧耗时超过前 3 帧平均耗时的 2 倍)。
  • 三档绝对耗时门槛
    • SmallJank:$FT_n > D_n \quad \text{且} \quad FT_n > 41.66\text{ ms}$(相当于 24fps 门槛)。
    • Jank:$FT_n > D_n \quad \text{且} \quad FT_n > 83.33\text{ ms}$(相当于 12fps 门槛)。
    • BigJank:$FT_n > D_n \quad \text{且} \quad FT_n > 125.00\text{ ms}$(相当于 8fps 严重卡死门槛)。
  • Stutter(卡顿率积分): 统计所有被判定为 Jank 的帧中,超出基准耗时的累加耗时占测试总时长的百分比: $$\text{Stutter%} = \frac{\sum_{i \in \text{Jank Frames}} \max(0, FT_i - B_i)}{\text{Session Total Duration}} \times 100%$$

2. 能耗与硬件电池测量模型

  • Energy Cost 负值哨兵机制:Apple 系统的 Energy Monitor 会在特定低功耗切换时回传 -1 或巨大负数作为状态哨兵值。Perfowl 引擎在 metric_engine.py 中对 Total Energy 进行负数拦截;一旦 Total $< 0$,整帧标记 source-sentinel 隔离,绝不计算为错误峰值。
  • 物理断电放电门禁:通过 IOPMPowerSource 读取快照时,若 ExternalConnected == true,表示手机正在通过 USB 接收充电供电。此时测得的电流并非整机实际功耗,Perfowl 严格遵守诚实红线,将 Current、Voltage、Power 全部置为 null,并标注 external-power-connected。只有在 Wi-Fi 模式且完全断开外接充电器时,才激活真实放电功率换算。

3. Mach Absolute 时钟桥与启动 9 阶段切分

  • 时钟换算:iOS 内核 KDebug 记录使用 CPU 时钟滴答(Mach Absolute Ticks),Perfowl 通过会话开始时捕获的 numerdenom 时钟基(Timebase)精确折算纳秒: $$\text{Timestamp (ns)} = \frac{\text{mach_ticks} \times \text{numer}}{\text{denom}}$$
  • 生命周期锚点切分
    1. System Interface Initialization:系统接口初始化
    2. Static Runtime Initialization:动态链接库加载与静态运行时
    3. UIKit Initialization:UIKit 核心初始化
    4. UIKit Scene Creation:Scene 会话创建
    5. willFinishLaunchingWithOptions():应用即将完成启动回调
    6. didFinishLaunchingWithOptions():应用完成启动回调
    7. sceneWillConnectTo():Scene 窗口连接
    8. sceneWillEnterForeground():Scene 进入前台
    9. Initial Frame Rendering:首帧完成 GPU 渲染呈现 (debugid == 0x31CA0006)

五、 从 0 到 1 的宏观演进里程碑 (Milestones)

回顾从第一行代码到当前全功能基座,Perfowl 严格遵循“先调研、再文档、公式入档、用户批准、后开发”的七步流程,完成了七个核心阶段的蜕变:

  • P0 阶段 · 数据保真基座: 奠定 Append-only 不可变 Session 设计,确立数据源四属性(Source / Scope / Unit / Quality),确立“绝不用 0 冒充缺值”的诚信原则。
  • P1 阶段 · 基础指标承接 (Batch A): 接通 DVT Sysmontap 与 Graphics 基础流,实现 APP CPU、物理内存(Footprint/Resident)、线程数、上下文切换及秒级 FPS。
  • P2 阶段 · 系统级与网络级拓展: 解决 TotalCPU 归一化(除以 EnabledCPUs)、分核 CPU 解析;自研 pmd3_deep_services 突破单 PID 网络吞吐(NetworkStatistics),引入源时钟同步消除网络毛刺。
  • P3 阶段 · 毫秒级逐帧流水线: 突破 CoreProfile RAW v2 二进制流解码,定位 0x31800318 关键呈现事件;实现 29 项流畅度统计汇总与三档 Jank 判定模型。
  • P4 阶段 · 原生工作台与可复查测试链: 完成 SwiftUI 原生高性能客户端架构;实现实时多轨同轴时间线、截图时间轴关联、Session 离线复查、指标添加菜单(17 项全开)与数据导出。
  • P5 阶段 · 能耗与硬件电池模型: 接通 Diagnostics Relay 独立通道,建立 20 秒自适应采样时钟;引入严格的物理断电放电门禁与负值哨兵过滤,完成电池温度与热状态监控。
  • P6 阶段 · 冷启动阶段化分解: 实现受控冷启动(Controlled Launch),建立 Mach Absolute 时钟桥;实现从内核启动到首帧呈现的 9 阶段生命周期精准分解。

六、 质量语义、边界与复查实战

Perfowl · Performance Observer