179 lines
5.8 KiB
Python
179 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Timmy Bridge Monitor
|
|
Tracks heartbeat, artifacts, and response metrics for Local Timmy
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import sqlite3
|
|
import time
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
# Simple WebSocket client for monitoring
|
|
import websockets
|
|
|
|
DB_PATH = Path("/root/allegro/timmy_metrics.db")
|
|
RELAY_URL = "ws://167.99.126.228:3334"
|
|
|
|
class TimmyMonitor:
|
|
def __init__(self):
|
|
self.db = None
|
|
self.init_db()
|
|
|
|
def init_db(self):
|
|
"""Initialize SQLite database for metrics"""
|
|
self.db = sqlite3.connect(DB_PATH)
|
|
cursor = self.db.cursor()
|
|
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS heartbeats (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
timestamp TEXT,
|
|
timmy_pubkey TEXT,
|
|
event_id TEXT,
|
|
content_preview TEXT,
|
|
latency_ms INTEGER,
|
|
response_time_ms INTEGER
|
|
)
|
|
''')
|
|
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS artifacts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
timestamp TEXT,
|
|
timmy_pubkey TEXT,
|
|
artifact_type TEXT,
|
|
reference TEXT,
|
|
size_bytes INTEGER,
|
|
description TEXT
|
|
)
|
|
''')
|
|
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
session_id TEXT,
|
|
started_at TEXT,
|
|
ended_at TEXT,
|
|
turn_count INTEGER,
|
|
total_latency_ms INTEGER
|
|
)
|
|
''')
|
|
|
|
self.db.commit()
|
|
|
|
async def listen(self):
|
|
"""Listen to relay for Timmy events"""
|
|
print(f"[Monitor] Connecting to {RELAY_URL}")
|
|
|
|
while True: # Main reconnect loop - NOT recursive
|
|
try:
|
|
async with websockets.connect(RELAY_URL) as ws:
|
|
# Subscribe to all events (filter by Timmy's pubkey once known)
|
|
sub_id = "timmy-monitor"
|
|
req = ["REQ", sub_id, {}]
|
|
await ws.send(json.dumps(req))
|
|
|
|
print("[Monitor] Listening for events...")
|
|
|
|
while True:
|
|
msg = await ws.recv()
|
|
data = json.loads(msg)
|
|
await self.handle_event(data)
|
|
|
|
except Exception as e:
|
|
print(f"[Monitor] Error: {e}")
|
|
print("[Monitor] Reconnecting in 5 seconds...")
|
|
await asyncio.sleep(5)
|
|
# Loop continues automatically - no recursion
|
|
|
|
async def handle_event(self, data):
|
|
"""Process incoming events"""
|
|
if data[0] != "EVENT":
|
|
return
|
|
|
|
event = data[2]
|
|
kind = event.get("kind")
|
|
pubkey = event.get("pubkey")
|
|
content = event.get("content", "")
|
|
created_at = event.get("created_at")
|
|
|
|
timestamp = datetime.fromtimestamp(created_at).isoformat()
|
|
|
|
if kind == 1: # Short text note - heartbeat
|
|
print(f"[Heartbeat] {timestamp} from {pubkey[:16]}...")
|
|
self.log_heartbeat(pubkey, event["id"], content[:100])
|
|
|
|
elif kind == 30078: # Artifact event
|
|
print(f"[Artifact] {timestamp} from {pubkey[:16]}...")
|
|
self.log_artifact(pubkey, content, event.get("tags", []))
|
|
|
|
def log_heartbeat(self, pubkey, event_id, content):
|
|
"""Log heartbeat event"""
|
|
cursor = self.db.cursor()
|
|
cursor.execute('''
|
|
INSERT INTO heartbeats (timestamp, timmy_pubkey, event_id, content_preview)
|
|
VALUES (?, ?, ?, ?)
|
|
''', (datetime.now().isoformat(), pubkey, event_id, content))
|
|
self.db.commit()
|
|
|
|
def log_artifact(self, pubkey, content, tags):
|
|
"""Log artifact creation"""
|
|
# Extract artifact type from tags
|
|
artifact_type = "unknown"
|
|
for tag in tags:
|
|
if tag[0] == "t" and "artifact-type:" in tag[1]:
|
|
artifact_type = tag[1].split(":")[1]
|
|
|
|
cursor = self.db.cursor()
|
|
cursor.execute('''
|
|
INSERT INTO artifacts (timestamp, timmy_pubkey, artifact_type, reference, size_bytes, description)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
''', (datetime.now().isoformat(), pubkey, artifact_type, content[:64], len(content), content[:200]))
|
|
self.db.commit()
|
|
|
|
def generate_report(self):
|
|
"""Generate morning retrospective report"""
|
|
cursor = self.db.cursor()
|
|
|
|
# Last 24 hours
|
|
cursor.execute('''
|
|
SELECT COUNT(*), AVG(latency_ms) FROM heartbeats
|
|
WHERE timestamp > datetime('now', '-1 day')
|
|
''')
|
|
heartbeat_count, avg_latency = cursor.fetchone()
|
|
|
|
cursor.execute('''
|
|
SELECT COUNT(*), artifact_type FROM artifacts
|
|
WHERE timestamp > datetime('now', '-1 day')
|
|
GROUP BY artifact_type
|
|
''')
|
|
artifacts = cursor.fetchall()
|
|
|
|
report = f"""
|
|
# Timmy Retrospective - {datetime.now().strftime('%Y-%m-%d %H:%M')}
|
|
|
|
## Heartbeats
|
|
- Count: {heartbeat_count or 0}
|
|
- Avg latency: {avg_latency or 'N/A'} ms
|
|
|
|
## Artifacts Created
|
|
{chr(10).join([f"- {count} {atype}" for count, atype in artifacts]) if artifacts else "- None"}
|
|
|
|
## Status
|
|
{'✓ Active' if heartbeat_count and heartbeat_count > 0 else '✗ No activity detected'}
|
|
"""
|
|
return report
|
|
|
|
if __name__ == "__main__":
|
|
monitor = TimmyMonitor()
|
|
|
|
# Run listener
|
|
try:
|
|
asyncio.run(monitor.listen())
|
|
except KeyboardInterrupt:
|
|
print("\n[Monitor] Shutting down")
|
|
print(monitor.generate_report())
|