Läuft Ihre CAPTCHA-Pipeline sauber – oder verbrennt sie stillschweigend Guthaben? In Grafana sehen Sie es in Sekunden. Dieser Leitfaden liefert import-fertige PromQL-Abfragen für Lösungsrate, Latenzperzentile, Guthaben und Fehler, mit Prometheus als Datenquelle: kopieren, einfügen, Schwellenwerte anpassen.
So ist das Grafana-Dashboard aufgebaut
Vier Zeilen – Übersicht bis Worker:
┌───────────────────────────────────────────────┐
│ 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] │
└───────────────────────────────────────────────┘
Die Reihenfolge folgt der Dringlichkeit im Betrieb: Zeile 1 beantwortet in vier Kacheln die Frage, ob die Pipeline überhaupt läuft. Zeile 2 zeigt, wie schnell gelöst wird, Zeile 3 legt die Fehlerursachen offen, und Zeile 4 verrät, ob Ihre Worker die Last noch tragen. Für ein Wand-Dashboard im Team genügt oft Zeile 1; die übrigen Zeilen blenden Sie bei Bedarf ein.
Metriken aus dem CAPTCHA-Löser exportieren
Zuerst stellt Ihr Löser die Kennzahlen als Prometheus-Metriken bereit: ein Zähler, ein Histogramm für die Lösungszeit, Gauges für Guthaben und Queue. Vier Metrikfamilien reichen für das gesamte Dashboard: der Zähler captcha_solves_total mit den Labels captcha_type und status, das Histogramm captcha_solve_duration_seconds sowie die Gauges für Guthaben, Warteschlange und aktive Worker.
Python: Prometheus-Client
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)
start_http_server(9090) öffnet den /metrics-Endpunkt, den Prometheus im Scrape-Intervall abfragt. Jeder Aufruf von solve_with_metrics erhöht den Zähler und trägt die gemessene Lösungszeit in das Histogramm ein – die Datenbasis für sämtliche Panels weiter unten. Rufen Sie update_balance im Minutentakt auf, damit der Guthaben-Gauge nicht veraltet.
JavaScript: prom-client
prom-client liefert dieselben Metriktypen unter /metrics. Registrieren Sie alle Metriken in einer gemeinsamen Registry und geben Sie sie über eine Express-Route aus – so bleibt ein Node.js-Worker metrisch deckungsgleich mit dem Python-Setup.
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);
Prometheus als Datenquelle einbinden
Bevor ein Panel Daten zeigt, muss Prometheus Ihren Exporter kennen. Tragen Sie Host und Port 9090 als Scrape-Ziel ein und wählen Sie ein Intervall von 15 Sekunden – bei geringem Volumen genügen 30 Sekunden, unter 10 Sekunden lohnt sich nur bei echtem Bedarf an Echtzeit-Sichtbarkeit. In Grafana fügen Sie Prometheus anschließend als Datenquelle hinzu; jede folgende PromQL-Abfrage bezieht sich auf genau diese Quelle.
PromQL-Abfragen für jedes Grafana-Panel
Jede Abfrage entspricht einem Panel oben. Kopieren Sie den jeweiligen Ausdruck in das Query-Feld und wählen Sie den passenden Visualisierungstyp – Stat, Gauge, Zeitreihe oder Kreisdiagramm.
Zeile 1: Übersicht
Diese vier Kennzahlen gehören in jedes Team-Dashboard. Die Lösungsrate teilt erfolgreiche durch alle Versuche im gleitenden 5-Minuten-Fenster; die Multiplikation mit 100 ergibt die Prozentanzeige.
Lösungsrate (Stat-Panel):
sum(rate(captcha_solves_total{status="success"}[5m]))
/
sum(rate(captcha_solves_total[5m]))
* 100
Guthabenstand (Gauge-Panel):
captcha_balance_dollars
Warteschlangentiefe (Stat-Panel):
captcha_queue_depth
Aufgaben pro Minute (Stat-Panel):
sum(rate(captcha_solves_total[5m])) * 60
Zeile 2: Leistung
Perzentile sagen mehr als ein Mittelwert: Der P95 zeigt, was langsame Läufe tatsächlich kosten, während der Median gutmütige Ausreißer verschluckt.
Latenzperzentile (Zeitreihe):
# 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]))
Lösungsrate im Zeitverlauf (Zeitreihe):
sum(rate(captcha_solves_total{status="success"}[5m])) by (captcha_type) * 60
Zeile 3: Fehler
Die Fehlerrate ist das Spiegelbild der Lösungsrate. Die Aufschlüsselung nach status trennt Timeouts von echten API-Fehlern und zeigt, wo Sie zuerst ansetzen.
Fehlerrate (Zeitreihe):
sum(rate(captcha_solves_total{status!="success"}[5m]))
/
sum(rate(captcha_solves_total[5m]))
* 100
Fehleraufschlüsselung (Kreisdiagramm):
sum by (status) (increase(captcha_solves_total{status!="success"}[1h]))
Zeile 4: Worker
Sinkt die Zahl aktiver Worker, während die Warteschlange wächst, liegt der Engpass nicht beim Lösen, sondern bei der Kapazität.
Aktive Worker (Zeitreihe):
captcha_workers_active
Alarmregeln in Grafana einrichten
Drei Alarme decken die häufigsten Fälle ab: knappes Guthaben, steigende Fehlerrate, wachsende Latenz.
# 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
Der Guthaben-Alarm greift bei unter 10 $ und verhindert, dass eine leere Kasse die Pipeline mitten im Betrieb stoppt. HighErrorRate schlägt bei über 10 % Fehlern im 5-Minuten-Fenster an, HighLatency bei einem P95 über 120 Sekunden. Die for-Klausel unterdrückt Fehlalarme durch kurze Spitzen: Erst wenn die Bedingung dauerhaft anliegt, feuert der Alarm.
Fehlerbehebung: leere Panels und falsche Werte
Drei Symptome tauchen beim Einrichten am häufigsten auf – und lassen sich meist in Minuten beheben:
| Problem | Ursache | Lösung |
|---|---|---|
| Panel bleibt leer („No data") | Datenquelle falsch konfiguriert oder /metrics wird nicht gescrapt |
Target-Status prüfen, Metriknamen gegen den Exporter abgleichen |
| Fehlerrate springt nach Rollout | Neue Version ändert Session-, Proxy- oder Retry-Verhalten | Erfolgreiche und fehlgeschlagene Läufe zwischen den Versionen vergleichen |
| Latenzperzentile unplausibel | Falsches rate()-Fenster oder zu grobe Buckets |
[5m]-Fenster nutzen, bei Bedarf feinere Buckets ergänzen |
Häufige Fragen
Welche Panels sollten Sie zuerst einrichten?
Die vier Panels aus Zeile 1: Lösungsrate, Guthabenstand, Warteschlangentiefe und Aufgaben pro Minute. Sie zeigen sofort, ob die Pipeline läuft.
Woher stammt der Wert für captcha_balance_dollars?
Aus der update_balance-Funktion: Sie fragt res.php mit action=getbalance ab und schreibt den Kontostand in den Gauge – Basis für den Guthaben-Alarm bei unter 10 $.
Wie wählen Sie Schwellenwerte für die Latenz-Alarme?
Am P95, nicht am Median – ein langsamer Lauf verzerrt den Durchschnitt. Der Beispiel-Alarm feuert bei P95 über 120 Sekunden.
Lassen sich mehrere CAPTCHA-Typen getrennt darstellen?
Ja. Alle Metriken tragen das Label captcha_type. Gruppieren Sie mit by (captcha_type), um reCAPTCHA v2, Turnstile und GeeTest v3 zu trennen.
Wo hosten Sie Prometheus und Grafana am besten?
Ein VPS bei Hetzner, netcup oder IONOS genügt. Beachten Sie: erfasste IP-Adressen gelten nach DSGVO als personenbezogene Daten.
Verwandte Leitfäden
- CaptchaAI-Lösungsraten mit Prometheus und Grafana überwachen
- Fehlerbudgets für die CAPTCHA-Zuverlässigkeit verfolgen
- CAPTCHA-Logs mit dem ELK-Stack auswerten