Files
pnpm/.github/workflows/benchmark.yml
Zoltan KochanandClaude Opus 5 4d32532cc7 chore: install the Rust dependencies with pnpm (#14689)
* chore: install the Rust dependencies with pnpm

Turn on `cargo.enabled` so `pnpm install` installs the crates `Cargo.lock`
pins alongside the JavaScript dependencies. pnpm links the registry crates
into `.pnpm/crates/crates-io` and the git-sourced ones into
`.pnpm/crates/git`, then writes a source replacement block into
`.cargo/config.toml` that points Cargo at both.

The `node-semver` fork stays patched in. pnpm installs git-sourced crates
since pnpm/pnpm#14694, which shipped in the 12.4.1 the repository pins, so
enabling this no longer costs the fork's `<=` range and `Ord for Bound`
fixes.

Document the workflow, including the two things a contributor hits first:
`pnpm install` shells out to `cargo`, so it fails when `cargo` is off
`PATH`, and the generated block leaves the tracked `.cargo/config.toml`
modified after every install.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDxJNVEeyUBb4pcepEpGcj

* ci: initialize Rust before pnpm installs dependencies

Every `pnpm install` now shells out to `cargo metadata`, and there is no
way to opt one out: `cargo.enabled` is read from `pnpm-workspace.yaml`
only, no CLI flag or `PNPM_CONFIG_*` variable overrides it, and `--filter`
does not narrow the Cargo half. So each job whose install runs gets the
pinned toolchain first. `pnpm/setup` installs unless told not to,
`pnpm/update` runs its own install, and `pnpm pipeline` installs before it
runs anything.

The step has to precede the install rather than follow it. The rustup
action ends with `git restore .`, which would otherwise wipe the source
replacement block back out of `.cargo/config.toml`.

Keep that block out of the two workflows that commit. It points at the
gitignored `.pnpm/crates`, so a commit carrying it would break every
Rust-only job and every checkout that has not installed. Both workflows
also decide whether to commit at all by reading `git status --porcelain`,
which the block would make non-empty on every run. Each discards it right
before that check: the release PR in a step of its own, the lockfile
update through the `post-update` command pnpm/update runs between its
install and its commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDxJNVEeyUBb4pcepEpGcj

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 21:17:43 +02:00

212 lines
7.0 KiB
YAML

name: Benchmarks
on:
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to benchmark (works with fork PRs too)'
required: false
default: ''
type: string
runs:
description: 'Number of benchmark runs per scenario'
required: false
default: '10'
type: string
warmup:
description: 'Number of warmup runs before timing'
required: false
default: '1'
type: string
push:
# Build Bencher's continuous baseline for the `pnpm` testbed.
# Every merge to main that touches the TypeScript stack re-runs the
# bench so PR comparisons have an up-to-date reference; cancel-in-progress
# stays off below so we never throw away a partial run.
branches: [main]
paths:
- 'pnpm11/**'
- 'package.json'
- 'pnpm-workspace.yaml'
- 'pnpm-lock.yaml'
- '.github/workflows/benchmark.yml'
permissions:
contents: read
pull-requests: write
checks: write
# Don't cancel-in-progress — killing a bench mid-run wastes a long
# CI job and produces no usable data.
concurrency:
group: benchmark-${{ inputs.pr_number || github.ref }}
cancel-in-progress: false
jobs:
benchmark:
name: Run Benchmarks
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 180
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Checkout PR head
if: inputs.pr_number != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ inputs.pr_number }}
run: |
echo "Fetching PR #$PR_NUMBER head..."
git fetch origin "refs/pull/${PR_NUMBER}/head:refs/remotes/origin/pr-${PR_NUMBER}"
git checkout "origin/pr-${PR_NUMBER}"
echo "Checked out PR #$PR_NUMBER at $(git rev-parse --short HEAD)"
- name: Install Rust Toolchain
uses: $/.github/actions/rustup
with:
shared-key: pnpm-benchmark
- name: Install pnpm and Node
uses: pnpm/setup@703c52620218391530e48b9e8870d5c0082e1b9b # v2.1.0
with:
runtime: node@26.8.2
- name: Install hyperfine
uses: $/.github/actions/binstall
with:
packages: hyperfine@1.18.0
- name: Run benchmarks
id: bench
run: |
set -o pipefail
./pnpm11/benchmarks/bench.sh 2>&1 | tee bench-output.txt
BENCH_DIR=$(grep "Temp directory kept at:" bench-output.txt | sed 's/Temp directory kept at: //')
echo "bench_dir=$BENCH_DIR" >> "$GITHUB_OUTPUT"
env:
RUNS: ${{ inputs.runs }}
WARMUP: ${{ inputs.warmup }}
- name: Install Bencher CLI
if: steps.bench.outputs.bench_dir != ''
uses: bencherdev/bencher@8d75325c3bc59403a2186a056b472c4f49d42838 # v0.6.12
- name: Upload results to Bencher
if: steps.bench.outputs.bench_dir != ''
env:
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
BENCH_DIR: ${{ steps.bench.outputs.bench_dir }}
EVENT_NAME: ${{ github.event_name }}
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
REF_NAME: ${{ github.ref_name }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -z "${BENCHER_API_TOKEN:-}" ]; then
echo "::notice::BENCHER_API_TOKEN not set, skipping Bencher upload"
exit 0
fi
if [ ! -f "$BENCH_DIR/bencher-results.json" ]; then
echo "::warning::bencher-results.json not found, skipping upload"
exit 0
fi
# `bencher run --file` takes hyperfine JSON via the
# shell_hyperfine adapter. Branch policy:
# - push to main → record into the `main` branch (baseline)
# - workflow_dispatch with pr_number → record into `pr/<n>`,
# forked from main at the latest baseline
# - workflow_dispatch without pr_number → record into the
# ref's branch name (e.g. a feature branch), forked from main
args=(
--project pnpm
--testbed pnpm
--adapter shell_hyperfine
--file "$BENCH_DIR/bencher-results.json"
--github-actions "$GITHUB_TOKEN"
)
# `--start-point-clone-thresholds` so the forked branch inherits
# the threshold configured on main; `--err` so the workflow fails
# when a sample breaches the upper boundary. Main pushes skip
# both — by then the regression has already landed.
if [ "$EVENT_NAME" = "push" ] || [ "$REF_NAME" = "main" ]; then
args+=(--branch main)
elif [ -n "$INPUT_PR_NUMBER" ]; then
args+=(
--branch "pr/$INPUT_PR_NUMBER"
--start-point main
--start-point-reset
--start-point-clone-thresholds
--err
)
else
args+=(
--branch "$REF_NAME"
--start-point main
--start-point-reset
--start-point-clone-thresholds
--err
)
fi
bencher run "${args[@]}"
- name: Comment on PR
if: steps.bench.outputs.bench_dir != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BENCH_DIR: ${{ steps.bench.outputs.bench_dir }}
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
REF_NAME: ${{ github.ref_name }}
RUN_ID: ${{ github.run_id }}
SERVER_URL: ${{ github.server_url }}
REPO: ${{ github.repository }}
RUNS: ${{ inputs.runs }}
ACTOR: ${{ github.actor }}
run: |
RESULTS_FILE="$BENCH_DIR/results.md"
if [ ! -f "$RESULTS_FILE" ]; then
echo "::warning::Results file not found at $RESULTS_FILE"
exit 0
fi
echo "--- Benchmark Results ---"
cat "$RESULTS_FILE"
echo "-------------------------"
if [ -n "$INPUT_PR_NUMBER" ]; then
PR_NUMBER="$INPUT_PR_NUMBER"
else
PR_NUMBER=$(gh pr list --head "$REF_NAME" --json number --jq '.[0].number' 2>/dev/null || echo "")
fi
if [ -z "$PR_NUMBER" ]; then
echo "::notice::No open PR found for branch $REF_NAME. Results printed above."
exit 0
fi
MARKER="<!-- pnpm-benchmark-results -->"
{
echo "$MARKER"
cat "$RESULTS_FILE"
echo ""
echo "_Run [${RUN_ID}](${SERVER_URL}/${REPO}/actions/runs/${RUN_ID}) · ${RUNS} runs per scenario · triggered by @${ACTOR}_"
} > /tmp/comment-body.md
COMMENT_ID=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
--jq "[.[] | select(.body | startswith(\"$MARKER\"))] | .[0].id // empty" 2>/dev/null || echo "")
if [ -n "$COMMENT_ID" ]; then
echo "Updating existing benchmark comment $COMMENT_ID on PR #$PR_NUMBER"
gh api "repos/${REPO}/issues/comments/${COMMENT_ID}" \
-X PATCH \
-F "body=@/tmp/comment-body.md"
else
echo "Creating new benchmark comment on PR #$PR_NUMBER"
gh pr comment "$PR_NUMBER" --body-file /tmp/comment-body.md
fi