- gateway: separate config, models, state, audit, guardrail, embeddings, store, metrics, cleanup and routes; keep main.py as FastAPI bootstrap - extension: split client/config, six tools, config command and rules; preserve jiti entrypoint and registrations - Dockerfile copies the complete gateway module set - tests: update monkeypatch boundaries for modular config/state
71 lines
2.9 KiB
Python
71 lines
2.9 KiB
Python
"""Metriche in-memory e push Prometheus/VictoriaMetrics."""
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Any
|
|
|
|
from config import VM_PUSH_INTERVAL, VM_PUSH_URL, _metrics, log
|
|
|
|
|
|
def record_request(endpoint: str, duration: float, status_code: int) -> None:
|
|
_metrics["requests"][endpoint] += 1
|
|
_metrics["duration_sum"][endpoint] += duration
|
|
_metrics["duration_count"][endpoint] += 1
|
|
if status_code >= 400:
|
|
_metrics["errors"][(endpoint, status_code)] += 1
|
|
|
|
|
|
def record_search(hits: int) -> None:
|
|
_metrics["search_queries"] += 1
|
|
_metrics["search_hits"] += hits
|
|
|
|
|
|
def snapshot(qdrant: Any, collection: str) -> dict:
|
|
try:
|
|
points = qdrant.get_collection(collection).points_count
|
|
except Exception: # noqa: BLE001
|
|
points = None
|
|
return {
|
|
"requests": dict(_metrics["requests"]),
|
|
"avg_duration_ms": {
|
|
endpoint: round(_metrics["duration_sum"][endpoint] / _metrics["duration_count"][endpoint] * 1000, 2)
|
|
for endpoint in _metrics["duration_count"]
|
|
},
|
|
"errors": {f"{endpoint}:{status}": count for (endpoint, status), count in _metrics["errors"].items()},
|
|
"search_queries": _metrics["search_queries"],
|
|
"search_hits": _metrics["search_hits"],
|
|
"points": points,
|
|
}
|
|
|
|
|
|
def prometheus_lines(qdrant: Any, collection: str) -> list[str]:
|
|
lines: list[str] = []
|
|
for endpoint, count in _metrics["requests"].items():
|
|
lines.append(f'qmem_requests_total{{endpoint="{endpoint}"}} {count}')
|
|
for endpoint, total in _metrics["duration_sum"].items():
|
|
count = _metrics["duration_count"][endpoint]
|
|
lines.append(f'qmem_request_duration_seconds_sum{{endpoint="{endpoint}"}} {total:.6f}')
|
|
lines.append(f'qmem_request_duration_seconds_count{{endpoint="{endpoint}"}} {count}')
|
|
for (endpoint, status), count in _metrics["errors"].items():
|
|
lines.append(f'qmem_errors_total{{endpoint="{endpoint}",status="{status}"}} {count}')
|
|
lines.append(f"qmem_search_queries_total {_metrics['search_queries']}")
|
|
lines.append(f"qmem_search_hits_total {_metrics['search_hits']}")
|
|
try:
|
|
lines.append(f"qmem_points {qdrant.get_collection(collection).points_count}")
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
return lines
|
|
|
|
|
|
async def push_loop(qdrant: Any, collection: str, get_http) -> None:
|
|
while True:
|
|
try:
|
|
now_ms = int(time.time() * 1000)
|
|
body = "\n".join(f"{line} {now_ms}" for line in prometheus_lines(qdrant, collection)) + "\n"
|
|
response = await get_http().post(VM_PUSH_URL, content=body, headers={"Content-Type": "text/plain"})
|
|
if response.status_code >= 300:
|
|
log.warning("metrics push: HTTP %s", response.status_code)
|
|
except Exception as exc: # noqa: BLE001
|
|
log.warning("metrics push error: %s", exc)
|
|
await __import__("asyncio").sleep(VM_PUSH_INTERVAL)
|