Skip to content

feat(xtest): cross-SDK benchmark roll-up and decision-first summaries - #623

Draft
dmihalcik-virtru wants to merge 4 commits into
mainfrom
DSPX-4372-04-reporting-rollup
Draft

dmihalcik-virtru wants to merge 4 commits into
mainfrom
DSPX-4372-04-reporting-rollup

Conversation

@dmihalcik-virtru

Copy link
Copy Markdown
Member

Split out of #583. Stacked on #622.

Each SDK's bench job already reports its own verdict; nothing combined the three into one answer to "did this change regress anything," and a reviewer had to open three job summaries to find out.

  • perf/report.py: the per-SDK summary is reordered for progressive disclosure (TL;DR, compared builds, what changed, then the full table), and the effect-at-a-glance scale is tail-compressed so a planted outlier does not flatten every ordinary-sized effect. Renders bake-off results from the K-arm core alongside gated verdicts.
  • perf/aggregate.py (new): pure-stdlib cross-SDK roll-up read from the JSON artifacts, not the markdown, so a partially failed matrix still gets a combined answer for whichever SDKs reported.
  • xtest.yml: new benchmark-summary job downloads every SDK's bench-result-* artifact and publishes the roll-up as the step summary once all three matrix jobs finish. Renamed the per-SDK upload from the emoji-prefixed bench-<sdk> to bench-result-<sdk>, which is what the roll-up's artifact-name pattern actually matches.

Built on #622 (payload sizes) and #621 (K-arm core, for bake-off rendering).

A branch ref like 'feat/DSPX-2604-createtdf-chunked' resolved by name (not
by SHA) kept its slash in the tag, nesting dist/<tag>/ and src/<tag>/ one
level deeper than every consumer expects: xtest's all_versions_of() lists
dist/*/ and the Go Makefile finds src/*/, so the build was silently
discovered as a bare 'feat' directory with no cli.sh in it.

Flatten the tag the same way _classify_sha_match already flattens a branch
reached by SHA.
Generalizes the paired A/B benchmark from a fixed baseline/candidate pair to
2-4 named arms, so a bake-off between competing implementations of the same
feature is one measurement on one runner instead of two separate two-arm
dispatches whose ratios share no denominator.

- perf/runner.py: Arm/CellResult keyed by arm_ids and an explicit reference,
  replacing the baseline/candidate pair. The A/A control now runs K copies of
  the reference (not a cheap pair), since the last arm in a K-arm round
  carries more within-round drift than an adjacent pair does. Also folds in
  trunk's independent refinements: per-arm deadline capping so one slow arm
  can't run past the shared budget, and a strict configured-minimum-rounds
  check (previously only the statistical hard floor was enforced).
- perf/stats.py: every non-reference arm gets its own gated contrast against
  the reference; every pair of non-reference arms gets a symmetric
  FASTER/SLOWER/TIED head-to-head that never gates the build. Three
  BH-corrected families (gated / symmetric / ungated) so a bake-off costs the
  regression gate no power. Also folds in trunk's has_candidate_comparisons
  fix, which catches a control-only run that trunk's simpler nothing_measured
  check would have missed.
- perf/report.py, fixtures/bench.py, conftest.py: --bench-refs (with
  --bench-baseline/--bench-candidate kept as two-arm shorthand), arm
  resolution and comparability checks generalized to K arms, and bake-off
  results rendered in the job summary and terminal output.
- xtest.yml: bench-refs/bench-budget-seconds/bench-max-rounds inputs, a
  validate-and-normalize step, and a per-arm resolution step in the bench
  job. Budget defaults scale by arm-count/2 since a K-arm round costs K
  invocations, and the job timeout goes to 240m to cover it.

Depends on the otdf-sdk-mgr slash-flattening fix: named-ref arms can be
arbitrary branches, and an unflattened tag breaks build discovery.
At the default sizes (1 KiB, 1 MiB, 32 MiB) this harness cannot fail a build
on throughput: on a 4-core runner a go encrypt costs ~450ms of fixed startup
against ~72ms that scales with the payload, so the 1.15x gate is wider than
the whole payload-dependent part. --bench-payloads lets a manual dispatch ask
a throughput question the nightly cannot afford to ask every night.

- perf/cells.py: Payload/parse_payload/parse_payloads replace the fixed
  3-entry PAYLOADS tuple, so a spec like '1KiB,1GiB' builds an arbitrary cell
  matrix. CONTROL_PAYLOAD is a fixed 1 MiB literal rather than PAYLOADS[1] --
  same value, but no longer coupled to whatever the selection happens to be,
  since the control's CI width is the run's noise floor and it must not move
  with --bench-payloads.
- fixtures/bench.py: --bench-payloads wiring, chunked payload generation (a
  1 GiB file is written in 8 MiB chunks rather than held in memory), and an
  up-front disk-space check -- a run holds roughly twice the payload total
  plus one live output per arm of the largest, and running out mid-benchmark
  otherwise surfaces as a non-zero exit that reads as 'this build is broken'.
- xtest.yml: bench-payloads input, validated against the real parser before
  the platform spins up rather than re-implementing the grammar in bash.

Built on the K-arm core: the disk estimate and the default budget both scale
with arm count, since a K-arm cell holds K live outputs instead of 2.
Each SDK's bench job already reports its own verdict; nothing combined the
three into one answer to 'did this change regress anything', and a reviewer
had to open three job summaries to find out.

- perf/report.py: the per-SDK summary is reordered for progressive
  disclosure -- TL;DR, compared builds, what changed, then the full table --
  and the effect-at-a-glance scale is tail-compressed so a planted 3x outlier
  does not flatten every ordinary-sized effect into the same pixel. Renders
  bake-off results (from the K-arm core) alongside the gated verdicts.
- perf/aggregate.py (new): pure-stdlib -- no dependency sync needed in a job
  that runs after everything else already finished -- cross-SDK roll-up
  read from the JSON artifacts, not the markdown, so a partially failed
  matrix still gets a combined answer for whichever SDKs reported.
- xtest.yml: new benchmark-summary job downloads every SDK's bench-result-*
  artifact and publishes the roll-up as the step summary once all three
  matrix jobs finish. Renamed the per-SDK upload from the emoji-prefixed
  'bench-<sdk>' to 'bench-result-<sdk>', which is what the roll-up's
  artifact-name pattern actually matches -- the emoji form was never
  discoverable by it.

Built on the K-arm core (bake-off rendering) and payload sizes (the summary
reads whatever payload set the run measured).
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@dmihalcik-virtru
dmihalcik-virtru force-pushed the DSPX-4372-03-payload-sizes branch from 65e13ab to fffd648 Compare October 19, 2026 17:29
Base automatically changed from DSPX-4372-03-payload-sizes to main October 20, 2026 17:38
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