"""Schlanke In-Memory-Metriken (Counter + Timer) fuer einen Prozess. Bewusst ohne externe Dependency. Fuer mehrere Instanzen/Prozesse spaeter durch einen gemeinsamen Backend (z. B. Prometheus-Exporter) ersetzbar. """ import threading import time from collections import defaultdict class Metrics: def __init__(self): self._lock = threading.Lock() self._counters: dict[str, float] = defaultdict(float) self._timers: dict[str, list] = defaultdict(lambda: [0.0, 0]) # [sum, count] @staticmethod def _key(name: str, labels: dict | None) -> str: if not labels: return name rendered = ",".join(f'{k}="{v}"' for k, v in sorted(labels.items())) return f"{name}{{{rendered}}}" def inc(self, name: str, labels: dict | None = None, value: float = 1.0) -> None: with self._lock: self._counters[self._key(name, labels)] += value def observe(self, name: str, seconds: float, labels: dict | None = None) -> None: with self._lock: agg = self._timers[self._key(name, labels)] agg[0] += seconds agg[1] += 1 def snapshot(self) -> dict: with self._lock: counters = dict(self._counters) timers = { key: { "sum": agg[0], "count": agg[1], "avg": (agg[0] / agg[1] if agg[1] else 0.0), } for key, agg in self._timers.items() } return {"counters": counters, "timers": timers} def prometheus(self) -> str: snap = self.snapshot() lines = [] for key, value in sorted(snap["counters"].items()): lines.append(f"{key} {value}") for key, agg in sorted(snap["timers"].items()): base, _, labels = key.partition("{") suffix = ("{" + labels) if labels else "" lines.append(f"{base}_sum{suffix} {agg['sum']}") lines.append(f"{base}_count{suffix} {agg['count']}") return "\n".join(lines) + "\n" def reset(self) -> None: with self._lock: self._counters.clear() self._timers.clear() metrics = Metrics() class timer: """Context-Manager: misst die Dauer und schreibt sie als Timer-Beobachtung.""" def __init__(self, name: str, labels: dict | None = None): self.name = name self.labels = labels def __enter__(self): self._start = time.perf_counter() return self def __exit__(self, *exc): metrics.observe(self.name, time.perf_counter() - self._start, self.labels) return False