Skip to content

fix(suite): merge into the stored summary, not just the local one - #303

Closed
qu0b wants to merge 1 commit into
masterfrom
qu0b/converge-suite-summary
Closed

fix(suite): merge into the stored summary, not just the local one#303
qu0b wants to merge 1 commit into
masterfrom
qu0b/converge-suite-summary

Conversation

@qu0b

@qu0b qu0b commented Aug 11, 2026

Copy link
Copy Markdown
Member

Third in the sequence after #301 (multipart) and #302 (skip unchanged objects). Those two are about getting the suite into the bucket and not re-sending it; this one is about not degrading it once it is there.

The bug

CreateSuiteOutput is written to enrich an existing summary.json rather than replace it: it preserves the prior tests[], backfills payload sizes and tx counts from the step files, and folds in opcode data (suite.go:356-388 on master). That merge is gated on suiteExists, which is decided by reading <resultsDir>/suites/<hash>/summary.json from local disk.

On every CI worker the results directory is under RUNNER_TEMP (/opt/actions-runner/_work/_temp/results), which Actions wipes between jobs. So suiteExists is always false, the merge never runs, and each run rebuilds the summary from its own inputs and uploads it over whatever the store had accumulated.

Nothing is lost today, because every field currently in play is recomputable from the step files — which is why this has gone unnoticed. It stops being true the moment two runs of the same suite differ in config richness. tests.opcode_source is the clearest case: a run without it cannot recompute those counts, so it publishes a poorer summary and the richer one is gone. Whichever run finishes last wins.

The change

  • executor.Config gains an optional RemoteSuiteSummary hook — a plain func, so the executor takes on no storage dependency.
  • cmd/benchmarkoor/run.go wires it to upload.NewS3Reader when results upload is configured. Not configured (local runs, tests) → nil → behaviour is exactly as before.
  • CreateSuiteOutput takes the stored summary as a baseline and merges into it when there is no usable local one.

Two things I deliberately kept separate:

Materialising the step files stays keyed on the local summary alone. A stored summary says nothing about what is on local disk. Conflating them would let a wiped worker skip the copy and have nothing to upload — which is exactly how a partially uploaded suite would become permanent. TestCreateSuiteOutput_MergesStoredSummaryOnWipedWorker asserts the files are still written.

A fetch failure is not fatal. It logs and falls back to today's behaviour. The merge is an enrichment, never a precondition for writing the suite.

validSummary also replaces the inline check in both places: a truncated or test-less summary is not a merge baseline, local or remote. That is the condition the original comment warned about leaving "tests": null behind.

Tests

  • TestCreateSuiteOutput_MergesStoredSummaryOnWipedWorker — fresh results dir, no opcode source, stored summary carrying opcode counts. Asserts the counts survive, the step files are still materialised, and payload sizes are still backfilled from them.
  • TestCreateSuiteOutput_IgnoresUnusableStoredSummary — truncated / test-less / empty stored summaries all fall back to this run's own description.
  • TestCreateSuiteOutput_MergesPayloadSizesOnSecondRun (existing) still passes, so the local merge path is untouched.

What I left out

Concurrent writers. Six clients run the same suite in parallel and each uploads at the end, so this is read-modify-write without a compare-and-swap: two workers can both read baseline R and the second write wins. It does not matter while every field is content-derived and each worker computes identical bytes, which is the case today. If enrichment that varies per run ever lands, the fix is a conditional PutObjectIfMatch is on PutObjectInput in the SDK we already vendor, and R2 supports it — retrying the merge on 412. Not worth the machinery for a race that currently has nothing to lose.

summary.json is still never skipped by #302. With this change the regenerated summary is usually byte-identical to the stored one and the ETag check would skip it anyway, but the explicit exception costs ~5 MB a run and covers the case a content check cannot: a summary above the 64 MiB part size falls back to size-only comparison.

Verification

make build-core, make lint-core (0 issues), full go test ./pkg/... ./cmd/... all pass locally.

CreateSuiteOutput enriches an existing summary.json rather than replacing it —
it preserves the prior tests[], backfills payload sizes and tx counts from the
step files, and folds in opcode data. But it only ever looked for that prior
summary on local disk.

Every CI worker starts a job with an empty RUNNER_TEMP, so the local suite
directory never survives: suiteExists is always false, the merge never runs,
and the summary is rebuilt from that one run's inputs and uploaded over
whatever the store had accumulated. Two runs of the same suite with different
config richness — one with tests.opcode_source set, one without — silently
resolve to whichever finished last.

Pass the stored summary in as a baseline. The executor fetches it through an
optional RemoteSuiteSummary hook, wired to the S3 reader when results upload is
configured, so the executor keeps no storage dependency and a local-only run
behaves exactly as before.

Materialising the step files stays keyed on the local summary alone. A stored
summary says nothing about what is on local disk, and conflating the two would
leave a wiped worker with nothing to upload — which is how a partially uploaded
suite would become permanent.

A fetch failure logs and falls back to the previous behaviour: the merge is an
enrichment, never a precondition for writing the suite.
@qu0b qu0b closed this Aug 11, 2026
qu0b added a commit that referenced this pull request Aug 11, 2026
Implements @skylenet's suggestion: a size limit on pre-run bundles,
512MB default, anything over it skipped.

## Why

The jochemnet bloatnet pre-run bundle is a single **9.4 GiB**
`pre-run.request` — roughly 8k blocks of setup. `CreateSuiteOutput`
copies it into the suite directory on every job, and the runner then
uploads it. Nothing consumes it: the runner replays pre-runs from the
fixtures cache, not from the suite, and the UI has no use for a bundle
that size.

## What it does

A pre-run step over `runner.benchmark.tests.max_pre_run_step_size`
(default `512MB`) is recorded in `summary.json` and not copied, so it
never reaches the bucket. `0` disables the limit.

```json
"pre_run_steps": [
  { "og_path": "pre_run/pre-run.request", "size_bytes": 10062313486, "omitted": true }
]
```

Two details worth flagging for review:

**Checked with a `stat` before the copy, not at upload time.** Skipping
it at the upload layer would still leave a 9.4 GiB write into
`RUNNER_TEMP` on every job. Doing it here costs neither the write nor
the transfer, and keeps the uploader generic — it has no business
knowing which files are pre-run steps.

**The entry is recorded as `omitted`, not dropped.** The suite stays
honest about what the run replayed, and the UI gets something to check
before offering the file for viewing — `TestFilesList.tsx:147` currently
links `suites/<hash>/<og_path>/pre_run.request` unconditionally, which
would 404 for an omitted bundle. That UI guard is a separate change;
this PR gives it the flag to read.

## Tests

- `TestCreateSuiteOutput_OmitsOversizedPreRunSteps` — over the limit: no
file written, summary records `omitted` and the true size, test steps
unaffected.
- `TestCreateSuiteOutput_KeepsPreRunStepsWithinLimit` — under the limit
and with the limit disabled, the bundle is stored exactly as before.

## How this relates to the other PRs

- **#301** (merged) makes the upload of any large file work at all.
Still wanted — it is what stops a >5 GiB file failing the whole suite
upload, including the `summary.json` that the UI actually needs — but
with this cap the 9.4 GiB path stops being exercised.
- **#302** (skip unchanged objects) is orthogonal: it stops re-sending
the ~2.96 GB of fixture payloads on every run, which this PR does not
touch. Together the steady-state suite upload goes from ~12.4 GB a run
to ~5 MB.
- **#303** (merge the stored summary) is a different bug and independent
of both. Note it also adds a parameter to `CreateSuiteOutput`, so
whichever of #303/#304 merges second needs a trivial rebase — happy to
fold both into an options struct if you'd rather.

## Verification

`make build-core`, `make lint-core` (0 issues), full `go test ./pkg/...
./cmd/...` all pass locally.
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.

1 participant