#!/usr/bin/env bash # The profiler is off by default, and "off" has to mean the JVMs launch exactly # as they did before it existed - a profiling build that quietly changes an # ordinary match is worse than no profiling build. # # So: the pins agree, an unset ARENA_PROFILE renders the old vmArgs unchanged, a # set one renders a well-formed config carrying the agent, and finalize reduces # the Suramadu stats lines it already had into a table. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT fail=0 check() { # if "${@:2}"; then echo "ok $1"; else echo "FAIL $1"; fail=1; fi } not() { ! "$@"; } valid_json() { jq -e . "$1" >/dev/null; } # --- the pins agree ---------------------------------------------------------- # scripts/build.sh does not pass the async-profiler args yet, so the Dockerfile # carries defaults. Two copies of a version and a digest drift silently; this is # what stops them. # shellcheck source=/dev/null . "$ROOT/versions.env" for var in ASYNC_PROFILER_VERSION ASYNC_PROFILER_ARCHIVE ASYNC_PROFILER_SHA256 ASYNC_PROFILER_DIR; do want="${!var}" got="$(sed -n "s/^ARG $var=//p" "$ROOT/Dockerfile")" check "$var matches the Dockerfile default" test "$got" = "$want" done # --- rendering --------------------------------------------------------------- render() { # render [env assignments...] local out="$1"; shift mkdir -p "$out/run/state" "$out/mm/mmconf" "$out/home/config" \ "$out/home/async-profiler/lib" "$out/home/instrument" echo 'ARENA_CLIENT_SLOT=TraineeA' > "$out/run/players.env" printf 'did:plc:aaa\tTraineeA\n' > "$out/run/clients.tsv" # The renderer refuses to launch a JVM against an agent that is not there, so # the fixture needs a file at the path the image puts it. : > "$out/home/async-profiler/lib/libasyncProfiler.so" # Same story for lazy-png-agent, which has its own switch (ARENA_LAZY_PNG) # rather than riding on ARENA_PROFILE. Staged in every fixture so the "off" # cases prove the switch, not a missing file. : > "$out/home/instrument/lazy-png-agent.jar" # Every template the image stages into config/, by glob rather than by name: # the Dockerfile copies exactly these two directories' templates there, and # naming them one by one is what broke this fixture before. The renderer cp's # each one unconditionally, so a template it reads and the fixture has not # staged fails the render outright - and with the render wrapped in an if, # that reads as three profiler assertions passing when none of them ran. cp "$ROOT"/suramadu/*.template "$ROOT"/megamek/*.template "$out/home/config/" ln -sfn "$ROOT/container" "$out/home/container" env "$@" ARENA_HOME="$out/home" ARENA_RUN="$out/run" MM_HOME="$out/mm" \ JAVA="/opt/arena/jre/bin/java" ARENA_PORT_WS=9080 ARENA_PORT_MM=9850 \ bash "$ROOT/container/init/40-render-config.sh" >/dev/null 2>&1 } # The template app is deleted after the per-seat clones are made, so the # flags are asserted on the app that actually serves - the stub's one seat. vmargs() { jq -r '."/did:plc:aaa".swingConfig.vmArgs' "$1/run/suramadu/suramadu.config"; } # Unprofiled: no async-profiler. The lazy-png javaagent *is* expected here now - # it is the shipped rendering behaviour rather than instrumentation, and its # own switch turns it off below. if render "$TMP/off"; then OFF="$(vmargs "$TMP/off")" check "off: config is valid JSON" valid_json "$TMP/off/run/suramadu/suramadu.config" check "off: no async-profiler agent on the client JVM" not grep -q agentpath <<< "$OFF" check "off: the lazy-png javaagent is on by default" grep -q javaagent <<< "$OFF" check "off: no placeholder survives" not grep -q '@[A-Z_]*@' <<< "$OFF" check "off: vmArgs ends with the javaagent and nothing else" \ grep -q -- "--add-opens java.base/java.util.concurrent=ALL-UNNAMED -javaagent:$TMP/off/home/instrument/lazy-png-agent.jar\$" <<< "$OFF" else echo "FAIL off: 40-render-config.sh exited non-zero"; fail=1 fi # Explicitly on is the same as the default, and does not drag the profiler in. if render "$TMP/png" ARENA_LAZY_PNG=1; then PNG="$(vmargs "$TMP/png")" check "lazy-png: config is valid JSON" valid_json "$TMP/png/run/suramadu/suramadu.config" check "lazy-png: the javaagent is loaded" \ grep -q -- "-javaagent:$TMP/png/home/instrument/lazy-png-agent.jar" <<< "$PNG" check "lazy-png: no async-profiler came with it" not grep -q agentpath <<< "$PNG" check "lazy-png: no placeholder survives" not grep -q '@[A-Z_]*@' <<< "$PNG" else echo "FAIL lazy-png: 40-render-config.sh exited non-zero"; fail=1 fi # 0 is the bisecting escape hatch: stock Suramadu encode behaviour. if render "$TMP/png0" ARENA_LAZY_PNG=0; then check "lazy-png: 0 means off" not grep -q javaagent <<< "$(vmargs "$TMP/png0")" else echo "FAIL lazy-png0: 40-render-config.sh exited non-zero"; fail=1 fi # Profiled: wall clock, collapsed stacks, output where finalize collects from. if render "$TMP/on" ARENA_PROFILE=1; then ON="$(vmargs "$TMP/on")" check "on: config is valid JSON" valid_json "$TMP/on/run/suramadu/suramadu.config" check "on: no placeholder survives" not grep -q '@[A-Z_]*@' <<< "$ON" check "on: loads the agent" \ grep -q -- "-agentpath:$TMP/on/home/async-profiler/lib/libasyncProfiler.so=start," <<< "$ON" check "on: wall clock by default" grep -q 'event=wall' <<< "$ON" check "on: collapsed stacks" grep -q ',collapsed,' <<< "$ON" check "on: writes where finalize collects" \ grep -q "file=$TMP/on/run/state/profile-client.collapsed" <<< "$ON" check "on: inlined frames stay attributable" \ grep -q -- '-XX:+DebugNonSafepoints' <<< "$ON" else echo "FAIL on: 40-render-config.sh exited non-zero"; fail=1 fi if render "$TMP/ev" ARENA_PROFILE=1 ARENA_PROFILE_CLIENT_EVENT=ctimer; then check "on: the event is overridable" grep -q 'event=ctimer' <<< "$(vmargs "$TMP/ev")" else echo "FAIL ev: 40-render-config.sh exited non-zero"; fail=1 fi # --- the host JVM ------------------------------------------------------------ # The launch itself needs three JVMs and a match, so what is checked here is the # switch and the fact that the array reaches the command line. eval "$(grep -m1 '^profiling() ' "$ROOT/container/entrypoint.sh")" prof_unset() { ( unset ARENA_PROFILE; profiling ); } prof_zero() { ( export ARENA_PROFILE=0; profiling ); } prof_one() { ( export ARENA_PROFILE=1; profiling ); } check "host: off when ARENA_PROFILE is unset" not prof_unset check "host: off when ARENA_PROFILE=0" not prof_zero check "host: on when ARENA_PROFILE=1" prof_one check "host: the agent array reaches the JVM" \ grep -qF '"${PROFILE_ARGS[@]}"' "$ROOT/container/entrypoint.sh" check "host: the array starts empty" \ grep -qF 'PROFILE_ARGS=()' "$ROOT/container/entrypoint.sh" # --- the stats table --------------------------------------------------------- FIN="$TMP/fin" mkdir -p "$FIN/state" "$FIN/spool" "$FIN/mm" echo '{"version":1,"matchId":"m"}' > "$FIN/manifest.json" { echo '18:30:12.345 stats,megamek1,latency,120.0' echo '18:30:13.345 stats,megamek1,latency,80' echo '18:30:14.345 stats,megamek1,latency,300.5' echo '18:30:14.345 stats,megamek1,latencyPing,12' # Not a stats line, and full of commas, which is the trap. echo '18:30:15.000 INFO [main] (Foo.java:1) loaded a, b, c' } > "$FIN/suramadu.log" ARENA_HOME="$ROOT" ARENA_RUN="$FIN" ARENA_STATE="$FIN/state" \ ARENA_SPOOL="$FIN/spool" MANIFEST="$FIN/manifest.json" MM_HOME="$FIN/mm" \ bash "$ROOT/container/exit/finalize.sh" >/dev/null 2>&1 SUM="$FIN/state/stats-summary.txt" check "stats: a summary is written" test -s "$SUM" check "stats: non-stats lines ignored" not grep -q INFO "$SUM" # 80, 120, 300.5 sorted: p50 is 120, p90 nearest-rank is 300.5. check "stats: latency is reduced" grep -qE '^latency +3 +166\.8 +120\.0 +300\.5 +300\.5' "$SUM" check "stats: every metric gets a row" grep -q '^latencyPing' "$SUM" exit "$fail"