| | #!/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 |
| |
|