Skip to content

build-telemetry: finalize pending history traces, raise the job-wide payload cap, and report export caps in the lifecycle - #135

Merged
piob-io merged 3 commits into
release/v2from
devin/1788538511-finalize-history-trace
Sep 4, 2026
Merged

piob-io merged 3 commits into
release/v2from
devin/1788538511-finalize-history-trace

Conversation

@piob-io

@piob-io piob-io commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Three follow-ups to the post-step history export from #128, each a self-contained commit: builds no longer lose their trace to buildkitd's asynchronous trace finalization, the job-wide payload cap is sized for the chunked reporting that #128 introduced, and every cap that can shrink the telemetry is reported in the job lifecycle so gaps are explainable from ClickHouse.

Finalize pending history records before exporting traces

buildkitd attaches a build's OTLP trace to its history record asynchronously: after emitting the COMPLETE event it keeps the span recorder open for up to 3s (waiting for a finalizer or the timer) before writing the trace blob to the content store and stamping record.trace. A post step that starts right after the last docker build lists a completed record with trace == undefined, ships it without a trace, and then prunes it — the row lands in docker_build_summary with trace_missing=1 and an empty steps array. In an end-to-end lab run this hit the final build of most multi-build jobs and every single-build job that ended just before teardown (69 of 205 rows).

exportBuildHistory runs a pre-pass before collection:

// completed records without a trace descriptor
for await (ev of control.listenBuildHistory({EarlyExit: true}))
  if (rec.CompletedAt !== undefined && rec.trace === undefined) pending.push(rec.Ref)

await Promise.all(pending.map(ref =>
  withTimeout(control.updateBuildHistory({Ref: ref, Finalize: true}), 5_000)))  // fail-soft

UpdateBuildHistory{Finalize:true} triggers the record's finalizer and blocks until the trace is written (no-op for records already finalized), so the collection pass sees the descriptor. Finalize failures/timeouts are logged at debug and the record is still exported (without trace). A lab rerun with this change went from 69/205 to 0/185 trace_missing rows.

Job-wide history payload cap: 32 MiB

MAX_TOTAL_PAYLOAD_BYTES bounds record + trace bytes for the whole job. At 8 MiB it was the single-request bound from before chunked reporting; with ~3 MiB ReportDockerBuild chunks the per-request size is already controlled, so the job-wide cap only decides how many builds of a many-build job keep their traces — at 8 MiB roughly a dozen builds with ~1 MiB traces fit and the rest ship truncated with no steps. 32 MiB lets such jobs ship whole while still bounding teardown. The per-record (512 KiB), per-trace (2 MiB), per-request (~3 MiB) and timeline (1 MiB) caps and the 15s export timeout are unchanged.

Export caps and timeline size reported in DockerJobLifecycle

Until now the only evidence that a cap fired was a core.debug line in the runner log. The lifecycle (sent in the first report chunk only) now carries:

field meaning
timeline_bytes / timeline_truncated runner step-timeline bytes actually shipped; whether the 1 MiB tail cap cut off the head (early steps may be unattributable)
history_export_bytes record + trace bytes exported across all chunks
traces_dropped_oversize traces omitted for a per-build reason: over the 2 MiB per-trace cap, or the content-store read failed
traces_dropped_payload_cap traces omitted because the 32 MiB job-wide cap was reached (record still shipped, truncated=1)
records_dropped_payload_cap builds omitted entirely because the job-wide cap was reached (no row at all)

Job-wide admission is factored into admitBuild(build, traceUnavailable, counters) so the three drop counters are disjoint — a build lands in at most one, whole-record drops taking precedence — and unit-tested. Counters accumulate during collection and are copied onto the lifecycle only when the export completes, so a timed-out export (which ships no builds) reports zeros alongside history_export_timed_out. readRunnerStepTimeline(lifecycle) records the shipped size/truncation via capRunnerStepTimeline.

Requires @buf/blacksmith_vm-agent.bufbuild_es 2.14.1-20260904184135-ab292bb91e69.3 (fields added agent-side in FastActions/fa#5449; columns in useblacksmith/web#12482). Agents that predate the fields ignore them; the row still inserts with the columns at their defaults.

dist/ is rebuilt.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled. (Staging)


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Link to Devin session: https://app.devin.ai/sessions/0c6ebc2a6de54933bebe821e21ab8b76
Open in Devin Desktop: https://app.devin.ai/desktop/session/0c6ebc2a6de54933bebe821e21ab8b76?variant=devin
Requested by: @piob-io

…aces

buildkitd writes a build's trace attachment asynchronously, keeping the
span recorder open for up to 3s after the record completes. A job whose
last build finished just before the post step therefore exported that
record without a trace descriptor and then pruned it, so the row landed
with trace_missing=1 and no steps.

Before collecting, completed records that lack a trace descriptor are
finalized via UpdateBuildHistory(Finalize: true), which blocks until the
trace is attached (no-op for already-finalized records). Fail-soft with a
5s timeout per record.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@piob-io
piob-io marked this pull request as ready for review September 4, 2026 16:20
@piob-io
piob-io requested a review from ajwerner September 4, 2026 16:20
The 8 MiB cap predates chunked reporting, when it was the bound on a single
ReportDockerBuild request. With ~3 MiB chunks the per-request size is
already controlled, so the job-wide cap only decides how many builds of a
many-build job keep their traces; at 8 MiB roughly a dozen builds with
~1 MiB traces fit and the rest ship truncated. 32 MiB lets such jobs ship
whole while still bounding teardown.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration devin-ai-integration Bot changed the title build-telemetry: finalize pending history records before exporting traces build-telemetry: finalize pending history traces and raise the job-wide payload cap Sep 4, 2026
…cycle

Every cap that can silently shrink the telemetry is now accounted for in
DockerJobLifecycle so absent traces and unattributed builds can be explained
from ClickHouse instead of runner debug logs:

- timeline_bytes / timeline_truncated: runner step-timeline bytes actually
  shipped and whether the 1 MiB tail cap cut off the head.
- history_export_bytes: record + trace bytes exported across all chunks.
- traces_dropped_oversize: traces omitted for a per-build reason (over the
  per-trace cap or content-store read failure).
- traces_dropped_payload_cap / records_dropped_payload_cap: traces, or whole
  records, omitted because the job-wide 32 MiB cap was reached.

Admission to the job-wide cap is factored into admitBuild so the counters
are disjoint (a whole-record drop is never also counted as a trace drop) and
unit-testable; counters are copied onto the lifecycle only once the export
completes, so a timed-out export leaves them at zero. Bumps
@buf/blacksmith_vm-agent.bufbuild_es to the BSR commit that carries the new
fields.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration devin-ai-integration Bot changed the title build-telemetry: finalize pending history traces and raise the job-wide payload cap build-telemetry: finalize pending history traces, raise the job-wide payload cap, and report export caps in the lifecycle Sep 4, 2026
@piob-io
piob-io merged commit 1921511 into release/v2 Sep 4, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants