如何在Bazel构建中获取各规则执行耗时并实现规则执行前扩展
好问题!确实Aspect本身是在规则执行完成后才触发的,没法直接拿到启动时间,但Bazel其实有几种实用的方式可以实现完整的规则执行耗时统计,下面给你详细拆解:
方法一:用Bazel内置Profile工具(零代码最省心)
这是官方提供的最简单方案,不需要写任何自定义代码就能拿到详细的时间统计:
- 构建时加上
--profile=build.profile参数,比如:bazel build //your:target --profile=build.profile - 构建完成后,运行
bazel analyze-profile build.profile命令,就能看到每个规则(以及对应的底层Action)的开始时间、结束时间、耗时,甚至还有CPU、内存等资源使用情况。 - 如果需要结构化数据做后续处理,可以加上
--profile_format=json参数,生成JSON格式的profile文件,方便自己解析。
方法二:自定义Action Listener(灵活定制统计逻辑)
如果需要更定制化的统计(比如把数据输出到特定日志系统、自定义格式),可以用Bazel的Experimental Action Listener特性——它能监听每个Action的开始和结束事件,而规则的执行本质上是由一个或多个Action组成的,我们可以通过Action关联到对应的规则信息。
具体步骤:
- 写一个Java类实现
com.google.devtools.build.lib.actions.ActionListener接口,这个接口有两个核心方法:actionStarted(Action启动时触发)和actionCompleted(Action结束时触发)。示例代码大概是这样:import com.google.devtools.build.lib.actions.Action; import com.google.devtools.build.lib.actions.ActionExecutionContext; import com.google.devtools.build.lib.actions.ActionListener; import com.google.devtools.build.lib.actions.ActionResult; import java.util.HashMap; import java.util.Map; public class TimingActionListener implements ActionListener { private final Map<Action, Long> startTimestampMap = new HashMap<>(); @Override public void actionStarted(Action action, ActionExecutionContext context) { long startTime = System.currentTimeMillis(); startTimestampMap.put(action, startTime); // 通过action.getOwner()拿到对应的规则信息 String ruleLabel = action.getOwner().getLabel().toString(); System.out.printf("[START] Rule %s at %d%n", ruleLabel, startTime); } @Override public void actionCompleted(Action action, ActionResult result, ActionExecutionContext context) { long startTime = startTimestampMap.getOrDefault(action, 0L); long duration = System.currentTimeMillis() - startTime; String ruleLabel = action.getOwner().getLabel().toString(); System.out.printf("[END] Rule %s completed in %d ms, status: %s%n", ruleLabel, duration, result.getExitCode()); startTimestampMap.remove(action); } } - 把这个类打包成JAR文件,注意要依赖Bazel的核心库(可以从Bazel安装目录下的lib文件夹获取相关依赖)。
- 构建时通过
--experimental_action_listener参数指定这个Listener,比如:
你需要在WORKSPACE文件里用bazel build //your:target --experimental_action_listener=//path/to:your_timing_listener_jarjava_import规则引入这个JAR,确保Bazel能找到它。
方法三:结合Aspect与Build Event Protocol(BEP)
如果还是想保留Aspect的自定义逻辑,同时获取规则的启动时间,可以结合Bazel的Build Event Protocol(BEP):
- 构建时加上
--build_event_json_file=build_events.json参数,把构建过程中的所有事件(包括规则启动的targetStarted事件、规则完成的targetCompleted事件)输出到JSON文件。 - 同时用Aspect在规则结束时补充你需要的自定义信息,之后自己解析JSON文件,将
targetStarted和targetCompleted事件通过规则标签关联起来,计算耗时。
这种方法适合需要整合Aspect逻辑和时间统计的场景。
内容的提问来源于stack exchange,提问作者lzf




