ENGINEERING ARTICLE

云端 Mac 用 xctrace 建立 iOS 能耗回归门禁

云端 Mac 用 xctrace 建立 iOS 能耗回归门禁

一段看似普通的列表刷新逻辑,可能让 CPU 持续活跃,也可能因为过密的定时器、重复网络请求或后台任务而增加唤醒次数。功能测试通常不会报错,代码覆盖率也看不出问题。要在合并前发现这类变化,可以在 OVPS 云端 Mac 上固定测试环境,用 xctrace 录制同一段用户动作,再把结果与经过确认的基线比较。

先定义门禁测量什么

能耗不是一个脱离场景的单值。首页静置、连续滚动、图片解码和后台同步的资源特征完全不同,不能混在同一条基线里。先选择一段持续 60 至 120 秒、能够稳定复现的核心路径,例如启动应用、进入消息列表、滚动三屏、打开详情并返回。

每个场景至少记录这些条件:

  • App 提交版本与构建配置;
  • 设备型号、系统版本及电量范围;
  • 屏幕亮度、网络类型和低电量模式状态;
  • 测试账户的数据规模;
  • 采样时长、预热次数与正式运行次数。

模拟器适合验证自动化脚本和动作是否稳定,但不能代表真机能耗。正式门禁应绑定固定真机;模拟器结果只能作为进程活动的辅助证据。

不要直接采用第一次运行。首次启动可能包含数据库迁移、着色器准备或缓存填充。先预热一次,再正式录制至少三次,使用中位数抵消偶发系统任务造成的噪声。

固定云端 Mac 与测试设备状态

执行节点应固定 Xcode 主版本,并显式选择开发者目录。不要依赖交互式 Shell 中碰巧生效的环境变量。

set -euo pipefail

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
xcodebuild -version
xcrun xctrace version
xcrun xctrace list devices
xcrun xctrace list templates

最后两条命令也是兼容性检查。不同 Xcode 版本可用模板及导出结构可能变化,因此脚本不要假定 Energy Log 永远存在。找不到模板时应立即退出并保留版本信息,而不是退化成一次没有意义的空采样。

真机在开始前应回到一致状态:关闭不相关应用,保持相近电量区间,确认温度已恢复,禁用自动更新,并让网络条件稳定。测试期间不要同时执行归档、依赖下载或磁盘清理任务。共享节点上的额外负载会污染 CPU 和 I/O 读数。

建立运行前检查

把设备 UDID、Bundle ID、场景名和提交号写入同一个运行目录。目录名应唯一,但不要把令牌或签名材料写进路径和日志。

RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)-${GIT_COMMIT:-local}"
OUT_DIR="artifacts/energy/${RUN_ID}"
mkdir -p "$OUT_DIR"

xcrun simctl list devices > "$OUT_DIR/devices.txt"
xcrun xctrace list templates > "$OUT_DIR/templates.txt"
git rev-parse HEAD > "$OUT_DIR/commit.txt"

连接真机时,可把第一条替换为团队自己的设备探测命令。关键不是命令形式,而是失败时停止测量,避免把“设备未连接”误判成“能耗下降”。

录制可重复的 xctrace 轨迹

先启动待测进程并完成预热,再按进程名附加。下面的脚本要求设备、进程和模板都由 CI 参数明确提供:

DEVICE_UDID="${DEVICE_UDID:?missing DEVICE_UDID}"
PROCESS_NAME="${PROCESS_NAME:?missing PROCESS_NAME}"
TRACE="$OUT_DIR/energy.trace"

xcrun xctrace record \
  --template "Energy Log" \
  --device "$DEVICE_UDID" \
  --attach "$PROCESS_NAME" \
  --time-limit 90s \
  --output "$TRACE"

录制开始后,再由 UI 自动化执行固定动作。动作脚本应使用可访问性标识,而不是屏幕坐标;测试数据应预先装载,网络请求最好指向稳定的测试环境。若某次运行发生登录失败、弹窗遮挡或页面未到达,应将该次标记为无效,不能纳入中位数。

xctrace 返回成功只代表 trace 已生成,不代表场景执行正确。每次录制还要保存 UI 测试结果、开始与结束时间、应用日志摘要和场景完成标记。缺少任一项时,门禁应报告“测量无效”,而不是报告性能通过。

导出指标并比较基线

先导出 trace 的目录结构,确认当前 Xcode 能提供哪些表,再为该版本维护解析规则:

xcrun xctrace export \
  --input "$TRACE" \
  --toc \
  --output "$OUT_DIR/toc.xml"

不要用一条脆弱的正则直接解析二进制 trace。更稳妥的方式是按 Xcode 主版本保存 XPath 或 XML 解析器,并把原始 trace 一并归档。门禁可以关注四类变化:持续 CPU 活跃、线程唤醒、定时器触发密度和网络传输量。它们是定位线索,不应被粗暴合成一个“能耗分数”。

基线来自同一设备、同一场景和同一构建类型的多次正常运行。建议保存最近确认版本的中位数,同时保留每次原始值。比较时使用相对变化与绝对下限双重条件:极小数值即使翻倍,也未必有工程意义。

分级处理异常

轻微偏移先产生告警,不阻断合并;连续多次超过阈值,或 CPU 与唤醒同时恶化时再失败。阈值必须通过团队历史样本确定,不应复制一个通用百分比。

失败报告至少包含提交号、设备与系统版本、三次采样值、中位数、基线、变化比例、trace 路径和 UI 场景结果。这样开发者可以直接打开证据,而不是面对一句“能耗过高”。

从异常轨迹回到代码

CPU 持续偏高时,先检查主线程轮询、图片处理、布局重复计算和未结束的后台队列。唤醒次数升高时,重点查看短周期定时器、频繁磁盘写入、重复通知和没有合并的网络重试。网络活动异常则应核对分页请求、缓存命中、遥测批次和重连策略。

常见误判也要单独排除:设备刚充电导致温度变化、首次运行仍在建缓存、测试账户数据量改变、系统弹窗打断动作,以及云端 Mac 同时执行其他重任务。修复后必须在同一条件下重新跑完整三次,不能拿一次较好的结果覆盖失败记录。

最终,能耗门禁的价值不在于生成一个漂亮分数,而在于把测试场景、设备条件、原始轨迹和代码提交绑定起来。只有这些证据能够重复采集,异常才有明确归属,门禁也不会沦为经常被跳过的噪声来源。

常见问题

iOS 模拟器能否替代真机完成能耗门禁?

不能完全替代。模拟器适合验证脚本、场景和进程活动是否稳定,但最终能耗基线应在固定型号、固定系统版本的真机上建立。

能耗指标超过基线一次就应该阻断合并吗?

不建议。先连续采样至少三次并使用中位数比较;轻微偏移只告警,持续超过阈值且证据完整时再阻断合并。

OVPS CLOUD MAC

把下一次构建放到独享物理节点

从三档 Apple Silicon 配置和六个在售节点中完成选择,实际可用状态以控制台实时返回为准。

选择配置并下单