Skip to content

ci(bench): reproduce idle-RSS + hot-path benchmarks in CI (roadmap 2.2) - #326

Merged
phsb5321 merged 6 commits into
mainfrom
326-bench-ci
Aug 10, 2026
Merged

ci(bench): reproduce idle-RSS + hot-path benchmarks in CI (roadmap 2.2)#326
phsb5321 merged 6 commits into
mainfrom
326-bench-ci

Conversation

@phsb5321

@phsb5321 phsb5321 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Roadmap 2.2 metric "benchmark artifact reproducible in CI" — slice brief wa-eng (w8:p2) (/tmp/sp-dispatch/wa-brief-eng.md).

What

Additive-only new workflow .github/workflows/bench.yml — no daemon code, no session/health/watchdog surface (reliability gates R26–R32 not applicable), no existing workflow modified, no secrets, no write permission, deliberately not a required check.

  • Triggers: push to main + pull_request; concurrency group per ref with cancel-in-progress on PR runs only (shape from ci(sonar): stop a runner backlog from failing an unrelated PR #325).
  • Harnesses (both, from bench/README.md):
    • ./bench/idle-rss.sh — builds wad from the working tree, unpaired boot into throwaway XDG root (no account, no credentials, no session files), samples VmRSS after 5 s.
    • go test ./internal/app/ -run xxx -bench 'BenchmarkEventFanout|BenchmarkChannelWrap|BenchmarkDraftCreate' -benchmem -count=1 -vet=off
    • go test ./cmd/wad/ -run xxx -bench BenchmarkDispatcherStatus -benchmem -count=1 -vet=off
  • Compare: sed/awk-only parse (no jq, no benchstat, no new deps), normalized to ns/op (handles µs/ms output), fails the job on regression vs committed thresholds.
  • Artifact: raw outputs (idle-rss.txt, bench-app.txt, bench-cmd.txt) uploaded as bench-results, retention 7 d (pin actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 from ci.yml).

Thresholds (honest, cited, factor > 1.0)

Baseline: 10/06/2026, commit 2f80ed3 (bench/README table) × documented slack — 2× RSS, ≥3× ns/op — because these single-goroutine microbenchmarks share two self-hosted runners with the rest of CI. Same-machine rerun on the baseline CPU (Xeon E5-2699 v4) with Go 1.26.5 measured 26 % above the baseline table, which is the empirical floor for the factor choice.

Metric Baseline (2f80ed3) Factor CI threshold
idle RSS 32 MiB 2.0× ≤ 64 MiB
DispatcherStatus 314 ns/op 3.2× ≤ 1000 ns/op
EventFanout 3.2 µs/op 3.1× ≤ 10000 ns/op
ChannelWrap 2.4 µs/op 3.3× ≤ 8000 ns/op
DraftCreate 4.1 µs/op 3.2× ≤ 13000 ns/op

Thresholds live as workflow-level env with the derivation commented inline; the policy is documented in bench/README.md.

Companion change (flagged — 4th file, not in the brief's 3-file list)

.github/zizmor.yml: +1 line adding bench.yml to the self-hosted-runner documented-exception list. Required to go green: the enforce-mode "workflow static analysis" check (actionlint + zizmor --persona=auditor) runs over .github/workflows/; bench.yml uses the house-style [self-hosted, dokku] pool, which zizmor flags unless the workflow is on the repo's own documented exception list (every other self-hosted workflow is). Without this line the check can never go green on this PR. No suppression semantics changed — only the new workflow joins an existing documented exception.

Verification (local, CI pins)

  • ./bench/idle-rss.sh → 32 MiB (this host), harness runs offline, unpaired.
  • Full compare step executed verbatim → all 5 checks OK (338.3 / 3822 / 2912 / 4750 ns/op vs thresholds).
  • actionlint 1.7.12 (CI pin) — clean. zizmor 1.24.1 (CI pin) --persona=auditor .github/workflows/No findings to report.
  • µs/ms normalization + failure path tested with synthetic lines.
  • Pre-push hooks green (test-race, gitleaks, actionlint, zizmor).

Acceptance-gate checklist (brief's falsifier)

  • bench.yml additive-only — diff = .github/workflows/bench.yml (new) + bench/README.md + .github/zizmor.yml (+1 line, see flag above)
  • thresholds numerically above baseline with documented factor + citation (commit 2f80ed3, 10/06/2026, factor rationale)
  • ./bench/idle-rss.sh runs offline, unpaired boot, no credentials
  • README delta documents the CI job + threshold policy + artifact reading
  • CI green on this PR — all 18 checks pass on head 8eae54c (verified gh pr checks 326 + rollup, 0 failed)
  • both harnesses ran + non-empty artifact uploaded — run 31133771349: RSS 32 MiB (≤64), DispatcherStatus 264.4 / EventFanout 2983 / ChannelWrap 1948 / DraftCreate 3775 ns/op, artifact bench-results 852 B

Escalation

[pending] Pedro: merge #326 — this PR touches .github/workflows/ → gated class per AGENTS.md R33 (GitHub Actions). Not self-merging.

Revert path: git revert <merge-sha> — additive-only change, no daemon surface.

Incident aftermath (2026-08-06)

This PR's first check run landed inside a GitHub Actions incident
(status page: critical "Incident with Actions", ~14:12→21:10 BRT;
webhooks throttled to ~15 %, runners assigned invalid jobs). Three
workflow runs (Reproducibility, CodeQL, quality-gates) were left
zombie-wedged — run-level queued with zero jobs, refusing both cancel
and rerun (HTTP 403 "already running"), and the incident-fix rollout
did not clear them. Empty commit 8eae54c moves the head SHA and
re-triggered every workflow fresh; all 18 checks passed on the new
head. No code or configuration changed in the re-trigger commit.

Roadmap 2.2's "benchmark artifact reproducible in CI" metric was the
last unmet repo-local roadmap item: nothing in .github/workflows/ ran
the bench harnesses and the bench/README reference numbers (10/06/2026,
commit 2f80ed3) were unguarded — a silent drift risk for the README's
RSS/market claims.

Additive-only bench.yml (new workflow, no existing file modified, no
secrets, no write surface, deliberately not a required check):
- triggers: push to main + pull_request, concurrency group per ref with
  PR-only cancel (shape from #325)
- builds wad from the working tree via ./bench/idle-rss.sh (unpaired
  boot) and runs the four hot-path microbenchmarks with -count=1
  -benchmem -vet=off
- compares results against committed thresholds (10/06 baseline x 2x
  RSS / 3x ns/op, derived + cited in the workflow header), fails on
  regression, uploads raw output as the bench-results artifact
- comparison is sed/awk only — no jq/benchstat, no new dependencies

bench/README.md documents the CI job, the threshold policy and how to
read the artifact; the 10/06 table is relabeled as the baseline it is.

.zizmor.yml: bench.yml joins the documented self-hosted-runner
exception list — every workflow on the [self-hosted, dokku] pool is
listed there, and the required "workflow static analysis" check would
otherwise stay red on the new workflow (zizmor enforce mode).

Verified locally: harnesses + compare step pass on this host
(32 MiB RSS; 338/3822/2912/4750 ns/op vs thresholds 1000/10000/8000/
13000); actionlint 1.7.12 clean; zizmor 1.24.1 --persona=auditor: no
findings.

Signed-off-by: Pedro H S Balbino <30302237+phsb5321@users.noreply.github.com>
@phsb5321
phsb5321 requested a review from a team as a code owner August 6, 2026 16:56
@phsb5321

phsb5321 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

wa-qa review — cross-family gate (OpenAI-lineage lane)

Review contract: /tmp/sp-dispatch/wa-contract-qa.md — 8 mechanical gates, R26–R32 correctly NOT applicable (no daemon/session/health surface — verified: diff touches only .github/workflows/bench.yml (new), .github/zizmor.yml (+1), bench/README.md).

VERDICT: CHANGES-REQUESTED — gates 5, 8. Two small fixes; nothing structural.

Gate verdicts

  • G1: WAVE — additive-only holds: no ci.yml/sonar.yml/quality-gates.yml or any existing workflow touched. .github/zizmor.yml +1 line is a config-file exception addition, not a workflow change, and follows the repo's existing documented-exception pattern (zizmor.yml:1-19, self-hosted-runner.ignore). Reason: not a scope violation — no required-check behavior altered; the new workflow joins a list every other self-hosted workflow is already on. See G8 for the wording fix in the justification.
  • G2: PASSpermissions: {} at workflow level (bench.yml:44); job contents: read with rationale comment (bench.yml:67-71); no secrets/tokens; harden-runner audit egress (bench.yml:73-76); persist-credentials: false (bench.yml:80); artifact upload needs no write grant.
  • G3: PASS (pending) — "workflow static analysis" (actionlint 1.7.12 + zizmor enforce) currently queued; the zizmor.yml exception is what lets it pass. Must be green at merge time — see Falsifier.
  • G4: PASS — harden-runner first step (bench.yml:73); all four actions SHA-pinned with # vX.Y.Z comments, byte-identical pins to ci.yml: harden-runner@f808768d… # v2.17.0 (bench.yml:74), checkout@11bd7190… # v4.2.2 (bench.yml:78), setup-go@d35c59ab… # v5.5.0 (bench.yml:81), upload-artifact@ea165f8d… # v4.6.2 (bench.yml:163); concurrency group per ref with cancel-in-progress on PR runs only, ci(sonar): stop a runner backlog from failing an unrelated PR #325 shape (bench.yml:37-39); go-version-file: go.mod (bench.yml:83); cache: false with PR chore: homebrew tap + flaky tests + sonar v6 + node 24 opt-in #14 rationale (bench.yml:84-89).
  • G5: FAIL — two committed thresholds sit below their own documented factor product:
    • DISPATCHER_STATUS_MAX_NS: "1000" (bench.yml:57): baseline 314 ns/op × documented 3.2× (bench.yml:16, README table) = 1004.8 ns → 1000 < 1004.8 (actual factor 3.185×).
    • DRAFT_CREATE_MAX_NS: "13000" (bench.yml:60): baseline 4.1 µs × 3.2 (bench.yml:18, README table) = 13120 ns → 13000 < 13120 (actual factor 3.171×).
    • The contract gate is literal: every threshold ≥ baseline × a DOCUMENTED slack factor — and the whole slice is about honest thresholds; a reader checking 314×3.2 against the README table gets 1004.8, not 1000. The ≥3× floor and the ≥2× RSS (32×2.0=64 exact, bench.yml:56) all pass — this is a numeric-documentation defect, not a tautology, hence CHANGES-REQUESTED not BLOCKED.
    • Fix (one line per site): commit 1005 / 13120 in bench.yml:57/:60 env + header comment (bench.yml:16,:18) + README threshold table, keeping baseline 2f80ed3 citations. (Alternatively document the actual factors 3.185×/3.171× — but round-up to the exact product is cleaner.)
  • G6: PASS — idle-rss.sh: unpaired boot into throwaway mktemp -d XDG root (idle-rss.sh:7-12), no account/credentials/session.db reads, trap kill + rm (idle-rss.sh:8); hot-path benches -run xxx in-memory adapter, no -race (ci.yml Test job is the race/vet gate — division of labor correct, bench.yml:92-98); network = module fetch only. Hand-traced the compare step (bench.yml:104-157): sed regex ^$2[^[:space:]]*[[:space:]]\+[0-9]\+[[:space:]]\+\([0-9.]*\) \([numµμ]s\)\/op matches real BenchmarkX-8 <iters> <val> ns/op lines (suffix consumed by [^[:space:]]*, both µ code points + u covered); µs/ms→ns normalization correct (bench.yml:137-138); awk exit !(v > m) → exit 0 = error branch correct in both comparisons (bench.yml:113-119, :140-145); empty/missing lines hit explicit ::error:: paths (bench.yml:110-112, :130-134); failed harness run fails the step before compare via pipefail.
  • G7: PASS — 2× RSS / ≥3.1× ns/op on shared [self-hosted, dokku] pool is defensible: empirical same-machine rerun 26% above table cited (bench.yml:21-23), job deliberately NOT a required check (bench.yml:8-10) so residual flake is trend noise, not a merge blocker; timeout-minutes: 20 (bench.yml:66) comfortably covers build + 2 bench runs + 5 s settle. No threshold < 2× on a timing bench.
  • G8: WAVE — the done-falsifier IS honest: body states what done looks like (both harnesses + artifact + thresholds), acceptance checklist marks the two CI-evidence items [ ] pending with run links pointed at, no "✓ verified" claim without run evidence. BUT the zizmor.yml justification asserts "workflow static analysis" is a required check — the active main-protection ruleset required contexts are CodeQL, Lint, Nix flake check, OSV-Scanner, PR title commitlint, Reproducible build check, SonarQube scan, SonarQube standalone scan, Test, code-slop + alignment; it is NOT required. Reason for wave: the exception line is still necessary (zizmor enforce mode would red the check either way) and the done-claims remain honest — request a one-line wording fix in the body ("the enforce-mode workflow static analysis check" / drop "required") so the justification doesn't propagate a false policy fact.

Hygiene

Conventional title, single-token scope bench, 71 ≤ 72 chars; DCO Signed-off-by present (commit 4e40793); no dependency bumps, Renovate pins untouched.

Falsifier for "review done"

  1. bench.yml:57 → 1005, bench.yml:60 → 13120 (+ header comment bench.yml:16,:18 and README threshold table) — or document 3.185×/3.171×.
  2. PR body zizmor justification: drop "required".
  3. gh pr checks 326 green — including workflow static analysis and Benchmarks (idle RSS + hot-path) with the bench-results artifact non-empty (run link).
    Then re-review → APPROVE. Merge stays [pending] Pedro (GitHub Actions class, AGENTS.md R33).

The initial run of this PR's checks landed inside a GitHub Actions
incident (status page: critical, 14:12-21:10 BRT). Three workflow runs
(Reproducibility, CodeQL, quality-gates) were left in a zombie state —
run-level 'queued' with zero jobs, refusing both cancel and rerun
(HTTP 403 'already running'). All other checks passed on the original
commit. This empty commit moves the head SHA so every workflow
re-triggers fresh; no code or configuration changed.

Signed-off-by: Pedro H S Balbino <30302237+phsb5321@users.noreply.github.com>
314×3.2 = 1004.8 → 1005 ns/op (was 1000, factor 3.185× below 3.2)
4.1 µs×3.2 = 13120 ns/op (was 13000, factor 3.171× below 3.2)

qa (w8:p3) issuecomment-5207658880 G5: committed thresholds sat below
their own documented slack product. Header comments and README table
updated in lockstep.

Signed-off-by: Pedro H S Balbino <30302237+phsb5321@users.noreply.github.com>
@phsb5321

phsb5321 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

wa-qa re-review — G5/G8 fixes verified → APPROVE

Re-verified at head 5963d12 (diff 4e40793...5963d12 = exactly the 6 threshold lines). Not merging — remains [pending] Pedro (Actions class, AGENTS.md R33).

G5 — FIXED (thresholds = documented factor products, lockstep)

  • DISPATCHER_STATUS_MAX_NS: "1005" (bench.yml:57) ≥ 314 × 3.2 = 1004.8 ✓
  • DRAFT_CREATE_MAX_NS: "13120" (bench.yml:60) = 4100 × 3.2 exactly ✓
  • Header derivation updated in lockstep (bench.yml:16, :18) and README threshold table rows (bench/README.md:72, :76). No other lines changed; the other three thresholds (64 / 10000 / 8000) and every gate 1–4/6–7 verdict from the prior review stand unchanged (file set vs main is still exactly .github/workflows/bench.yml + .github/zizmor.yml + bench/README.md).

G8 — FIXED (body now accurate)

PR body says: "the enforce-mode 'workflow static analysis' check (actionlint + zizmor --persona=auditor)" — the false "required" framing is gone; "Required to go green … Without this line the check can never go green on this PR" is factually accurate.

CI + artifact evidence (the falsifier, now closed)

All 18 checks green on head 5963d12, including the 10 ruleset-required contexts and:

  • Benchmarks (idle RSS + hot-path) — pass, 1m52s (run 31136756288)
  • workflow static analysis (actionlint + zizmor) — pass, 14s
  • Test (go test -race) — pass, 2m14s

bench-results artifact (827 B, not expired) downloaded and inspected — both harnesses' raw output present:

  • idle-rss.txt: "wad idle RSS: 32 MiB (33116 KiB)" ≤ 64 MiB
  • bench-app.txt: EventFanout 2597 ns/op ≤ 10000 · ChannelWrap 1763 ≤ 8000 · DraftCreate 6317 ≤ 13120 (runner: QEMU virtual CPU — the 3× slack proved correct on a virtualized dokku runner)
  • bench-cmd.txt: DispatcherStatus 223.1 ns/op ≤ 1005

The compare step passed, which itself proves all 5 thresholds parsed and satisfied (a missing line fails the job).

VERDICT: APPROVE. Safe-class in every respect except the Actions-touch — merge stays [pending] Pedro: merge #326 with one-line revert git revert <merge-sha>.

@phsb5321
phsb5321 merged commit bd1ccef into main Aug 10, 2026
18 checks passed
@phsb5321
phsb5321 deleted the 326-bench-ci branch August 10, 2026 14:09
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