github-actions[bot]
Deploy 93abd89d7b7f from cbratkovics/document-intelligence-ai
30e6218
Raw History Blame Contribute Delete
3.09 kB
"""Prometheus metrics with bounded label cardinality.
Labels use route templates (``/api/v1/documents/{doc_id}``), never document
IDs or query text. Metrics are optional: the app works without the
``prometheus_client`` package.
"""
from __future__ import annotations
import logging
import time
from contextlib import contextmanager
from typing import Any, Iterator, Optional
logger = logging.getLogger(__name__)
try:
from prometheus_client import Counter, Gauge, Histogram, Info
AVAILABLE = True
except ImportError: # pragma: no cover
AVAILABLE = False
request_count: Any = None
request_latency: Any = None
active_requests: Any = None
stage_latency: Any = None
stage_outcomes: Any = None
documents_ready: Any = None
chunks_active: Any = None
system_info: Any = None
if AVAILABLE:
request_count = Counter(
"docintel_requests_total", "HTTP requests", ["method", "route", "status"]
)
request_latency = Histogram(
"docintel_request_duration_seconds", "HTTP request latency", ["method", "route"]
)
active_requests = Gauge("docintel_active_requests", "In-flight HTTP requests")
stage_latency = Histogram(
"docintel_stage_duration_seconds",
"Pipeline stage latency",
["stage"],
buckets=(0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
stage_outcomes = Counter(
"docintel_stage_outcomes_total", "Pipeline stage outcomes", ["stage", "outcome"]
)
documents_ready = Gauge("docintel_documents_ready", "Documents with status READY")
chunks_active = Gauge("docintel_chunks_active", "Chunks of READY documents")
system_info = Info("docintel_build", "Build information")
def record_request(method: str, route: str, status_code: int, duration: float) -> None:
if not AVAILABLE:
return
status = (
"2xx"
if status_code < 300
else "3xx"
if status_code < 400
else "4xx"
if status_code < 500
else "5xx"
)
request_count.labels(method=method, route=route, status=status).inc()
request_latency.labels(method=method, route=route).observe(duration)
@contextmanager
def track_stage(stage: str) -> Iterator[None]:
start = time.perf_counter()
outcome = "ok"
try:
yield
except Exception:
outcome = "error"
raise
finally:
if AVAILABLE:
stage_latency.labels(stage=stage).observe(time.perf_counter() - start)
stage_outcomes.labels(stage=stage, outcome=outcome).inc()
def update_index_gauges(documents: int, chunks: int) -> None:
if AVAILABLE:
documents_ready.set(documents)
chunks_active.set(chunks)
def initialize_metrics(
app_version: str, environment: str, embedding: Optional[str], generation: str
) -> None:
if AVAILABLE:
system_info.info(
{
"version": app_version,
"environment": environment,
"embedding_provider": embedding or "none",
"generation_provider": generation,
}
)