#!/usr/bin/env bash # Shared helpers. Source this; do not run it. HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MOD="$(cd "$HERE/.." && pwd)" source "$HERE/env.sh" JAR="$MOD/target/catalog.jar" # ---- containers ----------------------------------------------------------------------------------------- # There is no Docker daemon in the sandbox, so limits are set the way Docker sets them: a cgroup with a # memory limit and a CPU quota. The JVM's container support reads exactly those files (cgroup v1 here). CGROOT=/sys/fs/cgroup cg_create() { # NAME MEMORY_BYTES CPUS mkdir -p "$CGROOT/memory/$1" "$CGROOT/cpu/$1" echo "$2" >"$CGROOT/memory/$1/memory.limit_in_bytes" echo 100000 >"$CGROOT/cpu/$1/cpu.cfs_period_us" echo $(( $3 * 100000 )) >"$CGROOT/cpu/$1/cpu.cfs_quota_us" } cg_remove() { rmdir "$CGROOT/memory/$1" "$CGROOT/cpu/$1" 2>/dev/null; } # In run_app the JVM is started as: bash -c '; exec java ...' & # so that $! is the JVM's own pid (a function wrapper would make $! the wrapper's pid). CG_JOIN='n=$1; shift; echo $$ >'"$CGROOT"'/memory/$n/cgroup.procs; echo $$ >'"$CGROOT"'/cpu/$n/cgroup.procs; exec "$@"' # run_app JAVA_HOME N OUTDIR [jvm flags...] # Starts the catalog on that JVM, loads N products, and leaves these files in OUTDIR: # startup.txt memory-idle.txt load.txt memory.txt lookups.txt histogram.txt outcome.txt # Set CG_MEM (bytes) and CG_CPUS to run inside a container-style cgroup. Set NMT=1 for native memory tracking. # Returns 1 if the load did not complete; outcome.txt says why: java-OOM, killed (kernel OOM kill), # no-result-in-25s (the JVM is alive but collecting garbage without making progress; a healthy load takes # about a second), or error. run_app_once() { local jh="$1" n="$2" out="$3"; shift 3 local port=$((20000 + RANDOM % 20000)) local cg="" mkdir -p "$out" local extra=() if [ -n "${NMT:-}" ]; then extra+=(-XX:NativeMemoryTracking=summary); fi if [ -n "${CG_MEM:-}" ]; then cg="jm$$-$RANDOM"; cg_create "$cg" "$CG_MEM" "${CG_CPUS:-2}" bash -c "$CG_JOIN" _ "$cg" "$jh/bin/java" "${extra[@]}" "$@" -jar "$JAR" --server.port=$port >"$out/app.log" 2>&1 & else "$jh/bin/java" "${extra[@]}" "$@" -jar "$JAR" --server.port=$port >"$out/app.log" 2>&1 & fi local pid=$! local up=0 for _ in $(seq 1 400); do kill -0 $pid 2>/dev/null || break if curl -sf "localhost:$port/ping" >/dev/null 2>&1; then up=1; break; fi sleep 0.25 done local rc=0 if [ $up = 0 ]; then wait $pid 2>/dev/null; local st=$? if grep -q OutOfMemoryError "$out/app.log"; then echo "java-OOM-at-startup" >"$out/outcome.txt" elif [ $st -ge 128 ]; then echo "killed(exit $st)" >"$out/outcome.txt"; else echo "error(exit $st)" >"$out/outcome.txt"; fi : >"$out/startup.txt"; [ -n "$cg" ] && cg_remove "$cg"; return 1 fi grep -o 'Started MemoryApplication in [0-9.]* seconds' "$out/app.log" >"$out/startup.txt" curl -s --max-time 60 "localhost:$port/memory" >"$out/memory-idle.txt" curl -s --max-time 25 "localhost:$port/load?n=$n" >"$out/load.txt"; local crc=$? if ! grep -q '^loaded=' "$out/load.txt"; then rc=1 if [ $crc -eq 28 ]; then echo "no-result-in-25s" >"$out/outcome.txt" elif ! kill -0 $pid 2>/dev/null; then wait $pid 2>/dev/null; echo "killed(exit $?)" >"$out/outcome.txt" elif grep -q OutOfMemoryError "$out/app.log" "$out/load.txt"; then echo "java-OOM" >"$out/outcome.txt" else echo "error" >"$out/outcome.txt"; fi else curl -s --max-time 60 "localhost:$port/memory" >"$out/memory.txt" curl -s --max-time 60 "localhost:$port/lookups?n=2000000" >"$out/lookups.txt" "$jh/bin/jcmd" $pid GC.class_histogram >"$out/histogram.txt" 2>&1 if [ -n "${NMT:-}" ]; then "$jh/bin/jcmd" $pid VM.native_memory summary >"$out/nmt.txt" 2>&1; fi if kill -0 $pid 2>/dev/null; then echo "ok" >"$out/outcome.txt"; else wait $pid 2>/dev/null; echo "killed(exit $?)" >"$out/outcome.txt"; rc=1; fi fi kill $pid 2>/dev/null; wait $pid 2>/dev/null [ -n "$cg" ] && cg_remove "$cg" return $rc } # run_app: run_app_once, but a run whose JVM never came up for a reason that is neither OutOfMemoryError # nor a kernel kill (in practice: the random port was taken) is retried, and the retry is counted in # $out/retries so that a summary can say so instead of hiding it. run_app() { local out="$3" tries=0 while :; do run_app_once "$@"; local rc=$? if [ $rc -ne 0 ] && grep -q '^error(exit' "$out/outcome.txt" 2>/dev/null && [ ! -s "$out/startup.txt" ] && [ $tries -lt 2 ]; then tries=$((tries+1)); echo $tries >"$out/retries"; continue fi return $rc done } # kv FILE KEY -> value kv() { grep "^$2=" "$1" | head -1 | cut -d= -f2-; } # median of numbers on stdin median() { sort -n | awk '{a[NR]=$1} END{ if(NR%2){print a[(NR+1)/2]} else {printf "%d\n",(a[NR/2]+a[NR/2+1])/2} }'; }