honcho/src/prometheus.py

122 lines
3.6 KiB
Python

"""
Prometheus metrics for Honcho.
This module defines all Prometheus metrics for all Honcho processes and exposes them via the /metrics endpoint.
"""
import logging
from typing import cast
from prometheus_client import (
CONTENT_TYPE_LATEST,
REGISTRY,
Counter,
disable_created_metrics,
generate_latest,
)
from starlette.responses import Response as StarletteResponse
from src.config import settings
METRICS_ENABLED = settings.METRICS.ENABLED
disable_created_metrics() # Disables _created metrics on counters, histograms, and summaries
logger = logging.getLogger(__name__)
class NamespacedCounter(Counter):
"""Counter that automatically includes namespace label"""
def labels(self, **kwargs: str) -> "NamespacedCounter":
"""Override labels to automatically appends namespace label"""
# METRICS.NAMESPACE is guaranteed to be non-None by AppSettings.propagate_namespace validator
kwargs["namespace"] = cast(str, settings.METRICS.NAMESPACE)
return super().labels(**kwargs)
# Tracks all requests to the Honcho API.
#
# Incremented in: src/main.py middleware for every request
# Labels:
# - method: HTTP method (GET, POST, PUT, DELETE, etc.)
# - endpoint: FastAPI route template (e.g., "/v2/workspaces/{workspace_id}/sessions/{session_id}/messages")
# - status_code: HTTP response status code (200, 404, 500, etc.)
API_REQUESTS = NamespacedCounter(
"api_requests_total",
"Total API requests",
[
"namespace",
"method",
"endpoint",
"status_code",
],
)
# Tracks the total number of honcho messages created.
#
# Incremented in: src/routers/messages.py when messages are successfully created
# Labels:
# - workspace_name: The workspace where messages were created
MESSAGES_CREATED = NamespacedCounter(
"messages_created_total",
"Total messages created",
[
"namespace",
"workspace_name",
],
)
# Tracks the total number of dialectic calls made.
#
# Incremented in: src/routers/peers.py when successful dialectic calls are made
# Labels:
# - workspace_name: The workspace where the dialectic call was made
DIALECTIC_CALLS = NamespacedCounter(
"dialectic_calls_total",
"Total dialectic calls",
[
"namespace",
"workspace_name",
],
)
# Tracks the total number of queue items successfully processed by the deriver.
#
# Incremented in: src/deriver/queue_manager.py when queue items are processed
# Labels:
# - workspace_name: The workspace where items were processed
# - task_type: The type of task processed (e.g., "representation", "summary")
DERIVER_QUEUE_ITEMS_PROCESSED = NamespacedCounter(
"deriver_queue_items_processed_total",
"Total deriver queue items processed",
["namespace", "workspace_name", "task_type"],
)
# Tracks the total number of input and output tokens processed by the deriver.
#
# Incremented in: src/deriver/deriver.py after the critical analysis call is made
# Labels:
# - task_type: The type of task that processed the tokens (e.g., "representation")
DERIVER_TOKENS_PROCESSED = NamespacedCounter(
"tokens_processed_total",
"Total tokens processed",
[
"namespace",
"task_type",
],
)
async def metrics() -> StarletteResponse:
"""Prometheus metrics endpoint"""
if not settings.METRICS.ENABLED:
return StarletteResponse("Metrics are disabled", status_code=404)
try:
return StarletteResponse(
generate_latest(REGISTRY), media_type=CONTENT_TYPE_LATEST
)
except Exception as e:
logger.error(f"Failed to generate metrics: {e}", exc_info=True)
return StarletteResponse("Failed to generate metrics", status_code=500)