Skip to content

English · Español

Lab 01 — Instrumentar el servidor de la Fase 33 con métricas RED + USE + LLM

Objetivo: las seis métricas de Prometheus core, cableadas en el servidor de la Fase 33, scrapeadas correctamente.

Tiempo estimado: 2-3 horas.

Prerrequisito: stack del Lab 00 arriba; servidor de la Fase 33 ejecutable localmente.


Lo que produces

  • src/observability/__init__.py
  • src/observability/metrics.py — las definiciones de métricas + un pequeño middleware que las registra.
  • Servidor de la Fase 33 modificado para registrar el middleware de métricas y exponer /metrics.
  • Un dashboard "borrador" de Grafana (cualquier layout — se refinará en el lab 03) con al menos un panel por métrica.

Las seis métricas

Implementa exactamente estas. Ni menos, ni más — métricas adicionales pertenecen al lab 02 (específicas de LLM, más allá de las seis core) o lab 03 (coste).

Nombre Tipo Labels Qué
request_total Counter endpoint, method, status RED: rate + errores
request_duration_seconds Histogram (buckets LLM) endpoint, method RED: duration
tokens_total Counter kind{prompt, completion}, model_name LLM: throughput de entrada
time_to_first_token_seconds Histogram (buckets TTFT) model_name LLM: UX de streaming
kv_cache_slots_used Gauge (sin labels) USE: saturación de KV
queue_depth Gauge (sin labels) USE: saturación del batcher

Buckets:

LLM_LATENCY_BUCKETS = (0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 20, 30, 60, 120, float("inf"))
TTFT_BUCKETS = (0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, float("inf"))

TODOs

Bloque A — src/observability/metrics.py

  • Importa prometheus_client (ya está en el grupo serve de pyproject.toml).
  • Define las seis métricas como globales a nivel de módulo, usando los labels y listas de buckets de arriba.
  • Exporta un metrics_app (un prometheus_client.make_asgi_app()) para que la app FastAPI de la Fase 33 pueda montarlo en /metrics.
  • Escribe un helper record_request(endpoint, method, status, duration_s) que haga las dos observaciones RED (counter + histogram).

Bloque B — cablear en el servidor de la Fase 33

  • En src/miniserve/app.py, añade el middleware de métricas:
@app.middleware("http")
async def observe(request, call_next):
    t0 = time.perf_counter()
    try:
        response = await call_next(request)
        status = response.status_code
    except Exception:
        status = 500
        raise
    finally:
        record_request(request.url.path, request.method, str(status), time.perf_counter() - t0)
    return response
  • Monta el endpoint de métricas:
app.mount("/metrics", metrics_app)
  • En el batcher (src/miniserve/batcher.py de la Fase 33), expón kv_cache_slots_used y queue_depth vía los .set_function(...) de los gauges (pull-on-scrape) o .set(...) tras cada cambio de estado (push-on-event). Pull-on-scrape es más simple — úsalo.

Bloque C — observar tokens

  • En el paso de tokenización del prompt, tras contar los tokens de entrada: tokens_total.labels(kind="prompt", model_name=model).inc(n_tokens).
  • En el bucle de decode, en cada token emitido: tokens_total.labels(kind="completion", model_name=model).inc(1).
  • En el primer token emitido de una petición, registra el TTFT: time_to_first_token_seconds.labels(model_name=model).observe(time.perf_counter() - request_start).

Bloque D — verificar en Prometheus

  • Arranca el servidor de la Fase 33.
  • curl http://localhost:8000/metrics — debería devolver ~30 líneas de exposición Prometheus.
  • En la UI de Prometheus: query request_total — debería ser > 0 tras unas pocas llamadas curl /v1/completions.
  • Query histogram_quantile(0.95, sum by(le) (rate(request_duration_seconds_bucket[1m]))) — debería devolver un número.

Bloque E — esqueleto del dashboard de Grafana

  • Crea un nuevo dashboard en Grafana titulado "lynx-cortex LLM serving".
  • Añade seis paneles, uno por métrica. Cualquier layout. El lab 03 pule.
  • Guarda el dashboard. Exporta JSON. Comitea a infra/grafana/dashboards/llm.json.

Restricciones

  • Un registry global. prometheus_client.REGISTRY (el default). No crees un CollectorRegistry custom — multi-registry es para librerías, no para código de aplicación.
  • Sin labels por petición en métricas. Sin user_id, sin prompt_hash. Regla de cardinalidad del archivo de teoría 01.
  • Los counters son monotónicos. Si te encuentras queriendo counter.set(0), quieres un gauge.
  • Sin Summaries. Sólo Histograms.

Condiciones de parada

Hecho cuando:

  1. src/observability/metrics.py existe y exporta las seis métricas + el helper record_request + el metrics_app.
  2. El endpoint /metrics en el servidor de la Fase 33 corriendo devuelve exposición Prometheus válida.
  3. La página de targets de Prometheus muestra miniserve como UP.
  4. PromQL request_total{status="200"} > 0 devuelve verdadero tras una sola prueba de carga (for i in $(seq 1 20); do curl ...; done).
  5. Dashboard de Grafana guardado + exportado + comiteado.

Trampas (lee antes de debuggear)

  • /metrics 404. Lo más probable es que el mount ocurrió después de que la app FastAPI empezó a servir, o usaste app.add_route en lugar de app.mount. Monta la sub-app ASGI antes de uvicorn.run.
  • Observaciones de Histogram no aparecen. Comprueba el nombre de la métrica en PromQL — Prometheus añade automáticamente los sufijos _bucket, _sum, _count. El nombre base (request_duration_seconds) sin sufijo no devuelve nada; request_duration_seconds_count devuelve el conteo de observaciones.
  • Advertencia de alta cardinalidad de Prometheus. Los logs dicen "scrape took N seconds" o "discarding sample with reset value". Comprueba la cardinalidad de labels. Causa más común: olvidar que request.url.path de FastAPI incluye parámetros de ruta — para /v1/users/42, obtienes una serie por user id. O bien quita los parámetros de ruta o usa el patrón de la ruta (/v1/users/{id}).
  • Gauge no se actualiza. Si usas .set_function(), la función se llama en cada scrape. Si lanza, la métrica se descarta silenciosamente. Envuelve en try/except y loggea.

Cuándo consultar solutions/

Después de que las cinco condiciones de parada pasen. Solución en solutions/01-instrument-server-ref.md.


Siguiente lab: lab/02-tracing-end-to-end.md.