#!/usr/bin/env bash # batch-watch.sh — single-shot snapshot of an apricot batch dir's progress. # # Usage: # tools/batch-watch.sh # tools/batch-watch.sh p1-22-budget-20260425_180742 # tools/batch-watch.sh matchup-grid-20260425_193656 # # Prints date, godot proc count, per-game (turn / outcome / age-since-last-write), # and the tail of batch.log if present. Reads $AUTOPLAY_HOST from the calling # environment and routes via ssh. set -euo pipefail if [[ $# -lt 1 ]]; then echo "Usage: $0 " >&2 exit 2 fi BATCH="$1" HOST="${AUTOPLAY_HOST:-apricot}" # Strip any user@ prefix and rely on ssh config alias when bare. REMOTE="$HOST" case "$REMOTE" in *.local|*.local:*) REMOTE="${REMOTE/.local/}" ;; esac ssh "$REMOTE" "BATCH=$BATCH bash -s" <<'REMOTE_SCRIPT' set -uo pipefail ROOT="$HOME/Code/@projects/@magic-civilization/.local/iter/$BATCH" echo "=== date ===" date echo "=== godot count ===" ps -eo pid,etime,cmd | grep godot-bin | grep -v grep | wc -l echo "=== per-game state ===" if [[ ! -d "$ROOT" ]]; then echo " (no such batch dir: $ROOT)" exit 0 fi # Games may live one or two levels under $ROOT (matchup-grid puts them under pair/as_clan/) while IFS= read -r g; do name=${g#"$ROOT/"} if [[ ! -f "$g/turn_stats.jsonl" ]]; then echo " $name: NO_STATS" continue fi n=$(wc -l < "$g/turn_stats.jsonl" 2>/dev/null || echo 0) last=$(tail -1 "$g/turn_stats.jsonl" 2>/dev/null | python3 -c ' import sys, json try: d = json.loads(sys.stdin.read() or "{}") print(d.get("turn", "-"), d.get("outcome", "-"), d.get("winner_personality") or d.get("winner_clan") or "") except Exception: print("PARSE_ERR") ' 2>/dev/null) mt=$(stat -c %Y "$g/turn_stats.jsonl" 2>/dev/null || echo 0) now=$(date +%s) age=$((now - mt)) echo " $name: turns=$n last=$last age=${age}s" done < <(find "$ROOT" -maxdepth 3 -mindepth 1 -type d -name 'game_*' | sort) echo "=== batch.log tail ===" tail -8 "$ROOT/batch.log" 2>/dev/null || echo " (no batch.log)" REMOTE_SCRIPT