Observabilité — 02 — Métriques : compteurs, jauges, histogrammes, RED et USE

Les 4 types de métriques Prometheus, les méthodes RED et USE pour choisir quoi mesurer, et l'implémentation avec prom-client (TypeScript) et prometheus-client (Python).

02 — Métriques : compteurs, jauges, histogrammes, RED et USE

Ce que tu vas apprendre

  • Les 4 types de métriques Prometheus et quand utiliser chacun
  • La méthode RED (services orientés requêtes)
  • La méthode USE (ressources système)
  • Implémentation avec prom-client (TypeScript) et prometheus-client (Python)
  • PromQL : les requêtes de base pour alerter

Prérequis


Les 4 types de métriques Prometheus

Prometheus est le standard de facto pour les métriques dans les systèmes cloud-native. Il définit 4 types de métriques.

Counter

Un compteur qui ne peut qu'augmenter. Se remet à zéro au redémarrage.

Usage : nombre de requêtes, nombre d'erreurs, nombre de tâches traitées.

typescriptconst httpRequestsTotal = new Counter({
  name: "http_requests_total",
  help: "Nombre total de requêtes HTTP",
  labelNames: ["method", "route", "status"],
});

// À chaque requête
httpRequestsTotal.inc({ method: "POST", route: "/orders", status: "201" });

On ne lit jamais un counter brut — on calcule son taux : rate(http_requests_total[5m]) = requêtes par seconde sur les 5 dernières minutes.

Gauge

Une valeur qui peut monter et descendre.

Usage : utilisation mémoire, connexions actives, longueur d'une file.

typescriptconst activeConnections = new Gauge({
  name: "db_active_connections",
  help: "Connexions actives vers la base de données",
});

pool.on("connect", () => activeConnections.inc());
pool.on("remove", () => activeConnections.dec());

Histogram

Répartit les observations dans des buckets prédéfinis. Permet de calculer des percentiles (p50, p95, p99).

Usage : durée des requêtes, taille des réponses.

typescriptconst httpDuration = new Histogram({
  name: "http_request_duration_seconds",
  help: "Durée des requêtes HTTP en secondes",
  labelNames: ["method", "route"],
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5], // en secondes
});

const end = httpDuration.startTimer({ method: req.method, route: req.path });
// ... traitement ...
end(); // Enregistre la durée dans le bon bucket

PromQL pour le p99 : histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

Summary

Comme l'histogram, mais calcule les percentiles côté client sur une fenêtre glissante. Moins flexible que l'histogram (pas d'agrégation côté serveur possible).

Préférer l'histogram dans la grande majorité des cas.


La méthode RED

Proposée par Tom Wilkie (Grafana Labs) en 2015, RED définit les 3 métriques à instrumenter sur tout service qui reçoit des requêtes :

  • Rate — le nombre de requêtes par seconde
  • Errors — le taux d'erreurs
  • Duration — la distribution de la latence
typescript// Les 3 métriques RED pour un service HTTP
const requestsTotal = new Counter({
  name: "requests_total",
  help: "Nombre total de requêtes",
  labelNames: ["route", "method", "status_class"], // "2xx", "4xx", "5xx"
});

const errorsTotal = new Counter({
  name: "errors_total",
  help: "Nombre total d'erreurs",
  labelNames: ["route", "method", "error_type"],
});

const requestDuration = new Histogram({
  name: "request_duration_seconds",
  help: "Durée des requêtes",
  labelNames: ["route", "method"],
  buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
});

Alertes RED typiques :

promql# Rate — requêtes/s sur les 5 dernières minutes
rate(requests_total[5m])

# Error rate — % d'erreurs
rate(errors_total[5m]) / rate(requests_total[5m]) > 0.05
# Alerte si > 5% d'erreurs

# Duration — p99 > 1 seconde
histogram_quantile(0.99, rate(request_duration_seconds_bucket[5m])) > 1

La méthode USE

Proposée par Brendan Gregg dans Systems Performance (2020), USE s'applique aux ressources système (CPU, mémoire, réseau, disque) :

  • Utilization — % du temps où la ressource est occupée
  • Saturation — profondeur de la file d'attente quand la ressource est saturée
  • Errors — nombre d'erreurs de la ressource
CPU :
  Utilization  → cpu_usage_percent
  Saturation   → load_average_15m (processus en attente CPU)
  Errors       → machine_cpu_thermal_throttle_events_total

Mémoire :
  Utilization  → memory_used_bytes / memory_total_bytes
  Saturation   → memory_swap_used_bytes (swap = saturation mémoire)
  Errors       → memory_oom_kill_total

Disque :
  Utilization  → node_disk_io_time_seconds_total (% I/O occupé)
  Saturation   → node_disk_io_time_weighted_seconds_total
  Errors       → node_disk_read_errors_total

RED pour les services, USE pour les ressources — les deux ensemble couvrent l'essentiel.


Implémentation TypeScript avec prom-client

bashnpm install prom-client
typescript// metrics.ts
import { Registry, Counter, Histogram, Gauge, collectDefaultMetrics } from "prom-client";

export const registry = new Registry();

// Métriques système (CPU, mémoire, Node.js event loop)
collectDefaultMetrics({ register: registry });

// Métriques RED
export const httpRequestsTotal = new Counter({
  name: "http_requests_total",
  help: "Nombre total de requêtes HTTP",
  labelNames: ["method", "route", "status"],
  registers: [registry],
});

export const httpRequestDuration = new Histogram({
  name: "http_request_duration_seconds",
  help: "Durée des requêtes HTTP en secondes",
  labelNames: ["method", "route"],
  buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
  registers: [registry],
});

// Métriques métier
export const ordersTotal = new Counter({
  name: "orders_total",
  help: "Nombre total de commandes",
  labelNames: ["status"], // "created", "confirmed", "cancelled"
  registers: [registry],
});

Middleware Express pour instrumenter automatiquement :

typescript// middleware/metrics.middleware.ts
import { httpRequestsTotal, httpRequestDuration } from "../metrics";

export function metricsMiddleware(req: Request, res: Response, next: NextFunction): void {
  const end = httpRequestDuration.startTimer({
    method: req.method,
    route: req.route?.path ?? req.path,
  });

  res.on("finish", () => {
    const route = req.route?.path ?? "unknown";
    httpRequestsTotal.inc({
      method: req.method,
      route,
      status: String(res.statusCode),
    });
    end({ route });
  });

  next();
}

// Endpoint Prometheus scrape
app.get("/metrics", async (req, res) => {
  res.set("Content-Type", registry.contentType);
  res.send(await registry.metrics());
});

Implémentation Python avec prometheus-client

bashpip install prometheus-client
python# metrics.py
from prometheus_client import Counter, Histogram, Gauge, CollectorRegistry, generate_latest, CONTENT_TYPE_LATEST
import time

registry = CollectorRegistry()

http_requests_total = Counter(
    "http_requests_total",
    "Nombre total de requêtes HTTP",
    ["method", "route", "status"],
    registry=registry,
)

http_request_duration_seconds = Histogram(
    "http_request_duration_seconds",
    "Durée des requêtes HTTP",
    ["method", "route"],
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0],
    registry=registry,
)

orders_total = Counter(
    "orders_total",
    "Nombre total de commandes",
    ["status"],
    registry=registry,
)

Middleware FastAPI :

python# middleware/metrics.py
import time
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from .metrics import http_requests_total, http_request_duration_seconds

class MetricsMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        start = time.perf_counter()
        response = await call_next(request)
        duration = time.perf_counter() - start

        route = request.scope.get("path", "unknown")
        http_requests_total.labels(
            method=request.method,
            route=route,
            status=str(response.status_code),
        ).inc()

        http_request_duration_seconds.labels(
            method=request.method,
            route=route,
        ).observe(duration)

        return response

# Endpoint scrape
from fastapi import APIRouter
from fastapi.responses import Response
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST, CollectorRegistry

router = APIRouter()

@router.get("/metrics")
def metrics():
    return Response(generate_latest(registry), media_type=CONTENT_TYPE_LATEST)

Nommage des métriques

Prometheus a des conventions strictes à respecter :

# Format : {namespace}_{subsystem}_{name}_{unit}
http_requests_total           ✓ — counter, unité implicite (total)
http_request_duration_seconds ✓ — histogram, unité explicite en secondes
process_memory_bytes          ✓ — gauge, unité en bytes

httpRequests                  ✗ — pas de camelCase
http-requests-total           ✗ — pas de tirets
requests                      ✗ — trop vague, pas de namespace

Règles :

  • Snake_case uniquement
  • Toujours inclure l'unité dans le nom pour les histogrammes et gauges
  • Terminer les counters par _total
  • Préfixer par le namespace du service

Résumé

Type Monte/descend Usage Opération PromQL
Counter Monte seulement Requêtes, erreurs, tâches rate()
Gauge Les deux Mémoire, connexions, file Valeur brute
Histogram N/A Latence, taille histogram_quantile()
Méthode S'applique à Les 3 métriques
RED Services (API, workers) Rate, Errors, Duration
USE Ressources (CPU, RAM, disque) Utilization, Saturation, Errors

Étape suivante : 03 — Traces distribuées — corréler les métriques et les logs avec le chemin exact d'une requête entre services.


Sources

  • Wilkie, T. (2015). The RED Method: Key Metrics for Microservices Architecture. Grafana blog. (Rate, Errors, Duration)
  • Gregg, B. (2020). Systems Performance: Enterprise and the Cloud (2nd ed.), Chapitre 2 "Methodologies". Addison-Wesley. (Modèle USE)
  • Prometheus Authors. Metric Types. prometheus.io/docs/concepts/metric_types/
  • Prometheus Authors. Naming best practices. prometheus.io/docs/practices/naming/
  • Beyer, B., et al. (2016). Site Reliability Engineering, Chapitre 6. O'Reilly. sre.google/sre-book/monitoring-distributed-systems/

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.