mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
5f688d274a
Added a bunch of stuff to the bench. --- **New metrics in the HTTP benchmark** - Report TTFB per route (time to first body byte), next to total latency. - Report each route's document size, how many bytes are inline Flight payload, and the Flight share. **New script: `pnpm bench:render-pipeline:client`** - Loads each route in Chrome with tracing and 4x CPU throttling, and breaks down where client time goes: evaluating chunks, evaluating inline Flight scripts, compiling, background parsing, GC, time to hydration, and blocking time before hydration. - Also prints FCP/LCP/DOMContentLoaded/load from the same trace, and JS transferred vs parsed. - Off by default, separate from the timing benchmark, since tracing perturbs timing. - Hydration time comes from a small client component added to the fixture root layout that calls `performance.mark`. **Bug fixes** - The benchmark was replacing the fixture's `next.config.js` with an empty one during runs. - If the port was already taken, the benchmark could silently measure whatever server was already running there. Both scripts now refuse to start if something is already on the port. - A server that died on startup used to look like a slow server; now it errors immediately. - One failed request used to abort the whole run and throw away all results. Now it costs one sample and gets counted in `errors`. - Killing an already-dead server used to hang the script. - Bad flags now error upfront instead of crashing at the end. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
119 lines
3.0 KiB
Bash
Executable File
119 lines
3.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# Benchmark script for Node streams render pipeline performance.
|
|
# Uses the minimal server (bench/next-minimal-server) for lowest overhead.
|
|
# Warms up with 50 requests, then runs two phases:
|
|
# Phase 1: 10s at concurrency=1 (single-client latency)
|
|
# Phase 2: 10s at concurrency=100 (throughput under load)
|
|
# Reports throughput and latency percentiles for each phase.
|
|
#
|
|
# Usage:
|
|
# ./benchmark.sh [duration] [warmup_requests]
|
|
#
|
|
# Defaults: 10s duration per phase, 50 warmup requests
|
|
|
|
set -euo pipefail
|
|
|
|
DURATION=${1:-10}
|
|
WARMUP_REQS=${2:-50}
|
|
PORT=3199
|
|
NEXT_BIN="../../packages/next/dist/bin/next"
|
|
MINIMAL_SERVER="../next-minimal-server/bin/minimal-server.js"
|
|
|
|
if ! command -v npx &>/dev/null; then
|
|
echo "npx is required (for autocannon)"
|
|
exit 1
|
|
fi
|
|
|
|
cleanup() {
|
|
lsof -ti :"$PORT" 2>/dev/null | xargs kill -9 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
start_server() {
|
|
cleanup
|
|
sleep 0.5
|
|
PORT=$PORT node "$MINIMAL_SERVER" &>/dev/null &
|
|
SERVER_PID=$!
|
|
|
|
# Wait for server to be ready
|
|
local retries=0
|
|
while ! curl -sf "http://localhost:$PORT" >/dev/null 2>&1; do
|
|
retries=$((retries + 1))
|
|
if [ "$retries" -gt 30 ]; then
|
|
echo "ERROR: Server failed to start after 15s"
|
|
exit 1
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
}
|
|
|
|
stop_server() {
|
|
kill "$SERVER_PID" 2>/dev/null || true
|
|
wait "$SERVER_PID" 2>/dev/null || true
|
|
cleanup
|
|
sleep 1
|
|
}
|
|
|
|
warmup() {
|
|
echo " Warming up ($WARMUP_REQS requests)..."
|
|
for i in $(seq 1 "$WARMUP_REQS"); do
|
|
curl -sf "http://localhost:$PORT" >/dev/null 2>&1 || true
|
|
done
|
|
sleep 0.5
|
|
}
|
|
|
|
run_phase() {
|
|
local label="$1"
|
|
local connections="$2"
|
|
|
|
echo ""
|
|
echo " --- $label (${DURATION}s, c=$connections) ---"
|
|
|
|
local result
|
|
result=$(npx autocannon -d "$DURATION" -c "$connections" -j "http://localhost:$PORT" 2>/dev/null)
|
|
|
|
node -e "
|
|
const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
const r = d.requests;
|
|
const l = d.latency;
|
|
console.log(' Throughput:');
|
|
console.log(' avg: ' + r.average + ' req/s');
|
|
console.log(' mean: ' + r.mean + ' req/s');
|
|
console.log(' total: ' + r.total + ' requests in ${DURATION}s');
|
|
console.log(' Latency:');
|
|
console.log(' avg: ' + l.average.toFixed(2) + ' ms');
|
|
console.log(' p50: ' + l.p50.toFixed(2) + ' ms');
|
|
console.log(' p90: ' + l.p90.toFixed(2) + ' ms');
|
|
console.log(' p99: ' + l.p99.toFixed(2) + ' ms');
|
|
console.log(' max: ' + l.max.toFixed(2) + ' ms');
|
|
" <<< "$result"
|
|
}
|
|
|
|
run_benchmark() {
|
|
local mode="$1"
|
|
|
|
echo ""
|
|
echo "============================================"
|
|
echo " $mode"
|
|
echo "============================================"
|
|
|
|
start_server
|
|
warmup
|
|
run_phase "Single client" 1
|
|
run_phase "Under load" 100
|
|
stop_server
|
|
}
|
|
|
|
echo "Benchmark: node streams"
|
|
echo "======================="
|
|
echo "Duration: ${DURATION}s per phase | Warmup: ${WARMUP_REQS} reqs"
|
|
echo "Server: minimal-server (minimalMode: true)"
|
|
|
|
echo ""
|
|
echo "Building..."
|
|
node "$NEXT_BIN" build &>/dev/null
|
|
run_benchmark "Node Streams (default)"
|
|
|
|
echo ""
|
|
echo "Done."
|