DevOps & Scaling

Modèles de tableau de bord Grafana pour les métriques CaptchaAI

Pour piloter un pipeline CAPTCHA en production, quatre chiffres suffisent : le taux de réussite, la latence P95, le solde du compte et la profondeur de la file d'attente. Ces modèles de tableau de bord Grafana les affichent d'emblée, à partir de métriques Prometheus, et s'importent tels quels. Le reste de ce guide vous donne le client d'instrumentation, les requêtes PromQL panneau par panneau et les règles d'alerte à copier.

L'intérêt d'un tableau de bord n'est pas décoratif : quand le taux de réussite décroche ou que la latence P99 grimpe, vous voulez le voir avant vos utilisateurs, pas après. Un francophone qui héberge ses workers sur Scaleway ou OVHcloud (région eu-west-3 Paris pour limiter la latence réseau) branche exactement le même Prometheus sur ces panneaux.

Structure du tableau de bord

Quatre lignes, du général au détail : la vue d'ensemble en haut pour un coup d'œil, puis la performance, les erreurs et enfin les workers. C'est l'ordre de lecture d'un opérateur pendant un incident.

Ligne Panneaux Ce qu'ils révèlent
Vue d'ensemble Taux de réussite, solde, file, tâches/min L'état de santé instantané
Performance Latence P50/P95/P99, débit Les ralentissements qui montent
Erreurs Taux d'erreur, répartition par type La nature d'une panne
Workers Workers actifs, tâches par worker La capacité réellement utilisée
┌───────────────────────────────────────────────┐
│ Row 1: Overview                               │
│ [Solve Rate %] [Balance $] [Queue Depth] [TPM]│
├───────────────────────────────────────────────┤
│ Row 2: Performance                            │
│ [Latency P50/P95/P99]  [Solve Rate Over Time] │
├───────────────────────────────────────────────┤
│ Row 3: Errors                                 │
│ [Error Rate %]  [Error Breakdown by Type]      │
├───────────────────────────────────────────────┤
│ Row 4: Workers                                │
│ [Active Workers]  [Tasks Per Worker]           │
└───────────────────────────────────────────────┘

Exposer les métriques vers Prometheus

Avant de tracer quoi que ce soit, votre solveur CAPTCHA doit publier ses métriques sur un endpoint que Prometheus vient collecter. Instrumentez le code qui appelle l'API CaptchaAI : un compteur pour les tentatives, un histogramme pour la latence, et des jauges pour le solde, la file et les workers.

Python : instrumenter le solveur

import os
import time
import requests
from prometheus_client import (
    Counter, Histogram, Gauge, start_http_server
)

API_KEY = os.environ["CAPTCHAAI_API_KEY"]

# Define metrics
captcha_solves = Counter(
    "captcha_solves_total",
    "Total CAPTCHA solve attempts",
    ["captcha_type", "status"]
)
captcha_latency = Histogram(
    "captcha_solve_duration_seconds",
    "CAPTCHA solve latency",
    ["captcha_type"],
    buckets=[5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 300]
)
captcha_balance = Gauge(
    "captcha_balance_dollars",
    "CaptchaAI account balance"
)
captcha_queue_depth = Gauge(
    "captcha_queue_depth",
    "Pending tasks in queue"
)
captcha_workers_active = Gauge(
    "captcha_workers_active",
    "Number of active workers"
)

session = requests.Session()


def solve_with_metrics(sitekey, pageurl, captcha_type="recaptcha_v2"):
    start = time.time()

    resp = session.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1
    })
    data = resp.json()
    if data.get("status") != 1:
        captcha_solves.labels(captcha_type, "error").inc()
        return {"error": data.get("request")}

    captcha_id = data["request"]
    for _ in range(60):
        time.sleep(5)
        result = session.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": captcha_id, "json": 1
        }).json()

        if result.get("status") == 1:
            elapsed = time.time() - start
            captcha_solves.labels(captcha_type, "success").inc()
            captcha_latency.labels(captcha_type).observe(elapsed)
            return {"solution": result["request"]}

        if result.get("request") != "CAPCHA_NOT_READY":
            captcha_solves.labels(captcha_type, "error").inc()
            return {"error": result.get("request")}

    captcha_solves.labels(captcha_type, "timeout").inc()
    return {"error": "TIMEOUT"}


def update_balance():
    resp = session.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "getbalance", "json": 1
    })
    if resp.json().get("status") == 1:
        captcha_balance.set(float(resp.json()["request"]))


# Start metrics server on port 9090
start_http_server(9090)

L'histogramme utilise des buckets larges (de 5 à 300 secondes) : ils couvrent aussi bien un reCAPTCHA v2 rapide qu'un défi CAPTCHA plus lent, sans écraser les percentiles. Le label captcha_type est ce qui vous permettra ensuite de ventiler la latence par type sans multiplier les métriques.

Node.js : le même export en JavaScript

const promClient = require("prom-client");
const axios = require("axios");

const API_KEY = process.env.CAPTCHAAI_API_KEY;
const register = new promClient.Registry();

const solvesTotal = new promClient.Counter({
  name: "captcha_solves_total",
  help: "Total CAPTCHA solve attempts",
  labelNames: ["captcha_type", "status"],
  registers: [register],
});

const solveLatency = new promClient.Histogram({
  name: "captcha_solve_duration_seconds",
  help: "CAPTCHA solve latency",
  labelNames: ["captcha_type"],
  buckets: [5, 10, 15, 20, 30, 45, 60, 90, 120, 180, 300],
  registers: [register],
});

const balance = new promClient.Gauge({
  name: "captcha_balance_dollars",
  help: "CaptchaAI account balance",
  registers: [register],
});

const queueDepth = new promClient.Gauge({
  name: "captcha_queue_depth",
  help: "Pending tasks in queue",
  registers: [register],
});

async function solveWithMetrics(sitekey, pageurl, captchaType = "recaptcha_v2") {
  const end = solveLatency.startTimer({ captcha_type: captchaType });

  try {
    const resp = await axios.post("https://ocr.captchaai.com/in.php", null, {
      params: {
        key: API_KEY, method: "userrecaptcha",
        googlekey: sitekey, pageurl, json: 1,
      },
    });

    if (resp.data.status !== 1) {
      solvesTotal.inc({ captcha_type: captchaType, status: "error" });
      return { error: resp.data.request };
    }

    const captchaId = resp.data.request;
    for (let i = 0; i < 60; i++) {
      await new Promise((r) => setTimeout(r, 5000));
      const poll = await axios.get("https://ocr.captchaai.com/res.php", {
        params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
      });
      if (poll.data.status === 1) {
        end();
        solvesTotal.inc({ captcha_type: captchaType, status: "success" });
        return { solution: poll.data.request };
      }
      if (poll.data.request !== "CAPCHA_NOT_READY") {
        solvesTotal.inc({ captcha_type: captchaType, status: "error" });
        return { error: poll.data.request };
      }
    }
    solvesTotal.inc({ captcha_type: captchaType, status: "timeout" });
    return { error: "TIMEOUT" };
  } catch (err) {
    solvesTotal.inc({ captcha_type: captchaType, status: "error" });
    throw err;
  }
}

// Expose metrics endpoint
const express = require("express");
const app = express();
app.get("/metrics", async (req, res) => {
  res.set("Content-Type", register.contentType);
  res.end(await register.metrics());
});
app.listen(9090);

Les deux implémentations exposent les mêmes noms de métriques (captcha_solves_total, captcha_solve_duration_seconds…). C'est volontaire : les requêtes PromQL et les panneaux ci-dessous fonctionnent à l'identique, que votre worker soit en Python ou en Node.js.

Les requêtes PromQL des panneaux

Chaque panneau correspond à une requête. Copiez-les telles quelles dans Grafana avec Prometheus comme source de données.

Ligne 1 : la vue d'ensemble

  • Taux de réussite (panneau Stat) :
sum(rate(captcha_solves_total{status="success"}[5m]))
/
sum(rate(captcha_solves_total[5m]))

* 100
  • Solde du compte (panneau de jauge) :
captcha_balance_dollars
  • Profondeur de la file d'attente (panneau Stat) :
captcha_queue_depth
  • Tâches par minute (panneau Stat) :
sum(rate(captcha_solves_total[5m])) * 60

La profondeur de file se lit toujours en regard de votre capacité de threads. Sur le plan ADVANCE ($90/mois, 50 threads), une file qui reste durablement au-dessus de 50 signale que vos requêtes arrivent plus vite qu'elles ne se résolvent : il faut soit lisser le débit, soit monter de palier.

Ligne 2 : latence et débit

  • Percentiles de latence (série chronologique) :
# p50
histogram_quantile(0.50, rate(captcha_solve_duration_seconds_bucket[5m]))

# p95
histogram_quantile(0.95, rate(captcha_solve_duration_seconds_bucket[5m]))

# p99
histogram_quantile(0.99, rate(captcha_solve_duration_seconds_bucket[5m]))
  • Taux de réussite dans le temps (série chronologique) :
sum(rate(captcha_solves_total{status="success"}[5m])) by (captcha_type) * 60

Surveillez la P95 plutôt que la médiane : c'est elle qui reflète l'expérience de vos requêtes les plus lentes. Le by (captcha_type) sépare les courbes par type, ce qui isole immédiatement le défi CAPTCHA responsable d'un pic.

Ligne 3 : les erreurs

  • Taux d'erreur (série chronologique) :
sum(rate(captcha_solves_total{status!="success"}[5m]))
/
sum(rate(captcha_solves_total[5m]))

* 100
  • Répartition des erreurs (graphique circulaire) :
sum by (status) (increase(captcha_solves_total{status!="success"}[1h]))

La répartition sépare les vraies erreurs des timeouts. Un pic de timeout pointe vers un souci de capacité ou de réseau ; un pic d'error pointe plutôt vers un sitekey ou un paramètre invalide.

Ligne 4 : les workers

  • Workers actifs (série chronologique) :
captcha_workers_active

Les alertes Grafana à configurer

Trois alertes couvrent l'essentiel : solde bas, taux d'erreur élevé et latence anormale. Les seuils ci-dessous sont des points de départ, à ajuster selon votre volume et votre tolérance.

# Grafana alert rules
groups:

  - name: captcha-alerts
    rules:

      - alert: LowBalance
        expr: captcha_balance_dollars < 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CaptchaAI balance low: {{ $value }}"

      - alert: HighErrorRate
        expr: |
          sum(rate(captcha_solves_total{status!="success"}[5m]))
          / sum(rate(captcha_solves_total[5m]))
          > 0.1
        for: 5m
        labels:
          severity: critical

      - alert: HighLatency
        expr: |
          histogram_quantile(0.95,
            rate(captcha_solve_duration_seconds_bucket[5m])
          ) > 120
        for: 10m
        labels:
          severity: warning

Note RGPD : un tableau de bord d'exploitation n'a pas à contenir de données personnelles. Ne mettez jamais d'e-mail, d'identifiant utilisateur ni d'URL cible complète dans un label Prometheus — les labels sont en clair et à forte cardinalité. Restez sur des dimensions techniques comme captcha_type et status.

Dépannage

Problème Cause Correctif
« No data » sur les panneaux Prometheus ne collecte pas l'endpoint des métriques Vérifiez les cibles dans prometheus.yml et que /metrics renvoie bien des données
Percentiles de latence incohérents Fenêtre rate() inadaptée ou buckets manquants Gardez la fenêtre [5m] et ajoutez des buckets plus fins
Les variables du tableau de bord restent vides Requête de variable de modèle incorrecte Utilisez label_values(captcha_solves_total, captcha_type)
Les alertes ne se déclenchent pas Intervalle d'évaluation trop long Réglez l'intervalle d'évaluation sur 1 minute

FAQ

Quelles métriques faut-il surveiller en priorité ?

Le taux de réussite et la latence P95 en premier : ce sont eux qui traduisent la santé réelle du pipeline. Ajoutez le solde du compte pour éviter les interruptions de facturation et la profondeur de file pour repérer un goulot d'étranglement avant qu'il ne dégrade les temps de réponse.

Comment ventiler la latence par type de CAPTCHA ?

Le label captcha_type est présent sur l'histogramme, il suffit donc d'ajouter by (captcha_type) à vos requêtes histogram_quantile. Vous obtenez une courbe par type et repérez tout de suite si un reCAPTCHA v3 ou un défi Cloudflare Turnstile tire la P99 vers le haut.

Grafana Cloud ou installation auto-hébergée ?

Les deux fonctionnent avec ces modèles. Grafana Cloud prend en charge le remote write de Prometheus : poussez-y vos métriques et réutilisez exactement les mêmes requêtes PromQL. L'auto-hébergement garde la donnée chez vous, ce qui simplifie souvent la conformité RGPD.

Comment éviter les fausses alertes de solde bas ?

Alignez le seuil sur votre consommation. Si vous rechargez régulièrement, un seuil à 10 $ prévient à temps ; sur un plus gros volume, remontez-le. Ajoutez for: 5m pour ne déclencher que sur un solde durablement bas, pas sur une lecture transitoire.

Articles connexes

Prochaines étapes

Donnez de la visibilité à votre pipeline CAPTCHA : récupérez votre clé API CaptchaAI et importez ces tableaux de bord Grafana en quelques minutes.

Guides associés :

Les commentaires sont désactivés pour cet article.