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__.pysrc/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 gruposervedepyproject.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(unprometheus_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:
- En el batcher (
src/miniserve/batcher.pyde la Fase 33), expónkv_cache_slots_usedyqueue_depthví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 llamadascurl /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 unCollectorRegistrycustom — multi-registry es para librerías, no para código de aplicación. - Sin labels por petición en métricas. Sin
user_id, sinprompt_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:
src/observability/metrics.pyexiste y exporta las seis métricas + el helperrecord_request+ elmetrics_app.- El endpoint
/metricsen el servidor de la Fase 33 corriendo devuelve exposición Prometheus válida. - La página de targets de Prometheus muestra
miniservecomo UP. - PromQL
request_total{status="200"} > 0devuelve verdadero tras una sola prueba de carga (for i in $(seq 1 20); do curl ...; done). - Dashboard de Grafana guardado + exportado + comiteado.
Trampas (lee antes de debuggear)¶
/metrics404. Lo más probable es que el mount ocurrió después de que la app FastAPI empezó a servir, o usasteapp.add_routeen lugar deapp.mount. Monta la sub-app ASGI antes deuvicorn.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_countdevuelve 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.pathde 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 entry/excepty 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.