#!/usr/bin/env bash
# perf-smoke.sh — substrate perf-regression alarm.
#
# Runs lib/perf-smoke.sx via sx_server.exe and asserts each micro-benchmark's
# wall-clock time is within REGRESSION_FACTOR× of the reference number. Exits
# 0 if all are within budget, 1 if any has regressed.
#
# Reference numbers: measured on a quiet dev machine (Linux, 2 vCPU, 7.6 GiB
# RAM, OCaml 5.2.0). Document the machine in jit-perf-regression.md when
# updating.
#
# Usage:
#   bash scripts/perf-smoke.sh           # check (default factor 5×)
#   FACTOR=3 bash scripts/perf-smoke.sh  # tighter threshold
#   bash scripts/perf-smoke.sh --update  # rewrite the reference numbers in
#                                        # this script with current run's
#                                        # numbers (use only on a quiet
#                                        # reference machine; commit the diff)
#
# The signal is *change* relative to the reference, not absolute number.
# Drift is fine; reset the reference when the substrate changes intentionally
# (e.g. after a JIT improvement).

set -uo pipefail
cd "$(git rev-parse --show-toplevel)"

# ── Reference numbers (median of 5 runs on the reference machine) ──────────
# Update these via `bash scripts/perf-smoke.sh --update` on a quiet machine.
REF_FIB18=1216
REF_LET1000=194
REF_MAP500=21
REF_TAIL5000=430
# ── End reference numbers ──────────────────────────────────────────────────

FACTOR="${FACTOR:-5}"

SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
  SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
  echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build" >&2
  exit 2
fi

TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT

cat > "$TMPFILE" <<'EPOCHS'
(epoch 1)
(load "lib/perf-smoke.sx")
(epoch 2)
(eval "(perf-smoke)")
EPOCHS

OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>&1)
LINE=$(echo "$OUTPUT" | grep -E '^"perf-smoke ' | head -1 | tr -d '"')

if [ -z "$LINE" ]; then
  echo "ERROR: no perf-smoke result line; sx_server output:" >&2
  echo "$OUTPUT" | tail -20 >&2
  exit 2
fi

# Parse: perf-smoke fib18=N let1000=N map500=N tail5000=N
get() { echo "$LINE" | grep -oE "$1=[0-9]+" | cut -d= -f2; }
FIB18=$(get fib18)
LET1000=$(get let1000)
MAP500=$(get map500)
TAIL5000=$(get tail5000)

if [ "${1:-}" = "--update" ]; then
  echo "Measured (this run): fib18=$FIB18 let1000=$LET1000 map500=$MAP500 tail5000=$TAIL5000"
  echo "Rewriting reference numbers in $0…"
  sed -i \
    -e "s/^REF_FIB18=.*/REF_FIB18=$FIB18/" \
    -e "s/^REF_LET1000=.*/REF_LET1000=$LET1000/" \
    -e "s/^REF_MAP500=.*/REF_MAP500=$MAP500/" \
    -e "s/^REF_TAIL5000=.*/REF_TAIL5000=$TAIL5000/" \
    "$0"
  echo "Done. Commit the diff."
  exit 0
fi

if [ "$REF_FIB18" -eq 0 ] || [ "$REF_LET1000" -eq 0 ] || \
   [ "$REF_MAP500" -eq 0 ] || [ "$REF_TAIL5000" -eq 0 ]; then
  echo "WARN: reference numbers not yet set (all zero)." >&2
  echo "Run \`bash scripts/perf-smoke.sh --update\` on a quiet reference machine first." >&2
  echo "Measured (this run): fib18=$FIB18 let1000=$LET1000 map500=$MAP500 tail5000=$TAIL5000"
  exit 0
fi

verdict() {
  local name="$1" got="$2" ref="$3"
  local budget=$((ref * FACTOR))
  if [ "$got" -le "$budget" ]; then
    printf '  ok    %-12s %5d ms (ref %d, %d×)\n' "$name" "$got" "$ref" "$FACTOR"
    return 0
  else
    printf '  FAIL  %-12s %5d ms (ref %d, budget %d×=%d ms)\n' \
      "$name" "$got" "$ref" "$FACTOR" "$budget"
    return 1
  fi
}

FAIL=0
echo "perf-smoke (factor ${FACTOR}× of reference):"
verdict fib18    "$FIB18"    "$REF_FIB18"    || FAIL=1
verdict let1000  "$LET1000"  "$REF_LET1000"  || FAIL=1
verdict map500   "$MAP500"   "$REF_MAP500"   || FAIL=1
verdict tail5000 "$TAIL5000" "$REF_TAIL5000" || FAIL=1

if [ "$FAIL" -eq 0 ]; then
  echo "ok perf-smoke within ${FACTOR}× of reference."
  exit 0
else
  echo "FAIL one or more benchmarks regressed. Investigate before merging."
  exit 1
fi
