| #!/bin/bash |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| set -e |
|
|
| RUNS=${1:-5} |
| TEST_DIR=${2:-/tmp/next-boot-test} |
| NEXT_BIN="$(dirname "$0")/../packages/next/dist/bin/next" |
| PORT=3456 |
|
|
| echo "=== Dev Server Boot Time Benchmark ===" |
| echo "Runs: $RUNS" |
| echo "Test dir: $TEST_DIR" |
| echo "Next.js: $NEXT_BIN" |
| echo "" |
| echo "Metrics:" |
| echo " listen_time: TCP port accepting connections" |
| echo " ready_time: First HTTP request succeeds" |
| echo " delta: ready_time - listen_time (deferred init)" |
| echo "" |
|
|
| |
| if [ ! -f "$TEST_DIR/package.json" ]; then |
| echo "Creating test app..." |
| mkdir -p "$TEST_DIR/app" |
| cat > "$TEST_DIR/package.json" << 'EOF' |
| { |
| "name": "boot-test", |
| "private": true, |
| "dependencies": { |
| "react": "19.0.0", |
| "react-dom": "19.0.0" |
| } |
| } |
| EOF |
| cat > "$TEST_DIR/app/layout.tsx" << 'EOF' |
| export default function RootLayout({ children }: { children: React.ReactNode }) { |
| return <html><body>{children}</body></html> |
| } |
| EOF |
| cat > "$TEST_DIR/app/page.tsx" << 'EOF' |
| export default function Home() { return <h1>Hello</h1> } |
| EOF |
| (cd "$TEST_DIR" && npm install --silent) |
| |
| (cd "$TEST_DIR" && npm link "$(dirname "$NEXT_BIN")/.." 2>/dev/null || true) |
| fi |
|
|
| |
| pkill -f "next dev.*$PORT" 2>/dev/null || true |
| sleep 0.5 |
|
|
| |
| benchmark_run() { |
| local label=$1 |
| local clean_next=$2 |
|
|
| if [ "$clean_next" = "true" ]; then |
| rm -rf "$TEST_DIR/.next" |
| fi |
|
|
| |
| local start_time=$(python3 -c 'import time; print(int(time.time() * 1000))') |
|
|
| "$NEXT_BIN" dev --turbopack --port $PORT "$TEST_DIR" > /dev/null 2>&1 & |
| local pid=$! |
|
|
| local timeout=600 |
| local listen_time="" |
| local ready_time="" |
|
|
| |
| for i in $(seq 1 $timeout); do |
| if nc -z localhost $PORT 2>/dev/null; then |
| listen_time=$(python3 -c 'import time; print(int(time.time() * 1000))') |
| break |
| fi |
| sleep 0.05 |
| done |
|
|
| |
| if [ -n "$listen_time" ]; then |
| for i in $(seq 1 $timeout); do |
| if curl -s "http://localhost:$PORT" > /dev/null 2>&1; then |
| ready_time=$(python3 -c 'import time; print(int(time.time() * 1000))') |
| break |
| fi |
| sleep 0.05 |
| done |
| fi |
|
|
| |
| kill $pid 2>/dev/null || true |
| wait $pid 2>/dev/null || true |
|
|
| if [ -n "$listen_time" ] && [ -n "$ready_time" ]; then |
| local listen_delta=$((listen_time - start_time)) |
| local ready_delta=$((ready_time - start_time)) |
| echo "$listen_delta,$ready_delta" |
| else |
| echo "TIMEOUT,TIMEOUT" |
| fi |
| } |
|
|
| run_benchmark_series() { |
| local series_name=$1 |
| local clean_next=$2 |
|
|
| echo "--- $series_name ---" |
| echo "Run | Listen | Ready | Delta" |
| echo "----|--------|-------|------" |
|
|
| local listen_times="" |
| local ready_times="" |
| local deltas="" |
|
|
| for i in $(seq 1 $RUNS); do |
| RESULT=$(benchmark_run "$series_name-$i" "$clean_next") |
| LISTEN=$(echo "$RESULT" | cut -d',' -f1) |
| READY=$(echo "$RESULT" | cut -d',' -f2) |
|
|
| if [ "$LISTEN" != "TIMEOUT" ] && [ "$READY" != "TIMEOUT" ]; then |
| DELTA=$((READY - LISTEN)) |
| printf "%3d | %5dms | %5dms | %5dms\n" "$i" "$LISTEN" "$READY" "$DELTA" |
| listen_times="$listen_times $LISTEN" |
| ready_times="$ready_times $READY" |
| deltas="$deltas $DELTA" |
| else |
| printf "%3d | TIMEOUT | TIMEOUT | -\n" "$i" |
| fi |
| done |
|
|
| |
| local listen_avg=$(echo $listen_times | tr ' ' '\n' | grep -v '^$' | awk '{sum+=$1; count++} END {if(count>0) printf "%.0f", sum/count; else print "N/A"}') |
| local ready_avg=$(echo $ready_times | tr ' ' '\n' | grep -v '^$' | awk '{sum+=$1; count++} END {if(count>0) printf "%.0f", sum/count; else print "N/A"}') |
| local delta_avg=$(echo $deltas | tr ' ' '\n' | grep -v '^$' | awk '{sum+=$1; count++} END {if(count>0) printf "%.0f", sum/count; else print "N/A"}') |
|
|
| echo "" |
| echo "Average: listen=${listen_avg}ms, ready=${ready_avg}ms, delta=${delta_avg}ms" |
| echo "" |
|
|
| |
| export "${series_name}_LISTEN_AVG=$listen_avg" |
| export "${series_name}_READY_AVG=$ready_avg" |
| export "${series_name}_DELTA_AVG=$delta_avg" |
| } |
|
|
| |
| run_benchmark_series "COLD" true |
|
|
| |
| echo "--- Warming up bytecode cache (12s) ---" |
| "$NEXT_BIN" dev --turbopack --port $PORT "$TEST_DIR" > /dev/null 2>&1 & |
| WARMUP_PID=$! |
| for i in $(seq 1 200); do |
| if curl -s "http://localhost:$PORT" > /dev/null 2>&1; then |
| break |
| fi |
| sleep 0.05 |
| done |
| sleep 12 |
| kill $WARMUP_PID 2>/dev/null || true |
| wait $WARMUP_PID 2>/dev/null || true |
| echo "" |
|
|
| |
| run_benchmark_series "WARM" false |
|
|
| |
| echo "==============================================" |
| echo " SUMMARY" |
| echo "==============================================" |
| echo "" |
| echo "Cold Start ($RUNS runs):" |
| echo " Port listening: ${COLD_LISTEN_AVG}ms" |
| echo " First request: ${COLD_READY_AVG}ms" |
| echo " Deferred init: ${COLD_DELTA_AVG}ms" |
| echo "" |
| echo "Warm Start ($RUNS runs):" |
| echo " Port listening: ${WARM_LISTEN_AVG}ms" |
| echo " First request: ${WARM_READY_AVG}ms" |
| echo " Deferred init: ${WARM_DELTA_AVG}ms" |
| echo "" |
|
|
| if [ "$COLD_READY_AVG" != "N/A" ] && [ "$WARM_READY_AVG" != "N/A" ]; then |
| CACHE_BENEFIT=$((COLD_READY_AVG - WARM_READY_AVG)) |
| echo "Cache benefit: ${CACHE_BENEFIT}ms (cold - warm ready)" |
| fi |
|
|