flutter-devtools-mcp 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +208 -0
- package/README.zh-CN.md +292 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/services/ai-analysis.d.ts +6 -0
- package/dist/services/ai-analysis.d.ts.map +1 -0
- package/dist/services/ai-analysis.js +728 -0
- package/dist/services/ai-analysis.js.map +1 -0
- package/dist/services/collect-progress.d.ts +6 -0
- package/dist/services/collect-progress.d.ts.map +1 -0
- package/dist/services/collect-progress.js +23 -0
- package/dist/services/collect-progress.js.map +1 -0
- package/dist/services/cpu-profiler-service.d.ts +42 -0
- package/dist/services/cpu-profiler-service.d.ts.map +1 -0
- package/dist/services/cpu-profiler-service.js +208 -0
- package/dist/services/cpu-profiler-service.js.map +1 -0
- package/dist/services/cpu-symbol-filter.d.ts +10 -0
- package/dist/services/cpu-symbol-filter.d.ts.map +1 -0
- package/dist/services/cpu-symbol-filter.js +90 -0
- package/dist/services/cpu-symbol-filter.js.map +1 -0
- package/dist/services/http-profile-parser.d.ts +4 -0
- package/dist/services/http-profile-parser.d.ts.map +1 -0
- package/dist/services/http-profile-parser.js +71 -0
- package/dist/services/http-profile-parser.js.map +1 -0
- package/dist/services/json-parse-aggregate.d.ts +30 -0
- package/dist/services/json-parse-aggregate.d.ts.map +1 -0
- package/dist/services/json-parse-aggregate.js +155 -0
- package/dist/services/json-parse-aggregate.js.map +1 -0
- package/dist/services/network-aggregate.d.ts +50 -0
- package/dist/services/network-aggregate.d.ts.map +1 -0
- package/dist/services/network-aggregate.js +185 -0
- package/dist/services/network-aggregate.js.map +1 -0
- package/dist/services/network-capture-service.d.ts +50 -0
- package/dist/services/network-capture-service.d.ts.map +1 -0
- package/dist/services/network-capture-service.js +187 -0
- package/dist/services/network-capture-service.js.map +1 -0
- package/dist/services/network-capture.d.ts +2 -0
- package/dist/services/network-capture.d.ts.map +1 -0
- package/dist/services/network-capture.js +2 -0
- package/dist/services/network-capture.js.map +1 -0
- package/dist/services/performance-session.d.ts +41 -0
- package/dist/services/performance-session.d.ts.map +1 -0
- package/dist/services/performance-session.js +697 -0
- package/dist/services/performance-session.js.map +1 -0
- package/dist/services/performance-thresholds.d.ts +17 -0
- package/dist/services/performance-thresholds.d.ts.map +1 -0
- package/dist/services/performance-thresholds.js +19 -0
- package/dist/services/performance-thresholds.js.map +1 -0
- package/dist/services/profiler.d.ts +71 -0
- package/dist/services/profiler.d.ts.map +1 -0
- package/dist/services/profiler.js +314 -0
- package/dist/services/profiler.js.map +1 -0
- package/dist/services/rebuild-business-resolver.d.ts +24 -0
- package/dist/services/rebuild-business-resolver.d.ts.map +1 -0
- package/dist/services/rebuild-business-resolver.js +599 -0
- package/dist/services/rebuild-business-resolver.js.map +1 -0
- package/dist/services/rebuild-tracker-service.d.ts +45 -0
- package/dist/services/rebuild-tracker-service.d.ts.map +1 -0
- package/dist/services/rebuild-tracker-service.js +196 -0
- package/dist/services/rebuild-tracker-service.js.map +1 -0
- package/dist/services/session-types.d.ts +240 -0
- package/dist/services/session-types.d.ts.map +1 -0
- package/dist/services/session-types.js +180 -0
- package/dist/services/session-types.js.map +1 -0
- package/dist/services/threshold-alerts.d.ts +49 -0
- package/dist/services/threshold-alerts.d.ts.map +1 -0
- package/dist/services/threshold-alerts.js +258 -0
- package/dist/services/threshold-alerts.js.map +1 -0
- package/dist/services/timeline-cpu-parser.d.ts +28 -0
- package/dist/services/timeline-cpu-parser.d.ts.map +1 -0
- package/dist/services/timeline-cpu-parser.js +174 -0
- package/dist/services/timeline-cpu-parser.js.map +1 -0
- package/dist/services/timeline-extras.d.ts +61 -0
- package/dist/services/timeline-extras.d.ts.map +1 -0
- package/dist/services/timeline-extras.js +449 -0
- package/dist/services/timeline-extras.js.map +1 -0
- package/dist/services/vm-service-client.d.ts +123 -0
- package/dist/services/vm-service-client.d.ts.map +1 -0
- package/dist/services/vm-service-client.js +418 -0
- package/dist/services/vm-service-client.js.map +1 -0
- package/dist/tools/connection.d.ts +4 -0
- package/dist/tools/connection.d.ts.map +1 -0
- package/dist/tools/connection.js +127 -0
- package/dist/tools/connection.js.map +1 -0
- package/dist/tools/debug-actions.d.ts +4 -0
- package/dist/tools/debug-actions.d.ts.map +1 -0
- package/dist/tools/debug-actions.js +210 -0
- package/dist/tools/debug-actions.js.map +1 -0
- package/dist/tools/discover.d.ts +4 -0
- package/dist/tools/discover.d.ts.map +1 -0
- package/dist/tools/discover.js +183 -0
- package/dist/tools/discover.js.map +1 -0
- package/dist/tools/memory.d.ts +4 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +145 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/tools/network.d.ts +4 -0
- package/dist/tools/network.d.ts.map +1 -0
- package/dist/tools/network.js +265 -0
- package/dist/tools/network.js.map +1 -0
- package/dist/tools/performance-session.d.ts +4 -0
- package/dist/tools/performance-session.d.ts.map +1 -0
- package/dist/tools/performance-session.js +105 -0
- package/dist/tools/performance-session.js.map +1 -0
- package/dist/tools/profiling.d.ts +5 -0
- package/dist/tools/profiling.d.ts.map +1 -0
- package/dist/tools/profiling.js +142 -0
- package/dist/tools/profiling.js.map +1 -0
- package/dist/tools/rebuild-tracker.d.ts +4 -0
- package/dist/tools/rebuild-tracker.d.ts.map +1 -0
- package/dist/tools/rebuild-tracker.js +228 -0
- package/dist/tools/rebuild-tracker.js.map +1 -0
- package/dist/tools/snapshot-diff.d.ts +4 -0
- package/dist/tools/snapshot-diff.d.ts.map +1 -0
- package/dist/tools/snapshot-diff.js +235 -0
- package/dist/tools/snapshot-diff.js.map +1 -0
- package/dist/tools/widget-tree.d.ts +4 -0
- package/dist/tools/widget-tree.d.ts.map +1 -0
- package/dist/tools/widget-tree.js +156 -0
- package/dist/tools/widget-tree.js.map +1 -0
- package/docs/README.md +9 -0
- package/docs/performance-audit-requirements.md +403 -0
- package/docs/performance-metrics-guide.md +207 -0
- package/docs/performance-session-simple-design.md +399 -0
- package/examples/app_network_image.dart +242 -0
- package/examples/app_network_image_README.md +51 -0
- package/package.json +69 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Flutter 性能采集 — 指标与使用指南
|
|
2
|
+
|
|
3
|
+
> 面向开发者:如何读报告、各指标能发现什么问题、profile/debug 怎么选。
|
|
4
|
+
> 产品交互与工具参数见 [`performance-session-simple-design.md`](./performance-session-simple-design.md)。
|
|
5
|
+
> 图片 URL 埋点样板见 [`../examples/app_network_image_README.md`](../examples/app_network_image_README.md)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 推荐用法(两分钟)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 1) 性能主采(帧 / 业务CPU / GC / 图片解码更准)
|
|
13
|
+
flutter run --profile
|
|
14
|
+
|
|
15
|
+
# 2) AI 对话
|
|
16
|
+
连接 Flutter App,开始采集性能数据 30s,场景:首页列表滚动,结合代码给优化方案
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| 目的 | 模式 | 说明 |
|
|
20
|
+
|------|------|------|
|
|
21
|
+
| 卡顿体感、业务慢函数、GC、图片解码 | **profile** | 接近真实性能 |
|
|
22
|
+
| Widget 谁重建太多 | **debug 再采一轮** | profile **没有**重建追踪扩展 |
|
|
23
|
+
|
|
24
|
+
报告优先看 **「运行时摘要」**(带 🟢/🟡/🔴),再看「开发者结论」与 P0/P1/P2 详单。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. 报告结构(当前)
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
## 运行时摘要 ← 每行:状态 + 数字 + → 下一步
|
|
32
|
+
## 开发者结论(先看这里) ← P0/P1 + 改法
|
|
33
|
+
## P0 / P1 / P2 ← 详单(数据依据 / 原因 / 改法)
|
|
34
|
+
## 分析线索 / 耗时函数 Top10 / GC / 图片解码 / Isolate …
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
状态含义:
|
|
38
|
+
|
|
39
|
+
| 标记 | 含义 |
|
|
40
|
+
|------|------|
|
|
41
|
+
| 🟢正常 | 当前窗口未达告警阈值 |
|
|
42
|
+
| 🟡注意 | 有风险,建议结合业务热点处理 |
|
|
43
|
+
| 🔴异常 | 建议优先处理 |
|
|
44
|
+
| ⚪无数据 | 本模式采不到或 Timeline 不足(不是「没问题」) |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. 核心指标一览
|
|
49
|
+
|
|
50
|
+
### 3.1 体感:有没有卡
|
|
51
|
+
|
|
52
|
+
| 指标 | 来源 | 经验阈值(60Hz) | 能发现什么 |
|
|
53
|
+
|------|------|------------------|------------|
|
|
54
|
+
| **掉帧率 jank%** | Timeline 帧;单帧 **>16.67ms**(固定 60FPS 预算) | >5% 要查,>15% 明显卡 | UI 线程整体过载 |
|
|
55
|
+
| **P99 帧时** | 同上 | >32ms 易抖一下 | 最差帧尖刺 |
|
|
56
|
+
| **滚动段 FPS / 段 jank** | 2s 窗口分段 | 滑动 <50FPS 或段 jank 高 | 列表滑动卡顿 |
|
|
57
|
+
| **Build / Layout / Paint max** | Timeline 阶段 | 单阶段经常 >8~16ms | 卡在 build / 布局 / 绘制哪一段 |
|
|
58
|
+
|
|
59
|
+
**限制**:Android profile 下 Timeline 帧事件偶发偏少 → 摘要可能 ⚪无数据,此时以 **业务CPU / 图片解码** 为准。
|
|
60
|
+
|
|
61
|
+
### 3.2 归因:卡在哪
|
|
62
|
+
|
|
63
|
+
| 指标 | 来源 | 能发现什么 | 注意 |
|
|
64
|
+
|------|------|------------|------|
|
|
65
|
+
| **耗时函数 Top(lib/)** | VM `getCpuSamples` + 路径过滤;**Self >20ms → 🔴异常** | 哪个业务方法占时间 | 比框架 `drawFrame` 有用;框架热点不进开发者结论 |
|
|
66
|
+
| **Widget 重建 Top** | inspector 扩展 | 哪个组件重建过多、文件:行号 | **仅 debug**;profile 固定 ⚪无数据 |
|
|
67
|
+
| **filesToInspect** | 重建 + 业务CPU 文件 | 该打开哪些源码 | AI/人工优先读这些文件 |
|
|
68
|
+
|
|
69
|
+
### 3.3 尖刺:偶发卡一下
|
|
70
|
+
|
|
71
|
+
| 指标 | 来源 | 能发现什么 | 注意 |
|
|
72
|
+
|------|------|------------|------|
|
|
73
|
+
| **GC 次数 / 最长停顿 / Top5** | Timeline `GC` 流 | 停顿尖刺;次数高暗示分配偏勤;Top5 看最长几次事件名 | 见 §4 |
|
|
74
|
+
| **图片解码** | Timeline + 可选业务埋点 | 大图解码拖垮帧;带 URL 可点名 | 见 §5 |
|
|
75
|
+
| **Isolate CPU** | 多 isolate 采样 | 重活是否仍在主 isolate | 不把 `drawFrame` 当业务根因 |
|
|
76
|
+
|
|
77
|
+
### 3.4 其它
|
|
78
|
+
|
|
79
|
+
| 指标 | 能发现什么 |
|
|
80
|
+
|------|------------|
|
|
81
|
+
| **网络 total / errors / slow** | 失败重试、慢接口;图片请求多属正常 |
|
|
82
|
+
| **内存 heap / 利用率** | 堆压力;泄漏需看趋势,单次会话只能提示 |
|
|
83
|
+
|
|
84
|
+
### 3.5 明确不作为主结论的
|
|
85
|
+
|
|
86
|
+
- 框架链路:`drawFrame` / `_handlePersistentFrameCallback` 等(只说明在刷帧)
|
|
87
|
+
- 内存里的 VM 内部类(`Code` / `Field` / `InstructionsSection` 等)
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 4. GC 指标怎么读
|
|
92
|
+
|
|
93
|
+
示例:`GC [🔴异常]: 473 次, 最长停顿 28.1ms Top5[CollectNewGeneration 28.1ms, …] → 减少每帧临时对象分配`
|
|
94
|
+
|
|
95
|
+
| 字段 | 含义 |
|
|
96
|
+
|------|------|
|
|
97
|
+
| 次数 | 窗口内 GC 发生次数 |
|
|
98
|
+
| 最长停顿 | 单次卡住多久;相对 ~16ms 帧预算 |
|
|
99
|
+
| Top5 | 最长 5 次停顿(事件名 + ms),便于区分 Young/Old/MarkSweep |
|
|
100
|
+
| >8ms 次数 | 可能顶掉一帧的停顿次数(规则引擎内部) |
|
|
101
|
+
|
|
102
|
+
**能发现:**
|
|
103
|
+
|
|
104
|
+
1. **卡顿尖刺来自 GC**:最长经常 >16~32ms,或 >8ms 很多次
|
|
105
|
+
2. **分配偏勤(间接)**:次数极高 → 短命对象多(临时 List、字符串、闭包、解码缓冲);需结合业务CPU/重建
|
|
106
|
+
3. **交叉验证**:与 setState / 热循环 / 大图同时出现时,GC 高往往是副作用
|
|
107
|
+
|
|
108
|
+
**发现不了:**
|
|
109
|
+
|
|
110
|
+
- 具体哪行在分配(要看业务热点 / 分配采样)
|
|
111
|
+
- 单纯用「次数多」定罪卡顿(最长 7ms 时体感可能仍顺)
|
|
112
|
+
- 内存泄漏(要看堆是否持续涨、GC 后降不下来)
|
|
113
|
+
|
|
114
|
+
**判定倾向(实现侧):**
|
|
115
|
+
|
|
116
|
+
- 最长 ≤8ms 且长停顿不多 → 🟢(停顿不是主因)
|
|
117
|
+
- 长停顿偏多 / 最长 >8ms → 🟡
|
|
118
|
+
- 最长 >32ms 或长停顿很多 → 🔴 / P1
|
|
119
|
+
|
|
120
|
+
处理方向:减每帧临时对象、缩小重建、大图与重计算下沉;**不要去「调 GC 参数」**。
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 5. 图片解码与 URL
|
|
125
|
+
|
|
126
|
+
| 能力 | 是否要业务改代码 | 结果 |
|
|
127
|
+
|------|------------------|------|
|
|
128
|
+
| 发现「有慢解码」 | 否(旁路 Timeline) | 常 **无 URL** |
|
|
129
|
+
| 点名「哪张图、多大、多久」 | **是(一处侵入)** | 有 url / width / height / bytes |
|
|
130
|
+
|
|
131
|
+
**推荐**:全站网络图走统一入口 [`AppNetworkImage`](../examples/app_network_image.dart):
|
|
132
|
+
|
|
133
|
+
- Timeline 事件名:`app.imageDecode`
|
|
134
|
+
- arguments:`url`(必填),`width` / `height` / `bytes`(可选)
|
|
135
|
+
|
|
136
|
+
报告示例:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
慢解码: app.imageDecode 1788ms | https://cdn.xxx/cover.jpg | 800x600 | 118KB
|
|
140
|
+
→ 针对该 URL:ResizeImage / 预缓存 / 避免 UI 路径同步大图解码
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
零侵入只能告警;要链接必须轻量埋点(统一加载器即可,不必每个页面手写)。
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 6. 重建为什么 profile 无数据
|
|
148
|
+
|
|
149
|
+
重建依赖:
|
|
150
|
+
|
|
151
|
+
`ext.flutter.inspector.trackRebuildDirtyWidgets`
|
|
152
|
+
|
|
153
|
+
| 模式 | 重建追踪 | 帧/CPU |
|
|
154
|
+
|------|----------|--------|
|
|
155
|
+
| debug | ✅ | 偏慢、易夸大 jank |
|
|
156
|
+
| profile | ❌(扩展不可用) | 更接近真实 |
|
|
157
|
+
| release | ❌ | 无此类 VM 调试能力 |
|
|
158
|
+
|
|
159
|
+
因此摘要写「profile 下常无数据 → 需要时用 debug 补采」是 **Flutter 平台限制**,不是采集失败。
|
|
160
|
+
|
|
161
|
+
**实战**:profile 定「有多卡、哪个业务函数」;debug 定「哪个 Widget 重建爆了」。
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 7. 业务 CPU 为什么曾为空 / 怎么读
|
|
166
|
+
|
|
167
|
+
- 过滤规则:业务 `lib/` / `package:<app>/`,排除 Flutter SDK、`dart:ui` 等误归一路径
|
|
168
|
+
- Android profile 下 Self 时间常落在框架叶子;业务方法靠采样 + 路径/符号命中
|
|
169
|
+
- Demo 可用 `Timeline` / `@pragma('vm:never-inline')` 提高命中率
|
|
170
|
+
- **看 `projectTopFunctions`,不要看框架 `topFunctions` 当根因**
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 8. 排查优先级(建议)
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
1. 开发者结论里的 P0
|
|
178
|
+
2. 🔴 行:图片解码(带 URL)/ 高 jank / 滚动最差段
|
|
179
|
+
3. 业务CPU Top → 打开 filesToInspect
|
|
180
|
+
4. 🟡 GC / 内存 → 当旁证,回到分配与重建
|
|
181
|
+
5. debug 补采重建(若怀疑 setState 风暴)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 9. 相关文档索引
|
|
187
|
+
|
|
188
|
+
| 文档 | 内容 |
|
|
189
|
+
|------|------|
|
|
190
|
+
| [performance-session-simple-design.md](./performance-session-simple-design.md) | 产品形态、工具参数、数据结构、AI 话术 |
|
|
191
|
+
| [performance-audit-requirements.md](./performance-audit-requirements.md) | 重型 MD/HTML 审计(P2 规划,非当前主路径) |
|
|
192
|
+
| [../examples/app_network_image_README.md](../examples/app_network_image_README.md) | 图片统一入口接入 |
|
|
193
|
+
| [../README.zh-CN.md](../README.zh-CN.md) | 安装、回归命令、工具清单 |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 10. 回归
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
cd flutter-devtools-mcp
|
|
201
|
+
npm run build
|
|
202
|
+
npm run test:regression:android:profile
|
|
203
|
+
# 或 30s:
|
|
204
|
+
npm run test:regression:android:profile:30s
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
报告默认落在 Flutter 工程下:`performance-sessions/*.json` + `*.ai.md`。
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# Flutter 性能采集会话 — 方案设计(两轮对话)
|
|
2
|
+
|
|
3
|
+
> **产品形态**:用户用两句话完成一次采集;MCP 负责采数据;AI 负责结合代码出优化方案。
|
|
4
|
+
> **优先级**:本方案为 P0;[`performance-audit-requirements.md`](./performance-audit-requirements.md) 中的重型报告降为 P2 可选。
|
|
5
|
+
> **指标怎么读(开发者)**:见 [`performance-metrics-guide.md`](./performance-metrics-guide.md)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 核心交互(标准话术 — 单轮)
|
|
10
|
+
|
|
11
|
+
用户对 AI 说(一句话):
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
连接 Flutter App,开始采集性能数据 30s,结合代码给优化方案
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
可加场景:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
连接 Flutter App,开始采集性能数据 30s,场景:首页列表滚动,结合代码给优化方案
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**流程:**
|
|
24
|
+
|
|
25
|
+
1. AI 调用 `discover_apps` / `connect`
|
|
26
|
+
2. AI 调用 `collect_performance_session`(**默认阻塞 30 秒**,期间你在 App 内操作)
|
|
27
|
+
3. 工具返回 JSON 后,AI **自动**读取 `filesToInspect` 源码,输出 P0/P1/P2 优化方案
|
|
28
|
+
|
|
29
|
+
**无需第二句「可以停了」**——30 秒到后工具返回即通知 AI 继续分析。
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### 可选:分两轮(手动停)
|
|
34
|
+
|
|
35
|
+
若需自定义时长或提前结束,仍可使用 `durationSec` 参数;提前结束需等当前实现扩展(本期以固定阻塞为主)。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. 端到端流程
|
|
40
|
+
|
|
41
|
+
```mermaid
|
|
42
|
+
sequenceDiagram
|
|
43
|
+
participant User as 你
|
|
44
|
+
participant AI as AI
|
|
45
|
+
participant MCP as flutter_devtools_mcp
|
|
46
|
+
participant App as Flutter_App
|
|
47
|
+
|
|
48
|
+
User->>AI: 连接并开始采集 30s,结合代码给优化方案
|
|
49
|
+
AI->>MCP: discover_apps / connect
|
|
50
|
+
AI->>MCP: collect_performance_session(durationSec=30)
|
|
51
|
+
Note over User,App: 工具阻塞 30s,用户操作 App
|
|
52
|
+
MCP->>App: 并行采集
|
|
53
|
+
MCP-->>AI: PerformanceSessionResult JSON
|
|
54
|
+
AI->>AI: 读取 filesToInspect 源码
|
|
55
|
+
AI-->>User: 数据摘要 + P0/P1/P2 优化方案
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 3. 角色分工
|
|
61
|
+
|
|
62
|
+
| 角色 | 职责 | 不做 |
|
|
63
|
+
|------|------|------|
|
|
64
|
+
| **你** | `flutter run --profile`;两轮对话;App 内操作 30s | 不调 MCP 工具名 |
|
|
65
|
+
| **MCP** | 连接 VM;并行采集;`stop` 时输出结构化 JSON | 不调 LLM;不读源码 |
|
|
66
|
+
| **AI** | 解析人话 → 调工具;`stop` 后读代码 → 写优化方案 | 不代替你在 App 里操作 |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 4. AI 行为约定
|
|
71
|
+
|
|
72
|
+
### 收到「连接…开始采集性能数据 30s…结合代码给优化方案」
|
|
73
|
+
|
|
74
|
+
AI **必须依次**:
|
|
75
|
+
|
|
76
|
+
1. `discover_apps` / `connect`(若未连接)
|
|
77
|
+
2. `collect_performance_session({ scenario, durationSec: 30 })` — **会阻塞约 30s**
|
|
78
|
+
3. 在工具返回后,读取 `filesToInspect` 中的 Dart 文件
|
|
79
|
+
4. 按 §7 模板输出 P0/P1/P2 优化方案
|
|
80
|
+
|
|
81
|
+
**不要在工具返回前编造优化建议。**
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 5. MCP 工具设计
|
|
86
|
+
|
|
87
|
+
### 5.1 沿用现有(第一轮会用到)
|
|
88
|
+
|
|
89
|
+
| 工具 | 用途 |
|
|
90
|
+
|------|------|
|
|
91
|
+
| `discover_apps` | 自动发现并连接 |
|
|
92
|
+
| `connect` | 手动 URI 连接 |
|
|
93
|
+
|
|
94
|
+
### 5.2 新增(本方案核心)
|
|
95
|
+
|
|
96
|
+
#### `collect_performance_session`(主工具)
|
|
97
|
+
|
|
98
|
+
| 参数 | 类型 | 默认 | 说明 |
|
|
99
|
+
|------|------|------|------|
|
|
100
|
+
| `scenario` | string | `manual-session` | 场景标识 |
|
|
101
|
+
| `durationSec` | number | **30** | 阻塞等待秒数,到时自动 stop |
|
|
102
|
+
| `enableNetwork` | boolean | true | 是否采网络 |
|
|
103
|
+
| `enableCpuProfile` | boolean | true | 是否采 Top 耗时函数 |
|
|
104
|
+
| `topN` | number | 15 | Top 列表条数 |
|
|
105
|
+
| `saveToFile` | boolean | false | 写入 `{projectRoot}/performance-sessions/*.json` |
|
|
106
|
+
| `projectRoot` | string | - | Flutter 工程根目录,用于报告落盘与业务代码过滤 |
|
|
107
|
+
|
|
108
|
+
**行为:** start 全部采集器 → `sleep(durationSec)` → stop → 返回 JSON + 中文摘要 + `aiNextStep` 提示 AI 读源码。
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 6. 数据结构 `PerformanceSessionResult`(当前实现)
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
interface PerformanceSessionResult {
|
|
116
|
+
scenario: string;
|
|
117
|
+
durationSec: number;
|
|
118
|
+
profileModeHint: string; // 若非 profile,提示数据可能失真
|
|
119
|
+
|
|
120
|
+
frames: {
|
|
121
|
+
total: number;
|
|
122
|
+
jankCount: number;
|
|
123
|
+
jankPct: number;
|
|
124
|
+
avgMs: number;
|
|
125
|
+
p99Ms: number;
|
|
126
|
+
buildMaxMs: number;
|
|
127
|
+
layoutMaxMs: number;
|
|
128
|
+
paintMaxMs: number;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
topFunctions: Array<{
|
|
132
|
+
name: string;
|
|
133
|
+
file: string;
|
|
134
|
+
selfMs: number;
|
|
135
|
+
pct: number;
|
|
136
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
137
|
+
}>;
|
|
138
|
+
|
|
139
|
+
/** 仅业务工程 lib/ 的耗时方法(Top10) */
|
|
140
|
+
projectTopFunctions: Array<{
|
|
141
|
+
name: string;
|
|
142
|
+
file: string;
|
|
143
|
+
selfMs: number;
|
|
144
|
+
pct: number;
|
|
145
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
146
|
+
}>;
|
|
147
|
+
|
|
148
|
+
topRebuilds: Array<{
|
|
149
|
+
widget: string;
|
|
150
|
+
file: string;
|
|
151
|
+
line: number;
|
|
152
|
+
count: number;
|
|
153
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
154
|
+
}>;
|
|
155
|
+
|
|
156
|
+
memory: {
|
|
157
|
+
heapMb: number;
|
|
158
|
+
utilizationPct: number;
|
|
159
|
+
topClasses: Array<{ name: string; instances: number; bytesMb: number }>;
|
|
160
|
+
suspicious: string[];
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
network: {
|
|
164
|
+
total: number;
|
|
165
|
+
errors: number;
|
|
166
|
+
slow: Array<{ method: string; url: string; ms: number; status?: number }>;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** GC 停顿(Timeline GC 流) */
|
|
170
|
+
gc?: {
|
|
171
|
+
count: number;
|
|
172
|
+
totalPauseMs: number;
|
|
173
|
+
maxPauseMs: number;
|
|
174
|
+
avgPauseMs: number;
|
|
175
|
+
longPauseCount: number;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** 滚动/交互分段 FPS(默认 2s 窗口) */
|
|
179
|
+
scrollFps?: {
|
|
180
|
+
segmentSec: number;
|
|
181
|
+
overallFps: number;
|
|
182
|
+
worstSegments: Array<{
|
|
183
|
+
startSec: number;
|
|
184
|
+
endSec: number;
|
|
185
|
+
frames: number;
|
|
186
|
+
fps: number;
|
|
187
|
+
jankCount: number;
|
|
188
|
+
jankPct: number;
|
|
189
|
+
avgMs: number;
|
|
190
|
+
maxMs: number;
|
|
191
|
+
}>;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/** 图片解码;slow[] 可含 url/width/height/bytes(需业务 Timeline 埋点) */
|
|
195
|
+
imageDecode?: {
|
|
196
|
+
count: number;
|
|
197
|
+
totalMs: number;
|
|
198
|
+
maxMs: number;
|
|
199
|
+
slow: Array<{
|
|
200
|
+
name: string;
|
|
201
|
+
ms: number;
|
|
202
|
+
url?: string;
|
|
203
|
+
width?: number;
|
|
204
|
+
height?: number;
|
|
205
|
+
bytes?: number;
|
|
206
|
+
}>;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/** 主/后台 isolate CPU 对比 */
|
|
210
|
+
isolateCpu?: Array<{
|
|
211
|
+
isolateId: string;
|
|
212
|
+
name: string;
|
|
213
|
+
isMain: boolean;
|
|
214
|
+
sampleCount: number;
|
|
215
|
+
topSelfMs: number;
|
|
216
|
+
topName: string;
|
|
217
|
+
}>;
|
|
218
|
+
|
|
219
|
+
/** AI 应优先打开的文件路径(去重) */
|
|
220
|
+
filesToInspect: string[];
|
|
221
|
+
|
|
222
|
+
/** 预生成线索,AI 结合源码展开 */
|
|
223
|
+
hintsForAnalysis: string[];
|
|
224
|
+
|
|
225
|
+
cpuProfileSource?: "vm-samples" | "timeline-dart" | "timeline-hotspot" | "none";
|
|
226
|
+
aiAnalysis?: string;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**`filesToInspect` 生成规则:**
|
|
231
|
+
|
|
232
|
+
- `topRebuilds` 的 `file`
|
|
233
|
+
- `projectTopFunctions` 的 `file`
|
|
234
|
+
- 上限 10 个文件
|
|
235
|
+
|
|
236
|
+
**`hintsForAnalysis` 生成规则(规则引擎,非 LLM):**
|
|
237
|
+
|
|
238
|
+
- 重建 > 50 次 → `过度重建: {widget} @ {file}:{line}`
|
|
239
|
+
- jank > 5% → `掉帧率偏高: {jankPct}%`
|
|
240
|
+
- GC 长停顿 / 图片慢解码 / 滚动最差段 / 仅主 isolate 等 → 对应中文线索
|
|
241
|
+
- 慢请求 > 2s → `慢接口: {method} {url}`
|
|
242
|
+
- 堆利用率 > 85% → `内存利用率偏高`
|
|
243
|
+
- **不再**把框架 `drawFrame` 链路写入线索(对业务定位无帮助)
|
|
244
|
+
|
|
245
|
+
**CPU 数据来源字段:**
|
|
246
|
+
|
|
247
|
+
- `cpuProfileSource = vm-samples | timeline-dart | timeline-hotspot | none`
|
|
248
|
+
- 优先 `vm-samples`,为空时回退 Timeline
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 6.1 业务耗时方法统计
|
|
253
|
+
|
|
254
|
+
### 目标
|
|
255
|
+
|
|
256
|
+
避免报告被 Flutter 框架热点淹没,单独给出业务 `lib/` 方法 Top10。
|
|
257
|
+
|
|
258
|
+
### 规则
|
|
259
|
+
|
|
260
|
+
1. 从完整采样表过滤业务方法(`lib/`、`package:<app>/`,排除 SDK / `dart:ui` 误归一)
|
|
261
|
+
2. 业务条目即使不在全局 Top 也会合并进结果
|
|
262
|
+
3. 若为空,AI 报告显示「未命中业务方法」并给重采建议
|
|
263
|
+
4. AI 报告小节:`业务耗时方法 Top10(lib/)`;**开发者结论不展示框架 CPU Top**
|
|
264
|
+
|
|
265
|
+
### 已知限制
|
|
266
|
+
|
|
267
|
+
- Android profile 下 Self 时间常落在框架叶子,业务符号命中率依赖采样窗口与路径解析
|
|
268
|
+
- 建议 30~60s 采集,并在窗口内持续操作页面
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 6.2 扩展指标(GC / 滚动 / 图片 / Isolate)
|
|
273
|
+
|
|
274
|
+
详见 [`performance-metrics-guide.md`](./performance-metrics-guide.md) §3~§5。
|
|
275
|
+
|
|
276
|
+
| 字段 | 说明 |
|
|
277
|
+
|------|------|
|
|
278
|
+
| `gc` | Timeline GC:次数、停顿;用于尖刺与分配偏勤旁证 |
|
|
279
|
+
| `scrollFps` | 2s 分段 FPS/jank;帧事件不足时可能回退整体 FPS |
|
|
280
|
+
| `imageDecode` | 解码次数/耗时;`slow[].url` 需 `app.imageDecode` 埋点 |
|
|
281
|
+
| `isolateCpu` | 主/后台 isolate 采样对比 |
|
|
282
|
+
|
|
283
|
+
图片带 URL 的业务接入:[`../examples/app_network_image_README.md`](../examples/app_network_image_README.md)。
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 7. AI 优化方案输出模板
|
|
288
|
+
|
|
289
|
+
规则引擎生成的 `.ai.md` **必须**接近以下结构(实现见 `ai-analysis.ts`):
|
|
290
|
+
|
|
291
|
+
```markdown
|
|
292
|
+
# 性能优化方案(规则引擎 + 源码对照)
|
|
293
|
+
|
|
294
|
+
**场景**: … | **采集**: …s | **掉帧**: …%
|
|
295
|
+
|
|
296
|
+
## 运行时摘要
|
|
297
|
+
- 帧 [🟢/🟡/🔴/⚪]: … → …
|
|
298
|
+
- 重建 […]
|
|
299
|
+
- 耗时函数 […](Self >20ms → 异常)
|
|
300
|
+
- 滚动 / GC / 图片解码 / Isolate / 网络 / 内存 …
|
|
301
|
+
|
|
302
|
+
## 开发者结论(先看这里)
|
|
303
|
+
- [P0] … — 改法
|
|
304
|
+
- [P1] … — 改法
|
|
305
|
+
|
|
306
|
+
## P0(必须修复)
|
|
307
|
+
### …
|
|
308
|
+
- **数据依据**: …
|
|
309
|
+
- **原因**: …
|
|
310
|
+
- **改法**: …
|
|
311
|
+
|
|
312
|
+
## P1 / P2 …
|
|
313
|
+
## 耗时函数 Top10 / GC / 图片解码 / Isolate …
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
验证:改完后再采一轮,对比 jank、耗时函数、图片解码最长耗时。
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 8. 实现清单
|
|
321
|
+
|
|
322
|
+
| # | 模块 | 说明 |
|
|
323
|
+
|---|------|------|
|
|
324
|
+
| 1 | `services/rebuild-tracker-service.ts` | Widget 重建(debug) |
|
|
325
|
+
| 2 | `services/network-capture-service.ts` | HttpClient 网络 |
|
|
326
|
+
| 3 | `services/cpu-profiler-service.ts` | `getCpuSamples` + 业务过滤 |
|
|
327
|
+
| 4 | `services/timeline-extras.ts` | GC / 滚动 FPS / 图片解码 |
|
|
328
|
+
| 5 | `services/performance-session.ts` | 编排采集,产出 JSON |
|
|
329
|
+
| 6 | `services/ai-analysis.ts` | 规则引擎 `.ai.md`(开发者结论) |
|
|
330
|
+
| 7 | `tools/performance-session.ts` | MCP `collect_performance_session` |
|
|
331
|
+
| 8 | `examples/app_network_image.dart` | 图片 URL 埋点样板 |
|
|
332
|
+
|
|
333
|
+
**本期不做:** HTML 报告、健康评分、自动化场景、`performance-audit` 全套。
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 9. 前置条件
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# 终端 1:启动 App(推荐 profile)
|
|
341
|
+
flutter run --profile
|
|
342
|
+
|
|
343
|
+
# Cursor:配置 MCP 指向 dist/index.js
|
|
344
|
+
# 工作区:打开 Flutter 项目根目录(AI 才能读 lib/)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## 10. 异常处理
|
|
350
|
+
|
|
351
|
+
| 情况 | AI 回复要点 |
|
|
352
|
+
|------|-------------|
|
|
353
|
+
| 未发现 App | 提示先 `flutter run`,或粘贴 VM URI |
|
|
354
|
+
| 重复 start | 先说「已在采集中」,或先 stop 再 start |
|
|
355
|
+
| 未 start 就 stop | 提示先执行第一轮话术 |
|
|
356
|
+
| 采集 < 5s | 警告数据可能不足,建议重新采 30s |
|
|
357
|
+
| 网络为空 | 说明可能未走 dart:io,其他维度仍给方案 |
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 11. 验收标准
|
|
362
|
+
|
|
363
|
+
1. 第一轮话术后,MCP 返回「已开始采集」
|
|
364
|
+
2. 操作 30s 后第二轮话术,`stop` 返回合法 JSON
|
|
365
|
+
3. JSON 含 `topRebuilds[].line`、`topFunctions[].file`、`hintsForAnalysis`
|
|
366
|
+
4. AI 能根据 `filesToInspect` 打开对应 dart 文件并输出 P0/P1 方案
|
|
367
|
+
5. 原有 21 个工具行为不变
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 13. 回归命令(当前)
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# Android debug 回归
|
|
375
|
+
npm run test:regression:android
|
|
376
|
+
|
|
377
|
+
# Android profile 回归(默认时长)
|
|
378
|
+
npm run test:regression:android:profile
|
|
379
|
+
|
|
380
|
+
# Android profile 回归(30s)
|
|
381
|
+
npm run test:regression:android:profile:30s
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
> 说明:profile 模式不支持 Widget 重建追踪;帧数偶发偏低时,以 `vm-samples` + 网络 + AI 规则分析为准。
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 12. 对话速查卡
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
【一句搞定】
|
|
392
|
+
连接 Flutter App,开始采集性能数据 30s,场景:<场景名>,结合代码给优化方案
|
|
393
|
+
|
|
394
|
+
【你要做】
|
|
395
|
+
工具运行后立刻在 App 里操作约 30 秒(工具会自动等待并返回)
|
|
396
|
+
|
|
397
|
+
【AI 自动做】
|
|
398
|
+
返回 JSON → 读 filesToInspect → 输出 P0/P1/P2 优化方案
|
|
399
|
+
```
|