Files
boc/life-weather/latency_audit.py
T

150 lines
4.3 KiB
Python
Raw Normal View History

#!/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)