| |
| """FSI_FELON Training Monitor — watches over the forge while you sleep.""" |
| import os, time, json, sys, subprocess |
| from datetime import datetime |
|
|
| LOG = "/tmp/opencode/snca/train_live.log" |
| SCRIPT = "/tmp/opencode/snca/train_live.py" |
| SCREEN = "deep_train" |
| CHECK_INTERVAL = 600 |
| REPORT = "/tmp/fsi_felon/training_report.json" |
| ALERT_FILE = "/tmp/fsi_felon/training_alert" |
|
|
| def tail_lines(path, n=5): |
| try: |
| with open(path) as f: |
| lines = f.readlines() |
| return lines[-n:] |
| except: |
| return [] |
|
|
| def check_training(): |
| now = datetime.now().strftime("%H:%M:%S") |
| lines = tail_lines(LOG, 3) |
| if not lines: |
| return {"status": "NO_LOG", "time": now, "msg": "No log file found"} |
|
|
| last = lines[-1].strip() |
| |
| try: |
| parts = last.split() |
| time_str = parts[0].strip("[]") |
| step = int(parts[1].split("=")[1].replace(",", "")) |
| loss = float(parts[2].split("=")[1]) |
| lr = float(parts[3].split("=")[1]) |
| except: |
| return {"status": "PARSE_ERROR", "time": now, "msg": f"Could not parse: {last}"} |
|
|
| report = { |
| "status": "OK", |
| "time": now, |
| "step": step, |
| "loss": loss, |
| "lr": lr, |
| "msg": f"step={step:,} loss={loss:.4f} lr={lr:.2e}" |
| } |
|
|
| |
| if loss > 15.0: |
| report["alert"] = f"Loss spike: {loss:.4f}" |
| report["status"] = "ALERT" |
| with open(ALERT_FILE, "w") as f: |
| f.write(json.dumps(report)) |
|
|
| |
| if os.path.exists(REPORT): |
| try: |
| with open(REPORT) as f: |
| prev = json.load(f) |
| if prev.get("step") == step and prev.get("status") != "RESTARTED": |
| report["status"] = "STALLED" |
| report["msg"] += " — LOG NOT UPDATING" |
| except: |
| pass |
|
|
| with open(REPORT, "w") as f: |
| json.dump(report, f, indent=2) |
| return report |
|
|
| def restart_training(): |
| print(f"[{datetime.now().strftime('%H:%M:%S')}] Training stalled or crashed — restarting...") |
| |
| subprocess.run(["screen", "-S", SCREEN, "-X", "quit"], capture_output=True) |
| time.sleep(2) |
| |
| cmd = f"cd /tmp/opencode/snca && screen -dmS {SCREEN} python3 train_live.py" |
| subprocess.run(cmd, shell=True) |
| time.sleep(5) |
| report = check_training() |
| report["status"] = "RESTARTED" |
| with open(REPORT, "w") as f: |
| json.dump(report, f, indent=2) |
| print(f" Restarted at step {report.get('step', '?')}") |
| return report |
|
|
| def monitor(): |
| print("=" * 55) |
| print(" FSI_FELON TRAINING MONITOR") |
| print(" Checking every 10 minutes (~100 steps)") |
| print(f" Started: {datetime.now().strftime('%H:%M:%S')}") |
| print("=" * 55) |
| print() |
|
|
| |
| if os.path.exists(ALERT_FILE): |
| os.remove(ALERT_FILE) |
|
|
| cycle = 0 |
| while True: |
| cycle += 1 |
| report = check_training() |
| ts = report["time"] |
| status = report["status"] |
|
|
| if status == "OK": |
| print(f"[{ts}] ✓ step={report['step']:,} loss={report['loss']:.4f} lr={report['lr']:.2e}") |
| elif status == "STALLED": |
| print(f"[{ts}] ⚠ STALLED — restarting") |
| report = restart_training() |
| elif status == "ALERT": |
| print(f"[{ts}] 🔴 ALERT: {report.get('alert')}") |
| elif status == "NO_LOG": |
| print(f"[{ts}] 🔴 No log — restarting") |
| report = restart_training() |
| else: |
| print(f"[{ts}] ? {status}: {report.get('msg')}") |
|
|
| |
| r = subprocess.run(["screen", "-ls"], capture_output=True, text=True) |
| if SCREEN not in r.stdout: |
| print(f"[{datetime.now().strftime('%H:%M:%S')}] 🔴 Screen dead — restarting") |
| restart_training() |
|
|
| time.sleep(CHECK_INTERVAL) |
|
|
| if __name__ == "__main__": |
| try: |
| monitor() |
| except KeyboardInterrupt: |
| print("\nMonitor stopped.") |
|
|