Skip to content

CI: gate PRs on the sharded JVM-vs-native CPU/RSS head-to-head - #241

Open
m-hu wants to merge 5 commits into
perf/h2h-cpu-memfrom
perf/h2h-ci-gate
Open

m-hu wants to merge 5 commits into
perf/h2h-cpu-memfrom
perf/h2h-ci-gate

Conversation

@m-hu

@m-hu m-hu commented Nov 4, 2026

Copy link
Copy Markdown

Stacked on #225 (base: perf/h2h-cpu-mem, merges after it).

Problem

PR225 proved the 4-leg head-to-head sweep — recipes + algorithms suites, JVM vs clojure-rs, per-case median cpu_s/maxrss_kb — by hand on a workstation. That signal rots the moment the next perf PR lands: nothing re-runs it, nothing reds when the native runtime regresses against the JVM on a case.

Root cause

The sweep recipe (Go harnesses, pinned suites, GNU-time signal, median-compare verdict) lives only in a session log and an uncommitted /tmp script. No workflow encodes it, so every future perf claim re-pays the manual-sweep cost or skips the evidence.

Solution

Encode the session recipe as a required workflow + committed gate script:

  • .github/workflows/h2h-cpu-mem.yml: 8 shards x 2 runtimes x 2 suites (32 measure jobs) + fan-in compare job. Triggers pull_request + workflow_dispatch; cancel-in-progress concurrency like rust.yml.
    • build-rs builds clojure-rs from the PR itself (cargo build --release --locked --no-default-features, interpreter-only) once per run and fans it out as an artifact to the 16 native legs.
    • Measure legs mirror the session flags exactly (--repeat 3 --no-alloc --no-perf --jobs 3, temurin 21.0.13, Clojure 1.12.0 jars, GNU time, 0-based --shard K/8).
    • Suite revisions pin to master SHAs (SNIPPETS_SHA_DEFAULT, ALGORITHMS_SHA_DEFAULT) with dispatch inputs to override either for bisection.
    • Recipes legs decrypt tests/resources.tar.xz.asc exactly like the snippet repo and fail loud with a clear message if RESOURCES_PASSPHRASE is missing (the algorithms suite carries resources/ in-repo, so it needs no decrypt).
  • scripts/ci/h2h_compare.py (stdlib-only): ports the session compare script — median verdicts, README min/max cross-check, loss/failure/missing/drift lists — extended to union multiple shard JSONs per leg (duplicate case keys are a hard error), append a Markdown verdict table to $GITHUB_STEP_SUMMARY, write merged leg JSONs (uploaded as artifacts), and exit non-zero on any CPU or RSS loss (also on failures, missing, not-ok, drift, or leg mismatch). The two known-random algos namespaces get the same :pass-zeroing the algos workflow's own drift gate uses. Triage mode: dispatch with report_only=true (or flip the gate) to get verdict tables green.
  • No Bencher reporting, deliberate: this is a gate, not a tracker.

No regressions

New workflow + new script only; no existing file touched, no behavior change to any current job. The workflow only adds checks.

Honest notes for the reviewer

  1. The strict gate is expected-RED until the 47 known recipes CPU losses are fixed. Session numbers on the pinned suites: recipes CPU 1707 wins / 47 losses / 0 ties, MEM 1754/0/0 (worst: search/bfs/test_case_5 at 23.3x). If you would rather land this green first, dispatch/merge with report_only=true (triage mode) or say the word and I will flip the default to report-only.
  2. A maintainer must add RESOURCES_PASSPHRASE to hhh-pa/clojure secrets (copy the value from hhh-pa/clojure-code-snippet, Settings -> Secrets and variables -> Actions) or every recipes leg fails loud at decrypt.

Test Plan

  • python3 -c yaml.safe_load on the workflow: parses; jobs build-rs, h2h-compare, measure-algos, measure-recipes, resolve-suites; triggers pull_request, workflow_dispatch.
  • actionlint 1.7.7 on the workflow: clean. (shellcheck unavailable here — x86_64-only binary on an arm64 Mac — so all 14 inline run: blocks were extracted and bash -n checked instead: all OK.)
  • Ported h2h_compare.py --jvm /tmp/recipes-jvm.json --rs /tmp/recipes-rs.json --label recipes: verdicts identical to the session script — CPU 1707/47/0, MEM 1754/0/0, same worst ratios (23.300x CPU, 0.560x MEM), same 47-loss list.
  • Sharded-union check: session JSONs split 8 ways, compared as 8+8 inputs: identical verdicts; merged leg JSON round-trips to 1754/1754 results.
  • Exit codes: strict mode 1 on the session data (47 losses), 0 with --report-only, 2 on duplicate case keys across shards, 2 on missing input files.
  • Fan-in glob layout simulated with download-artifact-style directories: globs resolve, both suites compare, GATE: FAIL lands in the summary, merged JSONs upload.
  • git diff --check: clean.
  • Not verified: the full workflow run itself — it needs GitHub runners plus the RESOURCES_PASSPHRASE secret, neither available from here. The first CI run on this PR (after the secret lands) is the real proof; expect red per note 1.

AI-Tool: Muse Code (Meta Muse Spark)

@m-hu
m-hu requested a review from michael-h-hu as a code owner November 4, 2026 18:30
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Coverage report: 100.00% (lines)

Threshold 100%: PASS (enforced by the quality gates).

File Lines Hit %%
rust/crates/clj-aot/src/driver.rs 31 31 100.00%
rust/crates/clj-aot/src/jit.rs 156 156 100.00%
rust/crates/clj-aot/src/lib.rs 72 72 100.00%
rust/crates/clj-aot/src/link.rs 246 246 100.00%
rust/crates/clj-aot/src/lower.rs 785 785 100.00%
rust/crates/clj-aot/src/target.rs 48 48 100.00%
rust/crates/clj-core/src/lib.rs 3 3 100.00%
rust/crates/clj-eval/src/aot_subset.rs 2027 2027 100.00%
rust/crates/clj-eval/src/arrays.rs 1123 1123 100.00%
rust/crates/clj-eval/src/async.rs 1125 1125 100.00%
rust/crates/clj-eval/src/builtin_expand.rs 358 358 100.00%
rust/crates/clj-eval/src/classemu.rs 3580 3580 100.00%
rust/crates/clj-eval/src/coll.rs 2090 2090 100.00%
rust/crates/clj-eval/src/collmisc.rs 784 784 100.00%
rust/crates/clj-eval/src/compiler.rs 211 211 100.00%

Lowest 15 of 99 files. Full HTML report: download the coverage-report artifact.

Updated by the quality gates run on every push.

@codecov

codecov Bot commented Nov 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@m-hu
m-hu added this pull request to stack #242 November 4, 2026 21:42
m-hu and others added 3 commits November 4, 2026 22:43
PR225 proved the 4-leg sweep (recipes + algorithms, JVM vs clojure-rs,
per-case median cpu_s/maxrss_kb) on a workstation; without CI the signal
rots the moment the next perf PR lands. Encode the session recipe as a
required workflow: 8 shards x 2 runtimes x 2 suites (32 measure jobs)
plus a strict fan-in gate that fails the PR on ANY loss.

The runtime under test builds from the PR itself (interpreter-only
release, once per run, fanned out as an artifact); suite revisions pin
to master SHAs with dispatch overrides for bisection. No Bencher
reporting by design: this gates, it does not track history.

AI-Tool: Muse Code (Meta Muse Spark
actions/download-artifact does not preserve the executable bit, so
every clojure-rs leg failed with '/usr/bin/time: cannot run
clojure-rs: Permission denied' (exit 126). Restore the bit before
adding the directory to PATH, in both the recipes and algos legs.
…ret)

The org-level RESOURCES_PASSPHRASE decrypts only the bundle rebuilt in
hhh-pa/clojure-code-snippet#51; the old pin predates the rotation, so
every recipes leg failed in gpg with 'Bad session key'.

AI-Tool: Muse Code (Meta Muse Spark)
@m-hu
m-hu force-pushed the perf/h2h-ci-gate branch from e58f74e to 015cc7d Compare November 4, 2026 21:43
automation added 2 commits November 5, 2026 00:29
Sweep-skipped namespaces are recorded run-less in EVERY shard by
design; the shard union treated the repeats as conflicting dupes and
aborted the algos gate with exit 2. Dedupe identical run-less markers
(and report them as SKIPPED-NO-RUNS instead of crashing the median
comparison); genuine duplicate timed cases still fail loudly.

AI-Tool: Muse Code (Meta Muse Spark)
Build both ends of the change (PR base/head, or master/dispatched
ref) and sweep each suite under jvm + rs-from + rs-to (48 legs).
The gate still fails only on JVM losses; the rs-from-vs-rs-to
verdict tables are report-only (near-tie wobble), showing what the
PR or branch changed against its base. rs_from/rs_to dispatch
inputs take a SHA or branch/tag for bisection.

AI-Tool: Muse Code (Meta Muse Spark)
@m-hu

m-hu commented Nov 5, 2026

Copy link
Copy Markdown
Author

Changelog: native-vs-native comparison added on top of the JVM gate.

  • Each suite now sweeps 3 runtimes (jvm, rs-from, rs-to, 8 shards each = 48 measure jobs); both native revisions are built from source in-matrix and fanned out as separate artifacts.
  • Revision pair resolves automatically: PR base/head on pull_request (stacked PRs diff against their parent), master/dispatched-ref on workflow_dispatch; rs_from/rs_to inputs take a SHA or branch/tag for bisection.
  • The gate still fails only on JVM-vs-rs-to losses; the rs-from-vs-rs-to verdict tables are informational (report-only) so near-tie wobble can't red a PR. (h2h_compare.py learned --base-name/--challenger-name display labels; verdict math unchanged.)
  • Also refreshed the stale decrypt error text for the org-level secret.

Validation: YAML structural check (jobs/needs/matrices/outputs), bash -n on all 14 inline scripts, and the compare script re-verified (session verdicts reproduced with default labels; self-compare all-ties+PASS; inverted-direction mirror image; sharded skip-union and dupe-rejection from the previous fix intact).

Note: on this PR the native diff should come back ~all ties (workflow-only delta over PR225), which itself proves the new plumbing.

AI-Tool: Muse Code (Meta Muse Spark)

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