java-topology/tools/mc-bench/bench-server.sh
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

205 lines
7.1 KiB
Bash
Executable file

#!/usr/bin/env bash
# bench-server.sh — benchmark a Minecraft server: startup + /reload + bot load
#
# Usage: ./bench-server.sh <tier> [bot-count] [--play]
#
# Tiers:
# unpatched control — vanilla jar, depth-16 datapack (defect present)
# mitigated fix applied — patched jar, depth-16 datapack (same game, fast)
# enriched new territory — patched jar, depth-24 300-ns datapack (impossible before patch)
#
# Aliases: vanilla=unpatched, patched=mitigated
#
# --play: after benchmarks, keep server up for human play testing.
# Prints connection info and waits for Ctrl-C.
set -euo pipefail
export PYTHONUNBUFFERED=1
TIER="${1:-unpatched}"
BOTS="${2:-20}"
# --play flag: accept anywhere in args
PLAY_MODE=0
for _arg in "$@"; do [ "$_arg" = "--play" ] && PLAY_MODE=1; done
# ── Tier configuration ────────────────────────────────────────────────────────
case "$TIER" in
unpatched|vanilla)
TIER=unpatched
DIR=/tmp/mc-bench/unpatched
PORT=25565
RCON=25575
SERVER_JAR=/home/fox/Downloads/server.jar
DATAPACK_DEPTH=16
DATAPACK_NS=200
DATAPACK_XREFS=6
TIER_NOTE="control — DependencySorter defect present"
;;
mitigated|patched)
TIER=mitigated
DIR=/tmp/mc-bench/mitigated
PORT=25566
RCON=25576
SERVER_JAR=/tmp/mc-bench/server-patched.jar
DATAPACK_DEPTH=16
DATAPACK_NS=200
DATAPACK_XREFS=6
TIER_NOTE="fix applied — same game, defect eliminated"
;;
enriched)
DIR=/tmp/mc-bench/enriched
PORT=25567
RCON=25577
SERVER_JAR=/tmp/mc-bench/server-patched.jar
DATAPACK_DEPTH=48
DATAPACK_NS=1000
DATAPACK_XREFS=32
TIER_NOTE="enriched — D=48 / 1000 NS / 32 xrefs: impossible before patch"
;;
*)
echo "Usage: $0 unpatched|mitigated|enriched [bot-count] [--play]"
exit 1
;;
esac
JAVA=/tmp/jdk25/bin/java
LOG="$DIR/bench.log"
FIFO="$DIR/server.stdin"
pkill -f "port $PORT " 2>/dev/null || true
sleep 1
rm -f "$LOG" "$FIFO"
rm -rf "$DIR/world"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
python3 "$SCRIPT_DIR/gen-modpack-tags.py" "$DIR" "$DATAPACK_DEPTH" "$DATAPACK_NS" "$DATAPACK_XREFS"
mkfifo "$FIFO"
echo "=========================================="
echo " $TIER — Minecraft Server Benchmark"
echo " $TIER_NOTE"
echo "=========================================="
echo " depth-${DATAPACK_DEPTH} ${DATAPACK_NS} namespaces ${DATAPACK_XREFS} xrefs · $BOTS bots"
echo ""
# ── §1 Startup ────────────────────────────────────────────────────────────────
echo "§1 Starting server..."
START_MS=$(date +%s%3N)
exec 3<>"$FIFO"
(cd "$DIR" && exec $JAVA -Xmx3G -Xms512m \
--enable-native-access=ALL-UNNAMED \
-DbundlerRepoDir=. \
-jar "$SERVER_JAR" \
--nogui --port "$PORT" \
<"$FIFO" >>"$LOG" 2>&1) &
SERVER_PID=$!
for i in $(seq 1 180); do
sleep 1
if grep -q "Done (" "$LOG" 2>/dev/null; then
STARTUP_MS=$(( $(date +%s%3N) - START_MS ))
DONE_LINE=$(grep "Done (" "$LOG" | tail -1)
echo " startup: ${STARTUP_MS} ms"
echo " server: $DONE_LINE"
break
fi
if ! kill -0 $SERVER_PID 2>/dev/null; then
echo "ERROR: server exited prematurely"
tail -12 "$LOG"
exit 1
fi
[ $(( i % 10 )) -eq 0 ] && echo " ...${i}s"
done
if ! grep -q "Done (" "$LOG" 2>/dev/null; then
echo "ERROR: did not start in 180s"
kill $SERVER_PID 2>/dev/null
exit 1
fi
sleep 2 # let RCON bind
# ── §2 First bot wave ─────────────────────────────────────────────────────────
echo ""
echo "§2 First bot wave: $BOTS bots joining..."
node "$SCRIPT_DIR/raw-connect.mjs" localhost "$PORT" "$BOTS" 2>&1
sleep 2
# ── §3 /reload timing ─────────────────────────────────────────────────────────
echo ""
echo "§3 /reload timing..."
RELOAD_LINE=$(wc -l < "$LOG")
RELOAD_START=$(date +%s%3N)
python3 "$SCRIPT_DIR/rcon.py" localhost "$RCON" benchpass "reload" 2>&1 || \
{ echo "RCON failed, sending via stdin"; echo "reload" >&3; }
RELOAD_MS=""
for i in $(seq 1 180); do
sleep 1
if tail -n +"$RELOAD_LINE" "$LOG" 2>/dev/null | grep -qE "Loaded [0-9]+ recipes"; then
RELOAD_MS=$(( $(date +%s%3N) - RELOAD_START ))
echo " /reload: ${RELOAD_MS} ms"
tail -n +"$RELOAD_LINE" "$LOG" | grep -E "Loaded [0-9]+ (recipes|advancements)" | head -3 | while IFS= read -r l; do echo " $l"; done
break
fi
done
[ -z "$RELOAD_MS" ] && { echo " /reload: did not complete in 180s"; RELOAD_MS="-1"; }
# ── §4 Second bot wave (post-reload) ─────────────────────────────────────────
echo ""
echo "§4 Post-reload bot wave: $BOTS bots..."
node "$SCRIPT_DIR/raw-connect.mjs" localhost "$PORT" "$BOTS" 2>&1
# ── §5 Play test mode (human) ─────────────────────────────────────────────────
if [ "$PLAY_MODE" = "1" ]; then
echo ""
echo "=========================================="
echo " PLAY TEST MODE — server staying up"
echo "=========================================="
echo " Connect to: localhost:$PORT"
echo " Tier: $TIER ($TIER_NOTE)"
echo " Log: $LOG"
echo ""
echo " Press Ctrl-C to stop the server and finish benchmark."
echo ""
# Wait for Ctrl-C; trap cleans up
trap 'echo ""; echo "Stopping server..."; echo "stop" >&3; exec 3>&-; wait $SERVER_PID 2>/dev/null || true; rm -f "$FIFO"; echo "Server stopped."; exit 0' INT TERM
while kill -0 $SERVER_PID 2>/dev/null; do sleep 5; done
echo "Server exited on its own."
exec 3>&- 2>/dev/null || true
rm -f "$FIFO"
exit 0
fi
# ── Stop ─────────────────────────────────────────────────────────────────────
echo ""
echo "Stopping server..."
echo "stop" >&3
exec 3>&-
wait $SERVER_PID 2>/dev/null || true
rm -f "$FIFO"
# ── Results ──────────────────────────────────────────────────────────────────
echo ""
echo "=========================================="
echo " $TIER RESULTS"
echo "=========================================="
echo " startup: ${STARTUP_MS} ms"
echo " reload: ${RELOAD_MS} ms"
echo " bots: $BOTS"
echo " depth: $DATAPACK_DEPTH ns: $DATAPACK_NS xrefs: $DATAPACK_XREFS"
echo ""
cat > "$DIR/results.txt" << EOF
label=$TIER
startup_ms=$STARTUP_MS
reload_ms=$RELOAD_MS
bots=$BOTS
datapack_depth=$DATAPACK_DEPTH
datapack_ns=$DATAPACK_NS
datapack_xrefs=$DATAPACK_XREFS
EOF