#!/usr/bin/env python3
"""
cog-monitor.py -- Passive observer for corpus learning.
Polls CogServer state via WebSocket and prints a summary every N seconds.
No intervention, just observation.
Usage:
python3 cog-monitor.py # Poll every 10s
python3 cog-monitor.py 30 # Poll every 30s
"""
import asyncio
import sys
import time
try:
import websockets
except ImportError:
print("pip3 install websockets", file=sys.stderr)
sys.exit(1)
WS_URL = "ws://localhost:18080/scm"
async def query(ws, expr):
"""Send expression, get result (stripped of whitespace)."""
await ws.send(expr)
reply = await asyncio.wait_for(ws.recv(), timeout=10)
return reply.strip()
async def monitor(interval):
"""Continuously poll CogServer and display learning state."""
print(f"Monitoring CogServer at {WS_URL} every {interval}s")
print(f"{'time':>8} {'sent':>7} {'learn':>6} {'rate':>6} {'surp':>7} {'base':>7} {'dirty':>7} {'cls':>5} {'lv':>3} {'s/s':>6} {'status':>6}")
print("-" * 80)
prev_sent = 0
t0 = time.time()
while True:
try:
async with websockets.connect(WS_URL, ping_interval=10, ping_timeout=30) as ws:
while True:
sent = await query(ws, '*cl-total-sentences*')
learns = await query(ws, '*cl-total-learns*')
surprise = await query(ws, '(cl-average-surprise)')
baseline = await query(ws, '(cl-level-baseline *cl-current-level*)')
dirty = await query(ws, '*cl-dirty-count*')
classes = await query(ws, '(hash-count (const #t) *word-classes*)')
level = await query(ws, '*cl-current-level*')
running = await query(ws, '*cl-running*')
sent_i = int(sent)
learns_i = int(learns)
rate = f"{100.0 * learns_i / max(1, sent_i):.1f}%"
elapsed = time.time() - t0
sps = (sent_i - prev_sent) / interval if prev_sent > 0 else 0
status = "RUN" if running == '#t' else "STOP"
ts = f"{elapsed:.0f}s"
print(f"{ts:>8} {sent_i:>7} {learns_i:>6} {rate:>6} {float(surprise):>7.3f} {float(baseline):>7.3f} {int(dirty):>7} {int(classes):>5} {int(level):>3} {sps:>5.1f}/s {status:>6}")
prev_sent = sent_i
if running != '#t' and sent_i > 0:
print("\n[Loop stopped]")
return
await asyncio.sleep(interval)
except (websockets.exceptions.ConnectionClosed, ConnectionRefusedError, OSError) as e:
print(f"\n[Connection lost: {e} — retrying in 5s]")
await asyncio.sleep(5)
except KeyboardInterrupt:
print("\n[Monitor stopped]")
return
def main():
interval = int(sys.argv[1]) if len(sys.argv) > 1 else 10
try:
asyncio.run(monitor(interval))
except KeyboardInterrupt:
print("\n[Monitor stopped]")
if __name__ == "__main__":
main()