Skip to content

Compare pull requests against the right benchmark baseline - #1893

Merged
jprendes merged 1 commit into
hyperlight-dev:mainfrom
jprendes:ci-fresh-baseline-listing
Oct 7, 2026
Merged

jprendes merged 1 commit into
hyperlight-dev:mainfrom
jprendes:ci-fresh-baseline-listing

Conversation

@jprendes

@jprendes jprendes commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

A pull request is measured against the daily benchmark run at the commit it branched from. That run was being chosen out of a listing GitHub served stale, so reports compared against commits weeks older than the branch point.

PR #1881 branched at 66d5dd3e on 2 October and was reported against c3e1d311 from 4 September. 102 of its 114 table cells held no comparison at all, because most of the reported benchmarks did not exist a month earlier, and two configurations announced a 3.79x regression for a change that redirects fuzzer output.

Cause

run_at asked GitHub for the successful runs of the workflow. Narrowing a listing server-side is what leaves it answering out of a cold index:

?status=success   newest run 2026-09-06, in 4 of 10 tries
no filter         newest run 2026-10-05, in 5 of 5 tries

The runs it omits are indistinguishable from the ones it returns, both status=completed conclusion=success. The existing guard asks until two replies agree on the newest run, which does not help: the stale answer is stable, so consecutive replies agree on it.

The narrowing happens locally now, on a conclusion field the listing already carries.

Also here

The run reports how many commits separate the baseline from the branch point, read from the comparison it already performs:

Pull request 1881 branched at 66d5dd3ed585
Taking benchmarks of 66d5dd3ed585, 0 commits back

A baseline a month back reads the same as one next door. This is what would have made the bug visible the first time it happened.

Verified

Re-running the same report against the same run:

cells without baseline verdicts
before 114 102 (89%) ❌ 3.79x, ❌ 2.42x, stable x4
after 114 0 (0%) stable x6

latest_run_for still filters by --branch, the same class of server-side narrowing. It measured current in 5 of 5 tries, so this leaves it alone.

Copilot AI balanced review requested due to automatic review settings October 6, 2026 15:04
@jprendes jprendes added the C-bug For PRs that fix bugs label Oct 6, 2026
@jprendes
jprendes requested a review from simongdavies as a code owner October 6, 2026 15:04
@jprendes jprendes added the A-testing Related to tests or testing infrastructure label Oct 6, 2026
@jprendes jprendes added C-bug For PRs that fix bugs A-testing Related to tests or testing infrastructure labels Oct 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The core baseline-selection behavior needs regression coverage.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Selects benchmark baselines reliably by filtering workflow conclusions locally and reporting baseline distance.

Changes:

  • Adds workflow conclusion data and local success filtering.
  • Reports commit distance from the requested baseline.
File Description
src/​hyperlight_ci/​src/​remote.rs Improves benchmark run selection and diagnostics.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/hyperlight_ci/src/remote.rs Resolved
A pull request is measured against the daily run at the commit it branched
from, found by walking the successful runs of that workflow. Asking GitHub for
only the successful ones is what made it answer out of a cold index: that
listing came back weeks stale in four of ten tries, stably enough that two
replies agreed on it, while the same listing unnarrowed was current every
time. The narrowing happens here now.

PR hyperlight-dev#1881 branched at `66d5dd3e` on 2 October and was reported against
`c3e1d311` from 4 September. Of 114 table cells, 102 held no comparison at
all, because most of the reported benchmarks did not exist a month earlier,
and two configurations announced a regression of 3.79x for a change that
redirects fuzzer output. Reading the same run now finds `66d5dd3e` itself and
fills every cell.

A baseline that far back reads the same as one next door, so the run says how
many commits separate them.

Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
@jprendes
jprendes force-pushed the ci-fresh-baseline-listing branch from be59a3b to 37a61cc Compare October 6, 2026 15:17
@hyperlight-gh-bot

Copy link
Copy Markdown

Benchmark Results

Measured commit: 37a61cc0cfe7
Baseline commit: 3ee2d4cb3cbf

kvm / amd (Linux) (❌ 1.64x)

Top regressions

  • payload_allocation/slot_pool_segmented/262144 — ❌ 1.64x slower

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 775.76 ns (➖ 1.00x slower)
vec_bytes 581.36 ns (➖ 1.05x faster)
373.19 µs (➖ 1.00x slower)

payload_allocation

slot_pool_segmented
262144 834.63 ns (❌ 1.64x slower)
65536 141.58 ns (➖ 1.10x faster)

sandboxes

create_initialized_and_drop
medium 79.10 ms (➖ 1.01x slower)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
7.74 ns (➖ 1.08x faster) 7.72 ns (➖ 1.00x faster) 7.73 ns (➖ 1.00x faster)

snapshot_files

load_snapshot_unverified
small 92.81 µs (➖ 1.00x slower)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 7.17 µs (➖ 1.07x faster) 7.16 µs (➖ 1.04x faster)
65536 2.07 µs (➖ 1.01x slower) 1.93 µs (➖ 1.05x faster)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 6.25 µs (➖ 1.00x faster) 6.35 µs (➖ 1.07x faster)
8192 1.06 µs (➖ 1.01x faster) 1.07 µs (➖ 1.00x faster)
262144 27.13 µs (➖ 1.02x slower)
kvm / intel (Linux) (❌ 1.56x)

Top regressions

  • slot_pool/alloc_dealloc_1500 — ❌ 1.56x slower

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 700.10 ns (➖ 1.15x faster)
vec_bytes 512.49 ns (➖ 1.20x faster)
662.98 µs (➖ 1.16x faster)

payload_allocation

slot_pool_segmented
262144 500.65 ns (➖ 1.22x faster)
65536 136.75 ns (➖ 1.19x faster)

sandboxes

create_initialized_and_drop
medium 78.76 ms (➖ 1.47x faster)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
7.71 ns (❌ 1.56x slower) 6.94 ns (➖ 1.20x faster) 6.92 ns (➖ 1.19x faster)

snapshot_files

load_snapshot_unverified
small 44.89 µs (➖ 1.16x faster)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 7.58 µs (➖ 1.19x faster) 7.59 µs (➖ 1.20x faster)
65536 2.12 µs (➖ 1.20x faster) 2.16 µs (➖ 1.19x faster)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 7.07 µs (➖ 1.21x faster) 6.99 µs (➖ 1.20x faster)
8192 749.13 ns (➖ 1.15x faster) 728.00 ns (➖ 1.18x faster)
262144 29.49 µs (➖ 1.19x faster)
mshv3 / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 950.26 ns (➖ 1.02x faster)
vec_bytes 700.48 ns (➖ 1.01x faster)
282.29 µs (➖ 1.22x faster)

payload_allocation

slot_pool_segmented
262144 731.04 ns (➖ 1.01x slower)
65536 194.84 ns (➖ 1.03x faster)

sandboxes

create_initialized_and_drop
medium 61.16 ms (➖ 1.12x slower)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
10.16 ns (➖ 1.06x slower) 9.73 ns (➖ 1.00x faster) 9.79 ns (➖ 1.02x slower)

snapshot_files

load_snapshot_unverified
small 84.36 µs (➖ 1.00x slower)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 9.36 µs (➖ 1.04x faster) 8.79 µs (➖ 1.01x slower)
65536 2.26 µs (➖ 1.01x slower) 2.36 µs (➖ 1.04x faster)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 8.17 µs (➖ 1.07x faster) 8.47 µs (➖ 1.02x slower)
8192 1.29 µs (➖ 1.03x faster) 1.33 µs (➖ 1.02x slower)
262144 36.01 µs (➖ 1.01x faster)
mshv3 / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 945.39 ns (➖ 1.03x slower)
vec_bytes 675.54 ns (➖ 1.00x slower)
715.18 µs (➖ 1.10x faster)

payload_allocation

slot_pool_segmented
262144 624.30 ns (➖ 1.00x faster)
65536 165.11 ns (➖ 1.00x slower)

sandboxes

create_initialized_and_drop
medium 68.04 ms (➖ 1.03x slower)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
9.65 ns (➖ 1.01x slower) 8.76 ns (➖ 1.00x faster) 8.80 ns (➖ 1.00x faster)

snapshot_files

load_snapshot_unverified
small 46.36 µs (➖ 1.03x slower)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 7.79 µs (➖ 1.00x faster) 7.95 µs (➖ 1.02x slower)
65536 2.29 µs (➖ 1.00x faster) 2.27 µs (➖ 1.01x faster)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 7.52 µs (➖ 1.01x faster) 7.54 µs (➖ 1.01x faster)
8192 865.07 ns (➖ 1.12x faster) 873.22 ns (➖ 1.01x faster)
262144 35.46 µs (➖ 1.01x faster)
hyperv-ws2025 / amd (Windows) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 1.16 µs (➖ 1.00x slower)
vec_bytes 751.47 ns (➖ 1.04x faster)
2.32 ms (➖ 1.05x slower)

payload_allocation

slot_pool_segmented
262144 826.81 ns (➖ 1.06x slower)
65536 230.60 ns (➖ 1.03x faster)

sandboxes

create_initialized_and_drop
medium 109.33 ms (➖ 1.02x faster)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
10.67 ns (➖ 1.06x slower) 10.57 ns (➖ 1.01x slower) 10.55 ns (➖ 1.06x slower)

snapshot_files

load_snapshot_unverified
small 718.62 µs (➖ 1.15x faster)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 8.74 µs (➖ 1.09x faster) 8.62 µs (➖ 1.16x faster)
65536 2.52 µs (➖ 1.05x slower) 2.50 µs (➖ 1.01x faster)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 8.98 µs (➖ 1.01x slower) 8.90 µs (➖ 1.03x slower)
8192 1.36 µs (➖ 1.02x faster) 1.36 µs (➖ 1.02x slower)
262144 55.61 µs (➖ 1.41x slower)
hyperv-ws2025 / intel (Windows) (❌ 1.60x)

Top regressions

  • virtq_readwrite/slot_pool_segmented/262144 — ❌ 1.60x slower

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 1.24 µs (➖ 1.06x slower)
vec_bytes 810.43 ns (➖ 1.12x slower)
3.61 ms (➖ 1.03x faster)

payload_allocation

slot_pool_segmented
262144 744.28 ns (➖ 1.06x slower)
65536 211.96 ns (➖ 1.07x slower)

sandboxes

create_initialized_and_drop
medium 119.86 ms (➖ 1.08x faster)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
10.11 ns (➖ 1.12x slower) 10.68 ns (➖ 1.18x slower) 10.05 ns (➖ 1.11x slower)

snapshot_files

load_snapshot_unverified
small 645.09 µs (➖ 1.15x slower)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 7.64 µs (➖ 1.21x faster) 7.69 µs (➖ 1.19x faster)
65536 2.31 µs (➖ 1.18x faster) 2.31 µs (➖ 1.15x faster)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 8.70 µs (➖ 1.13x faster) 8.56 µs (➖ 1.11x faster)
8192 1.12 µs (➖ 1.08x faster) 1.22 µs (➖ 1.02x slower)
262144 59.46 µs (❌ 1.60x slower)

Reported by cargo ci bench-report --candidate run:37486281215 --baseline run:37393536548 --config-file bench_report.toml.

@jprendes
jprendes merged commit 5ddb37a into hyperlight-dev:main Oct 7, 2026
267 of 275 checks passed
jprendes added a commit that referenced this pull request Oct 8, 2026
…#1894)

A pull request is measured against the daily benchmark run at the commit
it
branched from. That run came from another machine. The runner pools hold
more
than one processor model per vendor and are shared with whatever else is
scheduled there, so the difference between two machines reads as a
difference
between two commits.

PR #1893 touches one file of CI tooling and cannot alter a benchmark. It
was
reported as a 1.58x regression on `hyperv-ws2025 / intel`, because its
baseline
landed on a processor the rest of the week did not:

| run | `virtq_readwrite/slot_pool_segmented/262144` | processor |
|:----|---:|:---|
| 2 Oct | 43.2k | Model 106 |
| 3 Oct | 43.0k | Model 106 |
| 4 Oct | 45.2k | Model 106 |
| 5 Oct | 43.1k | Model 106 |
| **6 Oct, the baseline** | **37.6k** | **Model 143** |
| **the pull request** | **59.5k** | **Model 106** |

## Cause

Two machines, not two commits. Of the six configurations, two draw from
pools
holding more than one processor model, where the models differ by 10%
and 11.9%
while runs on the same model differ by 2% and 4.4%.

Shared tenancy moves the rest.
`payload_allocation/slot_pool_segmented/262144`
sat at 524, 526, 526, 533 and 593 ns across the week on one processor
model,
and the same commit measured 835 ns on the pull request's runner.

Criterion's confidence interval does not see either. It measures the
samples
within one run, which vary by 0.90%, while a run measured again the next
day
moves 7.65%, more than the interval allows in 111 of 114 benchmarks.

## What this does

`cargo ci bench --baseline-ref <COMMIT>` measures that commit before the
one
checked out, saving its results as the baseline criterion then compares
against. The two sit in one criterion directory, so a report reads the
comparison the run carries and names no baseline of its own.

The baseline is built in a git worktree, so the checkout in hand is
never moved
and a run that fails leaves nothing to restore. Each benchmark binary
resolves
its guests relative to its own source tree, so the baseline reads the
worktree
while the current tree keeps its own, with no swapping between passes.

The guests the baseline needs are built by the job that already holds
the
toolchain for them. `HEAD^1` names the branch point because the checkout
is the
merge its validation builds, and both jobs assert that before relying on
it,
since on anything else `HEAD^1` is the previous commit and would measure
against it without saying so.

## Also here

criterion-markdown 0.2.2. A report shows criterion's typical estimate,
the
regression slope wherever linear sampling fits one, while the change
beside it
read the mean. The mean weights every sample alike and so carries the
warm-up
the slope leaves behind: `slot_pool/alloc_dealloc_1500` reported 1.56x
slower
while its slope held 7% faster, from 32 samples that ran cold before the
rest
settled.

## Verified

The pass and its comparison run end to end, leaving `base`, `new` and
`change`
in one directory for the report to read, and the worktree removed after
it.
`cargo ci bench` without `--baseline-ref` is unchanged, which is what
the daily
runs use.

The benchmark report on this pull request is the first one measured this
way.

---------

Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-testing Related to tests or testing infrastructure C-bug For PRs that fix bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants