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,156 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function flattenWidgetTree(node, depth = 0, maxDepth = 15, projectOnly = false) {
|
|
3
|
+
if (depth > maxDepth)
|
|
4
|
+
return [];
|
|
5
|
+
const isProjectWidget = node.createdByLocalProject ?? false;
|
|
6
|
+
if (projectOnly && !isProjectWidget && depth > 2) {
|
|
7
|
+
const childResults = [];
|
|
8
|
+
for (const child of node.children ?? []) {
|
|
9
|
+
childResults.push(...flattenWidgetTree(child, depth, maxDepth, projectOnly));
|
|
10
|
+
}
|
|
11
|
+
return childResults;
|
|
12
|
+
}
|
|
13
|
+
const widgetName = node.creationLocation?.name ??
|
|
14
|
+
node.widgetRuntimeType ??
|
|
15
|
+
node.description ??
|
|
16
|
+
node.type ??
|
|
17
|
+
"Unknown";
|
|
18
|
+
const flat = {
|
|
19
|
+
type: widgetName,
|
|
20
|
+
depth,
|
|
21
|
+
id: node.valueId,
|
|
22
|
+
isProjectWidget,
|
|
23
|
+
childCount: node.children?.length ?? 0,
|
|
24
|
+
};
|
|
25
|
+
if (isProjectWidget && node.creationLocation?.file) {
|
|
26
|
+
const file = node.creationLocation.file.replace(/^file:\/\//, "");
|
|
27
|
+
const shortFile = file.split("/lib/").pop() ?? file.split("/").pop() ?? file;
|
|
28
|
+
flat.sourceFile = shortFile;
|
|
29
|
+
flat.sourceLine = node.creationLocation.line;
|
|
30
|
+
}
|
|
31
|
+
if (node.properties && node.properties.length > 0) {
|
|
32
|
+
flat.properties = node.properties
|
|
33
|
+
.filter((p) => p.description && p.description !== "null")
|
|
34
|
+
.slice(0, 10)
|
|
35
|
+
.map((p) => ({
|
|
36
|
+
name: p.name,
|
|
37
|
+
value: String(p.description ?? p.value ?? ""),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
const results = [flat];
|
|
41
|
+
for (const child of node.children ?? []) {
|
|
42
|
+
results.push(...flattenWidgetTree(child, depth + 1, maxDepth, projectOnly));
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
function formatTreeAsText(widgets) {
|
|
47
|
+
return widgets
|
|
48
|
+
.map((w) => {
|
|
49
|
+
const indent = " ".repeat(w.depth);
|
|
50
|
+
const projectMarker = w.isProjectWidget ? " ★" : "";
|
|
51
|
+
const childInfo = w.childCount > 0 ? ` (${w.childCount} children)` : "";
|
|
52
|
+
const sourceInfo = w.sourceFile ? ` [${w.sourceFile}:${w.sourceLine}]` : "";
|
|
53
|
+
let line = `${indent}${w.type}${projectMarker}${childInfo}${sourceInfo}`;
|
|
54
|
+
if (w.properties && w.properties.length > 0) {
|
|
55
|
+
const props = w.properties.map((p) => `${p.name}: ${p.value}`).join(", ");
|
|
56
|
+
line += ` [${props}]`;
|
|
57
|
+
}
|
|
58
|
+
return line;
|
|
59
|
+
})
|
|
60
|
+
.join("\n");
|
|
61
|
+
}
|
|
62
|
+
export function registerWidgetTreeTools(server, client) {
|
|
63
|
+
server.tool("get_widget_tree", "Get the current widget tree of the running Flutter app. Returns a structured representation of all widgets on screen. Widgets marked with ★ are from your project code (not framework widgets).", {
|
|
64
|
+
maxDepth: z
|
|
65
|
+
.number()
|
|
66
|
+
.min(1)
|
|
67
|
+
.max(50)
|
|
68
|
+
.default(15)
|
|
69
|
+
.describe("Maximum depth of the widget tree to return (default: 15)"),
|
|
70
|
+
projectOnly: z
|
|
71
|
+
.boolean()
|
|
72
|
+
.default(false)
|
|
73
|
+
.describe("If true, only show widgets created by the project (skip framework internals)"),
|
|
74
|
+
}, async ({ maxDepth, projectOnly }) => {
|
|
75
|
+
if (!client.connected) {
|
|
76
|
+
return {
|
|
77
|
+
content: [
|
|
78
|
+
{
|
|
79
|
+
type: "text",
|
|
80
|
+
text: "Not connected. Use the `connect` tool first.",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
isError: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const tree = (await client.getWidgetTree());
|
|
88
|
+
const flattened = flattenWidgetTree(tree, 0, maxDepth, projectOnly);
|
|
89
|
+
const text = formatTreeAsText(flattened);
|
|
90
|
+
const stats = {
|
|
91
|
+
totalWidgets: flattened.length,
|
|
92
|
+
projectWidgets: flattened.filter((w) => w.isProjectWidget).length,
|
|
93
|
+
maxDepthReached: Math.max(...flattened.map((w) => w.depth)),
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: `Widget Tree (${stats.totalWidgets} widgets, ${stats.projectWidgets} from project, depth: ${stats.maxDepthReached})\n${"─".repeat(60)}\n${text}`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: `Failed to get widget tree: ${error instanceof Error ? error.message : String(error)}`,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
isError: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
server.tool("inspect_widget", "Get detailed information about a specific widget by its ID (obtained from get_widget_tree). Returns render details, constraints, size, and state.", {
|
|
117
|
+
widgetId: z
|
|
118
|
+
.string()
|
|
119
|
+
.describe("The widget ID from the widget tree (valueId field)"),
|
|
120
|
+
}, async ({ widgetId }) => {
|
|
121
|
+
if (!client.connected) {
|
|
122
|
+
return {
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: "text",
|
|
126
|
+
text: "Not connected. Use the `connect` tool first.",
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
isError: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const details = await client.getWidgetDetails(widgetId);
|
|
134
|
+
return {
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: "text",
|
|
138
|
+
text: JSON.stringify(details, null, 2),
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{
|
|
147
|
+
type: "text",
|
|
148
|
+
text: `Failed to inspect widget: ${error instanceof Error ? error.message : String(error)}`,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
isError: true,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=widget-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-tree.js","sourceRoot":"","sources":["../../src/tools/widget-tree.ts"],"names":[],"mappings":"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"}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# 文档索引
|
|
2
|
+
|
|
3
|
+
| 文档 | 受众 | 说明 |
|
|
4
|
+
|------|------|------|
|
|
5
|
+
| [performance-metrics-guide.md](./performance-metrics-guide.md) | **开发者(优先读)** | 指标含义、报告怎么读、GC/重建/图片解码、profile vs debug |
|
|
6
|
+
| [performance-session-simple-design.md](./performance-session-simple-design.md) | 产品 / 实现 | `collect_performance_session` 交互、参数、数据结构 |
|
|
7
|
+
| [performance-audit-requirements.md](./performance-audit-requirements.md) | 规划 | 重型 MD/HTML 审计(P2,非当前主路径) |
|
|
8
|
+
| [../examples/app_network_image_README.md](../examples/app_network_image_README.md) | 业务接入 | 统一图片入口,解码带 URL |
|
|
9
|
+
| [../README.zh-CN.md](../README.zh-CN.md) | 全员 | 安装、快速开始、工具表、回归命令 |
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Flutter App 全方位性能测试报告 — 需求开发文档
|
|
2
|
+
|
|
3
|
+
> 状态说明(2026-07):本方案属于重型审计设计(Markdown+HTML / 自动化场景),当前仓库已优先落地轻量方案 `collect_performance_session`。
|
|
4
|
+
> **实际开发与回归请优先参考:**
|
|
5
|
+
> - [`performance-metrics-guide.md`](./performance-metrics-guide.md)(指标怎么读)
|
|
6
|
+
> - [`performance-session-simple-design.md`](./performance-session-simple-design.md)(会话设计)
|
|
7
|
+
> 本文件作为 P2 规划保留。
|
|
8
|
+
|
|
9
|
+
## 1. 背景与目标
|
|
10
|
+
|
|
11
|
+
### 1.1 背景
|
|
12
|
+
|
|
13
|
+
`flutter-devtools-mcp` 已通过 MCP 协议将 AI 助手(Cursor、Claude 等)连接到运行中的 Flutter App,提供 21 个独立调试工具(Widget 树、重建追踪、性能分析、内存、网络等)。
|
|
14
|
+
|
|
15
|
+
**现状问题**:
|
|
16
|
+
|
|
17
|
+
- 各能力分散,需多次 `start/stop` 手动串联
|
|
18
|
+
- 报告为 MCP 对话内纯文本,无法存档、对比、分享
|
|
19
|
+
- 重建追踪、网络抓包的状态写在 tool 闭包内,无法被统一编排复用
|
|
20
|
+
|
|
21
|
+
### 1.2 目标
|
|
22
|
+
|
|
23
|
+
新增**统一性能审计**能力,在一次会话中并行采集多维度数据,生成可存档的 **Markdown + HTML** 全方位性能报告。
|
|
24
|
+
|
|
25
|
+
### 1.3 已确认的产品决策
|
|
26
|
+
|
|
27
|
+
| 决策项 | 选择 |
|
|
28
|
+
|--------|------|
|
|
29
|
+
| 报告格式 | Markdown + HTML 双格式导出 |
|
|
30
|
+
| 采集模式 | 第一阶段:手动操作会话;第二阶段:自动化场景驱动 |
|
|
31
|
+
| 基础平台 | 基于现有 `flutter-devtools-mcp` 扩展,不另起项目 |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. 用户故事
|
|
36
|
+
|
|
37
|
+
### 2.1 手动审计(P0)
|
|
38
|
+
|
|
39
|
+
> 作为 Flutter 开发者,我希望在 Cursor 中说「开始性能审计」,在 App 里操作 10–30 秒后说「停止并生成报告」,自动得到包含帧率、重建、内存、网络、**Top 耗时函数**的完整报告文件。
|
|
40
|
+
|
|
41
|
+
### 2.2 报告存档与分享(P0)
|
|
42
|
+
|
|
43
|
+
> 作为团队负责人,我希望报告以 `.md` / `.html` 落地到本地目录,便于 PR 附件、飞书分享、版本对比。
|
|
44
|
+
|
|
45
|
+
### 2.3 自动化审计(P1,骨架)
|
|
46
|
+
|
|
47
|
+
> 作为 QA,我希望预置场景(如首页滚动、列表加载)能通过 integration_test 自动驱动,无需人工操作。
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 3. 功能范围
|
|
52
|
+
|
|
53
|
+
### 3.1 本期做(In Scope)
|
|
54
|
+
|
|
55
|
+
| 模块 | 内容 |
|
|
56
|
+
|------|------|
|
|
57
|
+
| Collector 服务化 | 抽取 `RebuildTracker`、`NetworkCapture`、`MemoryCollector` |
|
|
58
|
+
| CPU 函数采样 | `CpuProfileAnalyzer`:VM `getCpuSamples` 解析 Top 耗时函数 |
|
|
59
|
+
| 报告模型 | 统一 `PerformanceReport` 数据结构 |
|
|
60
|
+
| 报告格式化 | Markdown + HTML 生成、健康评分、综合建议 |
|
|
61
|
+
| 审计编排 | `PerformanceAuditSession` 并行启停采集器 |
|
|
62
|
+
| MCP 工具 | `start_performance_audit`、`stop_performance_audit` |
|
|
63
|
+
| 文件导出 | 写入 `./performance-reports/` |
|
|
64
|
+
| 自动化 | 可运行 Dart 示例 + MCP `list_audit_scenarios` / `run_automated_audit` |
|
|
65
|
+
| 文档 | README 更新 |
|
|
66
|
+
|
|
67
|
+
### 3.2 本期不做(Out of Scope)
|
|
68
|
+
|
|
69
|
+
- Shader 编译 jank 检测
|
|
70
|
+
- 实时 jank 监控
|
|
71
|
+
- PDF 导出
|
|
72
|
+
- npm publish
|
|
73
|
+
- 图形化火焰图 / 调用树可视化(仅表格 Top N)
|
|
74
|
+
- 完整 Patrol / integration_test 端到端自动化
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 4. 系统架构
|
|
79
|
+
|
|
80
|
+
```mermaid
|
|
81
|
+
flowchart TB
|
|
82
|
+
subgraph client [用户侧]
|
|
83
|
+
Cursor[Cursor_AI]
|
|
84
|
+
FlutterApp[Flutter_App_profile_mode]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
subgraph mcp [flutter_devtools_mcp]
|
|
88
|
+
Tools[MCP_Tools]
|
|
89
|
+
Audit[PerformanceAuditSession]
|
|
90
|
+
Profiler[Profiler]
|
|
91
|
+
CpuProfile[CpuProfileAnalyzer]
|
|
92
|
+
Rebuild[RebuildTracker]
|
|
93
|
+
Network[NetworkCapture]
|
|
94
|
+
Memory[MemoryCollector]
|
|
95
|
+
Formatter[ReportFormatter]
|
|
96
|
+
Writer[ReportWriter]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
Cursor -->|stdio| Tools
|
|
100
|
+
Tools --> Audit
|
|
101
|
+
Audit --> Profiler
|
|
102
|
+
Audit --> CpuProfile
|
|
103
|
+
Audit --> Rebuild
|
|
104
|
+
Audit --> Network
|
|
105
|
+
Audit --> Memory
|
|
106
|
+
Audit --> Formatter
|
|
107
|
+
Formatter --> Writer
|
|
108
|
+
Tools -->|WebSocket| FlutterApp
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 4.1 与现有架构关系
|
|
112
|
+
|
|
113
|
+
- **不破坏**现有 21 个 MCP 工具 API
|
|
114
|
+
- 新 Collector 服务供「旧 tool」和「新 audit」共用
|
|
115
|
+
- 底层仍走 Dart VM Service Protocol(与 DevTools 相同)
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 5. 报告内容设计(8 节)
|
|
120
|
+
|
|
121
|
+
| 章节 | 数据来源 | 核心指标 |
|
|
122
|
+
|------|----------|----------|
|
|
123
|
+
| 1. 执行摘要 | 综合计算 | 健康评分 0–100、严重问题数、Top 3 修复项 |
|
|
124
|
+
| 2. 环境信息 | `get_app_info` | VM 版本、平台、isolate、刷新率、profile 模式提示 |
|
|
125
|
+
| 3. 帧性能 | `Profiler` | FPS、jank%、P90/P99、Build/Layout/Paint、Timeline CPU 热点 |
|
|
126
|
+
| 4. **Top 耗时函数** | `CpuProfileAnalyzer` | 函数名、文件、Self 耗时、占比、严重等级(项目内 + 全局) |
|
|
127
|
+
| 5. Widget 重建 | `RebuildTracker` | Top N 重建 Widget、源码位置、严重等级 |
|
|
128
|
+
| 6. 内存 | `MemoryCollector` | 堆用量/利用率、Top 类、可疑分配 |
|
|
129
|
+
| 7. 网络 | `NetworkCapture` | 请求数、错误率、慢请求、大响应 |
|
|
130
|
+
| 8. 综合建议 | 跨维度合并 | P0/P1/P2 分级建议 |
|
|
131
|
+
|
|
132
|
+
### 5.1 Top 耗时函数(CPU 采样)
|
|
133
|
+
|
|
134
|
+
与 DevTools CPU Profiler 同源,通过 VM Service 采样实现**函数级**定位(非 Timeline 事件名)。
|
|
135
|
+
|
|
136
|
+
**采集流程**:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
start:setFlag('profiler', 'true') + getVMTimelineMicros() 记录起始时间
|
|
140
|
+
stop:getCpuSamples(isolateId, origin, extent) + setFlag('profiler', 'false')
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**报告输出示例**:
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
## 四、Top 耗时函数(CPU 采样)
|
|
147
|
+
|
|
148
|
+
### 项目内 Top 20(Self 时间)
|
|
149
|
+
| 排名 | 函数 | 文件 | Self 耗时 | 占比 | 等级 |
|
|
150
|
+
| 1 | _OrderListState.build | order_list.dart | 420ms | 18% | 严重 |
|
|
151
|
+
| 2 | FeedRepository.fetchFeed | feed_repo.dart | 280ms | 12% | 高 |
|
|
152
|
+
|
|
153
|
+
### 全局 Top 10(含框架)
|
|
154
|
+
| 1 | Build | flutter/framework.dart | 890ms | 38% | 严重 |
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**过滤规则**:
|
|
158
|
+
|
|
159
|
+
- 默认展示 **项目内 Top N**(`lib/` 路径,排除 `dart:`、`package:flutter/` 等)
|
|
160
|
+
- 附加 **全局 Top 10** 供参考
|
|
161
|
+
- 按 `exclusiveTicks`(Self 时间)排序,同时记录 `inclusiveTicks`(含子调用)
|
|
162
|
+
|
|
163
|
+
**限制**:
|
|
164
|
+
|
|
165
|
+
- 需在 debug/profile 模式运行(Release 无 VM Service)
|
|
166
|
+
- 性能数据建议在 **profile 模式** 下采集
|
|
167
|
+
- 极短函数可能被采样间隔漏掉(`profile_period` 最小 50μs)
|
|
168
|
+
- 不提供火焰图,仅表格 Top N
|
|
169
|
+
|
|
170
|
+
### 5.2 健康评分规则(初版)
|
|
171
|
+
|
|
172
|
+
| 维度 | 权重 | 扣分逻辑 |
|
|
173
|
+
|------|------|----------|
|
|
174
|
+
| Jank | 30% | jank% > 5% 开始扣分,> 20% 严重 |
|
|
175
|
+
| Top 函数 | 15% | 单函数 Self 时间占比 > 15% 或 Self > 500ms |
|
|
176
|
+
| 重建 | 20% | 单 Widget > 100 次重建扣分 |
|
|
177
|
+
| 内存 | 20% | 堆利用率 > 80% 或可疑类 > 500 实例 |
|
|
178
|
+
| 网络 | 15% | 错误率 > 0 或慢请求 > 3 个 |
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 6. MCP 工具设计
|
|
183
|
+
|
|
184
|
+
### 6.1 新增工具
|
|
185
|
+
|
|
186
|
+
#### `start_performance_audit`
|
|
187
|
+
|
|
188
|
+
| 参数 | 类型 | 默认 | 说明 |
|
|
189
|
+
|------|------|------|------|
|
|
190
|
+
| `scenarioName` | string? | — | 场景名,用于报告命名 |
|
|
191
|
+
| `enableNetwork` | boolean | true | 是否采集网络 |
|
|
192
|
+
| `forceGCOnMemory` | boolean | true | 停止时是否先 GC 再取内存 |
|
|
193
|
+
| `includeWidgetTreeSummary` | boolean | true | 是否在 start 时采集 Widget 树概览(约 1–2s) |
|
|
194
|
+
| `enableCpuProfiling` | boolean | true | 是否开启 CPU 函数采样(`getCpuSamples`) |
|
|
195
|
+
|
|
196
|
+
**行为**:并行启动 Profiler + RebuildTracker + NetworkCapture + CPU Profiler,记录 `appInfo`;若 `includeWidgetTreeSummary` 为 true,采集 Widget 树概览(总数、项目 widget 数、最大深度)。CPU Profiler 通过 `setFlag('profiler', 'true')` 开启,并记录 `getVMTimelineMicros()` 起始时间。
|
|
197
|
+
|
|
198
|
+
#### `stop_performance_audit`
|
|
199
|
+
|
|
200
|
+
| 参数 | 类型 | 默认 | 说明 |
|
|
201
|
+
|------|------|------|------|
|
|
202
|
+
| `outputDir` | string | `./performance-reports` | 输出目录(支持相对路径或绝对路径) |
|
|
203
|
+
| `reportName` | string? | scenarioName 或 `audit` | 报告基础名 |
|
|
204
|
+
| `topN` | number | 20 | 各 Top 列表条数 |
|
|
205
|
+
|
|
206
|
+
**行为**:停止采集 → `getCpuSamples` 取函数采样 → 取内存快照 → 组装报告 → 写 `.md` + `.html` → 返回路径与摘要。停止时 `setFlag('profiler', 'false')` 关闭 CPU 采样。
|
|
207
|
+
|
|
208
|
+
### 6.2 自动化(第一期含可运行示例)
|
|
209
|
+
|
|
210
|
+
| 工具 | 说明 |
|
|
211
|
+
|------|------|
|
|
212
|
+
| `list_audit_scenarios` | 读取 `default-scenarios.json` 列出预置场景 |
|
|
213
|
+
| `run_automated_audit` | 通过 `evaluate_expression` 调用 App 侧 `DevToolsAuditRunner.runScenario(name)`;未集成时返回接入指引 |
|
|
214
|
+
|
|
215
|
+
配套交付 `examples/flutter_audit_runner.dart`,含至少 `home-scroll` 可执行场景。
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 7. 文件结构(计划新增)
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
flutter-devtools-mcp/
|
|
223
|
+
├── src/
|
|
224
|
+
│ ├── services/
|
|
225
|
+
│ │ ├── rebuild-tracker.ts # 新建
|
|
226
|
+
│ │ ├── network-capture.ts # 新建
|
|
227
|
+
│ │ ├── memory-collector.ts # 新建
|
|
228
|
+
│ │ ├── cpu-profile-analyzer.ts # 新建:getCpuSamples 解析 Top 函数
|
|
229
|
+
│ │ ├── report-types.ts # 新建
|
|
230
|
+
│ │ ├── report-formatter.ts # 新建
|
|
231
|
+
│ │ ├── report-writer.ts # 新建
|
|
232
|
+
│ │ └── performance-audit.ts # 新建
|
|
233
|
+
│ │ # vm-service-client.ts 扩展:setFlag、getVMTimelineMicros、getCpuSamples
|
|
234
|
+
│ ├── tools/
|
|
235
|
+
│ │ ├── performance-audit.ts # 新建
|
|
236
|
+
│ │ └── automated-audit.ts # 新建(stub)
|
|
237
|
+
│ └── scenarios/
|
|
238
|
+
│ └── default-scenarios.json # 新建
|
|
239
|
+
├── examples/
|
|
240
|
+
│ └── flutter_audit_runner.dart # 新建
|
|
241
|
+
└── performance-reports/ # gitignore
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 8. 开发阶段与任务拆分
|
|
247
|
+
|
|
248
|
+
| 阶段 | 任务 | 预估 | 依赖 |
|
|
249
|
+
|------|------|------|------|
|
|
250
|
+
| **Phase 1** | 抽取 3 个 Collector 服务,改造现有 tool | 1d | — |
|
|
251
|
+
| **Phase 1.5** | VM CPU 采样:`setFlag` / `getCpuSamples` / `CpuProfileAnalyzer` | 1d | Phase 1 |
|
|
252
|
+
| **Phase 2** | `report-types` + `report-formatter`(MD/HTML/评分,含 Top 函数节) | 1d | Phase 1.5 |
|
|
253
|
+
| **Phase 3** | `PerformanceAuditSession` + MCP 工具 | 1d | Phase 1–2 |
|
|
254
|
+
| **Phase 4** | `report-writer` + `.gitignore` | 0.5d | Phase 2 |
|
|
255
|
+
| **Phase 5** | 自动化骨架 + Dart 示例 | 0.5d | Phase 3 |
|
|
256
|
+
| **Phase 6** | README + build + 手动 E2E 验证 | 0.5d | 全部 |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 9. 使用流程
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# 1. App 以 profile 模式运行(性能数据更准确)
|
|
264
|
+
flutter run --profile
|
|
265
|
+
|
|
266
|
+
# 2. Cursor 对话
|
|
267
|
+
# "连接我的 Flutter App" → discover_apps / connect
|
|
268
|
+
# "开始全方位性能审计,场景 home-scroll" → start_performance_audit
|
|
269
|
+
# (用户在 App 中滚动、切换页面 10–30 秒)
|
|
270
|
+
# "停止审计并生成报告" → stop_performance_audit
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**输出示例**:
|
|
274
|
+
|
|
275
|
+
- `./performance-reports/home-scroll-2026-07-09T11-50-00.md`
|
|
276
|
+
- `./performance-reports/home-scroll-2026-07-09T11-50-00.html`
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## 10. 风险与限制
|
|
281
|
+
|
|
282
|
+
| 风险 | 影响 | 缓解 |
|
|
283
|
+
|------|------|------|
|
|
284
|
+
| debug 模式性能数据失真 | 帧率/jank 偏高 | 报告内提示「建议 profile 模式」 |
|
|
285
|
+
| 网络仅覆盖 `dart:io` | Ferry 等客户端抓不到 | 报告标注「未检测到请求」及原因 |
|
|
286
|
+
| Collector 状态冲突 | 审计中与单独 tool 互斥 | 审计进行中禁止重复 start;文档说明 |
|
|
287
|
+
| CPU 采样漏掉极短函数 | 部分耗时函数未出现在 Top 列表 | 报告注明采样机制;建议审计时长 ≥ 10s |
|
|
288
|
+
| 无 Flutter App 联调 | E2E 难验证 | 先 `npm run build`,有 App 时再做手动 E2E |
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 11. 验收标准
|
|
293
|
+
|
|
294
|
+
1. `npm run build` 编译通过,无 TypeScript 错误
|
|
295
|
+
2. 连接 profile 模式 App 后,`start` → 操作 → `stop` 能生成 MD + HTML
|
|
296
|
+
3. 报告含 **8 节**内容(含 Top 耗时函数)+ 健康评分 + 综合建议
|
|
297
|
+
4. Top 耗时函数节包含项目内 Top N 与全局 Top 10,含函数名与文件路径
|
|
298
|
+
5. 原有 `start_profiling`、`stop_tracking_rebuilds` 等工具行为不变
|
|
299
|
+
6. 边界:未连接、重复 start、无网络请求、CPU 采样为空时报告仍完整生成
|
|
300
|
+
7. README 含新工具说明与使用示例
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## 12. 已确认决策(2026-07-09)
|
|
305
|
+
|
|
306
|
+
以下事项已讨论确认,开发按此执行。
|
|
307
|
+
|
|
308
|
+
### 12.1 报告输出目录
|
|
309
|
+
|
|
310
|
+
**决策**:默认 `./performance-reports`,同时支持 `outputDir` 传入**绝对路径**;不单独做「项目根目录配置文件」。
|
|
311
|
+
|
|
312
|
+
**理由**:
|
|
313
|
+
|
|
314
|
+
- `./performance-reports` 作为相对路径默认值足够:MCP 进程 cwd 通常是 Cursor 打开的工作区根目录,报告自然落在项目内,便于 gitignore 和 PR 附件。
|
|
315
|
+
- `stop_performance_audit` 的 `outputDir` 参数已可覆盖为绝对路径(如 `/Users/xxx/reports`),满足 CI、多项目等场景,无需额外 config 文件。
|
|
316
|
+
- 第一期不做 `.flutter-devtools.json` 等持久化配置,避免增加维护面;若后续有强需求再扩展。
|
|
317
|
+
|
|
318
|
+
**实现约定**:
|
|
319
|
+
|
|
320
|
+
- 默认:`./performance-reports`
|
|
321
|
+
- 相对路径:相对于 MCP 进程 cwd 解析
|
|
322
|
+
- 绝对路径:原样使用
|
|
323
|
+
- 目录不存在时自动创建
|
|
324
|
+
|
|
325
|
+
### 12.2 健康评分展示
|
|
326
|
+
|
|
327
|
+
**决策**:本期**仅单次评分**,不做历史对比与趋势图。
|
|
328
|
+
|
|
329
|
+
**理由**:
|
|
330
|
+
|
|
331
|
+
- 历史对比需要持久化多次审计结果(索引文件或数据库),复杂度和边界(同场景不同分支、不同设备)较高。
|
|
332
|
+
- 单次评分 + 文件名带时间戳,已支持人工对比多次报告文件。
|
|
333
|
+
- 趋势对比可作为 P2 能力(读取 `performance-reports/` 下历史 JSON 元数据)。
|
|
334
|
+
|
|
335
|
+
### 12.3 Widget 树摘要
|
|
336
|
+
|
|
337
|
+
**决策**:**默认开启**,审计 `start` 时采集 Widget 树概览;提供参数 `includeWidgetTreeSummary`(默认 `true`)可关闭。
|
|
338
|
+
|
|
339
|
+
**理由**:
|
|
340
|
+
|
|
341
|
+
- 1–2s 额外耗时相对整段 10–30s 审计可接受,且只在 start 时发生一次。
|
|
342
|
+
- 概览(总 widget 数、项目 widget 数、最大深度)对报告「环境快照」有价值,便于判断页面复杂度是否异常。
|
|
343
|
+
- 可关闭选项兼顾大页面或仅需帧率/内存的轻量审计。
|
|
344
|
+
|
|
345
|
+
**采集内容**(轻量,不拉完整树):
|
|
346
|
+
|
|
347
|
+
- 总 widget 数量
|
|
348
|
+
- 项目内 widget 数量(`createdByLocalProject`)
|
|
349
|
+
- 树最大深度
|
|
350
|
+
- 当前根页面主要 Screen 名称(若可解析)
|
|
351
|
+
|
|
352
|
+
### 12.4 自动化优先级
|
|
353
|
+
|
|
354
|
+
**决策**:第一期交付**可运行的 Dart 示例**(`examples/flutter_audit_runner.dart`),含至少 1 个可执行场景(如 `home-scroll`);MCP 侧 `list_audit_scenarios` + `run_automated_audit` 与示例打通。
|
|
355
|
+
|
|
356
|
+
**范围说明**:
|
|
357
|
+
|
|
358
|
+
- 示例为**可复制到业务 App** 的 mixin + integration_test 样板,不强制业务方已集成。
|
|
359
|
+
- `run_automated_audit` 通过 `evaluate_expression` 调用 `DevToolsAuditRunner.runScenario(name)`;App 未集成时返回明确错误与接入指引。
|
|
360
|
+
- 完整 Patrol 编排、CI 流水线集成留 P2。
|
|
361
|
+
|
|
362
|
+
### 12.5 报告语言
|
|
363
|
+
|
|
364
|
+
**决策**:全方位性能报告正文**统一中文**(执行摘要、章节标题、建议、verdict)。
|
|
365
|
+
|
|
366
|
+
**说明**:
|
|
367
|
+
|
|
368
|
+
- 新增 `start/stop_performance_audit` 相关 MCP 工具描述与返回摘要也用中文。
|
|
369
|
+
- 现有 21 个独立工具(`stop_profiling` 等)**本期不改动**,避免大范围回归;后续可单独做 i18n。
|
|
370
|
+
|
|
371
|
+
### 12.6 Top 耗时函数(CPU 采样)
|
|
372
|
+
|
|
373
|
+
**决策**:第一期纳入报告,作为独立章节「Top 耗时函数」,基于 VM Service `getCpuSamples` 实现。
|
|
374
|
+
|
|
375
|
+
**理由**:
|
|
376
|
+
|
|
377
|
+
- 用户需定位业务函数级耗时(如 `fetchFeed`、`jsonDecode`),Timeline 事件热点无法满足。
|
|
378
|
+
- 与 DevTools CPU Profiler 同源,技术成熟;审计会话天然覆盖采样时间窗。
|
|
379
|
+
- 实现成本约 1 人天,与审计编排一并交付。
|
|
380
|
+
|
|
381
|
+
**实现约定**:
|
|
382
|
+
|
|
383
|
+
- `start` 时 `setFlag('profiler', 'true')`,记录 `getVMTimelineMicros()`
|
|
384
|
+
- `stop` 时 `getCpuSamples` 拉取采样,`setFlag('profiler', 'false')` 关闭
|
|
385
|
+
- 报告分「项目内 Top N」与「全局 Top 10」两表
|
|
386
|
+
- 参数 `enableCpuProfiling`(默认 `true`)可关闭
|
|
387
|
+
- 健康评分增加「Top 函数」维度(权重 15%)
|
|
388
|
+
|
|
389
|
+
**不做**:
|
|
390
|
+
|
|
391
|
+
- 火焰图 / 调用树可视化(P2)
|
|
392
|
+
- 精确到源码行号(首期到文件级;行号解析 `tokenPos` 可后续增强)
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 13. 小结
|
|
397
|
+
|
|
398
|
+
| 项目 | 说明 |
|
|
399
|
+
|------|------|
|
|
400
|
+
| **做什么** | 统一性能审计 + MD/HTML 报告 + **Top 耗时函数(CPU 采样)** |
|
|
401
|
+
| **核心价值** | 一次会话、一份报告、可定位到函数/文件/Widget 行号 |
|
|
402
|
+
| **工作量** | 约 5–6 人天 |
|
|
403
|
+
| **当前状态** | 已由 [`performance-session-simple-design.md`](./performance-session-simple-design.md) 取代为 P0;本文档为 P2 存档报告参考 |
|