from __future__ import annotations import time from collections import deque from typing import Any class NextcloudTalkMetrics: def __init__(self, max_latency_samples: int = 100): self._max_latency_samples = max_latency_samples self._send_latencies: deque[float] = deque(maxlen=max_latency_samples) self._receive_latencies: deque[float] = deque(maxlen=max_latency_samples) self._send_count: int = 0 self._receive_count: int = 0 self._send_error_count: int = 0 self._receive_error_count: int = 0 self._circuit_breaker_trips: int = 0 self._started_at: float = time.time() def record_send_success(self, latency_s: float) -> None: self._send_latencies.append(latency_s) self._send_count += 1 def record_send_error(self) -> None: self._send_error_count += 1 def record_receive_success(self, latency_s: float) -> None: self._receive_latencies.append(latency_s) self._receive_count += 1 def record_receive_error(self) -> None: self._receive_error_count += 1 def record_circuit_breaker_trip(self) -> None: self._circuit_breaker_trips += 1 def snapshot(self) -> dict[str, Any]: uptime_s = time.time() - self._started_at send_latencies = list(self._send_latencies) recv_latencies = list(self._receive_latencies) avg_send = sum(send_latencies) / len(send_latencies) if send_latencies else None avg_recv = sum(recv_latencies) / len(recv_latencies) if recv_latencies else None return { "uptime_seconds": round(uptime_s, 1), "send": { "count": self._send_count, "error_count": self._send_error_count, "avg_latency_ms": round(avg_send * 1000, 1) if avg_send is not None else None, "p50_latency_ms": round(_percentile(send_latencies, 50) * 1000, 1) if send_latencies else None, "p95_latency_ms": round(_percentile(send_latencies, 95) * 1000, 1) if send_latencies else None, "p99_latency_ms": round(_percentile(send_latencies, 99) * 1000, 1) if send_latencies else None, }, "receive": { "count": self._receive_count, "error_count": self._receive_error_count, "avg_latency_ms": round(avg_recv * 1000, 1) if avg_recv is not None else None, "p50_latency_ms": round(_percentile(recv_latencies, 50) * 1000, 1) if recv_latencies else None, "p95_latency_ms": round(_percentile(recv_latencies, 95) * 1000, 1) if recv_latencies else None, "p99_latency_ms": round(_percentile(recv_latencies, 99) * 1000, 1) if recv_latencies else None, }, "circuit_breaker": { "trip_count": self._circuit_breaker_trips, }, } def _percentile(data: list[float], percentile: float) -> float: if not data: return 0.0 sorted_data = sorted(data) k = (len(sorted_data) - 1) * percentile / 100.0 f = int(k) c = k - f if f + 1 < len(sorted_data): return sorted_data[f] + c * (sorted_data[f + 1] - sorted_data[f]) return sorted_data[f]