from __future__ import annotations from collections import deque from dataclasses import dataclass from datetime import UTC, datetime from time import monotonic import psutil from app.dmx.backends import BackendStatus @dataclass(slots=True) class TelemetryEvent: created_at: datetime category: str level: str message: str class TelemetryService: def __init__(self) -> None: self.started_at = monotonic() self.events: deque[TelemetryEvent] = deque(maxlen=1000) self.last_sent_at: datetime | None = None self.last_error: str | None = None self.reconnect_count = 0 self.frames_sent = 0 self.send_errors = 0 def record_send_success(self, status: BackendStatus) -> None: self.last_sent_at = datetime.now(UTC) self.last_error = None self.reconnect_count = status.reconnect_count self.frames_sent = status.frames_sent self.send_errors = status.send_errors def record_send_failure(self, error: str, status: BackendStatus) -> None: self.last_error = error self.reconnect_count = status.reconnect_count self.frames_sent = status.frames_sent self.send_errors = status.send_errors self.events.append( TelemetryEvent(datetime.now(UTC), "DMX", "ERROR", error) ) def snapshot(self, queue_depth: int = 0) -> dict[str, object]: temperature = None try: readings = psutil.sensors_temperatures() if readings: first = next(iter(readings.values())) if first: temperature = float(first[0].current) except Exception: temperature = None return { "uptime_seconds": round(monotonic() - self.started_at, 2), "cpu_percent": psutil.cpu_percent(interval=None), "ram_percent": psutil.virtual_memory().percent, "temperature_c": temperature, "queue_depth": queue_depth, "reconnect_count": self.reconnect_count, "last_error": self.last_error, "last_sent_at": self.last_sent_at, "frames_sent": self.frames_sent, "send_errors": self.send_errors, "events": [ { "created_at": event.created_at.isoformat(), "category": event.category, "level": event.level, "message": event.message, } for event in list(self.events)[-50:] ], }