#!/bin/bash # chrome-error-scan.sh - scan per-profile chrome logs for concerning patterns. # Self-contained: scans, compares against watermark, reports only NEW matches. # # Usage: chrome-error-scan.sh [--json] [--no-advance] # Default output: "profile:new_count" lines for profiles with new matches, # or "OK: no new errors" if clean. # --json: output JSON {"profile": {"total": N, "new": M}, ...} # --no-advance: report against the watermark WITHOUT advancing it. # Peek-only read for high-frequency pollers (e.g. the web # surface via `box-ctl chrome-errors --no-advance`). Runs # without the flag keep the classic advance-on-read semantics. # # Watermark: /home/super/Projects/NetVM/chrome-error-watermark.json # Patterns: FATAL, crash, segfault, out of memory (case-insensitive) LOGDIR="/home/super/Projects/NetVM" WATERMARK="$LOGDIR/chrome-error-watermark.json" AS_JSON=0 NO_ADVANCE=0 for arg in "$@"; do case "$arg" in --json) AS_JSON=1 ;; --no-advance) NO_ADVANCE=1 ;; *) echo "chrome-error-scan.sh: unknown argument: $arg" >&2; exit 2 ;; esac done # Gather current counts per profile (grep -c prints 0 with exit 1 on no match; # the || true masks the exit code while preserving the "0" on stdout) get_count() { local f="$LOGDIR/chromebox-$1.log" if [ -f "$f" ]; then grep -ciE "FATAL|crash|segfault|out of memory" "$f" 2>/dev/null || true else echo 0 fi } MUSE_C=$(get_count muse) PIP_C=$(get_count pip) N646_C=$(get_count 646) OPM_C=$(get_count opm) python3 - "$WATERMARK" "$MUSE_C" "$PIP_C" "$N646_C" "$OPM_C" "$AS_JSON" "$NO_ADVANCE" <<'PYEOF' import json, sys, os watermark_path = sys.argv[1] current = { "muse": int(sys.argv[2]), "pip": int(sys.argv[3]), "646": int(sys.argv[4]), "opm": int(sys.argv[5]), } as_json = len(sys.argv) > 6 and sys.argv[6] == "1" no_advance = len(sys.argv) > 7 and sys.argv[7] == "1" # Load watermark (tolerate missing/corrupt file -> treat as all-zero) watermark = {} if os.path.exists(watermark_path): try: with open(watermark_path) as f: data = json.load(f) watermark = data.get("counts", data) if isinstance(data, dict) else {} except (ValueError, IOError, OSError): watermark = {} new_counts = {} for prof in ("muse", "pip", "646", "opm"): old = watermark.get(prof, 0) try: old = int(old) except (TypeError, ValueError): old = 0 delta = current[prof] - old new_counts[prof] = max(delta, 0) if as_json: out = {p: {"total": current[p], "new": new_counts[p]} for p in current} print(json.dumps(out)) else: any_new = False for prof in ("muse", "pip", "646", "opm"): if new_counts[prof] > 0: print("%s:%d" % (prof, new_counts[prof])) any_new = True if not any_new: print("OK: no new errors") # Update watermark atomically (skipped in --no-advance peek mode: the # caller gets a read-only view and the CLI's advance-on-read semantics are # left untouched). if not no_advance: tmp = watermark_path + ".tmp" with open(tmp, "w") as f: json.dump({"counts": current}, f, indent=2) os.replace(tmp, watermark_path) PYEOF