5.4.6 Execution Log Graph

extra

Two Bazel logs with similar names answer different questions. The execution log records spawn details for cache and reproducibility investigations. The experimental execution graph log records the actions observed during one invocation, their timing metrics, and selected dependency edges. Use the second only when a trace has identified an important timed chain and you need structural evidence for why its actions could not run independently.

Choose the evidence before choosing the flag

The three nearby data sources are complementary:

EvidenceBest questionImportant boundary
JSON trace profileWhen did work run, overlap, queue, or block completion?A timeline does not expose every artifact dependency.
aqueryWhich actions did analysis register, with which commands, inputs, and outputs?It is a planned action graph, not a record of the actions that ran.
Execution graph logWhich observed execution nodes were connected, and what duration components did they report?It is an invocation-specific executed graph, not the complete analyzed action DAG.
Four evidence surfaces, four different claims
Choose the artifact that records the fact you need. Correlate the evidence before changing the graph.
ORDINARY EXECUTION LOG
Spawn details
Arguments · environment · inputs · outputs · execution metadata
compare runs for cache and reproducibility evidence
AQUERY
Planned action graph
Actions registered by analysis · commands · declared inputs and outputs
planned, not proof of execution
EXECUTION GRAPH LOG
Observed nodes + enabled dependency edges
Invocation-local indexes · direct dependencies · duration components
structural execution evidence
JSON TRACE PROFILE
Observed timeline
Start · duration · overlap · queueing · completion-gating chain
temporal execution evidence

Adjacency ≠ dependency. Sequential-looking trace events may be independent. Require a graph edge and confirm the artifact relationship.

Emitted ≠ computed. Nodes emit edges and duration fields. An analyzer computes critical paths, near-critical paths, slack, and drag.

That last boundary prevents a common misreading. A node missing from the log is not proof that the action does not exist. It may be outside the requested graph, reused rather than executed, or omitted by the experimental logger's selected dependency detail. Use 5.2.3 bazel aquery — Action Graph for the registered action graph and the trace workflow in 5.4.2 Critical Path for the invocation's timeline.

The ordinary execution log serves another purpose. Its binary, JSON, and compact forms describe executed spawns, including details such as arguments, environment, inputs, outputs, and execution metadata. The compact form uses back-references and compression to avoid repeating large input sets, making cross-invocation comparison practical.1 It is the better artifact for “why was this a cache miss?” The execution graph log is the better raw artifact for “which observed producer must precede this consumer?”

Capture the experimental graph deliberately

The execution graph logger is an experimental, undocumented interface. The exact flags must therefore be checked against the Bazel binary that will produce the data. The following command was verified with Bazel 9.0.0:

bazel build //app:release \
  --experimental_enable_execution_graph_log \
  --experimental_execution_graph_log_path=execution-graph.proto.zst \
  --experimental_execution_graph_log_dep_type=all

The Bazel-9-pinned reproduction runs two dependent genrule actions with those exact options. Its source-derived decoder checks the Zstandard frame and the matching Bazel 9 protobuf field numbers, then asserts that the consumer's dependent_index list contains the producer's node index. The captured result is:

zstd frame: yes
decoded schema: Bazel 9.0.0 execution_graph.Node
//:produce mnemonic: Genrule
//:consume mnemonic: Genrule
consumer depends on producer index: yes

The 9.0.0 source declares these options as UNDOCUMENTED, which is why normal bazel help output does not list them. Newer official documentation instead shows --experimental_execution_graph_log together with --experimental_execution_graph_log_dep_type=all. Treat that as version evolution rather than assuming the newer spelling is a Bazel 9 alias. The fixture's source evidence links the exact 9.0.0 option declarations and schema.

dep_type=all requests every inter-action edge available to the logger. The output is a Zstandard-compressed stream of length-delimited protobuf Node messages, not JSON, DOT, or a ready-made visualization. Each node can carry a description, index, target label when available, mnemonic, runner information, direct dependency indexes, and timing components such as total duration, processing, setup, queueing, fetching, network transfer, and output processing. The format itself does not contain a critical_path, near_critical, or drag annotation.

That distinction reconciles two descriptions often collapsed into one. Bazel's performance guide says the graph data can be used to understand an action's critical-path drag—the potential time saved by removing a node—and to predict the impact of graph changes.2 Those are analyses computed from dependency edges and durations. They are not per-node annotations emitted by the protobuf. A BazelCon case study likewise describes using the data to compare critical and near-critical alternatives, but also describes an internal ingestion and analysis system rather than a bundled command that produces those answers.3

There is currently no stable Bazel command that turns this file into a graph or critical-path report. Before enabling collection at scale, choose and version a consumer that can decompress the stream, decode the matching execution_graph.proto, and calculate the graph metric your decision requires. Keep the Bazel version, target pattern, flags, cache state, and invocation ID beside the log. Both the flag surface and protobuf are experimental.

Read edges without inventing causality

An edge says that the logger observed an execution dependency. It supports a structural statement: the consumer could not complete before the producer made the required result available. It does not say that the edge consumed all of the intervening wall time, nor that removing it is correct.

Use this sequence for a defensible investigation:

  1. Capture a representative JSON trace and locate the chain that gates the user-visible build.
  2. Capture the execution graph log for the same targets, configuration, cache state, and environment.
  3. Resolve the trace action and graph node by concrete identity—prefer mnemonic, target label when present, description, and primary output over a transient node index.
  4. Walk direct dependency indexes backward from the gated node. Confirm the producer/consumer artifact relationship with aquery before proposing an edge change.
  5. Compute path length or drag in the chosen analyzer. Also inspect paths with little slack. Shortening one route may merely expose another.
  6. Make one correctness-preserving change, then repeat the same captures and compare end-to-end time.

Node indexes are dense identifiers for one build, not stable action IDs. Do not join logs from two invocations by index. Target labels may also be absent for shared actions, so a production analyzer needs a documented matching policy and must report ambiguous matches instead of silently pairing them.

The graph explains dependency serialization, but not every sequential-looking interval. Independent actions can still run one after another because of CPU, memory, remote queue capacity, execution strategy, or scheduler choices. Read those waits in the trace and node metrics. Conversely, never remove a real input or dependency merely to shorten a computed path: first establish that the edge is unnecessary to the artifact's correctness.

key takeaway

The experimental execution graph log is a compressed protobuf record of invocation-specific execution nodes, timing metrics, and requested dependency edges. It complements the JSON trace's timeline and aquery's planned action graph. It replaces neither. The compact execution log is a different format for spawn-level cache and reproducibility diagnosis.

Critical path, near-critical paths, and drag are results an analyzer computes from graph edges and durations, not annotations promised by the emitted node format. Pin the Bazel version and parser, correlate nodes with trace and aquery evidence, and validate any graph change with a comparable end-to-end measurement.

Check your understanding · 4 questions

1.Match each evidence source to the question it is best suited to answer:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
Ordinary execution log
Experimental execution graph log
aquery
JSON trace profile

2.Two actions appear one after another in a trace. What evidence is needed before claiming that the first is a dependency of the second?

Select one answer

3.Which values can the execution graph node records emit directly?

Select all that apply

4.Assess these claims about the experimental execution graph log:

Choose True or False for each sentence

A node missing from the graph log proves that analysis did not register that action.
Node indexes are invocation-local and should not be used to join captures from different builds.
The graph log replaces the trace because its nodes contain timing components.
The producing Bazel version and matching protobuf decoder should be retained with a capture.
0 of 4 answered

Footnotes

  1. Bazel's Tracing and Logging Facilities — execution-log spawn content, compact encoding, and comparison workflow

  2. Extracting build performance metrics — execution graph logs, inter-action dependencies, and drag as an analysis use case

  3. Bazel Build Data: Avoiding Pitfalls in Debugging and Optimizing Builds — structural graph use, action contribution, and near-critical-path analysis in an internal ingestion pipeline