构建扫描与瀑布图:先搞懂这俩是什么

说实话,我第一次接触 Gradle 构建扫描时,心里是有点懵的。

当时项目编译越来越慢,从 2 分钟拖到 8 分钟,团队都在抱怨。我试了各种优化——改配置、加缓存、升级版本——效果都不明显。后来有个老同事说:「你跑一次构建扫描看看。」

好,那我们就从最基础的东西聊起。你理解了构建扫描和瀑布图,后面的优化才有的放矢。

什么是 Gradle 构建扫描

说白了,构建扫描就是 Gradle 在构建过程中记录的一份详细「体检报告」。它把每一步做了什么、花了多久、依赖了哪些文件、执行了哪些任务,全都存下来,然后在一个网页上展示给你看。

要生成构建扫描,很简单。命令行加一个 --scan 参数就行:
./gradlew build --scan
完成后终端会输出一个链接,点开就能看到完整的构建报告。

我个人习惯在 CI 上自动上传构建扫描,这样每次构建都能追溯。遇到问题直接拉出那次扫描,不用再问「你本地到底跑了什么」。我记得有次排查一个诡异的 task 顺序错误,就是靠扫描里的任务依赖图定位到的。

构建扫描里能看到的东西包括:

  • 构建时间线:每个任务开始结束的时间戳,精确到毫秒。
  • 任务依赖图:哪个任务依赖哪个,一目了然。
  • 输入输出哈希:判断任务是否增量执行,有没有被缓存命中。
  • 插件和脚本耗时:配置阶段和应用插件花了多少时间。
  • 网络瀑布图:这是我们今天的重点,下面细讲。

网络瀑布图——任务执行的「时间轴」

在构建扫描的 Performance 页面里,有一个叫「Waterfall」的选项卡。很多人第一次看到它,会以为是网络请求的瀑布图。其实不一样——这里展示的是任务在 CPU 核心上的执行时间轴。

每一个横条代表一个任务的执行时段。横条越长,任务耗时越久。多个横条如果堆叠在一起,说明它们在并行执行;如果有明显的空隙,说明任务在「干等」——等前序任务完成。

核心价值:瀑布图能帮你一眼看出「哪些任务本该并行却串行了」「哪些任务在白白浪费 CPU 时间」。我遇到过一个坑:所有任务都在同一个线程里跑,瀑布图上看去全是一长串,没有任何重叠。后来发现是没开 --parallel 参数,加上后构建时间直接砍半。

举个例子,下面是一个简化的瀑布图结构:

Gradle 构建 → 构建扫描 → 瀑布图解读 项目代码 构建配置 插件依赖 Gradle Daemon Gradle 构建扫描 构建时间线 任务依赖图 网络瀑布图 瀑布图示例 Task A Task B Task C Task D Task E 瀑布图关键点 • 横条长度 = 任务耗时 • 重叠 = 并行执行 • 空隙 = CPU闲置等待 • 颜色 = 不同任务类型

从上图你能看到,瀑布图把构建扫描里的关键数据可视化成了时间条。每个任务就像一根小柱子,排在一起就知道谁先谁后、谁在等谁。

为什么需要瀑布图来诊断性能

光看构建扫描的汇总数字,比如「总耗时 6 分钟」,你很难下手。但一张瀑布图,能直击痛点。我把它总结为三个核心用途:

诊断维度瀑布图表现典型问题
并行度任务条是否重叠大量串行,未开启 --parallel 或 max-workers 设置过低
任务耗时单个横条特别长该任务内部有瓶颈(如编译、测试、打包)
依赖链空隙段(等待时间)前序任务太慢,下游任务被迫干等;或者配置阶段太长

举个例子。我参与过一个微服务项目,构建一次 15 分钟。我看瀑布图,发现所有模块的 compileJava、processResources、compileTestJava 全被排成了一串——明明都是独立子项目,结果一个接一个跑。后来在根 gradle.settings 里加了 parallel=true,并且每个子项目的 compileJava 任务之间没有任何实际依赖,直接让它们并行,构建时间降到 8 分钟。

注意:Gradle 默认是串行的(至少在 5.x 以前)。如果你没有显式配置并行,任务会乖乖排队。我曾经吃过一个大亏:一个 200+ 模块的项目,因为没开并行,CI 构建超时 30 分钟。开了 org.gradle.parallel=true 之后,直接降到 12 分钟。所以瀑布图第一眼看什么?看有没有大量非重叠的任务条。

如何快速上手生成扫描

咱们别光说不练。你可以在任何 Gradle 项目里试一下:

# 用 --scan 运行任意 task
./gradlew build --scan

# 或者你想让团队每次构建都自动上传
# 在 build.gradle 里加插件
plugins {
  id 'com.gradle.build-scan' version '3.16'
}
gradleEnterprise {
  buildScan {
    termsOfServiceUrl = "https://gradle.com/terms-of-service"
    termsOfServiceAgree = "yes"
  }
}

扫描链接生成后,打开它。找到「Performance」→「Task Execution」→「Waterfall」。嗯,这就是我们的核心阵地了。

我个人习惯在本地开发时也用 --scan,特别是改动大配置之后。有一次我升级了 Gradle 8,觉得快了不少,但跑了一次扫描发现配置阶段反而慢了——原来是新版插件配置变更导致的。没有扫描,我永远不会注意到这 0.5 秒的退化。

小结

好了,这一章我们搞清楚了构建扫描是什么、瀑布图怎么来的。它们不是花架子,而是直接能帮你找到编译慢的「真凶」。你想想看,如果没有瀑布图,你只能对着总时间瞎猜,或者用 --profile 看一堆文本表格——效率差太多了。

下个知识点,我们准备深入瀑布图的坐标轴和色块含义。不过别急,先把基础打牢。今天就先练一下:在你自己的项目里跑一次 --scan,打开瀑布图,看看有没有明显的串行瓶颈。