让 AI Agent 自己把 App 的性能问题修好 —— 而且你能验证它没在骗你。
一套面向 iOS / React Native / HarmonyOS 的移动端 APM Agent 能力集: 发现 → 定位根因 → 修复 → 验证 → 防劣化,每一步都有可复现的证据。
claude plugin marketplace add davidtan2008/mobileAutoAPM
claude plugin install mobile-apm@mobile-apm-marketplace真实 iOS 工程的一次完整闭环,非演示数据 · [看案例全过程](docs/case-study-translation-persistence.md) · [证据包](docs/demo/translation-persistence/README.md)
然后在你自己的工程里说一句话:
冷启动太慢了,帮我看看
Agent 会自己走完:测量 → 与基线对比 → 分行定位 → 改代码 → 同口径复测 → 给出带显著性的结论。
崩溃上报工具(Sentry / Bugly / Firebase)解决的是**「线上出事了,怎么看到」**。
它们不回答的问题有三个,而这些恰恰是性能优化的全部难点:
| 问题 | 为什么难 |
|---|---|
| 这个目标到底可达吗? | 很多"优化"其实物理上不可能——空壳 App 的启动地板就在那儿 |
| 这次改动真的有效果吗? | 性能数据方差常常比优化幅度还大,测两次不一样太正常了 |
| 为什么突然变慢了? | 同一个 App 里方差源能有两倍差,挑样本很容易得到漂亮但假的结论 |
大多数"性能优化"失败,不是因为改错了,而是因为无法判断有没有改对。
mobileAutoAPM 的做法是:先让"有没有效果"变成一个可判定的问题,再谈优化。
某 iOS 项目的目标是「冷启动 ≤200ms」。Agent 做了三轮优化,测出来 280ms → 279ms,p=0.605。
它没有宣布失败就收工,而是先造了一个最小对照组:把首页内容换成 Text("x"),其余全保留,测出 p50 = 210ms。
目标 200ms < 空壳 App 地板 210ms
→ blocked_by_control_floor
→ 停止局部优化,升级为架构决策
这个"失败"比任何优化成果都值钱——它避免了在物理上不可能的方向上继续投入。
| Sentry / Firebase | mobileAutoAPM | |
|---|---|---|
| 定位 | 线上一旦出事怎么看到 | 动手之前怎么判断值不值得做 |
| 输入 | 线上崩溃/性能采样 | 你的仓库 + 一台真机 |
| 输出 | 告警、趋势图、根因线索 | 改完之后的显著性结论 |
| 谁来用 | 值班工程师、SRE | AI Agent(也可人用) |
| 核心能力 | 采集与聚合 | 测量纪律 + 判定逻辑 |
| 失败模式 | 漏报、噪音告警 | 把"测不出来"包装成"优化成功" |
两者是互补的,不是替代。mobileAutoAPM 负责动手前的判断和事后的验证,Sentry 负责线上真实世界的监控。
一句话:Sentry 告诉你"出事了",mobileAutoAPM 告诉你"到底改没改对"。
这五条写进了每一个技能里,Agent 违反任何一条就算任务失败。
| # | 铁律 | 为什么 |
|---|---|---|
| 1 | 无基线不优化 | 没有可比基线之前的"优化"全是盲改 |
| 2 | 单变量 | 一次只改一处,否则无法归因 |
| 3 | 必须复测 | 同设备、同构建、同命令、同样本量 |
| 4 | 绝不伪造 | 不许用估算值/历史值/典型值充当实测 |
| 5 | 结论带来源 | 每个数字都要能追到:哪条命令、哪台设备、哪个 commit |
「LLM 做性能优化最常见的失败模式,是编一个看起来合理的数字,然后基于它改一堆代码。」
这条在本项目里被真实触发过。一个 iOS 目标工程的冷启动数据呈双峰分布—— 快的一簇 235ms、慢的一簇 500ms,挑两个快的就能报出"优化了 11%"。
那是伪造。所以工具的做法是:方差超阈值就整轮拒绝(实测有一次 CV=21.5% 的 run 被整轮作废,前 3 次异常样本完整留档),并明确告诉你"本次无法判定",而不是换个口径重测到好看为止。
核心设计原则:确定性的计算交给脚本,判断留给 Agent。
需要"算"的事情(解析 sourcemap、算 CV、跑置换检验)全部是零依赖的确定性脚本; 需要"判断"的事情(改哪里、要不要问人)才交给模型。这样同样的输入永远得到同样的结论。
┌─────────────────────────────────────────────────────────────┐
│ 技能层(7) │
│ apm-loop(编排) · doctor · startup · render · memory │
│ crash · autotest │
├─────────────────────────────────────────────────────────────┤
│ 子 Agent(4) profiler · reviewer · crash-triager · │
│ perf-guard │
├─────────────────────────────────────────────────────────────┤
│ 数据平面(11 个脚本 · 零第三方依赖 · 纯标准库) │
│ 测量 apm_measure 诊断 apm_diagnose 判定 apm_baseline│
│ 可行性 apm_feasibility 截图 apm_screenshot 白屏 apm_white_screen
│ 符号化 rn_symbolicate 符号门禁 rn_build_symbols │
│ 友好度 ai_readiness 改造 ai_remediate 体检 apm_doctor │
├─────────────────────────────────────────────────────────────┤
│ 知识库(7 篇) 指标定义 · 选型硬限制 · 测量协议 · │
│ 可行性协议 … │
└─────────────────────────────────────────────────────────────┘
| 能力 | 脚本 | 干什么 |
|---|---|---|
| 测量 | apm_measure.py |
统一口径采集,物理设备硬校验,保留逐次样本与原始日志 |
| 诊断 | apm_diagnose.py |
CV、多簇检测、分段 CV、可疑变量、跨 run 漂移 |
| 判定 | apm_baseline.py |
置换检验判显著性(不依赖正态假设) |
| 可行性 ⭐ | apm_feasibility.py |
动手前测最小对照地板,判断目标可达吗 |
| 符号化 | rn_symbolicate.py |
Hermes 两步合成,字节码偏移 → 源码行号 |
| 改造闭环 | ai_remediate.py |
扫描 → 生成 → 人审 → 复扫量化 |
跨 agent 可移植:由 tools/build-portable.py 从单一真源生成 dist/,
同一份能力包可装进 opencode / Claude Code / 其他 Agent,CI 校验生成物与源头一致。
# 造最小对照组,把业务内容换成 Text("x"),其余全保留
python3 scripts/apm_feasibility.py plan --metric startup.cold.first_frame --target 200
python3 scripts/apm_feasibility.py check --control control/metrics.json \
--candidate candidate/metrics.json --metric startup.cold.first_frame --target 200| 退出码 | 含义 | 该做什么 |
|---|---|---|
| 0 | potentially_reachable |
可以进单变量实验 |
| 1 | 证据不足 / 测量有问题 | 补样本或修测量,不下结论 |
| 2 | blocked_by_control_floor |
停止局部优化,升级架构决策 |
真实战绩:拦截了某项目"冷启动 ≤200ms"的目标。在开工前省掉了整轮无效优化。
python3 scripts/apm_baseline.py compare --baseline baseline.json --run run.json --json实测校准过:清晰改善 p=0.0088;而 −4.5% 的小改善被正确判为「不具实际意义」。
| 能力 | 验证方式 | 结果 |
|---|---|---|
| iOS 冷启动测量 | iPhone 13 / iOS 26.7 真机 | clean commit 正式 baseline,跨 run consistent(Δp50=5ms) |
| 真机 UI 自动化 | iPhone 13 全量套件 | 123 passed, 0 failed, 0 skipped |
| RN 符号化 | 真实 RN 工具链 + 真实 Hermes 堆栈 | 186,386 条映射 / 891 源文件 / 20/20 帧还原 |
| iOS 原生 shim | 真机加载 RN 模块 | phys_footprint 15.9MB vs resident 83.9MB(18.9%) |
这些数字全部来自真实命令输出,证据在
docs/evidence/。没有一条是估算值。
make gate # AI 友好度门禁(CI 同款)
make test # Python 123 + RN 74 + iOS 11 全部测试工具自己也被同一套纪律要求:CI 校验 dist/ 与源头一致、图表 SVG/PNG 不得漂移。
plugins/mobile-apm/ ★ 单一真源
│ make build-portable
▼
dist/ → opencode | Claude Code | 其他
# Claude Code
claude plugin marketplace add davidtan2008/mobileAutoAPM
claude plugin install mobile-apm@mobile-apm-marketplace
# 或手动(任何 agent 通用)
cp -R dist ~/.config/opencode/plugins/mobile-apmmake doctor✅ 启动耗时检测 ✅ 页面渲染/卡顿 ✅ 白屏检测 ✅ 内存检测
✅ 崩溃符号化 ✅ UI 自动化 ✅ RN 专项 ✅ Android 专项 ✅ 鸿蒙专项
不可用的能力会明确报 unavailable,不会静默降级。
cd /path/to/your/app
/apm-init # 或按 docs/usage-guide.md 手动建 .apm/然后直接提需求:
冷启动太慢了,帮我看看
Agent 会自动走完闭环,并给你一个带设备、样本量、显著性的结论。
# 测
python3 scripts/apm_measure.py --profile ios-native-startup \
--device 00008110-000805902684801E --package-id com.example.app \
--build-type Release --build-path /path/to/App.app --samples 10
# 判断数据能不能用
python3 scripts/apm_diagnose.py .apm/runs/latest --metric startup.cold.first_frame
# 判断改动有没有效果
python3 scripts/apm_baseline.py compare \
--baseline .apm/baseline/startup.json --run .apm/runs/new/metrics.json不吹,是这个项目的底线。
| 能力 | 状态 |
|---|---|
| iOS 原生冷启动测量 | ✅ 真机验证,clean commit 正式 baseline |
| 真机截图 / 白屏检测 | ✅ iPhone 13 真机 |
| RN 崩溃符号化 | ✅ 真实 Hermes 堆栈端到端 |
| RN / iOS SDK | ✅ 74 + 11 个测试 |
| Android / 鸿蒙 测量 profile | ⛔ 未实现——不会假装可用 |
| Android / 鸿蒙 原生 shim 真机验证 | ⛔ 未验证——手头无设备 |
| 新架构(Fabric/TurboModules)符号化 | ⛔ 未验证 |
已验证但有前提的:
- 端侧听写只在物理设备可用(模拟器
SpeechTranscriber.isAvailable=false) - 真机 UI 自动化需先预热
testmanagerd(见docs/usage-guide.md§7) - iOS 真机截图需可选依赖
pymobiledevice3
当前只承诺 ios-native-startup 一个测量 profile。 别的平台在真机验证之前,
工具会明确说"不可用",而不是给一个看起来能用的数字。
| 使用指南 | 能做什么 / 怎么用 —— 11 个脚本逐个说明、我想做的事→用哪个、典型工作流 |
| 交接文档 | 接手须知与踩过的坑 |
| 路线图 | 现状、下一步、发布前置条件 |
| 架构与数据流 | 整体形状、闭环机制、设计原则 |
| 成功闭环案例 | 一个完整闭环长什么样(含 60 秒 demo) |
| 核心实现细节 | 每个脚本「为什么这么设计」 |
| 项目约定 | 给 AI Agent 看的 |
| 进度与完成度 | 含已知限制与未验证项 |
| 图表源文件 | 架构图 / 闭环图 / 闸门图 / 能力矩阵 |
欢迎。这是个重证据的项目,PR 时请注意:
- 改动后
make test必须绿(Python / RN / iOS 三套) - 改了
plugins/→make build-portable(dist/是生成物,勿手改) - 改了 SVG →
make build-diagrams - 不要在文档里写没验证过的数字——这是本项目唯一不可让步的规则
完整说明见 CONTRIBUTING.md,里面有可追溯 / 不可追溯的写法对照、 三个「不要」,以及一份按难度排序的上手方向。
特别欢迎的一种 PR:文档失真。如果你发现哪个数字和实测对不上, 直接开 issue 指出即可 —— 这类贡献实打实。
MIT

