Flutter 基准性能看板(Dashboards)使用指南:在 Skia Perf 中检索与解读框架与引擎基准测试数据
Flutter 项目在 CI 与真机 DeviceLab 上持续运行大量性能基准测试(benchmark),并将结果汇总到性能看板中供开发者追踪回归与验证优化。本文以仓库内 Dashboards.md 为骨架,完整讲解 Flutter 看板体系的总览入口、如何使用两份 Skia Performance 数据源精确检索某一基准测试的历史曲线,并逐个剖析数据集参数(sub_result、branch、config、originId、test、unit)的真实含义;最后结合 devicelab 的源码,揭示"测试结果 JSON 中的每个 key 如何变成 Skia Perf 中的一个 sub_result"这条完整数据链路。读完本文,你将能够熟练定位任意框架/引擎基准的走势、理解看板上每个字段的来源,并能在仓库中追溯相关数据的生成与上传代码。
一、Flutter 看板体系总览:从哪个入口开始
Flutter 工程效能(EngProd)团队将各种 CI、构建、测试状态类仪表盘汇总在一个索引页中,总入口为 https://flutter-dashboard.appspot.com/。该入口聚合了多种用途的子看板,常见的一类就是本文后续重点讲解的 Flutter DeviceLab 构建/任务状态看板(路径形如 /#/build),它展示了真机任务(tasks)在 CI 上的最新状态、通过率、logs 链接与构建产物。这些任务即 devicelab 中运行的真机测试,也被称为"tasks",相关索引文档统一收录于 docs/infra/README.md(Dashboards、Autorollers、Flutter-Framework-Gardener-Rotation 等团队文档在此均可找到入口)。
理解这个总览入口很有必要,因为后面要用的"数据文件(timeline)下载"、"某次提交的构建页"等能力都挂在 /#/build 视图上,而性能曲线则存放在另一套独立的 Skia Perf 系统中。
二、Skia Performance Dashboard:框架与引擎两份数据源
Skia Perf 是负责存储与绘制性能时间序列的可视化系统。Flutter 为其提供两类数据来源,对应两个彼此独立的数据集:
| 数据源 | 覆盖范围 | 数据来源 |
|---|---|---|
https://flutter-flutter-perf.skia.org/e/ |
由运行 flutter/flutter(框架仓库)测试得出的基准 | 框架仓库的 devicelab / CI 基准测试任务 |
https://flutter-engine-perf.skia.org/e/ |
由 flutter/engine(引擎仓库)测试得出的基准 | 引擎仓库的基准测试任务 |
因此,在查询之前应当先判断目标基准测试归属于框架侧还是引擎侧。例如 flutter_gallery__transition_perf 这类基于 dev/benchmarks/macrobenchmarks 的 e2e 性能任务属于框架仓库数据,应在 flutter-flutter-perf 中检索;而引擎源码树内的纯引擎 benchmark(见 engine/src/flutter/benchmarking 等目录)则上报到 flutter-engine-perf。
三、逐条查询某个具体基准测试:完整操作步骤
每份数据集(dataset)本质上是一组"日期/数值"对,并关联一组参数,这些参数包括被测分支(branch)、基准测试名("test")、具体指标值名("sub_result")等。当你想查看某条特定指标随时间的变化曲线时,按下列步骤在 Skia Perf 中组合筛选条件:
- 在页面上点击 Query。
- 弹出对话框,此时 "Filter" 文本输入框处于聚焦状态。在框内输入你记忆中基准测试名称的片段(用于模糊过滤候选列表)。
- 在列表框中点击 "test" 项,界面会出现第二个列表框。
- 从第二个列表框中选择你关心的那条具体测试。
- 重新聚焦 "Filter" 文本框,输入你关心的具体数据点名称(即 sub_result 名称片段)。
- 点击列表框中的 "sub_result" 项。
- 选择你关心的具体 sub_result。
- 点击 "Time Range",再点击 "Date Range"。
- 点击 "Begin" 文本框旁的日历图标来选择开始日期(不要直接键入日期,直接输入会触发已知 bug,导致范围不生效)。
- 选择你关心的开始日期。
- 点击 "Plot" 绘制曲线。
绘制完成后,可以使用键盘上的 WASD 键来平移 X 轴(时间轴),便于查看曲线在某一区间的细节走势。这一套筛选流程的实质,就是"先锁定 test → 再锁定 sub_result → 再锁定时间范围",后面讲到的数据集参数会帮助你理解为什么筛选层级如此组织。
四、数据集参数详解:sub_result / branch / config / originId / test / unit
一份数据集的全部参数如下表所示,其中大部分来自每次任务上报指标时附带的一组"标签(tags)":
| 参数 | 含义与取值说明 |
|---|---|
sub_result |
该测试提供的具体数据流名称。测试可在一个 JSON map 中产出多个数据点,map 中的每一个 key 都会成为 Skia Perf 系统中的一个 sub_result |
branch |
被测分支,即该次运行所在的 git 分支 |
config |
固定为 "default" |
originId |
固定为 "devicelab";只有从旧数据库迁移过来的历史数据才标记为 "legacy-flutter" |
test |
执行采集数据的测试任务的名称 |
unit |
理论上为数据采集时的单位(因此每个 sub_result 只应关联一种单位);实际上该参数经常不准确 |
几个易误解点需要特别说明:
sub_result与test是两层概念。一个 test(如complex_layout_scroll_perf)在一次运行中会产出多个数值指标(如average_frame_build_time_millis、90th_percentile_frame_build_time_millis),每个指标各自是一条独立的曲线(一个 sub_result)。所以查询时要先选中 test,再从中挑选 sub_result。unit参数仅供参考。按官方文档原话,它"在实践中经常不正确"(in practice this parameter is often incorrect),因此判断指标含义时请以测试代码中该 key 的实际定义为准,不要盲信unit字段显示的数值单位。originId用于区分新旧两套数据管道。凡是历史从旧数据库迁移而来的数据都被标记为legacy-flutter,其余新采集数据均为devicelab,可用于在查询时排除或对比历史遗留数据。
五、从源码看参数如何产生:JSON 结果如何变成 sub_result
文档中"每个 key 成为一个 sub_result"的机制,可以在仓库源码中得到直接印证。这条链路分为三步:任务产出数据 → 结果序列化上报 → Cocoon/Skia Perf 解析为带标签的数据点。
第一步:任务以 JSON map 形式汇报成功结果。 task_result.dart 中 TaskResult.success(data, benchmarkScoreKeys: ...) 接受一个 Map<String, dynamic>? data,其中的 benchmarkScoreKeys 声明"data 中哪些 key 是会上报到 Cocoon 的分数",且构造时会逐一校验:key 必须真实存在于 data 中,且对应值必须是 num 类型,否则直接抛错。也就是说,最终会成为 sub_result 的每一个 key 在任务端就已受严格类型约束。
第二步:基准任务把运行时采集的指标写入该 map。 例如 perf_tests.dart 中的某个 e2e 性能任务,会把 janky_count、average_abs_jerk、dropped_frame_count 等指标连同后缀(如 _with_resampler_90Hz)写入 result map,最后以 TaskResult.success(result, benchmarkScoreKeys: result.keys.toList()) 返回——data 中每个 key 即刻成为待上报的候选 sub_result。devicelab 中几乎每个性能任务都遵循同一模式(如 gallery.dart、web_benchmarks.dart 等均调用 TaskResult.success(...) 并显式给出 benchmarkScoreKeys)。
第三步:CI 把结果文件转换为 Skia Perf 指标点。 upload_metrics.dart 定义了 upload-metrics 命令,接收 --results-file、--commit-time、--task-name、--benchmark-tags 四个参数。真正负责转换的是 metrics_center.dart 中的 parse(),其典型输入形如:
{
"CommitBranch": "master",
"CommitSha": "abc",
"BuilderName": "test",
"ResultData": {
"average_frame_build_time_millis": 0.4550425531914895,
"90th_percentile_frame_build_time_millis": 0.473
},
"BenchmarkScoreKeys": [
"average_frame_build_time_millis",
"90th_percentile_frame_build_time_millis"
]
}
parse() 对 BenchmarkScoreKeys 中的每个 key 生成一个 MetricPoint,并附带一组与本文第四节的参数一一对应的标签(见 metrics_center.dart):
kGithubRepoKey+kGitRevisionKey:标识仓库与提交哈希;branch:取resultsJson['CommitBranch'],即 Skia Perf 中的 branch 参数;kNameKey(值取 taskName):即 Skia Perf 中的 test 参数;kSubResultKey(值取当前 scoreKey):即 Skia Perf 中的 sub_result 参数。
额外的一组 benchmarkTags(如 arch、device_type、host_type 等,见 metrics_center.dart 注释示例)会被合并进标签,用来在前端进一步细分"同一任务在不同机型/架构/宿主上的运行结果"。由此可以清楚看到:文档第四节的 test、sub_result、branch 三个参数,正是上述 tags 在看板前端的直接呈现。
MetricPoint 随后由 upload() 批量写入 GCS 桶(见 metrics_center.dart),Skia Perf 会抓取该目录下的全部文件,且对重复条目具有鲁棒性。文件命名按 taskName 拼上 arch/host_type/device_type 生成,例如 complex_layout_scroll_perf__timeline_summary_values.json(见 metricFileName),同一 task 在不同平台上运行也会落到不同文件名,避免相互覆盖。
六、配套实战:从构建看板找到某个基准的 timeline 数据
性能曲线看板解决"看趋势",而定位某一次具体提交的原始 trace 则需要回到构建看板。官方配套文档 How-to-download-a-timeline-from-a-benchmark.md 给出了在 flutter-dashboard.appspot.com/#/build 上提取某基准单次运行原始数据的流程:
- 在右上角齿轮设置中按 "Task Name" 搜索你关心的基准任务;
- 点击列表列头的相关图标;
- 点击你关心的提交对应的 build 链接,进入构建详情页;
- 展开该构建的 "log links" 步骤;
- 下载其中的 "timeline.json" 文件。
该 timeline.json 即该次运行采集到的完整时间线 trace,是离线做逐帧分析、与本地复现结果做 diff 的重要素材。若需本地复现某个失败的 devicelab 基准,可按 devicelab/README.md 中 Reproducing broken builds locally 一节的说明,git checkout 对应版本后用 bin/run.dart -t <task名>(如 -t flutter_gallery__transition_perf)本地运行同款任务。
七、使用注意事项与实用提示汇总
结合 Dashboards.md 与仓库实际,汇总如下几条易踩坑点:
- 日期选择务必使用日历控件:在 "Begin" 文本框直接键入日期会触发已知 bug,导致 Date Range 不生效,应点击旁边的日历图标选择。
- 曲线平移用键盘:Skia Perf 图表支持用 W、A、S、D 键平移 X 轴,比反复拖拽时间轴更精确。
unit字段不可全信:它经常与实际不符,判断指标物理含义请回到产出该数据的测试源码,以TaskResult.success的 data key 定义为准。- 区分框架/引擎两份数据源:框架仓库基准查
flutter-flutter-perf.skia.org,引擎仓库基准查flutter-engine-perf.skia.org,二者的 devicelab/CI 任务归属不同。 originId提示数据新旧:标记为legacy-flutter的是旧数据库迁移而来的历史数据,与当前devicelab管道产生的数据在口径上可能不完全一致,做长周期对比时需留意。- 新基准上报的前置条件:若你在 devicelab 中新增性能任务并希望其指标进入看板,须通过
TaskResult.success(data, benchmarkScoreKeys: [...])显式声明待上报 key(且值必须为 num),再由 CI 调用upload-metrics命令上传,详见 task_result.dart 与 upload_metrics.dart。
总之,Flutter 性能看板是一个"devicelab 任务采集指标 → JSON 结果上传 Cocoon/Skia Perf → 前端以 test/sub_result/时间范围组织查询"的完整链路。掌握本文的查询操作与参数语义,再结合 metrics_center.dart 等源码,你既能熟练检索任意基准曲线,也能理解每个数据点背后在仓库中的真实来源,从而更可靠地定位性能回归。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00