Баланс внезапно ушёл в ноль, а очередь задач за ночь выросла в разы — и вы узнали об этом только утром от коллег? Дашборд Grafana с метриками CaptchaAI закрывает этот разрыв: доля успешных решений, процентили задержки, динамика баланса и разбивка ошибок видны на одном экране, а не разбросаны по логам и res.php-запросам вручную. Ниже — готовые к импорту панели и запросы PromQL с Prometheus в качестве источника данных: скопируйте JSON-схему макета, подставьте свой job-label и разверните за один вечер.
Из чего собран дашборд
┌───────────────────────────────────────────────┐
│ 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] │
└───────────────────────────────────────────────┘
Логика раскладки по рядам такая:
- Overview — моментальный снимок здоровья пайплайна: доля успешных решений, баланс, глубина очереди, задач в минуту.
- Performance — куда уходит время: процентили задержки и динамика доли успешных решений.
- Errors — что именно ломается: суммарный коэффициент ошибок и разбивка по типу.
- Workers — хватает ли мощности: число активных воркеров и нагрузка на каждый из них.
Экспорт метрик в Prometheus
Прежде чем строить панели, дашборду нужно откуда брать данные. Решатель CAPTCHA должен сам отдавать метрики на /metrics — Prometheus подключается к нему как к обычному scrape-таргету. Ниже — минимальный экспортёр на Python и на Node.js: оба считают количество решений по статусам, время решения гистограммой и текущий баланс аккаунта отдельным Gauge.
Python — клиент Prometheus
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)
Node.js — тот же экспортёр на JavaScript
Логика идентична: три метрики (счётчик решений, гистограмма задержки, баланс) плюс отдельный /metrics-эндпоинт на Express для scrape-запросов Prometheus.
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);
PromQL-запросы для панелей дашборда
Ниже — запросы для каждой панели по рядам макета выше. Расставляйте их в том же порядке: обзорные показатели наверху, детальная диагностика ниже.
Ряд 1 — что видно сразу при открытии дашборда
Четыре Stat-панели закрывают вопрос «всё ли в порядке прямо сейчас»: доля успешных решений, баланс аккаунта, размер очереди и текущая пропускная способность.
Доля успешных решений (панель Stat)
sum(rate(captcha_solves_total{status="success"}[5m]))
/
sum(rate(captcha_solves_total[5m]))
* 100
Баланс (панель «Датчик»)
captcha_balance_dollars
Глубина очереди (панель Stat)
captcha_queue_depth
Задач в минуту (панель Stat)
sum(rate(captcha_solves_total[5m])) * 60
Ряд 2 — задержка и динамика решений
Здесь удобнее видеть тренд, поэтому обе панели — Time series, а не единичное число.
Процентили задержки (Time series)
# 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]))
Доля успешных решений в динамике, по типу CAPTCHA (Time series)
sum(rate(captcha_solves_total{status="success"}[5m])) by (captcha_type) * 60
Ряд 3 — ошибки
Резкий скачок здесь обычно первым сигналит о проблеме — раньше, чем упадёт p95 задержки или опустеет баланс.
Коэффициент ошибок (Time series)
sum(rate(captcha_solves_total{status!="success"}[5m]))
/
sum(rate(captcha_solves_total[5m]))
* 100
Разбивка ошибок по типу (Pie chart)
sum by (status) (increase(captcha_solves_total{status!="success"}[1h]))
Ряд 4 — воркеры
Активные воркеры (Time series)
captcha_workers_active
Если этот график падает до нуля, а очередь при этом растёт — воркеры упали или зависли, а не просто не хватает задач.
Алерты Grafana
Три правила ниже покрывают самые частые причины простоя пайплайна: закончился баланс, начались массовые ошибки или задержка вышла за приемлемые рамки. LowBalance держите на severity: warning — это повод пополнить счёт, а не будить дежурного ночью; HighErrorRate уже critical, потому что за десятью процентами ошибок обычно стоит либо сбой на стороне решателя, либо неверный sitekey/pageurl в запросе. Порог 120 секунд для HighLatency — отправная точка: под свой SLA его стоит сузить или расширить, ориентируясь на типовое время решения конкретного типа CAPTCHA, а не на одно значение для всех.
# 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
Диагностика типичных проблем
| Проблема | Причина | Решение |
|---|---|---|
| На панелях «No data» | Prometheus не собирает метрики с эндпоинта | Проверьте targets в prometheus.yml; убедитесь, что /metrics действительно отдаёт данные |
| Процентили задержки выглядят неправдоподобно | Неверное окно rate() или слишком грубые buckets гистограммы |
Используйте окно [5m]; добавьте более гранулярные buckets в Histogram |
| Переменные дашборда не подставляются | Некорректный запрос template variable | Используйте label_values(captcha_solves_total, captcha_type) |
| Алерты не срабатывают вовремя | Слишком большой интервал evaluation | Установите интервал проверки правил в 1 минуту |
Частые вопросы
Как отправлять алерты Grafana в Telegram или Slack, а не только на почту?
Добавьте отдельный contact point с нужным типом интеграции (Telegram bot, Slack webhook, Discord, PagerDuty — на выбор) и привяжите к нему notification policy по label severity. Правила LowBalance, HighErrorRate и HighLatency из блока выше сработают в любой канал без изменения самих expr.
Сколько потоков закладывать, если очередь на дашборде растёт?
Смотрите на связку панелей Queue Depth и Active Workers: если очередь растёт, а число активных воркеров стабильно, узкое место — тарифный план CaptchaAI, а не код. Например, команда, стартовавшая на ADVANCE ($90/мес, 50 потоков), при устойчивом росте очереди в вечерние часы обычно переходит на CORPORATE ($240/мес, 150 потоков) или ENTERPRISE ($300/мес, 200 потоков) — план меняется через личный кабинет, дашборд только показывает, когда это пора сделать.
Какой scrape-интервал Prometheus выбрать для метрик CaptchaAI?
15 секунд — разумный дефолт для большинства пайплайнов. При низком объёме задач подойдут и 30 секунд — заметной потери точности на панелях не будет. Опускаться ниже 10 секунд стоит только если нужна почти реалтайм-реакция алертов, и только при достаточном ресурсе на стороне Prometheus.
Нужно ли учитывать 152-ФЗ или GDPR при хранении этих метрик?
Сами метрики CaptchaAI (счётчики решений, задержка, баланс) обезличены и рисков не несут. Риск появляется, если вы добавляете в лейблы pageurl или другие поля с персональными данными пользователей — тогда ретеншн и доступ к Prometheus стоит настраивать так же строго, как для любого другого хранилища с ПДн, будь то 152-ФЗ для аудитории в РФ или GDPR для остального рынка. Это ответственность вашей инфраструктуры, не CaptchaAI.
Дальнейшие шаги
Дашборд собран, алерты настроены — дальше стоит убедиться, что сам пайплайн решения CAPTCHA отправляет метрики корректно с самого первого запроса. Из связанных материалов пригодятся: