305 lines
11 KiB
Bash
Executable file
305 lines
11 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"
|
|
|
|
# ── System metrics helpers ────────────────────────────────────────────────────
|
|
# cpu_snap: echo "user nice sys irq softirq intr" from /proc/stat
|
|
cpu_snap() {
|
|
local cpu intr
|
|
read -r cpu user nice sys idle iowait irq softirq steal guest gnest < /proc/stat
|
|
intr=$(awk '/^intr / {print $2}' /proc/stat)
|
|
echo "$user $nice $sys $irq $softirq $intr"
|
|
}
|
|
|
|
# cpu_delta <snap_before> <snap_after> <elapsed_ms> → prints table row
|
|
# Computes jiffies burned per field; divides by total to get %.
|
|
# /proc/stat ticks at CONFIG_HZ (usually 100 Hz).
|
|
cpu_delta() {
|
|
local before="$1" after="$2" elapsed_ms="$3"
|
|
read -r bu bn bs br bsoft bi <<< "$before"
|
|
read -r au an as_ ar asoft ai <<< "$after"
|
|
local du=$(( au - bu ))
|
|
local dn=$(( an - bn ))
|
|
local ds=$(( as_ - bs ))
|
|
local dr=$(( ar - br ))
|
|
local dsoft=$(( asoft - bsoft ))
|
|
local dintr=$(( ai - bi ))
|
|
local total=$(( du + dn + ds + dr + dsoft ))
|
|
if [ "$total" -gt 0 ]; then
|
|
awk -v u=$du -v n=$dn -v s=$ds -v r=$dr -v soft=$dsoft -v intr=$dintr \
|
|
-v tot=$total -v ms=$elapsed_ms 'BEGIN {
|
|
printf " cpu user=%.1f%% nice=%.1f%% sys=%.1f%% irq=%.1f%% sirq=%.1f%% intr=%d (over %dms)\n",
|
|
u/tot*100, n/tot*100, s/tot*100, (r+soft)/tot*100, 0, intr, ms
|
|
}'
|
|
else
|
|
echo " cpu (no delta)"
|
|
fi
|
|
}
|
|
|
|
# mem_snap: echo free_mb available_mb from /proc/meminfo
|
|
mem_snap() {
|
|
awk '/^MemFree:/{f=$2}/^MemAvailable:/{a=$2} END{printf "%d %d", f/1024, a/1024}' /proc/meminfo
|
|
}
|
|
|
|
# Print and store a labelled memory reading
|
|
mem_print() {
|
|
local label="$1" snap="$2"
|
|
read -r free_mb avail_mb <<< "$snap"
|
|
echo " mem free=${free_mb}MB available=${avail_mb}MB ($label)"
|
|
}
|
|
|
|
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..."
|
|
CPU_BEFORE_STARTUP=$(cpu_snap)
|
|
MEM_BEFORE_STARTUP=$(mem_snap)
|
|
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 ))
|
|
CPU_AFTER_STARTUP=$(cpu_snap)
|
|
MEM_AFTER_STARTUP=$(mem_snap)
|
|
DONE_LINE=$(grep "Done (" "$LOG" | tail -1)
|
|
echo " startup: ${STARTUP_MS} ms"
|
|
echo " server: $DONE_LINE"
|
|
cpu_delta "$CPU_BEFORE_STARTUP" "$CPU_AFTER_STARTUP" "$STARTUP_MS"
|
|
mem_print "before" "$MEM_BEFORE_STARTUP"
|
|
mem_print "after" "$MEM_AFTER_STARTUP"
|
|
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")
|
|
CPU_BEFORE_RELOAD=$(cpu_snap)
|
|
MEM_BEFORE_RELOAD=$(mem_snap)
|
|
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 ))
|
|
CPU_AFTER_RELOAD=$(cpu_snap)
|
|
MEM_AFTER_RELOAD=$(mem_snap)
|
|
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
|
|
cpu_delta "$CPU_BEFORE_RELOAD" "$CPU_AFTER_RELOAD" "$RELOAD_MS"
|
|
mem_print "before" "$MEM_BEFORE_RELOAD"
|
|
mem_print "after" "$MEM_AFTER_RELOAD"
|
|
break
|
|
fi
|
|
done
|
|
[ -z "$RELOAD_MS" ] && { echo " /reload: did not complete in 180s"; RELOAD_MS="-1"; CPU_AFTER_RELOAD=$(cpu_snap); MEM_AFTER_RELOAD=$(mem_snap); }
|
|
|
|
# ── §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 ──────────────────────────────────────────────────────────────────
|
|
# Derive per-phase CPU/mem deltas for results.txt (machine-readable)
|
|
_cpu_fields() {
|
|
local before="$1" after="$2"
|
|
read -r bu bn bs br bsoft bi <<< "$before"
|
|
read -r au an as_ ar asoft ai <<< "$after"
|
|
local du=$(( au-bu )) dn=$(( an-bn )) ds=$(( as_-bs ))
|
|
local dr=$(( ar-br )) dsoft=$(( asoft-bsoft )) dintr=$(( ai-bi ))
|
|
local tot=$(( du+dn+ds+dr+dsoft ))
|
|
[ "$tot" -eq 0 ] && tot=1
|
|
awk -v u=$du -v n=$dn -v s=$ds -v r=$dr -v soft=$dsoft -v intr=$dintr -v tot=$tot \
|
|
-v pfx="$3" 'BEGIN {
|
|
printf "%scpu_user_pct=%.1f\n%scpu_nice_pct=%.1f\n%scpu_sys_pct=%.1f\n%scpu_irq_pct=%.1f\n%scpu_intr=%d\n",
|
|
pfx, u/tot*100, pfx, n/tot*100, pfx, s/tot*100, pfx, (r+soft)/tot*100, pfx, intr
|
|
}'
|
|
}
|
|
_mem_fields() {
|
|
read -r free_mb avail_mb <<< "$(mem_snap)"
|
|
echo "${1}mem_free_mb=$free_mb"
|
|
echo "${1}mem_avail_mb=$avail_mb"
|
|
}
|
|
|
|
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 ""
|
|
echo " --- CPU/mem during startup ---"
|
|
cpu_delta "$CPU_BEFORE_STARTUP" "$CPU_AFTER_STARTUP" "$STARTUP_MS"
|
|
mem_print "before" "$MEM_BEFORE_STARTUP"
|
|
mem_print "after" "$MEM_AFTER_STARTUP"
|
|
echo " --- CPU/mem during /reload ---"
|
|
cpu_delta "$CPU_BEFORE_RELOAD" "$CPU_AFTER_RELOAD" "$RELOAD_MS"
|
|
mem_print "before" "$MEM_BEFORE_RELOAD"
|
|
mem_print "after" "$MEM_AFTER_RELOAD"
|
|
echo ""
|
|
|
|
{
|
|
cat << 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
|
|
_cpu_fields "$CPU_BEFORE_STARTUP" "$CPU_AFTER_STARTUP" "startup_"
|
|
_mem_fields "startup_before_"
|
|
read -r f a <<< "$MEM_BEFORE_STARTUP"; echo "startup_before_mem_free_mb=$f"; echo "startup_before_mem_avail_mb=$a"
|
|
read -r f a <<< "$MEM_AFTER_STARTUP"; echo "startup_after_mem_free_mb=$f"; echo "startup_after_mem_avail_mb=$a"
|
|
_cpu_fields "$CPU_BEFORE_RELOAD" "$CPU_AFTER_RELOAD" "reload_"
|
|
read -r f a <<< "$MEM_BEFORE_RELOAD"; echo "reload_before_mem_free_mb=$f"; echo "reload_before_mem_avail_mb=$a"
|
|
read -r f a <<< "$MEM_AFTER_RELOAD"; echo "reload_after_mem_free_mb=$f"; echo "reload_after_mem_avail_mb=$a"
|
|
} > "$DIR/results.txt"
|