#!/bin/bash
#
# Benchmark dev server boot time (wall-clock)
#
# Measures TWO metrics:
# 1. listen_time: When server starts accepting TCP connections
# 2. ready_time: When server responds to first HTTP request
#
# The delta between these shows how much initialization is deferred after "Ready".
#
# Usage:
# ./scripts/benchmark-boot-time.sh [runs] [test-dir]
#
# Examples:
# ./scripts/benchmark-boot-time.sh # 5 runs, uses /tmp/next-boot-test
# ./scripts/benchmark-boot-time.sh 3 # 3 runs
# ./scripts/benchmark-boot-time.sh 5 ./my-app # 5 runs on existing app
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 ""
# Create test app if it doesn't exist
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
{children}
}
EOF
cat > "$TEST_DIR/app/page.tsx" << 'EOF'
export default function Home() { return Hello
}
EOF
(cd "$TEST_DIR" && npm install --silent)
# Link local next
(cd "$TEST_DIR" && npm link "$(dirname "$NEXT_BIN")/.." 2>/dev/null || true)
fi
# Kill any existing next dev on our port
pkill -f "next dev.*$PORT" 2>/dev/null || true
sleep 0.5
# Returns: listen_time,ready_time (comma-separated)
benchmark_run() {
local label=$1
local clean_next=$2
if [ "$clean_next" = "true" ]; then
rm -rf "$TEST_DIR/.next"
fi
# Measure wall-clock time from command start
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 # 30s at 50ms intervals
local listen_time=""
local ready_time=""
# Phase 1: Wait for port to be listening (nc -z)
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
# Phase 2: Wait for HTTP response (curl)
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 the server
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
# Calculate averages
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 for summary
export "${series_name}_LISTEN_AVG=$listen_avg"
export "${series_name}_READY_AVG=$ready_avg"
export "${series_name}_DELTA_AVG=$delta_avg"
}
# Run cold start benchmarks
run_benchmark_series "COLD" true
# Warmup for bytecode cache
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 warm start benchmarks
run_benchmark_series "WARM" false
# Summary
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