Skip to content

Let /benchmark compare against another framework#1031

Merged
MDA2AV merged 3 commits into
mainfrom
compare-against-framework
Jul 24, 2026
Merged

Let /benchmark compare against another framework#1031
MDA2AV merged 3 commits into
mainfrom
compare-against-framework

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Closes #741.

The delta table always baselined a framework against its own published results, which answers "did my change help?". When developing a tuned build or a successor entry, the useful question is usually "how does this compare to the entry it derives from?" — and there was no way to ask it.

/benchmark -f genhttp-11 --compare genhttp
./scripts/compare.sh genhttp-11 --compare genhttp

compare.sh gains --compare <framework>: it resolves that framework's display_name from its meta.json and looks that up in site/data instead of its own. The table itself is unchanged.

Three details that matter for reading the result

  • The output states the baseline above the tables. A raw +6361% against a different framework would otherwise be indistinguishable from an enormous regression against the framework's own history.
  • Missing profiles report n/a, not NEW. NEW means "no published result yet"; against a different entry it would read as though the profile were new, when it simply isn't run there.
  • A bad name is reported, not silently ignored — otherwise a typo shows NEW in every cell and looks like a successful comparison.
Deltas are against **genhttp** published on `main`, not against `genhttp-11`'s own results.

### baseline

| Metric | 512c |  |
|--------|------|--|
| **RPS** | 2.14M | +6361.0% |
| **p99** | 1.19ms | -98.6% |
| **CPU** | 5769% | +19.2% |
| **Memory** | 246MB | -19.6% |

Plumbing

pr-commands.yml parses --compare <fw> (and --compare=<fw>) and forwards it; benchmark-pr.yml takes it as an input, passes it to compare.sh, and records it in the comment header as Compared to: genhttp. The table format is untouched, so the existing parser that lifts deltas out of compare.sh output keeps working.

Documented in compare.md, the README command table and the pull request template.

Verification

check result
no flag output byte-identical to before
--compare genhttp deltas repriced against genhttp's 33,073 rps instead of genhttp-11's 1,997,039
unknown framework reports it, exits cleanly
comparison framework has no results for the profile says so, cells show n/a
both workflows still parse as YAML
comment parsing checked across flag orders and the --compare=<fw> form

Deliberately not done

The issue floated "or maybe even both" (deltas against the framework's own results and another's). That means three cells per connection count, and benchmark-pr.yml's comment parser indexes cells two-per-conn — so it would need changing in lockstep. Happy to add it as a follow-up if you want both baselines side by side.

🤖 Generated with Claude Code

Closes #741. The delta table always baselined a framework against its own
published results, which answers "did my change help?". When you are
developing a tuned build or a successor entry, the useful question is usually
"how does this compare to the entry it derives from?", and there was no way
to ask it.

    /benchmark -f genhttp-11 --compare genhttp
    ./scripts/compare.sh genhttp-11 --compare genhttp

compare.sh gains --compare <framework>: it resolves that framework's
display_name from its meta.json and looks up that name in site/data instead
of its own. The rest of the table is unchanged.

Three details that matter for reading the result:

The output states the baseline in a line above the tables, because a raw
"+6361%" against a different framework would otherwise be indistinguishable
from an enormous regression against the framework's own history.

Profiles the comparison framework does not run report n/a rather than NEW.
NEW means "this framework has no published result yet"; against a different
entry it would read as though the profile were new, when it simply isn't run
there.

A framework directory that doesn't exist is reported ("No such framework to
compare against: `nope`") instead of silently comparing against nothing,
which would have shown NEW for every cell.

Plumbing: pr-commands.yml parses --compare <fw> (and --compare=<fw>) from the
comment and forwards it; benchmark-pr.yml takes it as an input, passes it to
compare.sh, and records it in the comment header as "Compared to: `genhttp`".
The table format is untouched, so the existing parser that lifts deltas out
of compare.sh's output keeps working.

The flag is documented in compare.md, the README command table and the pull
request template.

Verified against real published data: with no flag the output is byte
-identical to before, --compare genhttp reprices every delta against
genhttp's 33,073 rps baseline instead of genhttp-11's 1,997,039, the typo and
no-results paths print their messages, and both workflow files still parse as
YAML. Comment parsing checked across flag orders and the --compare=<fw> form.
MDA2AV added 2 commits July 24, 2026 21:56
Adding --compare put a fourth comment-derived value into a shell script via
${{ }} interpolation, which is how GitHub Actions script injection works. The
value is parsed out of a PR comment with grep -oP '\S+', and \S includes
quotes and semicolons, so a comment like

    /benchmark -f x --compare a";touch${IFS}/tmp/PWNED;#

expands inside pr-commands.yml to

    gh workflow run benchmark-pr.yml ... -f compare="a";touch${IFS}/tmp/PWNED;#"

and the runner executes it. Confirmed locally by reproducing that expansion.

This matters because handle-benchmark has no approval gate: it fires on any
issue_comment containing /benchmark, from any user, with actions: write and a
GITHUB_TOKEN. The environment: runner gate protects the later benchmark job in
benchmark-pr.yml, not this one.

framework, profile and save were already interpolated the same way, so this
closes all four rather than only the one I added.

Two layers:

Sanitize at the source. Framework directories and profile names are
[A-Za-z0-9._-]; anything else becomes empty, so a crafted value is dropped
before it reaches any script. Verified: genhttp, genhttp-11 and
aspnet-minimal_nginx pass; the payload above, $(whoami), "a b" and
../../etc/passwd all become empty, and the run falls back to the normal
self-comparison instead of failing.

Pass through env. The dispatch step and the compare step now receive values
as environment variables instead of having them pasted into the script text,
so nothing in them can be parsed as code even if the whitelist is ever
loosened. compare_args also becomes an array, so the argument is passed
whole rather than relying on word splitting.

Functionally unchanged: the full chain still parses
"/benchmark -f genhttp-11 -t baseline --compare genhttp" correctly, produces
the same table, and the delta parser in the post-results step still reads it.
The command tables listed the flag but the text under them still said results
are "automatically compared against the current leaderboard", which no longer
describes what happens when --compare is used. Both now state what the deltas
are measured against by default, and show the flag in context.

The PR template was also missing the `-t <test> --save` combination that the
README already documented; both tables now list the same five forms.
@MDA2AV
MDA2AV merged commit f8c6c13 into main Jul 24, 2026
MDA2AV added a commit to Kaliumhexacyanoferrat/HttpArena that referenced this pull request Jul 24, 2026
Six merges landed since the last sync: MDA2AV#1030, MDA2AV#1031, MDA2AV#1032, MDA2AV#1033, MDA2AV#1034 and
MDA2AV#1036. One real conflict, in the composite blurb, where both sides had
changed the same line for different reasons:

  main (MDA2AV#1032)  made the text state which normalization rule is active, so a
                screenshot can't be ambiguous about whether scores were
                rescaled to the current filter
  this branch   pointed the "How it works" link at /docs/scoring/composite
                -score/ instead of the hash route

Both are kept: MDA2AV#1032's wording with this branch's real URL.

Everything else merged on its own, but the two structural ones were checked
rather than assumed:

MDA2AV#1034 moved results from 52 shared per-profile arrays to one file per
framework. Git applied that to the renamed generator correctly — it still
loads site/data/results/*.json and still emits 1941 rows, and compare.sh
kept both its per-framework read and the --compare flag from MDA2AV#1031.

MDA2AV#1030 added three echo-ws-limited doc pages after this branch's SEO pass, so
they were written with seo_title/description already; all 126 pages now carry
both.

Generator output on the merged tree: 1941 rows, 126 static doc pages, a 126
-page search index and a 127-URL sitemap. All six workflows validate against
a loader that rejects duplicate keys, which is what MDA2AV#1036 had to fix.
@MDA2AV
MDA2AV deleted the compare-against-framework branch July 24, 2026 21:54
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.

Compare benchmark results with another framework in PRs

1 participant