Bazel 构建性能指标提取全指南:BEP、查询命令、Trace Profile、执行日志与基准测试
【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel
构建变慢是几乎每个 Bazel 用户都会遇到的问题。对于高频迭代的核心开发目标、被大量目标依赖的公共库,或是自定义规则等代表性目标,提升单次构建性能的价值尤为突出。而改进性能的第一步,是搞清楚资源究竟消耗在哪里。本文基于 Bazel 官方文档整理了一套完整的构建性能指标采集体系:Build Event Protocol(BEP)、query/cquery/aquery 查询命令、JSON Trace Profile、Execution Log、Execution Graph Log,以及 bazel-bench 基准测试工具。读完本文,你将掌握每种指标采集途径的适用场景、关键参数与实现原理,能够针对自己的构建建立性能基线并定位瓶颈。
为什么要采集构建性能指标
Bazel 构建流程由加载(loading)、分析(analysis)和执行(execution)等多个阶段组成,耗时可能来自包加载、规则分析、action 执行调度、I/O 与 GC 等不同环节。仅有"构建很慢"的笼统感知是不够的,需要把时间拆解到具体阶段和具体 action,才能确定优化方向。官方建议优先关注三类目标:
- 核心开发者目标:被高频迭代和重建的目标,其单次构建的每次提速都会反复兑现。
- 被广泛依赖的公共库:它们的编译时间会传导到下游大量目标。
- 某类目标的代表(如自定义规则):诊断并修复一次构建中的问题,往往能推广到更大规模。
采集指标正是为了回答"资源花在了哪里"这个问题。下面逐一介绍 Bazel 提供的六种主要采集途径。
途径一:Build Event Protocol(BEP)
BEP 是 Bazel 对外输出构建过程与结果的标准协议。Bazel 会通过 build_event_stream.proto 中定义的一系列 protobuf 消息,将构建事件流发送给你指定的后端(backend),再由后端按需聚合。
常用 BEP 输出标志
BEP 数据可以落地为本地文件或推送到远程 Build Event Service:
--build_event_json_file=<path>:以换行分隔的 JSON 形式写出 BEP 事件流,便于脚本直接解析;--build_event_binary_file=<path>:以 length-delimited 二进制 protobuf 形式写出,体积更小;--bes_backend=<endpoint>:将事件流推送到指定的 Build Event Service 后端,适合团队级的集中式指标收集。
这些标志在源码中的定义可参见 BuildEventStreamOptions.java(build_event_binary_file、build_event_json_file)与 BuildEventServiceOptions.java(bes_backend)。
值得重点关注的 BEP 指标字段
无论聚合方式如何,BuildMetrics消息(定义于 build_event_stream.proto)中的以下子消息是通用的核心指标来源:
| 子消息 | 关键字段 | 含义 |
|---|---|---|
ActionSummary | actions_created/actions_executed | 本次构建创建/执行的总 action 数(含 aspects;actions_executed含远端缓存命中、不含本地 action 缓存命中);action_data按 mnemonic 细分每种 action 的执行数、首末执行时间、累计 CPU 时间 |
MemoryMetrics | used_heap_size_post_build、peak_post_gc_heap_size等 | JVM 堆使用情况,仅在设置--memory_profile(强制一次 Full GC)时采集 |
TargetMetrics | targets_configured | 本次构建配置的目标/aspect 数量 |
PackageMetrics | packages_loaded | 成功加载的 BUILD 文件(包)数量,附package_load_metrics明细 |
TimingMetrics | cpu_time_in_ms、wall_time_in_ms、analysis_phase_time_in_ms、execution_phase_time_in_ms | 各阶段耗时;在 Skymeld 下分析和执行阶段交错,两阶段时间之和可能大于墙钟时间 |
此外,BuildFinished事件携带退出码与完成时间,TargetSummary汇总每个目标的构建/测试状态,都是做构建健康度面板的基础数据。
途径二:query / cquery / aquery 查询命令
Bazel 提供三种查询模式,分别作用于目标图(target graph)、配置目标图(configured target graph)和 action 图(action graph):
- query:查询未配置的目标依赖关系;
- cquery:基于具体配置查询目标及其依赖,能反映配置对图的影响;
- aquery:查询 action 图,展示构建会产生哪些 action、每个 action 的输入输出与命令行,直接服务于性能与正确性分析。
三种模式共享同一套查询语言函数库,如deps()、rdeps()、somepath()等,可以组合出满足需求的复杂查询。例如用aquery查看某个目标的所有 action 及其 mnemonics,能够快速发现不必要的 action 数量或过大的 action 粒度。
途径三:JSON Trace Profile
每次构建类命令(以及query)执行时,Bazel 都会自动写出一份 JSON 格式的 trace profile,用于快速定位本次调用中时间花在了哪里。
Profile 文件的生成与配置
- 默认写入 output base 下的
command-$INVOCATION_ID.profile.gz,同时创建一个指向最近一次 profile 的符号链接command.profile.gz; - 是否生成由
--generate_json_trace_profile控制,输出位置由--profile指定,以.gz结尾的路径会启用 GZIP 压缩; - Bazel 默认在 output base 保留最近 5 份 profile(由
--profiles_to_retain配置),显式指定--profile路径会禁用自动回收。
可视化与分析工具
- chrome://tracing:在 Chrome 中打开
chrome://tracing,点击 "Load" 选择 profile 文件即可可视化。键盘快捷键:1选择模式(查看/聚合选中事件)、2平移、3缩放、4测量两个事件间距、?查看全部快捷键。下图为一份典型的 profile 可视化结果:
Bazel JSON Trace Profile 在 chrome://tracing 中的可视化界面
- jq:适合脚本化提取。例如提取本地 action 执行中沙箱创建步骤的所有耗时:
$ zcat $(bazel info output_base)/command.profile.gz | jq '.traceEvents | .[] | select(.name == "sandbox.createFileSystem") | .dur' 6378 7247 11850 ...Profile 中的特殊行与常见性能问题
Profile 中除各 Bazel 线程的事件外,还包含若干特殊行:
action count:并发执行的 action 数,干净构建时应逼近--jobs的值;CPU usage (Bazel):Bazel 每秒的 CPU 占用(值为 1 表示占满一个核);Critical Path:关键路径上的每个 action 一块;Main Thread:Bazel 主线程的高层事件,如 "Launch Blaze"、"runAnalysisPhase";Garbage Collector:minor/major GC 停顿。
分析时应重点查找:分析阶段(runAnalysisPhase)明显慢于预期(可能是不良 rule 实现或过度展开 depset);关键路径上的单个慢 action(可考虑拆分 action 或削减传递依赖);以及只有少数线程繁忙、其余都在等待的瓶颈现象。
Profile 文件格式
顶层对象包含元数据(otherData,如 invocation ID、日期、output base)和实际跟踪数据(traceEvents)。事件中的ts/dur单位为微秒,cat取自ProfilerTask枚举。极短且相邻的事件会被自动合并,如需保留原始粒度可传--noslim_profile。
途径四:Execution Log(spawn 执行日志)
当远端缓存命中率低于预期时,Execution Log 是排查机器/环境差异与 action 非确定性(non-determinism)的关键工具。它记录每个已执行 spawn 的完整信息:命令行、环境变量、输入输出文件及其 digest、执行平台与运行结果。
三种日志格式与对应标志
三个标志互斥,定义见 ExecutionOptions.java:
| 标志 | 格式 | 说明 |
|---|---|---|
--execution_log_binary_file | length-delimitedSpawnExecprotobuf | 旧格式,体积较大 |
--execution_log_json_file | 换行分隔的 JSON | 便于人工阅读与脚本处理 |
--execution_log_compact_file | length-delimitedExecLogEntryprotobuf,整文件 zstd 压缩 | 官方推荐的格式,显著更小、生成代价更低 |
三者均可接受布尔值或路径字符串:传路径则写入本地文件;传true则需配合--experimental_stream_log_file_uploads将日志流式上传到远端存储。
附加 spawn 指标
在 Execution Log 基础上,可额外开启--experimental_execution_log_spawn_metrics(Bazel 5.2 起可用)。开启后日志会包含详细的 spawn 指标,本地与远端执行的 action 均适用,例如:
- 排队时间(queuing):spawn 等待执行的时间;
- setup / fetch / upload / network 等细分耗时:定位执行中被持续拖慢的环节;
- 可用于对比本地与远端机器的执行性能差异。
典型用法是同一目标分别在本地与远端执行并导出两份日志,逐 spawn 对比指标,找出差异来源。
途径五:Execution Graph Log(执行图日志)
JSON Trace Profile 能给出关键路径信息,但有时还需要了解已执行 action 之间的完整依赖关系。从 Bazel 6.0 起,可以开启执行图日志:
bazel build //your:target \ --experimental_enable_execution_graph_log \ --experimental_execution_graph_log_dep_type=all其实现位于 ExecutionGraphModule.java,输出为一个 zstd 压缩、length-delimited 的execution_graph.Nodeprotobuf 文件(默认execution_graph_dump.proto.zst)。相关标志包括:
experimental_enable_execution_graph_log:开启执行图日志;experimental_execution_graph_log_dep_type:依赖信息粒度,none/runfiles/all,all报告每一条 action 间边;experimental_execution_graph_log_path:指定本地输出路径,支持绝对路径、相对路径或%workspace%前缀(设置该路径且未开启主开关会直接报错);experimental_execution_graph_log_queue_size/execution_graph_log_queued_bytes_limit:写盘队列大小与字节上限,用于在峰值内存与写盘阻塞之间权衡(-1 表示无界)。
日志中的每个节点记录Metrics(开始时间戳、总耗时、fetch/discover_inputs/parse/process/queue/retry/setup/upload/network等细分耗时)、mnemonic、所属 rule class 与 target label,并通过dependent_index记录依赖边。从源码看,节点还特别处理了 retry(retryOf指向先前尝试)与 action 缓存命中情况(ExecutionGraphModule.java 中CachedActionEvent的注释说明:关键路径中间存在缓存命中的 action 时,必须记录它们才能正确重建依赖树)。
该日志的核心价值在于计算drag——某个节点对关键路径施加的"拖累",即从执行图中移除该节点理论上能节省的时间。基于此,可以在真正改动之前预测对构建图与 action 图的影响。
途径六:使用 bazel-bench 做基准测试
除单次构建的指标采集外,跨版本、跨提交的性能回归检测需要可重复的基准测试。bazel-bench 是面向 Git 项目的基准测试工具,支持两类场景:
- Project benchmark:在同一个 Bazel 版本下对比两个 git commit 的构建性能,用于检测构建自身的回归(通常源于新增依赖);
- Bazel benchmark:在同一个 git commit 下对比两个 Bazel 版本,用于检测 Bazel 本身的回归(适合 Bazel 维护者或 fork 场景)。
它监控的指标包括墙钟时间(wall time)、CPU 时间、系统时间以及 Bazel 的 retained heap 大小。官方建议在专用的物理机上运行 bazel-bench,避免其他进程干扰,以降低结果方差。
六种途径的选型建议
| 需求 | 推荐途径 |
|---|---|
| 建立构建健康度基线、做团队级趋势面板 | BEP(BuildMetrics各字段) |
| 定位某次构建内部的耗时分布 | JSON Trace Profile(command.profile.gz+ chrome://tracing) |
| 排查远端缓存命中率低、action 非确定性 | Execution Log(--execution_log_compact_file+--experimental_execution_log_spawn_metrics) |
| 评估改动 action 图的影响、计算关键路径 drag | Execution Graph Log(--experimental_enable_execution_graph_log) |
| 检查目标/action 图结构本身 | query / cquery / aquery |
| 跨提交或跨版本做性能回归对比 | bazel-bench |
更完整的"指标 → 诊断 → 修复"闭环流程,可进一步阅读构建性能拆解相关章节,以及仓库中同目录下的迭代速度优化、内存优化等配套文档。掌握上述采集手段,你就拥有了把"感觉慢"转化为"数据慢"的能力,接下来的一切优化都将有据可依。
【免费下载链接】bazela fast, scalable, multi-language and extensible build system项目地址: https://gitcode.com/GitHub_Trending/ba/bazel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考