Skip to content

[ci] Overhaul performance benchmarks: focused metrics + sticky PR comment#2820

Merged
VaguelySerious merged 6 commits into
mainfrom
peter/benchmark-overhaul
Jul 8, 2026
Merged

[ci] Overhaul performance benchmarks: focused metrics + sticky PR comment#2820
VaguelySerious merged 6 commits into
mainfrom
peter/benchmark-overhaul

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Jul 8, 2026

Copy link
Copy Markdown
Member

What

Replaces the sprawling vitest-bench benchmark suite with three focused scenarios that measure the runtime's core latency metrics, each reported as avg / p50 / p90 / p99 in a single sticky PR comment. Re-runs update the same comment; older results collapse into a <details> history section (the data is embedded in the comment body as a base64 block, so history survives re-renders).

Metrics

Metric Definition
TTFS Client-side start() timestamp (run_created request) → first step body execution
STSO Gap between consecutive step body executions (steps[i].start - steps[i-1].end)
WO Time spent outside step bodies: (lastStep.end - clientStart) - Σ(step durations)
SO Step writes first chunk to the default output stream → chunk visible to a run.getReadable() reader

Scenarios (workbench/example/workflows/97_bench.ts)

  1. benchStreamWorkflow — 1 streaming step, no hooks → runs in turbo mode → TTFS/SO/WO (turbo)
  2. benchSequentialStepsWorkflow — 100 trivial sequential steps → STSO (99 samples/run) + WO
  3. benchHookStreamWorkflow — fire-and-forget createHook() before the same streaming step → exits turbo → TTFS/SO/WO (non-turbo)

Steps record Date.now() at body entry/exit and the workflow returns the timings; stream chunks embed their write timestamp. Iteration counts are env-tunable (BENCH_*, defaults: 30 stream iterations, 5×100 sequential).

CI

  • benchmarks.yml now runs a single Vercel benchmark job (nextjs-turbopack) driven by a matrix that can later grow postgres/local entries — the runner already picks its backend from the same env vars as the e2e tests.
  • New render-benchmark-comment.mjs renders the running/completed/failed comment states, with unit tests picked up by lint.yml's node --test.
  • Deleted: bench.bench.ts, aggregate-benchmarks.js, the render-benchmarks action, benchmark-community-world.yml, and the baseline/GH-Pages publishing plumbing (nothing consumes the published benchmark JSON).

Notes

  • TTFS/WO compare the CI runner clock against the deployment clock; SO compares the step runner's clock against the CI runner's. Both are NTP-synced, so skew is small relative to the measured values.
  • WO's endpoint is the last step body exit — the closest observable proxy for "final step_completed request sent" without instrumenting the SDK.
  • Validated locally against the nextjs-turbopack dev server (all 3 scenarios + results JSON + comment rendering).

Verification

  • Performance Benchmarks workflow passes on this PR (run)
  • Sticky comment shows the new table
  • Re-run collapses the previous results into history (verified via job re-run)

🤖 Generated with Claude Code

Replaces the sprawling vitest-bench suite with three focused scenarios
measuring TTFS, STSO, WO and SO (avg/p50/p90/p99), run many times against
a Vercel deployment. Results are posted to a single sticky PR comment
that keeps previous runs in a collapsed history section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@VaguelySerious VaguelySerious requested review from a team and ijjk as code owners July 8, 2026 17:33
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jul 8, 2026 9:38pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jul 8, 2026 9:38pm
example-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-astro-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-express-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-fastify-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-hono-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-nitro-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workbench-vite-workflow Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jul 8, 2026 9:38pm
workflow-swc-playground Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workflow-tarballs Ready Ready Preview, Comment Jul 8, 2026 9:38pm
workflow-web Ready Ready Preview, Comment Jul 8, 2026 9:38pm

@changeset-bot

changeset-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1ac3289

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 1ac3289 · Wed, 08 Jul 2026 21:54:07 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS stream 1035 1632 🔴 1761 🔴 1811 🔴 30
TTFS hook + stream 1626 1953 🔴 2023 🔴 2329 🔴 30
STSO 1020 steps (1-20) 280 285 🔴 485 🔴 535 🔴 19
STSO 1020 steps (101-120) 451 486 🔴 563 🔴 688 🔴 19
STSO 1020 steps (1001-1020) 908 946 🔴 1185 🔴 1339 🔴 19
WO stream 1035 1632 1761 1811 30
WO hook + stream 1626 1953 2023 2329 30
SL stream 4639 5526 🔴 5562 🔴 5798 🔴 30
SL hook + stream 4781 5436 🔴 5568 🔴 8202 🔴 30
📜 Previous results (5)

ab54b48

Wed, 08 Jul 2026 21:18:02 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS 1 step + stream (turbo) 1209 1719 1825 30
TTFS hook + 1 step + stream (non-turbo) 1651 1919 2127 30
STSO 1020 sequential steps (steps 1-20) 259 308 339 19
STSO 1020 sequential steps (steps 20-120) 329 433 665 100
STSO 1020 sequential steps (steps 120-1020) 674 855 1233 900
WO 1 step + stream (turbo) 1209 1719 1825 30
WO hook + 1 step + stream (non-turbo) 1651 1919 2127 30
SL 1 step + stream (turbo) 4995 5678 9264 30
SL hook + 1 step + stream (non-turbo) 4699 5407 7003 30

c2b5794

Wed, 08 Jul 2026 20:55:35 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS 1 step + stream (turbo) 1190 1737 1859 30
TTFS hook + 1 step + stream (non-turbo) 1865 1963 2230 29
STSO 1020 sequential steps (steps 1-20) 302 403 669 19
STSO 1020 sequential steps (steps 20-120) 317 440 542 100
STSO 1020 sequential steps (steps 120-1020) 666 844 1188 900
WO 1 step + stream (turbo) 1190 1737 1859 30
WO hook + 1 step + stream (non-turbo) 1865 1963 2230 29
SL 1 step + stream (turbo) 4571 5815 6016 30
SL hook + 1 step + stream (non-turbo) 4702 5157 5789 29

c2b5794

Wed, 08 Jul 2026 20:35:13 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS 1 step + stream (turbo) 1170 1699 1736 30
TTFS hook + 1 step + stream (non-turbo) 1635 2054 2079 30
WO 1 step + stream (turbo) 1170 1699 1736 30
WO hook + 1 step + stream (non-turbo) 1635 2054 2079 30
SL 1 step + stream (turbo) 4484 5605 5768 30
SL hook + 1 step + stream (non-turbo) 4550 5463 5578 30

874c3a1

Wed, 08 Jul 2026 19:06:06 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS 1 step + stream (turbo) 1049 1647 2144 30
TTFS hook + 1 step + stream (non-turbo) 2053 2106 5179 30
STSO 100 sequential steps 314 420 662 495
WO 1 step + stream (turbo) 1049 1647 2144 30
WO hook + 1 step + stream (non-turbo) 2053 2106 5179 30
WO 100 sequential steps 32710 37880 37880 5
so 1 step + stream (turbo) 4704 5841 5971 30
so hook + 1 step + stream (non-turbo) 4456 4788 5349 30

874c3a1

Wed, 08 Jul 2026 18:52:47 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Avg (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS 1 step + stream (turbo) 1289 1705 1886 30
TTFS hook + 1 step + stream (non-turbo) 1608 1996 2315 30
STSO 100 sequential steps 294 396 836 495
WO 1 step + stream (turbo) 1289 1705 1886 30
WO hook + 1 step + stream (non-turbo) 1608 1996 2315 30
WO 100 sequential steps 30449 31100 31100 5
so 1 step + stream (turbo) 4701 5986 6066 30
so hook + 1 step + stream (non-turbo) 4969 5673 5870 30

Metrics — TTFS: time to first step body execution · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (time outside step bodies, client start → last step body exit) · SL: stream latency (first chunk write → visible to the reader)

Scenarios — stream: one step that streams chunks back to the client; no hooks, so the run stays in turbo mode · hook + stream: registers a hook before the same streaming step, which exits turbo mode · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges

🟢/🔴 mark percentiles within/above target. Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · STSO (1-20) 20/30/60 · STSO (101-120) 30/45/90 · STSO (1001-1020) 40/60/120

TTFS/WO compare client vs deployment clocks and SL compares the step runner’s clock vs the client’s (NTP-synced in CI). WO ends at the last step body exit, the closest observable proxy for the final step-completion request.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1453 0 230 1683
✅ 💻 Local Development 1617 0 219 1836
✅ 📦 Local Production 1617 0 219 1836
✅ 🐘 Local Postgres 1617 0 219 1836
✅ 🪟 Windows 153 0 0 153
✅ 📋 Other 894 0 177 1071
Total 7351 0 1064 8415

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 126 0 27
✅ example 126 0 27
✅ express 126 0 27
✅ fastify 126 0 27
✅ hono 126 0 27
✅ nextjs-turbopack 150 0 3
✅ nextjs-webpack 150 0 3
✅ nitro 126 0 27
✅ nuxt 126 0 27
✅ sveltekit 145 0 8
✅ vite 126 0 27
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 128 0 25
✅ express-stable 128 0 25
✅ fastify-stable 128 0 25
✅ hono-stable 128 0 25
✅ nextjs-turbopack-canary 134 0 19
✅ nextjs-turbopack-stable 153 0 0
✅ nextjs-webpack-canary 134 0 19
✅ nextjs-webpack-stable 153 0 0
✅ nitro-stable 128 0 25
✅ nuxt-stable 128 0 25
✅ sveltekit-stable 147 0 6
✅ vite-stable 128 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 128 0 25
✅ express-stable 128 0 25
✅ fastify-stable 128 0 25
✅ hono-stable 128 0 25
✅ nextjs-turbopack-canary 134 0 19
✅ nextjs-turbopack-stable 153 0 0
✅ nextjs-webpack-canary 134 0 19
✅ nextjs-webpack-stable 153 0 0
✅ nitro-stable 128 0 25
✅ nuxt-stable 128 0 25
✅ sveltekit-stable 147 0 6
✅ vite-stable 128 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 128 0 25
✅ express-stable 128 0 25
✅ fastify-stable 128 0 25
✅ hono-stable 128 0 25
✅ nextjs-turbopack-canary 134 0 19
✅ nextjs-turbopack-stable 153 0 0
✅ nextjs-webpack-canary 134 0 19
✅ nextjs-webpack-stable 153 0 0
✅ nitro-stable 128 0 25
✅ nuxt-stable 128 0 25
✅ sveltekit-stable 147 0 6
✅ vite-stable 128 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 153 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 128 0 25
✅ e2e-local-dev-tanstack-start- 128 0 25
✅ e2e-local-postgres-nest-stable 128 0 25
✅ e2e-local-postgres-tanstack-start- 128 0 25
✅ e2e-local-prod-nest-stable 128 0 25
✅ e2e-local-prod-tanstack-start- 128 0 25
✅ e2e-vercel-prod-tanstack-start 126 0 27

📋 View full workflow run

@TooTallNate TooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the harness, the workflows, the CI plumbing, and the renderer. Approving — this is a big improvement, and it directly fixes the operational problem I've been triaging all week (benchmark lanes hanging for 60–90 minutes as unbounded cancellations across main and every PR).

What I verified:

  • Harness design is sound. Sequential (non-concurrent) iterations are the right call for latency percentiles; the bounded failure tolerance (MAX_FAILURE_RATIO) plus per-run withTimeout (with unref, and the documented decision to not reader.cancel() on timeout — consistent with the known cancel-can-hang behavior) means one stuck run can no longer eat the job. Clock-skew caveats for TTFS/WO/SO are documented honestly, and STSO is skew-free by construction (both timestamps from the deployment clock).
  • Metric derivations check out: WO = (lastStep.end - clientStart) - Σ(step durations) with body-entry/exit timestamps recorded in the workflow, STSO from consecutive body boundaries, SO from writtenAt embedded in chunks with the reader attached before the step runs.
  • CI structure: every phase has timeout-minutes (5/30/60/10) — no more unbounded hangs; fail-fast: false; concurrency group cancels superseded PR runs but not main; third-party actions are SHA-pinned; results artifact uploads on always() so partial data survives a failed scenario; GITHUB_SHA correctly reports the PR head SHA rather than the synthetic merge commit.
  • Cleanup is complete: no stale references to bench.bench.ts, aggregate-benchmarks, or the render-benchmarks action anywhere; vitest.config.ts benchmark block and root scripts updated; the workbench symlinks for 97_bench.ts are intact across apps.
  • Renderer: 8/8 node --test tests pass locally, and lint.yml's existing .github/scripts/**/*.test.js glob picks them up as claimed. The base64-embedded history block surviving re-renders is a neat, self-contained persistence trick.
  • Empty changeset is correct for a CI/test-infra-only change.

Two non-blocking notes:

  1. With 30 stream iterations, nearest-rank p99 equals the max sample — fine (and standard), just worth remembering when reading the comment table that p99 is effectively "worst observed" until iteration counts grow.
  2. bench:local was dropped from root scripts; DEPLOYMENT_URL=http://localhost:3000 APP_NAME=nextjs-turbopack pnpm bench covers it, but if local runs are common during perf work it may be worth keeping the alias. (Note the SO scenario's local-world limitation is already documented in the harness header.)

…ules

The download-artifacts + pnpm install pattern hangs in workbench postinstall
scripts (sveltekit prepare stalled for the full job timeout). Mirror the
e2e-vercel jobs: fresh install via setup-workflow-dev + turbo build of the
CLI dependency graph, which hits the remote cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ions

- Rename SO -> SL (stream latency)
- Metric definitions move from the table rows to the comment footer
- Sequential scenario: 1020 steps, 1 iteration, STSO reported per
  step-index range (1-20, 20-120, 120-1020); WO no longer reported for it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A single long sequential run can fail transiently (e.g. replay divergence
under a large event log — the known corrupted-event-log flake family), which
previously zeroed out the whole scenario at 1 iteration. Scenarios now
backfill failed iterations with spare attempts and only fail when the
budget can't produce the requested sample count.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Scenario names shortened; explanations move to a footer legend emitted
  by the runner in the results JSON
- ms moves into the table column headers
- p50 replaced with p75
- p75/p90/p99 cells get 🟢/🔴 marks against per-metric targets (TTFS
  200/300/600, STSO windows 20/30/60, 30/45/90, 40/60/120, SL 50/60/125);
  STSO windows are now steps 1-20, 101-120, 1001-1020

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Backport to stable failed for da4e099 due to a workflow error (backport job run).

This is usually an infrastructure problem (e.g. the configured AI model could not be found, an AI Gateway error, or an opencode crash) rather than a merge conflict. Check the job logs linked above for details.

Once the underlying issue is fixed, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

da4e0995b0490c9e7815e98637b19bafb09408f5

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.

2 participants