Files
max-sixty__worktrunk/.github/workflows/benchmarks.yaml
Maximilian Roos 00ab0ffa26 Canonicalize benchmark fixtures and variants (#3761)
Benchmark fixtures still encoded the benchmark that first needed each
repository state, which left overlapping recipes and variants after the
earlier harness consolidation. This change reduces the fixture catalog
to two provenance-based bases: `Generated` builds an ordinary Git
repository locally, while `Imported` copies the pinned `rust-lang/rust`
corpus. Worktree, branch, and remote-ref populations remain parameters
on `Generated`; prune candidates and backdrop are overlays that work
with either base.

The generated base deliberately combines heterogeneous worktree states,
history-spread branches, and optional remote refs so ordinary list,
completion, picker, first-output, alias, remove, and prune benchmarks
can share it. Imported history-spread branches and clean base-tip
worktrees carry their own commits, preserving the base populations
without making them incidental prune candidates when overlays advance
the default branch.

The benchmark matrix now keeps single-factor contrasts: list scaling
uses the 1- and 8-worktree endpoints; alias dispatch has a startup
floor, two population endpoints, and one warm/cold variable-resolution
pair; completion keeps one full-surface case; remove and prune vary
cache or hook state only where the command exercises it. Historical
recipes, redundant cache rows, and intermediate scaling points are
removed. Manual setup paths live under `target/`, and the benchmark
guide documents the resulting fixture and cache model.

Tests: `cargo run -- hook pre-merge --yes` after merging current `main`
(4,571 tests); targeted Criterion test-mode runs; `cargo test -p
wt-perf`; benchmark check, clippy, formatting, and diff checks.

> _This was written by Codex on behalf of max-sixty_
2026-08-08 13:38:00 -07:00

166 lines
5.9 KiB
YAML

name: benchmarks
# Full criterion suite plus the time-series gist append. Runs ~80 min and
# checks performance, not correctness, so it stands apart from the PR/merge
# flow and from nightly's correctness checks: a slow or failing bench never
# gates a merge. The daily cron is the perf-history feed (it appends to the
# gist); workflow_dispatch covers on-demand runs against a chosen branch.
#
# Not run on PRs or pushes, so the daily run is the only signal that the bench
# harness still builds — create-issue-on-benchmark-failure surfaces a break.
#
# Runner version pinned; see ci.yaml header comment for rationale.
on:
schedule:
# 3:47 UTC daily. Off-peak, offset from nightly's 5:37 so the two long
# runs don't contend, and off :00 to be a good citizen w.r.t. GitHub's
# cron scheduler.
- cron: '47 3 * * *'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
env:
# Match ci.yaml/nightly.yaml: Swatinem/rust-cache hashes CARGO*/RUST* into
# the cache key, so the shared `test` cache only restores when these agree
# (see .github/CLAUDE.md).
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: -C debuginfo=0
jobs:
benchmarks:
runs-on: ubuntu-24.04
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- name: 💰 Cache
uses: Swatinem/rust-cache@v2
with:
# Restore the shared `test` cache (registry + deps), never save —
# `cargo bench` is release-profile so it rebuilds its own artifacts,
# but the dependency download/extract is still worth restoring.
prefix-key: v1-rust
shared-key: shared
cache-bin: "false"
save-if: false
- name: Install shells and jq
run: sudo apt-get update && sudo apt-get install -y zsh fish jq
- name: Install nushell
uses: hustcer/setup-nu@v3
with:
version: '0.114.1'
- name: 💰 Imported fixture cache
uses: actions/cache@v6
with:
# The fixture file pins the corpus revision. Including its hash keeps
# a prior corpus out of the cache after either constant changes.
path: target/wt-perf/bench-repos
key: bench-repos-imported-${{ runner.os }}-${{ hashFiles('benches/imported-fixture') }}
- name: 📊 Run benchmarks
run: cargo bench
- name: 📦 Upload benchmark results
uses: actions/upload-artifact@v7
with:
name: benchmark-results-${{ github.run_id }}
path: target/criterion
# Time-series benchmark store, owned by worktrunk-bot:
# https://gist.github.com/worktrunk-bot/19bb23cb9658722abfe69479d0a4f9bf
#
# Cron-only: workflow_dispatch runs aren't appended (would pollute the
# time series). Skipped on forks: the environment's secrets aren't exposed
# there.
#
# Its own job so the token stays off `benchmarks`. The copy this job reads
# is in the `tend` environment, whose policy admits `main` alone, so a job
# naming it is refused on every other ref — which would take
# workflow_dispatch against a chosen branch with it. A job GitHub skips
# never requests its environment, so the cron gate above keeps the gate off
# the dispatch path.
append-gist:
needs: benchmarks
if: github.repository_owner == 'max-sixty' && github.event_name == 'schedule'
runs-on: ubuntu-24.04
environment:
name: tend
# A secret scope, not a deploy target — see .github/CLAUDE.md.
deployment: false
permissions:
contents: read
steps:
- name: 📦 Download benchmark results
uses: actions/download-artifact@v8
with:
name: benchmark-results-${{ github.run_id }}
path: target/criterion
- name: 💾 Append results to gist
env:
GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }}
GIST_ID: 19bb23cb9658722abfe69479d0a4f9bf
run: |
set -euo pipefail
timestamp="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
find target/criterion -path '*/new/estimates.json' -print0 \
| sort -z \
| xargs -0 -r jq -c --arg ts "$timestamp" --arg sha "$GITHUB_SHA" '
{
ts: $ts,
sha: $sha,
bench: (
input_filename
| sub("^target/criterion/"; "")
| sub("/new/estimates\\.json$"; "")
),
mean_ns: .mean.point_estimate,
stddev_ns: .std_dev.point_estimate
}
' > new-rows.jsonl
test -s new-rows.jsonl
git clone "https://x-access-token:${GITHUB_TOKEN}@gist.github.com/${GIST_ID}.git" /tmp/gist
cat new-rows.jsonl >> /tmp/gist/results.jsonl
git -C /tmp/gist \
-c user.name=worktrunk-bot \
-c user.email=worktrunk-bot@users.noreply.github.com \
commit -am "benchmarks: ${GITHUB_SHA::7}"
git -C /tmp/gist push
create-issue-on-benchmark-failure:
needs:
- benchmarks
- append-gist
if: always() && contains(needs.*.result, 'failure') && github.repository_owner == 'max-sixty' && github.event_name == 'schedule'
runs-on: ubuntu-24.04
environment:
name: tend
# A secret scope, not a deploy target — see .github/CLAUDE.md.
deployment: false
permissions:
contents: read
issues: write
steps:
- name: 📂 Checkout code
uses: actions/checkout@v7
- uses: JasonEtco/create-an-issue@v2
env:
# Use TEND_BOT_TOKEN for a consistent bot identity (per
# .github/CLAUDE.md) and so any future issue-triage automation can
# cascade off issue creation — events from the default GITHUB_TOKEN
# don't trigger other workflows.
GITHUB_TOKEN: ${{ secrets.TEND_BOT_TOKEN }}
LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
filename: .github/benchmark-failure.md
update_existing: true
search_existing: open