Android 性能诊断 V2:基于 Agent Skill 的原生 IDE 融合架构

0 阅读11分钟

前置阅读:本文档是 V1 方案(基于 Perfetto 与 AI 的 Android 性能自动化诊断方案) 的升级版。V1 中已详细介绍了 Perfetto 基础知识、SQL 表结构、采集类别、2_trace_filter.py 的完整源码和 JSON 输出协议、环境依赖安装等内容,本文不再重复,请按需查阅。

一、从 V1 到 V2

1.1 V1 的缺陷

缺陷表现
源码搜索粗糙靠正则 + find 搜类名,无法追踪接口实现、Lambda、依赖注入等间接调用,上下文残缺
上下文易失真源码全文 cat 拼进 Prompt,十几兆文本灌入单次 API 请求,极易触发大模型幻觉
重构无法落地结论以 Markdown 文件输出,开发者只能手动复制粘贴到 IDE 中
密钥硬编码API Key 明文写在脚本中随仓库提交

1.2 V2 怎么解决

核心思路:砍掉冗余编排,只保留底座( 2_trace_filter.py ),智能层上移至 IDE Agent。

能力维度V1(CLI 管线)V2(IDE Agent)
设备采样1_ai_sampler.sh(手动按 Enter 截断)Agent 直接执行 adb(按秒定时,自动停止)
Trace 解析2_trace_filter.py(六步 SQL 管线)2_trace_filter.py(复用,零改动)
源码搜索find + grep(正则,易漏间接调用)Agent 原生工具链(可递归追踪整条调用链)
AI 推理远端 API(需把全量源码外传到云端)IDE 内置 Agent(源码留在本地,按需精准读取)
Prompt 编排Bash 字符串拼接(脆弱,长度受限)SKILL.md 声明式协议(Agent 自主编排)
结果交付Markdown 文件(人工复制粘贴)IDE 内对话输出诊断与修复建议,询问用户是否需要直接修改源码
密钥安全脚本硬编码IDE 内置鉴权,无需暴露

二、V2 整体架构

image.png

一句话概括: 用户说一句话 → Agent 在真机上采集 Trace → 2_trace_filter.py 六步 SQL 管线提纯为 JSON → Agent 按 JSON 线索搜源码 → 读完后交付诊断报告与修复建议 → 询问用户是否需要修改代码。

V2 仅需两个文件

文件作用位置
2_trace_filter.py底座:把二进制 Trace 翻译成 Agent 能读懂的 JSON(详见 V1 文档tools/ai_profiler/
SKILL.md契约:告诉 Agent 该按什么流程、什么规则工作.agent/skills/performance-profiler/

三、行为契约 — SKILL.md

3.1 什么是 SKILL.md

简单说,SKILL.md 就是一份写给 AI Agent 看的"工作手册"。

你不需要写代码来编排 Agent 的行为——只需要用 Markdown 写清楚"先做什么、再做什么、什么不能做",Agent 就会严格照着执行。它取代了 V1 中 run_profiler.sh + 3_ai_reporter.sh 两个脚本的全部编排工作。

文件位置.agent/skills/performance-profiler/SKILL.md

3.2 四个阶段怎么流转

image.png

3.3 Perfetto Config 技术要点

Config 模式的 SELinux 绕过原理、Text Proto 格式说明、完整 TraceConfig 字段含义等技术细节,请参考 V1 文档 AI_EMPOWERMENT.md1_ai_sampler.sh 源码中的注释。V2 的 SKILL.md 中已内嵌与 1_ai_sampler.sh 完全一致的配置模板,Agent 只需替换 target_cmdline 即可,无需理解底层细节。

3.4 完整文件内容

以下是 SKILL.md 的完整内容。部署时应原封不动复制到 .agent/skills/performance-profiler/SKILL.md

---
name: Android 性能靶向诊断 (Performance Trace Profiler)
description: 当用户提出优化 Android 页面滑落卡顿、抓取底层 Trace 查找耗时点等诉求时自动被唤起。触发示例:帮我抓个 5s  Trace 看看滑动掉帧、抓 10s 诊断找找卡顿元凶。
---

# 绝对禁令

1. **必须透明化展示核心 JSON 特征**:在解析出最终指纹时,第一时间通过 ```json 向用户毫无保留地展示供其核验,但**严禁**吐出中间过程的繁杂跑库数据。
2. **严禁无源码回话**:在定位并阅读过相关源码之前,禁止输出诊断结论。
3. **严禁建议 ui.perfetto.dev**:除非你同时给出了源码级分析和修复代码,否则禁止提供该链接。

---

# Phase 1: Trace 采集

> 本阶段是唯一允许与用户交互的阶段。每个步骤必须严格按顺序执行,不得合并、不得跳过、不得自行推断"已完成"。
> 每个步骤执行前,须先向用户输出对应的「进度提示」,再执行命令。

**步骤 1:设备检测**
> 进度提示:输出 "🔍 **[1/14] 正在检测 Android 设备连接状态...**"

执行 `adb devices`。若无在线设备直接终止流程并告知用户;若有多台设备,列出序列号并询问用户选定目标。

---

**步骤 2:推导目标包名**
> 进度提示:输出 "📦 **[2/14] 正在推导目标应用包名...**"

按以下优先级确定包名:
1. 如果用户消息中已明确给出包名(如 `com.xxx.xxx`),直接使用。
2. 否则搜索项目中所有 `build.gradle` / `build.gradle.kts`,查找 `applicationId`:
   - 全项目只有 1 个明文 `applicationId`:直接使用。
   - 有多个或使用了动态变量:**立刻停止**,列出找到的信息,询问用户指定包名,等待回复后再继续。

---

**步骤 3:确定采集时长**
> 进度提示:输出 "⏱️ **[3/14] 正在确认采集时长...**"

从用户消息中提取采集时长(如"抓 8s"  8 秒),未指定时默认 **10 秒**。此时长仅用于步骤 11  `sleep`,不写入配置文件。

---

**步骤 4:生成探针配置文件**
> 进度提示:输出 "⚙️ **[4/14] 正在生成 Perfetto 探针配置(包名: <包名>,时长: <时长>s)...**"

将步骤 2 确定的包名填入下方模板的 `target_cmdline` 字段。**其余所有内容一字不改**,写入 **`/tmp/trace_config.pbtxt`**(系统临时目录,不污染项目工程)。

> ⚠️ 以下模板与 `1_ai_sampler.sh` 完全一致,不得增删任何字段!
> - `duration_ms` 固定 `600000`(10 分钟安全上限),由步骤 12  `pkill -INT` 控制实际采集时长。
> - 唯一需要替换的是 `target_cmdline` 的值。

```protobuf
buffers {
    size_kb: 65536
    fill_policy: RING_BUFFER
}
data_sources {
    config {
        name: "linux.ftrace"
        ftrace_config {
            atrace_categories: "sched"
            atrace_categories: "freq"
            atrace_categories: "idle"
            atrace_categories: "am"
            atrace_categories: "wm"
            atrace_categories: "gfx"
            atrace_categories: "view"
            atrace_categories: "binder_driver"
            atrace_categories: "hal"
            atrace_categories: "dalvik"
            atrace_categories: "memory"
            ftrace_events: "sched/sched_switch"
        }
    }
}
data_sources {
    config {
        name: "android.surfaceflinger.frametimeline"
    }
}
data_sources {
    config {
        name: "android.packages_list"
    }
}
data_sources {
    config {
        name: "linux.perf"
        perf_event_config {
            timebase {
                counter: SW_CPU_CLOCK
                frequency: 500
                timestamp_clock: PERF_CLOCK_MONOTONIC
            }
            callstack_sampling {
                kernel_frames: false
            }
            target_cmdline: "<替换为步骤2的包名>"
        }
    }
}
data_sources {
    config {
        name: "linux.process_stats"
        process_stats_config {
            scan_all_processes_on_start: true
            record_thread_names: true
        }
    }
}
duration_ms: 600000
```

---

**步骤 5:清理设备旧数据**
> 进度提示:输出 "🧹 **[5/14] 正在清理设备端旧 Trace 文件...**"

执行:`adb shell rm -f /data/misc/perfetto-traces/app.trace`

---

**步骤 6:推送配置文件到设备**
> 进度提示:输出 "📤 **[6/14] 正在将探针配置推送到设备...**"

通过 stdin 重定向将配置写入设备(与 `1_ai_sampler.sh` 完全一致的方式):
`adb shell "cat > /data/local/tmp/trace_config.pbtxt" < /tmp/trace_config.pbtxt`

写入成功后,立即删除本地临时文件(不留垃圾):`rm -f /tmp/trace_config.pbtxt`

---

**步骤 7:在后台启动 perfetto**
> 进度提示:输出 "🚀 **[7/14] 正在启动 Perfetto 性能探针...**"

执行(`>/dev/null 2>&1 &` 在引号外部,将 adb 进程放到本机后台,与 `1_ai_sampler.sh` 一致):
`adb shell "cat /data/local/tmp/trace_config.pbtxt | perfetto --txt -c - -o /data/misc/perfetto-traces/app.trace" >/dev/null 2>&1 &`

> 由于 `duration_ms: 600000`,perfetto 会持续运行最长 10 分钟,不会自动退出。

---

**步骤 8:等待 perfetto 初始化**
> 进度提示:输出 "⏳ **[8/14] 等待探针初始化(2秒)...**"

执行:`sleep 2`

---

**步骤 9:检查 perfetto 进程是否存活**
> 进度提示:输出 "🔎 **[9/14] 正在验证探针进程存活状态...**"

执行:`adb shell pidof perfetto`

- **有 PID 输出**:记录为 **Config 模式**,继续步骤 10
- **无 PID 输出**:记录为**降级模式**,先清理再启动降级命令:
  1. `adb shell rm -f /data/misc/perfetto-traces/app.trace`
  2. `adb shell "perfetto -o /data/misc/perfetto-traces/app.trace sched freq idle am wm gfx view binder_driver hal dalvik memory" >/dev/null 2>&1 &`
  3. `sleep 1`
  然后继续步骤 10

---

**步骤 10:【必须执行】通知用户开始复现卡顿**
> ⚠️ 此步骤是整个流程最关键的用户交互节点。
> **必须完整输出以下内容后,才能继续步骤 11。不得跳过,不得与任何步骤合并。**

向用户输出:

```
🟢 [10/14] 性能探针已就绪!

 采集模式:Config 模式(含 Frame Timeline + Callstack Sampling)/ 命令行降级模式
 目标应用:<包名>
 ⏱️ 采集倒计时:<步骤3的时长> 

👉  立刻 在手机上操作,复现您想诊断的卡顿场景!
   (如:快速滑动列表、打开目标页面、触发卡顿动作等)

采集将在 <时长> 秒后自动停止,无需手动操作。
```

---

**步骤 11:等待采集结束**
> 此步骤静默等待,不得输出任何内容。

执行:`sleep <步骤3的时长(秒)>`

---

**步骤 12:停止 perfetto**
> 进度提示:输出 "🛑 **[12/14] 采集窗口结束,正在停止探针...**"

执行:`adb shell pkill -INT perfetto`

---

**步骤 13:等待数据落盘**
> 进度提示:输出 "💾 **[13/14] 正在等待性能数据写入磁盘(2秒)...**"

执行:`sleep 2`

清理设备端临时配置文件:`adb shell rm -f /data/local/tmp/trace_config.pbtxt`

---

**步骤 14:拉取 Trace 文件**
> 进度提示:输出 "📥 **[14/14] 正在将 Trace 文件拉取到本地...**"

执行:`adb pull /data/misc/perfetto-traces/app.trace ./tools/ai_profiler/temp_issue.perfetto-trace`

---

# Phase 2: 协议解析

> 进度提示:输出 "🔬 **[Phase 2] 正在运行六步 SQL 分析管线,提取性能指纹...**"

执行以下命令(包名换成步骤 2 推导出的实际包名):
```bash
python3 ./tools/ai_profiler/2_trace_filter.py ./tools/ai_profiler/temp_issue.perfetto-trace "<包名>"
```

根据返回 JSON  `status` 字段处理:
- `perfect`:告知用户本次窗口未检测到卡顿帧,询问是否重新采集。
- `error`:终止流程,报告错误信息。
- `anomaly_detected`:**立即使用 ```json 代码块将完整 JSON 输出给用户核验**,并告知:"这是从内核中提纯的卡顿指纹,我正在顺这些指纹下潜至最深层的代码网寻找原凶...",随后进入 Phase 3

---

# Phase 3: 源码溯源

> 进度提示:输出 "🕵️ **[Phase 3] 正在深度溯源相关业务代码,请稍候...**"
> 本阶段禁止向用户输出任何未经证实的猜测或半成品推演。

基于 JSON 中以下字段,自主在项目中搜索并深度阅读所有相关源码(`.kt` / `.java` / `.xml`):

| JSON 字段 | 线索类型 | 诊断价值 |
|-----------|---------|---------|
| `suspect_root_cause_methods` | 主线程耗时方法名 | 定位卡顿函数 |
| `thread_os_states` | CPU 调度微状态 | 判断 I/O 阻塞 / 锁等待 / CPU 抢占 |
| `callstack_chains` | 函数级 CPU 调用链 | `[APP]` 标记的调用链是首要分析对象 |
| `frame_timeline_jank` | 帧级卡顿归因 | App / GPU / SurfaceFlinger 定责 |
| `top_jank_frames` | Top 3 卡顿帧 | 偶发 vs 持续性卡顿 |

---

# Phase 4: 诊断交付

完成 Phase 3 全部源码阅读后,输出诊断报告:

1. **肇事片段**:精确引用源码,标注文件路径和行号。
2. **根因剖析**:结合 Trace 数据和源码逻辑,分析为什么这些代码构成瓶颈。
3. **重构建议代码**:基于真实源码给出可直接应用的修改方案。
4. **用户确认**:"需要我帮你直接修改代码吗?"

---

# 失败准则

满足以下任一条件即判定为失败,必须立即纠正:

- 未完整输出步骤 10 的复现通知,就直接执行步骤 11  sleep。
- 自行推断 perfetto "已完成"或"已退出"而跳过步骤 10-13 中的任何步骤。
- 将配置文件写入项目目录而非 `/tmp/` 系统临时目录。
- 修改了配置模板中除 `target_cmdline` 以外的任何字段。
-  `duration_ms` 设为用户指定的采集时长(必须固定为 `600000`)。
- 未在项目中搜索和阅读源码就输出了诊断结论。
- 未将 JSON 特征展示给用户核验就直接进入源码分析。
- 诊断报告中未包含真实源码引用(文件路径 + 代码片段)。

四、部署指南

4.1 前置条件

环境依赖(adbpython3perfetto 库)的安装方式见 V1 文档 · 环境准备。

此外,需要确认你使用的 AI IDE 支持 Agent Skill:

IDE技能文件放在哪里
Claude Code.agent/skills/ 目录下
Cursor.cursor/skills/ 或等效目录下
其他 AI IDE需确认该 IDE 的 Agent Skill 目录约定

4.2 目录结构

部署完成后,项目中会多出以下文件:

<项目根目录>/
├── .agent/
│   └── skills/
│       └── performance-profiler/
│           └── SKILL.md                  ← Agent 行为契约(V2 新增)
├── tools/
│   └── ai_profiler/
│       ├── 2_trace_filter.py            ← Trace 解析脚本(V1 & V2 共用)
│       └── temp_issue.perfetto-trace    ← 运行时产物(建议加入 .gitignore)
└── ...

4.3 三步部署

第一步:创建技能目录

mkdir -p .agent/skills/performance-profiler

第二步:放入 SKILL.md

将本文档 第三章 3.3 节 代码框内的文本原封不动复制到 .agent/skills/performance-profiler/SKILL.md 中并保存。

第三步:安装 Python 依赖(如果 V1 已装过可跳过)

pip3 install perfetto

4.4 唤起方式

在 IDE 的 AI 对话窗口中用自然语言触发即可:

示例话术Agent 识别到的采集时长
"帮我抓个 8s 的 Trace 诊断下"8 秒
"抓 5s Trace 看看滑动为啥卡"5 秒
"帮我抓个 Trace 看看"10 秒(默认值)

以下是实际执行的一次诊断截图,从输入一句话到最终输出带源码引用的修复方案,全流程自动完成:

帮我抓个 8s 的 Trace 诊断下

五、总结

V2 升级为 IDE Agent 原生融合:砍掉了 3_ai_reporter.shrun_profiler.sh,只保留 2_trace_filter.py 作为分析底座,源码溯源和诊断推理全部由 Agent 在本地完成——源码不外传、API Key 不暴露。Agent 自动完成从采集到诊断的全流程,输出带源码引用的修复建议,询问用户是否需要执行代码修改。