Каждый, кто гоняет нагрузочные тесты, знает этот ритуал: запускаешь 30-минутный прогон и пялишься в терминал, ожидая, когда итоговая сводка расскажет о том, что уже случилось. k6 печатает summary по окончании прогона, JMeter — тоже. Хотите наблюдать за прогоном — поднимайте InfluxDB с Grafana, платите за k6 Cloud или собирайте xk6-расширение вывода — и всё это до того, как вы написали первого виртуального пользователя.
perfscale v0.18.0 переворачивает дефолт. Один YAML-блок — и движок стримит метрики на платформу прямо во время подачи нагрузки. И, что важнее, стриминг спроектирован так, чтобы он не мог исказить измерение, о котором отчитывается.
Что мы шлём
Каждые interval_ms (по умолчанию 5 с) движок снимает кумулятивный снапшот всего, что прогон насобирал к этому моменту, разворачивает его в сэмплы в стиле Prometheus и отправляет на платформу:
- Тренды и гистограммы — например,
http_req_duration— превращаются вhttp_req_duration{quantile="0.5|0.9|0.95|0.99"}плюсhttp_req_duration_countиhttp_req_duration_sum. Значения шлются в секундах, по конвенции Prometheus. - Счётчики становятся
*_total. - Доли ошибок становятся парой: rate-метрика вроде
http_req_failedшлёт<name>_total(вызовы) и<name>_failed_total(ошибки) — так rate остаётся вычислимым на любом окне. - Пользовательские метрики подхватываются автоматически. Всё, что эмитят ваши шаги — латентность gRPC, время запросов к БД,
llm_ttft_ms, счётчики WebSocket-сессий — оказывается в снапшоте без единой лишней строчки конфигурации. - Телеметрия GPU, когда включён
gpu.enabled:gpu_utilization_pct,gpu_memory_used_mib,gpu_temperature_c,gpu_power_wпо каждому устройству. На Apple Silicon (черезpowermetrics) сюда же попадаетane_power_w— потребление Neural Engine вашего Mac, вживую, на одной шкале времени с HTTP-латентностью.
Каждый сэмпл помечен лейблами task_id и machine_id, так что распределённый прогон чисто раскладывается по генераторам нагрузки. Снапшоты кумулятивные (HDR-гистограммы не сбрасываются в середине прогона); потребители диффают соседние снапшоты, чтобы получить значения за интервал.
Когда мы шлём
Батч запечатывается, когда набирает batch_size сэмплов (по умолчанию 500), либо по тику в 5 секунд — что наступит раньше — и уходит POST'ом на /api/v1/metrics со строго возрастающим seq. Платформа дедуплицирует по (task_id, seq), так что переотправленный батч никогда не задвоит данные.
Стриминг покрывает ровно фазу нагрузки: стартует непосредственно перед запуском виртуальных пользователей и заканчивается вместе с ними. Лог прогона сообщает, что всё работает:
during-run metrics: snapshots every 5000ms (batch ≤ 500, cpu gate 90%)
Когда прогон завершается, shipper сливает всё, что осталось в очереди (ограниченные ретраи, жёсткий предел 30 с), а следом, как всегда, приходит классическая итоговая сводка в формате k6. Живые данные — дополнение, а не замена.
Когда мы отказываемся слать
Вот этой частью мы гордимся по-настоящему. Мониторинг, который конкурирует с генератором нагрузки за CPU, в итоге измеряет сам себя. Поэтому shipper живёт по одному правилу: генерация нагрузки всегда важнее отчётности о ней.
Конкретно:
- Движок никогда не блокируется на метриках. Снапшоты уходят в ограниченный канал на 512 слотов через
try_send. Канал полон — shipper медлит, платформа тормозит, сеть пропала — снапшот выбрасывается с предупреждением (не чаще одного на десять). Вы теряете одну точку на графике, но ни в коем случае не точность самой нагрузки. - Shipper — отдельная асинхронная задача с таймаутом запроса в 10 секунд. Ничто из того, что он делает, не касается цикла виртуальных пользователей.
- CPU-гейт. Shipper следит за загрузкой CPU хоста (через
/proc/stat). Пока busy-CPU держится на уровнеmax_cpu_percentили выше — по умолчанию 90% — он придерживает все ожидающие батчи и складывает входящие снапшоты в очередь, а не выбрасывает: ноль HTTP-запросов, пока машина не придёт в себя, затем накопленные точки уходят по порядку. Если генератор нагрузки упёрся в потолок, perfscale замолкает, а не дерётся с VU за последние ядра — и не теряет ни одной точки. Честная оговорка: гейт читает/proc/stat, поэтому на сегодня он только для Linux; на macOS и Windows остаётся ограниченный канал из пункта выше. - Ничего не выбрасывается в середине прогона. Запечатанные, но недоставленные батчи хранятся, сколько бы ни длился сбой —
max_pending(по умолчанию 24) теперь мягкий предел, который управляет предупреждениями, а не шредером. Мёртвая платформа отложит ваши графики, но не съест их. - Плавная деградация вместо падений. Сетевые ошибки, 5xx и 429 ретраят тот же батч с тем же
seq(бэкофф ×2 от 1 с, потолок 60 с). Любой другой 4xx означает, что payload отравлен — он выбрасывается и никогда не ретраится.
Единственное исключение — финальный дренаж: как только VU остановились, CPU свободен, гейт открывается, и всё оставшееся уходит на платформу.
Что вы видите на платформе
Страница прогона рисует графики по мере прихода сэмплов — перцентили латентности, RPS, доля ошибок, кривые GPU — без обновления страницы и ожидания сводки. Под капотом — настоящее хранилище таймсерий: платформа отдаёт Prometheus-совместимый query API, на который можно натравить Grafana, и Remote Write, если удобнее пушить всё в свой Prometheus или Mimir. А поскольку сэмплы несут machine_id, вопрос «эта вспышка — один заболевший генератор или сервер?» решается запросом по лейблу, а не гаданием.
В демо-тенанте на perfscale.su уже заведена конфигурация Live Metrics (during-run) и небольшой демо-тест: войдите под демо-аккаунтом, запустите его и смотрите, как график загорается посреди прогона.
Как включить
# config.yaml
report:
url: https://perfscale.su
during_run: true # по умолчанию false — только итоговая сводка
interval_ms: 5000 # интервал снапшотов/флаша, минимум 1000
batch_size: 500 # запечатать батч при таком числе сэмплов
max_cpu_percent: 90 # CPU-гейт; 0 отключает его (Linux)
max_pending: 24 # мягкий предел — предупреждение, батчи не выбрасываются
Один флаг — это вся история: during_run: true. Остальные ручки идут с разумными дефолтами. Учтите: стриминг работает для нативного движка — прогоны совместимости --k6, --jmeter и --locust по-прежнему дают нормализованную k6-образную сводку в конце, но не стримят (пока).
Чем это отличается от чистых k6 и JMeter
Честности ради: живые метрики возможны в обоих инструментах — вопрос в том, что вам придётся построить и поддерживать, чтобы их получить.
| k6 OSS | JMeter | perfscale | |
|---|---|---|---|
| Живые метрики из коробки | ✗ (сводка в конце) | ✗ (сводка в конце) | ✓ один YAML-флаг |
| Для живого просмотра нужны | k6 Cloud или xk6-расширение + InfluxDB/Prometheus | Backend Listener + InfluxDB/Graphite + Grafana | ничего |
| Политика при перегрузке генератора | на вашем пайплайне вывода | на вашем конфиге listener'а | встроена: пауза POST'ов при 90% CPU, очередь снапшотов, ноль потерь в прогоне |
| Телеметрия железа на той же шкале | ✗ | ✗ | утилизация/VRAM/температура/мощность GPU, мощность ANE/CPU/пакета на Apple Silicon |
| Раскладка распределённого прогона по машинам | ручные теги | ручные теги | лейбл machine_id на каждом сэмпле |
Философская разница — в среднем ряду: в perfscale защита измерения от измеряющей системы — задача движка, а не плагина, который вы обязаны правильно настроить под давлением обстоятельств.
Попробуйте
perfscale run -f test.yaml -c config.yaml
Полный справочник — в docs/core/metrics.md и YAML reference. Движок открыт: логика load-shedding живёт в crates/perfscale-core/src/report/stream.rs — можете проверить наши утверждения по коду, а в демо-тенанте на perfscale.su уже подключён рабочий пример.
