5.4.1 Timing Profile Analysis

A timing profile answers a more useful question than “why was this build slow?”: which kind of work occupied the elapsed time in this particular invocation? Read it as a timeline first, then turn visible spans into hypotheses about loading, analysis, execution, caching, or remote work. The profile is evidence from one run, not a permanent description of the target.

Capture a comparable profile

Use an explicit path so that the artifact you inspect is unambiguous:

bazel build //app:release --profile=release-profile.json.gz

Current Bazel releases also generate profiles for build-like commands and queries by default, under the output base. That behavior, the retained-file count, and the available profile rows are version-sensitive. An explicit --profile path is therefore the safer choice for an investigation that others must reproduce.1,2

Record the Bazel version, revision, targets, flags, machine or worker class, and cache state beside the file. More importantly, name the workload:

  • cold-server build: start from a newly started Bazel server.
  • warm no-op build: repeat the same command without an edit.
  • warm incremental build: make one representative edit, then rebuild.

These are different experiments. A clean or cold build cannot explain a complaint about edit-build latency. Likewise, two profiles captured with different configurations or download policies are not a controlled comparison. The in-memory analysis state belongs to the Bazel server. A restart, clean, or a configuration change can prevent a later invocation from reusing it.3

Open the compressed profile in a trace viewer supported by your environment. The official documentation describes chrome://tracing. Perfetto is another commonly used viewer for Chrome trace data.1,4 A JSON trace is a collection of timestamped events arranged into process and thread lanes. Horizontal position is time and width is observed duration. Overlap across different thread lanes can show concurrent work; overlap within one thread can instead be nested parent/child instrumentation that describes the same interval at two levels. Do not add overlapping span widths to estimate elapsed time or consumed capacity. Start zoomed out. Find the main-thread phase markers, then inspect the busy rows beneath the long phase rather than immediately selecting the longest colored box.

Read rows as observations, not phase boundaries

2.2.1 Loading, Analysis & Execution supplies the conceptual loading, analysis, and execution model. A trace viewer does not reproduce that model as three perfectly separated bands. Work can overlap, events can be aggregated, and the special rows included in a profile depend on the Bazel version and profiler flags.1

Some profiles contain worker rows named like skyframe evaluator 0 and skyframe evaluator execution-0. In profiles where those labels appear, the evaluator rows are useful places to inspect graph evaluation, while the execution variants contain action-execution work.3 Treat the names as navigation hints, not a stable API. Do not compute “analysis overhead” by subtracting one row's width from another: several evaluator threads can overlap, and their spans are not a pair of enclosing phase timestamps. Use the main-thread phase markers and event categories to establish what was happening, then use the worker rows for detail.

Compare lanes. Do not subtract them.
Illustrative cold and warm no-op captures show what to compare. Use matched commands and treat overlapping worker rows as concurrent observations.
Broad phase marker Package / analysis work Execution work
COLD SERVER
Loading and analysis occupy more of this run
Same targets, flags, revision, and machine class. This run uses a newly started Bazel server.
analysis marker
package creation
evaluator 0
evaluator 1
execution-0
startelapsed time →
WARM NO-OP
Reused server state changes the event population
The same command runs again without an edit. Package work may shrink, while other work can still overlap.
analysis marker
package creation
evaluator 0
evaluator 1
execution-0
startelapsed time →
Rows overlap. Their widths are not nested phase timestamps, so subtracting one lane from another does not calculate analysis overhead.

The profile's Critical Path row is also a focused view rather than the whole elapsed-time explanation. It shows actions on the observed critical path: a timed chain whose dependencies gate completion in that capture. Loading, analysis, garbage collection, fetching, queueing, and other overhead can still contribute to wall time. 5.4.2 Critical Path develops how to compare that chain with near-critical routes and choose an intervention.

Diagnose cold and warm analysis

A cold server must initialize state, load packages, and evaluate the requested graph. A warm server may reuse much of that work. The useful comparison is therefore not “does this trace contain package work?” but “how did the same workload's loading and analysis change between controlled cold and warm captures?”3

Look for these signals:

  • A long runAnalysisPhase span on the main thread establishes that analysis deserves attention. On a warm incremental build, unexpectedly slow analysis can point to rule behavior such as repeatedly flattening depsets.1
  • Numerous or expensive events in the package creation category show package-loading work. Correlate them with package names and the loading portion of the invocation. They are not, by themselves, proof that Bazel downloaded external dependencies. The profile format uses package creation as an event category.1
  • Expensive configuredTargetFunction events, when present in the producing Bazel version, focus the investigation on configured-target analysis. Compare the same targets and configuration before blaming target count or a particular rule.
  • MerkleTree.build is associated with constructing remote input Merkle trees, not with constructing Bazel's target dependency graph. It can become visible around remote cache or remote execution work, including recomputation after a remote cache miss when Bazel has discarded in-memory Merkle trees.2 Correlate it with remote setup, uploads, cache outcomes, and execution rows before deciding that analysis is the cause.

No single event proves that an instance was cold. Heavy package loading strongly supports that hypothesis only when the warm comparison removes it and the command, configuration, and repository state otherwise match. If both captures remain expensive, investigate the packages reached, macros and globs, configured targets, rule implementations, and memory pressure instead of merely restarting the server. Official profiling guidance calls out excessive targets, complex macros, recursive globs, and poor incremental rule behavior as possible loading or analysis costs.1

think

Trace: A warm incremental profile has a short runAnalysisPhase, but the build is still slow. Several skyframe evaluator execution-* rows contain long download outputs spans. Is analysis-cache reuse the leading hypothesis?

Reveal

No. The short analysis span weakens that hypothesis, while the execution rows point toward time spent materializing remote-cache results. Check cache and download metrics under the same remote-output policy. A cache hit avoids executing an action, but downloading its outputs still consumes time.3

Turn a trace into a testable hypothesis

Use a fixed sequence for each investigation:

  1. Locate the long top-level phase or idle interval.
  2. Identify the rows and event categories that occupy it.
  3. Correlate those events with labels, mnemonics, package names, cache activity, or remote-operation stages.
  4. State one causal hypothesis, such as “the representative edit causes broad configured-target reevaluation” or “remote input-tree construction dominates cache misses.”
  5. Change one relevant factor and capture several comparable profiles.
  6. Keep the diagnosis only if the phase, event population, and end-to-end time move as predicted.

The trace tells you where to ask the next question. It rarely tells you the repair alone. A long action may require action-graph inspection. Package-loading cost may require examining macros or globs. Remote setup and transfers may require cache and execution evidence. The disciplined move is to narrow the search surface, not to assign a cause from a lane name.

key takeaway

Read a timing profile from broad phase markers down to worker rows and event categories. Compare explicitly defined cold, warm no-op, and warm incremental workloads under matched conditions. package creation, configured-target evaluation, and remote Merkle-tree construction are clues to different kinds of work, not automatic diagnoses.

Trace row names and contents vary by Bazel version. Use them as navigation aids, corroborate them with phase timing and related evidence, and validate one causal hypothesis with repeated comparable captures.

Check your understanding · 3 questions

1.An engineer compares a cold-server profile with a warm incremental profile and sees much less package creation work in the warm run. Which conclusion is best supported?

Select one answer

2.A timing profile shows several skyframe evaluator lanes overlapping one another and an execution lane. Which reading practices are sound?

Select all that apply

3.A warm incremental build has a short runAnalysisPhase, but long download outputs spans occupy execution lanes. What should the engineer investigate next?

Select one answer

0 of 3 answered

Footnotes

  1. JSON Trace Profile — profile capture, version-dependent rows, phase markers, event categories, and common performance issues 1 2 3 4 5 6

  2. Command-Line Reference — current profile flags and remote Merkle-tree retention behavior 1 2

  3. Why is my Bazel build so slow? — cold-versus-warm server state, evaluator-row examples, and cache/download diagnosis 1 2 3 4

  4. Sponsored Session: Bazel Management for Developer Productivity Experts - Instructor: Billy Autrey — profile viewing and practical interpretation of execution, transfer, and critical-path spans