#!/usr/bin/env python3 """ Reality Latency Audit Spårar varje tidsstämpel i kedjan för att hitta flaskhalsar """ import sqlite3 import json from datetime import datetime, timedelta from pathlib import Path DB_PATH = "/home/bernt/.openclaw/workspace/rivp-pilot-1/rivp.db" AUDIT_FILE = "/home/bernt/.openclaw/workspace/life-weather/latency_audit.json" def audit_latency(): """Granska latency i hela kedjan""" conn = sqlite3.connect(DB_PATH) c = conn.cursor() # Hämta senaste observation med alla tidsstämplar c.execute(""" SELECT id, road_id, observation_type, value, timestamp, created_at, source FROM weather_observations ORDER BY id DESC LIMIT 1 """) row = c.fetchone() if not row: print("Inga observationer att granska") return obs_id, road_id, obs_type, value, timestamp, created_at, source = row # Konvertera tidsstämplar now = datetime.now() # Source timestamp (när SMHI mätte) source_time = datetime.fromisoformat(timestamp.replace('Z', '+00:00').replace('+00:00', '')) # Created timestamp (när vi sparade) created_time = datetime.fromisoformat(created_at) # Beräkna latencies source_to_fetch = (created_time - source_time).total_seconds() / 60 fetch_to_now = (now - created_time).total_seconds() / 60 total_latency = (now - source_time).total_seconds() / 60 audit = { "observation_id": obs_id, "road_id": road_id, "type": obs_type, "timestamps": { "source": timestamp, "fetched": created_at, "audited": now.isoformat() }, "latencies_min": { "source_to_fetch": round(source_to_fetch, 2), "fetch_to_now": round(fetch_to_now, 2), "total": round(total_latency, 2) }, "bottleneck": "fetch_to_now" if fetch_to_now > source_to_fetch else "source_to_fetch" } # Spara audit audits = [] if Path(AUDIT_FILE).exists(): with open(AUDIT_FILE) as f: audits = json.load(f) audits.append(audit) with open(AUDIT_FILE, 'w') as f: json.dump(audits[-100:], f, indent=2) print(f"Latency Audit för observation {obs_id}:") print(f" Source → Fetch: {source_to_fetch:.1f} min") print(f" Fetch → Now: {fetch_to_now:.1f} min") print(f" Total: {total_latency:.1f} min") print(f" Bottleneck: {audit['bottleneck']}") conn.close() return audit def analyze_latency_distribution(): """Analysera latency-fördelning""" if not Path(AUDIT_FILE).exists(): print("Ingen audit-data än") return with open(AUDIT_FILE) as f: audits = json.load(f) if not audits: print("Ingen audit-data än") return total_latencies = [a["latencies_min"]["total"] for a in audits] total_latencies.sort() p50 = total_latencies[len(total_latencies) // 2] p95 = total_latencies[int(len(total_latencies) * 0.95)] p99 = total_latencies[int(len(total_latencies) * 0.99)] if len(total_latencies) > 100 else p95 print(f"\nLatency Distribution (n={len(total_latencies)}):") print(f" P50: {p50:.1f} min") print(f" P95: {p95:.1f} min") print(f" P99: {p99:.1f} min") print(f" Min: {min(total_latencies):.1f} min") print(f" Max: {max(total_latencies):.1f} min") return { "p50": p50, "p95": p95, "p99": p99, "min": min(total_latencies), "max": max(total_latencies) } if __name__ == "__main__": print("="*60) print("REALITY LATENCY AUDIT") print("="*60) audit = audit_latency() distribution = analyze_latency_distribution() print("\n" + "="*60) print("REKOMMENDATION:") print("="*60) if audit and audit["latencies_min"]["fetch_to_now"] > 60: print("⚠️ Fetch-to-now latency är för hög (>60 min)") print(" Åtgärd: Kör pipelinen oftare (var 15 min istället för varje timme)") if distribution and distribution["p95"] > 60: print("⚠️ P95 latency överstiger 60 min") print(" Åtgärd: Optimera pipeline-körtid eller öka frekvens") print("="*60)