You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
83 lines
2.7 KiB
Python
83 lines
2.7 KiB
Python
# backend/app/observability/metrics.py — 경량 Prometheus 메트릭 (의존성 없음)
|
|
import threading
|
|
from collections import defaultdict
|
|
|
|
|
|
class _Registry:
|
|
def __init__(self):
|
|
self._lock = threading.Lock()
|
|
self.counters: dict[tuple[str, tuple], float] = defaultdict(float)
|
|
self.gauges: dict[tuple[str, tuple], float] = {}
|
|
self.hist_sum: dict[tuple[str, tuple], float] = defaultdict(float)
|
|
self.hist_count: dict[tuple[str, tuple], int] = defaultdict(int)
|
|
self._help: dict[str, str] = {}
|
|
|
|
def _key(self, name, labels):
|
|
return (name, tuple(sorted((labels or {}).items())))
|
|
|
|
def inc(self, name, amount=1.0, **labels):
|
|
with self._lock:
|
|
self.counters[self._key(name, labels)] += amount
|
|
|
|
def set_gauge(self, name, value, **labels):
|
|
with self._lock:
|
|
self.gauges[self._key(name, labels)] = value
|
|
|
|
def observe(self, name, value, **labels):
|
|
with self._lock:
|
|
k = self._key(name, labels)
|
|
self.hist_sum[k] += value
|
|
self.hist_count[k] += 1
|
|
|
|
def help(self, name, text):
|
|
self._help[name] = text
|
|
|
|
def _fmt_labels(self, labels: tuple) -> str:
|
|
if not labels:
|
|
return ""
|
|
inner = ",".join(f'{k}="{v}"' for k, v in labels)
|
|
return "{" + inner + "}"
|
|
|
|
def render(self) -> str:
|
|
lines: list[str] = []
|
|
with self._lock:
|
|
for (name, labels), v in sorted(self.counters.items()):
|
|
lines.append(f"{name}_total{self._fmt_labels(labels)} {v}")
|
|
for (name, labels), v in sorted(self.gauges.items()):
|
|
lines.append(f"{name}{self._fmt_labels(labels)} {v}")
|
|
for (name, labels), s in sorted(self.hist_sum.items()):
|
|
lines.append(f"{name}_sum{self._fmt_labels(labels)} {s}")
|
|
lines.append(
|
|
f"{name}_count{self._fmt_labels(labels)} {self.hist_count[(name, labels)]}"
|
|
)
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
registry = _Registry()
|
|
|
|
|
|
# 핵심 메트릭 헬퍼(운영 런북 §15.2)
|
|
def http_request(method: str, path: str, status: int, dur_s: float) -> None:
|
|
registry.inc("ari_http_requests", method=method, status=str(status))
|
|
registry.observe("ari_http_latency_seconds", dur_s, method=method)
|
|
|
|
|
|
def llm_call(outcome: str) -> None:
|
|
registry.inc("ari_llm_calls", outcome=outcome)
|
|
|
|
|
|
def worker_job(job: str, outcome: str) -> None:
|
|
registry.inc("ari_worker_jobs", job=job, outcome=outcome)
|
|
|
|
|
|
def approval(risk: str, status: str) -> None:
|
|
registry.inc("ari_approvals", risk=risk, status=status)
|
|
|
|
|
|
def add_saved_minutes(m: float) -> None:
|
|
registry.inc("ari_saved_minutes", m)
|
|
|
|
|
|
def render() -> str:
|
|
return registry.render()
|