Документация Нагрузочное тестирование

Нагрузочное тестирование

Mockarty включает встроенный движок нагрузочного тестирования, позволяющий писать тесты на JavaScript для любого поддерживаемого протокола: HTTP, gRPC, SOAP, GraphQL, WebSocket, SSE, Kafka, RabbitMQ, MCP, SMTP и других.

Нагрузочное — редактор скрипта
Нагрузочное — метрики в реальном времени Скрипты поддерживают синтаксис k6 ESM (import http from 'k6/http'), поэтому разработчики, знакомые с k6, могут писать тесты в аналогичном стиле. Примечание: Mockarty использует собственную систему модулей (mockarty/http, mockarty/grpc и т.д.), которая предоставляет подмножество глобальных функций k6 (check, sleep, group, fail), но не является полным рантаймом k6 – продвинутые возможности k6 (k6/browser, k6/execution, расширения k6) недоступны.

URL-адреса в примерах: Во всех примерах используется localhost:5770 как адрес Mockarty по умолчанию. Если ваш экземпляр работает на удалённом сервере, замените localhost:5770 на реальный адрес (например, https://mockarty.company.com или http://192.168.1.50:5770). Подробнее — в разделе Полезные функции и советы.

JS-скрипт k6 ESM или нативный VU, этапы, пороги check() / sleep() Свои метрики Критерии прерывания Движок JS runtime Пул VU + планировщик Контроллер RPS Сбор временных рядов Расчёт APDEX Оценка порогов Целевые сервисы HTTP / REST gRPC / GraphQL SOAP / MCP Kafka / RabbitMQ WebSocket / SSE Redis / SQL Отчёт Перцентили задержки APDEX по маршрутам Графики RPS Пороги PASS/FAIL Сводки протоколов парсинг запросы ответы метрики 1. Скрипт 2. Выполнение 3. Нагрузка 4. Анализ Опционально: распределение на Runner Agent'ы через координатор (gRPC :5773)

Что такое нагрузочное тестирование?

Нагрузочное тестирование (performance testing) — это практика имитации реалистичного пользовательского трафика к вашему приложению для измерения его поведения под нагрузкой. В отличие от функциональных тестов, проверяющих корректность, нагрузочные тесты отвечают на вопросы: “Сколько пользователей выдержит мой API до того, как время ответа станет неприемлемым?” и “Переживёт ли мой сервис всплеск трафика в день запуска?”

Прежде чем углубиться, вот ключевые термины, которые встретятся в этом руководстве:

Термин Определение
Виртуальный пользователь (VU) Имитируемый пользователь, многократно выполняющий ваш тестовый скрипт. Если вы зададите vus: 50, Mockarty создаст 50 независимых “пользователей”, делающих запросы одновременно.
p95 задержка 95-й перцентиль времени ответа. Означает, что 95% всех запросов выполнились быстрее этого значения. Например, если p95 равен 200 мс, то лишь 5% запросов заняли более 200 мс. Эта метрика предпочтительнее средних значений, поскольку отражает опыт самых медленных пользователей.
APDEX Application Performance Index — отраслевой стандарт оценки от 0 до 1, переводящий время ответа в рейтинг удовлетворённости пользователей. Оценка выше 0.94 — “Отлично”, ниже 0.50 — “Неприемлемо”. Подробнее в разделе Оценка APDEX.
Пороги (Thresholds) Правила прохождения/провала, которые вы задаёте для теста. Например, 'p(95) < 500' означает, что тест проваливается, если 95-й перцентиль времени ответа превышает 500 мс. Пороги позволяют блокировать CI/CD-пайплайны по производительности.
Пропускная способность (Throughput) Количество запросов, обрабатываемых вашей системой в секунду (часто записывается как RPS — requests per second).

Зачем проводить нагрузочное тестирование API?

Проблемы производительности редко проявляются во время разработки. Один эндпоинт, отвечающий за 5 мс без нагрузки, может деградировать до 2 секунд при одновременном обращении 500 пользователей. Нагрузочное тестирование помогает:

  • Выявлять узкие места до попадания в продакшен. Найти эндпоинт, запрос к базе данных или внешнюю зависимость, которая ломается под нагрузкой.
  • Устанавливать базовые показатели. Знать пропускную способность (запросов/сек) и перцентили задержки (p50, p95, p99), чтобы отслеживать регрессии.
  • Проверять SLA. Задавать пороги прохождения/провала, например “p95 задержки должен быть менее 200 мс”, и автоматически помечать сборки, нарушающие их.
  • Планировать мощности. Определять, сколько одновременных пользователей выдержит ваша инфраструктура перед необходимостью масштабирования.
  • Тестировать отказоустойчивость. Моделировать всплески трафика, медленных потребителей и лавины ошибок для проверки корректной деградации.

Обзор движка скриптов

Страница запусков тестов

Движок производительности выполняет JavaScript-скрипты с использованием встроенного JS-рантайма. Скрипты внутренне используют паттерн модулей CommonJS. Mockarty автоматически конвертирует синтаксис k6 ESM (import http from 'k6/http') в нативный формат require('mockarty/http'). Основные функции k6 (check, sleep, group, fail) и конструкторы метрик (Counter, Trend, Rate, Gauge) доступны как глобальные переменные.

Ключевые концепции

Концепция Описание
Итерация Одно полное выполнение функции default. VU выполняет итерации непрерывно до окончания теста.
Длительность Время выполнения теста. По умолчанию 30 секунд, если не указано иное.
Этапы (Stages) Временные фазы, увеличивающие и уменьшающие количество VU (или RPS), обеспечивающие реалистичные паттерны трафика.
Режим RPS Вместо фиксированного числа VU вы задаёте целевое количество запросов в секунду, а движок автоматически регулирует число VU для его поддержания.
Критерии прерывания Условия, вызывающие досрочное завершение теста (например, частота ошибок выше 50% в течение 30 секунд).

Автоопределение формата скрипта

Mockarty автоматически определяет три формата скриптов и конвертирует их:

  1. Нативный Mockartyrequire('mockarty/http') с module.exports.default
  2. k6 ESMimport http from 'k6/http' с export default function
  3. Устаревший mk/pm — паттерны mk.http.get(...) и mk.test(...)

Вы можете писать в любом удобном формате. Движок конвертирует всё во внутренний формат CommonJS перед выполнением.


Написание первого нагрузочного теста

Вот минимальный HTTP-тест нагрузки:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 10,
  duration: '30s',
};

export default function () {
  const res = http.get('https://api.example.com/users');

  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });

  sleep(1);
}

Или эквивалент в нативном формате Mockarty:

var http = require('mockarty/http');

module.exports.options = {
  vus: 10,
  duration: '30s',
};

module.exports.default = function () {
  var res = http.get('https://api.example.com/users');

  check(res, {
    'status is 200': function (r) { return r.status === 200; },
    'response time < 500ms': function (r) { return r.timings.duration < 500; },
  });

  sleep(1);
};

Оба скрипта дают идентичные результаты. Движок автоматически конвертирует импорты k6.

Пошаговый разбор

  1. Импорт модулей — загрузите HTTP-модуль (и другие при необходимости).
  2. Определение параметров — задайте число VU, длительность, этапы, пороги.
  3. Написание функции default — это ваша итерация. Каждый VU вызывает её в цикле.
  4. Добавление проверок — валидируйте статус ответа, тело, время.
  5. Добавление sleep — имитируйте время обдумывания пользователя между запросами (в секундах).

Параметры теста

Параметры управляют выполнением теста. Определяйте их как export const options (формат k6) или module.exports.options (нативный формат).

Параметр Тип По умолчанию Описание
vus int 1 Количество одновременных виртуальных пользователей.
duration string "30s" Длительность теста (напр., "30s", "5m", "1h"). Формат длительности Go.
iterations int 0 (без ограничений) Общее число итераций по всем VU. Тест останавливается при достижении.
rps int 0 (режим VU) Целевое число запросов в секунду. Движок автоматически регулирует число VU.
maxVUs int 200 Предельное количество VU в режиме RPS. Предотвращает неконтролируемое масштабирование.
stages []Stage [] Временные этапы нарастания VU или RPS. Переопределяет vus и duration.
thresholds map {} Критерии прохождения/провала, оцениваемые по итоговым метрикам.
abortCriteria []Criterion [] Условия досрочного завершения теста.

Определение этапов

Каждый этап имеет длительность и целевое значение:

export const options = {
  stages: [
    { duration: '30s', target: 20 },   // Нарастание до 20 VU за 30 сек
    { duration: '1m', target: 20 },    // Удержание 20 VU в течение 1 минуты
    { duration: '30s', target: 0 },    // Снижение до 0 VU за 30 сек
  ],
};

Движок линейно интерполирует количество VU между этапами.

Этапы RPS

Для управления на основе RPS используйте targetRPS вместо target:

export const options = {
  maxVUs: 100,
  stages: [
    { duration: '30s', targetRPS: 50 },   // Нарастание до 50 запр/сек
    { duration: '1m', targetRPS: 200 },   // Нарастание до 200 запр/сек
    { duration: '30s', targetRPS: 0 },    // Снижение
  ],
};

Движок использует пропорциональный контроллер с демпфированием для регулировки числа VU, избегая осцилляций. Изменения VU ограничены ±20% в секунду.

Сценарии с фиксированной частотой (open model)

Когда нужна фиксированная частота СТАРТА итераций независимо от того, сколько выполняется каждая итерация, используйте arrival-rate сценарий. Новые итерации стартуют с заданной частотой и берут воркеров из пула; если все воркеры заняты, лишние итерации дропаются (в отчёте dropped_iterations), а не копятся в очереди — поэтому медленный таргет не может раздуть латентность накоплением работы.

export const options = {
  scenarios: {
    checkout: {
      executor: 'constant-arrival-rate',
      rate: 100,               // 100 итераций в секунду...
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 50,     // воркеры готовы заранее
      maxVUs: 200,             // растить пул до этого предела под нагрузкой
    },
  },
};

Используйте ramping-arrival-rate со stages, чтобы наращивать и снижать частоту во времени. Если dropped_iterations в отчёте не ноль — поднимите maxVUs (или снизьте rate): таргет не успевает за запрошенной частотой.

Режимы выполнения

Движок работает в одном из двух режимов:

  • Режим производительности — несколько VU, сбор временных рядов, графики. Активен при vus > 1, duration > 0, заданных этапах или iterations > 0.
  • Функциональный режим — один VU, одна итерация, подробные результаты проверок. Активен, когда параметры производительности не заданы. Полезен для проверки корректности скрипта перед масштабированием.

Управление нагрузкой в реальном времени

Нагрузку теста можно менять прямо во время выполнения — без перезапуска и без потери
уже собранных метрик. Это удобно для исследовательского нагрузочного тестирования:
поднимайте нагрузку до точки отказа, затем снижайте — всё в рамках одного непрерывного запуска.

Пока тест выполняется, на вкладке Performance (и в live-отчёте о запуске) показывается
панель Управление нагрузкой:

  • Поднять / снизить нагрузку — тащите ползунок или используйте кнопки −5 / +5, чтобы
    изменить число активных виртуальных пользователей (или, для теста с RPS-режимом, целевой
    RPS). Изменение применяется примерно за секунду; график VU это отражает.
  • Продлить длительность+30s, +1m, +5m отодвигают время остановки теста, чтобы
    продолжить перспективный запуск.
  • Вернуть план — снять ручной override и вернуться к заданному профилю (постоянная
    нагрузка / стадии / RPS).

Те же действия доступны из live-экрана запуска в CLI (+ / - — изменить нагрузку,
e — продлить длительность на 30 секунд).

Число VU в реальном времени ограничено выделенным потолком исполнителя (показан рядом с
целью). У тестов на admin-ноде потолок ниже; для крупномасштабного управления запускайте
тест на отдельном раннере (см. Распределённое выполнение).


Доступные модули

Mockarty предоставляет 14 встроенных модулей, охватывающих все поддерживаемые протоколы и распространённые утилиты.

mockarty/http

HTTP-клиент для тестирования REST API.

var http = require('mockarty/http');

// GET-запрос
var res = http.get('https://api.example.com/users');

// POST с JSON-телом и заголовками
var res = http.post('https://api.example.com/users',
  JSON.stringify({ name: 'Alice', email: 'alice@example.com' }),
  { headers: { 'Content-Type': 'application/json' } }
);

// Другие методы
http.put(url, body, params);
http.patch(url, body, params);
http.del(url, params);
http.head(url, params);
http.options(url, params);
http.request('CUSTOM_METHOD', url, body, params);

Объект ответа:

Поле Тип Описание
status int HTTP-код статуса
body string Тело ответа в виде строки
json() function Парсинг тела как JSON
headers object Заголовки ответа
timings.duration float Длительность запроса в миллисекундах
error string Сообщение об ошибке (пустое при успехе)

Автоматические метрики: http_reqs (счётчик), http_req_duration (тренд), http_req_failed (частота), data_sent, data_received.

Нормализация маршрутов: URL автоматически нормализуются для отчётов по маршрутам. UUID-сегменты заменяются на {uuid}, числовые сегменты — на {id}. Пример: GET /users/42/orders становится GET /users/{id}/orders.


mockarty/grpc

gRPC-клиент с поддержкой серверной рефлексии.

var grpc = require('mockarty/grpc');

// Подключение с рефлексией (автоматическое обнаружение сервисов)
var client = grpc.connect('localhost:4770', {
  reflect: true,
  plaintext: true,
});

// Вызов унарного RPC
var res = client.invoke('mypackage.UserService/GetUser', {
  user_id: '123',
}, {
  headers: { 'authorization': 'Bearer token123' },
});

check(res, {
  'gRPC status OK': function (r) { return r.status === 'OK'; },
  'has user name': function (r) { return r.message.name !== ''; },
});

// Доступны тайминги
// res.timings.duration — длительность запроса в мс

client.close();

Параметры подключения:

Параметр Тип Описание
reflect bool Использовать серверную рефлексию для обнаружения сервисов. Обязательно.
plaintext bool Отключить TLS (для локального тестирования).
tls bool Включить TLS с системными сертификатами.

Автоматические метрики: grpc_reqs (счётчик), grpc_req_duration (тренд), grpc_req_failed (частота).


mockarty/soap

Клиент SOAP/XML веб-сервисов.

var soap = require('mockarty/soap');

var res = soap.call('http://localhost:8080/soap', {
  action: 'GetWeather',
  body: '<GetWeatherRequest><City>London</City></GetWeatherRequest>',
  headers: {
    'X-Custom': 'value',
  },
});

check(res, {
  'SOAP status 200': function (r) { return r.status === 200; },
  'has body': function (r) { return r.body.length > 0; },
});

Если тело ещё не обёрнуто в SOAP-конверт, модуль оборачивает его автоматически.

Автоматические метрики: soap_reqs (счётчик), soap_req_duration (тренд), soap_req_failed (частота).


mockarty/ws

WebSocket-клиент для тестирования двунаправленной коммуникации.

var ws = require('mockarty/ws');

var conn = ws.connect('ws://localhost:8080/ws', {
  headers: { 'Authorization': 'Bearer token' },
});

// Отправка сообщения
conn.send(JSON.stringify({ type: 'subscribe', channel: 'orders' }));

// Отправка бинарных данных
conn.sendBinary('raw binary data');

// Получение сообщения (опциональный таймаут в секундах, по умолчанию 30)
var msg = conn.receive(5);

check(msg, {
  'received message': function (m) { return m.length > 0; },
});

conn.close();

Автоматические метрики: ws_sessions (счётчик), ws_messages_sent (счётчик), ws_messages_received (счётчик), ws_connecting (тренд), ws_send_duration (тренд), ws_recv_duration (тренд), ws_connect_failed (частота).


mockarty/sse

Клиент Server-Sent Events.

var sse = require('mockarty/sse');

// Подключение и сбор событий
var events = sse.connect('http://localhost:8080/events', {
  timeout: '10s',   // Таймаут подключения
  limit: 5,         // Максимум событий для сбора (0 = без ограничений до таймаута)
  headers: {
    'Authorization': 'Bearer token',
  },
});

check(events, {
  'received events': function (e) { return e.length > 0; },
  'first event has data': function (e) { return e[0].data !== ''; },
});

// Каждый объект события: { id, event, data, retry }

Автоматические метрики: sse_events_received (счётчик), sse_connecting (тренд), sse_connect_failed (частота).


mockarty/kafka

Продюсер и консюмер Apache Kafka. Mockarty работает с реальным кластером Kafka — он не эмулирует брокер. Подготовьте запущенный кластер (локальный docker-compose, dev-кластер или testcontainers) по адресу, который вы передаёте в brokers. Если брокер недоступен, скрипт сообщает об ошибках подключения, а метрики kafka_*_failed растут.

var kafka = require('mockarty/kafka');

// Создание продюсера
var producer = kafka.producer({
  brokers: ['localhost:9092'],
});

// Отправка сообщений
producer.produce({
  topic: 'orders',
  messages: [
    { key: 'order-1', value: JSON.stringify({ amount: 99.99 }) },
    { key: 'order-2', value: JSON.stringify({ amount: 149.50 }) },
  ],
});

// Создание консюмера
var consumer = kafka.consumer({
  brokers: ['localhost:9092'],
  groupId: 'perf-test-group',
});

// Потребление сообщений
var messages = consumer.consume({
  topic: 'orders',
  limit: 10,
  timeout: '5s',
});

check(messages, {
  'consumed messages': function (m) { return m.length > 0; },
});

// Каждое сообщение: { key, value, topic, partition, offset }

Автоматические метрики: kafka_messages_produced (счётчик), kafka_messages_consumed (счётчик), kafka_produce_duration (тренд), kafka_consume_duration (тренд), kafka_produce_failed (частота).


mockarty/rabbitmq

Издатель и потребитель RabbitMQ. Mockarty работает с реальным экземпляром RabbitMQ — он не эмулирует брокер. Подготовьте запущенный RabbitMQ (локальный docker-compose, dev-инстанс или testcontainers) по AMQP-адресу, который вы передаёте в connect(). Если брокер недоступен, скрипт сообщает об ошибках подключения, а метрики rmq_*_failed растут.

var rabbitmq = require('mockarty/rabbitmq');

var conn = rabbitmq.connect('amqp://guest:guest@localhost:5672/');

// Публикация сообщения
conn.publish({
  exchange: '',
  routingKey: 'task_queue',
  body: JSON.stringify({ task: 'process_order', orderId: '12345' }),
  contentType: 'application/json',
});

// Потребление сообщений
var messages = conn.consume({
  queue: 'task_queue',
  limit: 5,
  timeout: '10s',
});

check(messages, {
  'received messages': function (m) { return m.length > 0; },
});

// Каждое сообщение: { body, routingKey, exchange, contentType }

conn.close();

Автоматические метрики: rmq_messages_published (счётчик), rmq_messages_consumed (счётчик), rmq_publish_duration (тренд), rmq_consume_duration (тренд), rmq_publish_failed (частота).


mockarty/mcp

MCP-клиент (Model Context Protocol) для тестирования MCP-серверов.

var mcp = require('mockarty/mcp');

var client = mcp.connect('http://localhost:8910/sse');

// Получение списка доступных инструментов
var tools = client.listTools();

// Вызов инструмента
var result = client.callTool('get_weather', {
  city: 'London',
});

check(result, {
  'no error': function (r) { return !r.isError; },
  'has content': function (r) { return r.content && r.content.length > 0; },
});

// result.content = [{ type: "text", text: "..." }, ...]
// result.timings.duration — длительность в мс

client.close();

Автоматические метрики: mcp_reqs (счётчик), mcp_req_duration (тренд), mcp_req_failed (частота).


mockarty/redis

Redis-клиент с пулом соединений, разделяемым между VU.

var redis = require('mockarty/redis');

// Открытие соединения (по умолчанию использует переменную окружения MOCKARTY_REDIS_ADDR)
var client = redis.open('redis://localhost:6379/0');

// Строковые команды
client.set('user:1:name', 'Alice', 3600);   // ключ, значение, ttl_секунды
var result = client.get('user:1:name');       // { value: "Alice", error: null }
client.incr('counter');
client.decr('counter');

// Хеш-команды
client.hset('user:1', 'email', 'alice@example.com');
var email = client.hget('user:1', 'email');
var all = client.hgetall('user:1');

// Команды списков
client.lpush('queue', 'item1', 'item2');
var item = client.rpop('queue');
var items = client.lrange('queue', 0, -1);

// Команды множеств
client.sadd('tags', 'golang', 'performance');
var members = client.smembers('tags');
var exists = client.sismember('tags', 'golang');

// Команды отсортированных множеств
client.zadd('leaderboard', 100, 'player1');
var score = client.zscore('leaderboard', 'player1');
var top = client.zrange('leaderboard', 0, 9);

// Управление ключами
client.del('key1', 'key2');
client.exists('key1');
client.expire('key1', 3600);
var ttl = client.ttl('key1');

Все команды возвращают объекты { value, error }. Пулы соединений разделяются между VU и автоматически очищаются при завершении теста. Ключи, созданные через set, hset, lpush, rpush, sadd и zadd, получают TTL по умолчанию 12 часов для предотвращения остаточных тестовых данных.

Автоматические метрики: redis_commands (счётчик), redis_cmd_duration (тренд), redis_cmd_failed (частота).


mockarty/sql

SQL-клиент для баз данных (PostgreSQL) с пулом соединений.

var sql = require('mockarty/sql');

var db = sql.open('postgres', 'postgres://user:pass@localhost:5432/testdb?sslmode=disable');

// Запрос (возвращает массив объектов-строк)
var users = db.query('SELECT id, name, email FROM users WHERE active = $1 LIMIT $2', [true, 100]);

check(users, {
  'found users': function (u) { return u.length > 0; },
  'first user has name': function (u) { return u[0].name !== ''; },
});

// Выполнение (возвращает { rowsAffected: N })
var result = db.exec('UPDATE users SET last_login = NOW() WHERE id = $1', [users[0].id]);

// Параметризованные запросы — передавайте параметры массивом или аргументами
db.query('SELECT * FROM orders WHERE user_id = $1 AND status = $2', userId, 'pending');

Пулы соединений разделяются между VU. Метод close() на уровне VU ничего не делает; пулы очищаются при остановке движка.


mockarty/encoding

Утилиты кодирования/декодирования.

var encoding = require('mockarty/encoding');

// JSON
var obj = encoding.jsonParse('{"name":"Alice"}');   // → { name: "Alice" }
var str = encoding.jsonStringify({ name: 'Alice' }); // → '{"name":"Alice"}'

// Base64
var encoded = encoding.base64Encode('Hello, World!');   // → "SGVsbG8sIFdvcmxkIQ=="
var decoded = encoding.base64Decode('SGVsbG8sIFdvcmxkIQ=='); // → "Hello, World!"

mockarty/faker

Генерация фейковых данных для реалистичных тестовых нагрузок.

var faker = require('mockarty/faker');

var user = {
  id: faker.uuid(),
  firstName: faker.firstName(),
  lastName: faker.lastName(),
  email: faker.email(),
  username: faker.username(),
  password: faker.password(),
  phone: faker.phoneNumber(),
  ip: faker.ipv4(),
  bio: faker.sentence(),
  registered: faker.rfc3339(),
  creditCard: faker.ccNumber(),
  jwt: faker.jwt(),
};

Доступные функции:

Категория Функции
Персона firstName, firstNameMale, firstNameFemale, lastName, name, titleMale, titleFemale
Интернет email, username, password, url, domainName, ipv4, ipv6, macAddress
UUID uuid, uuidDigit
Адрес latitude, longitude
Дата и время date, timeString, monthName, yearString, dayOfWeek, dayOfMonth, timestamp, unixTime, rfc3339, century, timezone, timePeriod
Текст word, sentence, paragraph
Платежи ccType, ccNumber, currency, amountWithCurrency
Телефон phoneNumber, tollFreePhoneNumber, e164PhoneNumber
Типы bool, positiveInt, negativeInt, jwt

Все функции faker потокобезопасны и используют собственное состояние генератора случайных чисел.


mockarty/data

Общие массивы данных для data-driven тестов.

var data = require('mockarty/data');

// SharedArray выполняет фабрику ОДИН РАЗ, затем разделяет результат между всеми VU
var users = data.SharedArray('users', function () {
  return [
    { username: 'alice', password: 'pass123' },
    { username: 'bob', password: 'pass456' },
    { username: 'charlie', password: 'pass789' },
  ];
});

module.exports.default = function () {
  // Доступ по индексу (безопасно для VU, только чтение)
  var idx = Math.floor(Math.random() * users.length);
  var user = users.get(idx);

  var http = require('mockarty/http');
  http.post('https://api.example.com/login',
    JSON.stringify(user),
    { headers: { 'Content-Type': 'application/json' } }
  );
};

Фабричная функция выполняется ровно один раз (первым VU, запросившим данные), и последующие VU получают ссылку на те же данные без копирования. Это идеально для загрузки больших наборов данных (CSV-файлы, JSON-массивы) без дублирования памяти между VU.


mockarty/core

Базовые утилитные функции. Они также доступны как глобальные (check, sleep, group, fail), поэтому импорт модуля необязателен.

var core = require('mockarty/core');

// Check — валидация значения по именованным проверкам
core.check(response, {
  'status is 200': function (r) { return r.status === 200; },
});

// Sleep — пауза выполнения на N секунд
core.sleep(1.5);

// Group — организация проверок и запросов в именованные группы
core.group('user login flow', function () {
  // запросы и проверки внутри группы
});

// Fail — немедленное прерывание текущей итерации
core.fail('Unexpected response');

Глобальные функции

Следующие функции всегда доступны без импорта какого-либо модуля:

Функция Описание
check(value, checks) Выполнение именованных проверок. Возвращает true, если все пройдены.
sleep(seconds) Пауза выполнения VU.
group(name, fn) Группировка связанных операций для отчётности.
fail(reason) Прерывание текущей итерации с ошибкой.

Пользовательские метрики

Создание пользовательских метрик как глобальных объектов:

// Counter — накапливает итог
var myCounter = new Counter('my_custom_counter');
myCounter.add(1);

// Trend — отслеживает распределение (min, max, avg, перцентили)
var myTrend = new Trend('my_custom_trend');
myTrend.add(42.5);

// Rate — отслеживает соотношение успехов/неудач
var myRate = new Rate('my_custom_rate');
myRate.add(true);   // успех
myRate.add(false);  // неудача

// Gauge — отслеживает текущее значение
var myGauge = new Gauge('my_custom_gauge');
myGauge.add(7);

Пользовательские метрики отображаются в порогах и итоговом отчёте наряду со встроенными метриками.


Пороги и SLA

Пороги определяют критерии прохождения/провала теста. При нарушении любого порога результат теста помечается как проваленный.

export const options = {
  vus: 50,
  duration: '2m',
  thresholds: {
    'http_req_duration': [
      'avg < 200',       // Среднее время ответа менее 200 мс
      'p(95) < 500',     // 95-й перцентиль менее 500 мс
      'p(99) < 1000',    // 99-й перцентиль менее 1 сек
      'max < 3000',      // Ни одного запроса дольше 3 сек
    ],
    'http_req_failed': [
      'rate < 0.01',     // Менее 1% ошибок
    ],
    'checks': [
      'rate > 0.95',     // 95% проверок пройдено
    ],
    'iterations': [
      'count > 1000',    // Минимум 1000 итераций выполнено
    ],
    // Пользовательские метрики тоже работают
    'my_custom_trend': [
      'avg < 100',
      'p(90) < 200',
    ],
  },
};

Синтаксис выражений порогов

<статистика> <оператор> <значение>

Статистики: avg, min, max, med, count, rate, p(N) (где N — перцентиль, напр., p(50), p(95), p(99))

Операторы: <, <=, >, >=, ==, !=

Результаты порогов

После завершения теста каждый порог оценивается и выводится в отчёте:

All thresholds passed:
  PASS [http_req_duration] avg < 200: actual=87.34
  PASS [http_req_duration] p(95) < 500: actual=245.12
  PASS [http_req_failed] rate < 0.01: actual=0.00

Или с нарушениями:

Threshold violations:
  PASS [http_req_duration] avg < 200: actual=87.34
  FAIL [http_req_duration] p(95) < 500: actual=612.89
  PASS [http_req_failed] rate < 0.01: actual=0.00

Критерии прерывания

Критерии прерывания позволяют движку остановить тест досрочно, когда условия указывают на серьёзную проблему, предотвращая потерю времени и ресурсов.

export const options = {
  vus: 100,
  duration: '10m',
  abortCriteria: [
    {
      name: 'High error rate',
      metric: 'http_req_failed',
      stat: 'rate',
      condition: '>',
      value: 50,            // проценты (0-100): прервать при частоте ошибок выше 50%
      duration: '30s',      // Должно сохраняться 30 сек перед прерыванием
      enabled: true,
    },
    {
      name: 'Latency spike',
      metric: 'http_req_duration',
      stat: 'p95',
      condition: '>',
      value: 5000,           // p95 > 5 секунд
      duration: '1m',
      enabled: true,
    },
  ],
};

Поле duration требует, чтобы условие сохранялось в течение указанного периода перед срабатыванием. Это предотвращает прерывание при кратковременных всплесках. Если duration не указан или равен "0s", прерывание срабатывает немедленно.

Поддерживаемые метрики для прерывания: http_req_failed (rate), http_req_duration (avg, p90, p95, p99, min, max), http_reqs (count, rate), checks (rate).

Про шкалу rate. Значения rate в abort-criteria — это проценты (0-100): value: 50 = 50%. Это отличается от thresholds, где rate следует k6 и задаётся долей 0-1 ('rate<0.01' = 1%). Пороги дают финальный pass/fail; abort-criteria досрочно останавливают вышедший из-под контроля тест.

При срабатывании прерывания отчёт теста содержит stoppedReason: "auto_stop: High error rate".


Оценка APDEX

Каждый нагрузочный тест автоматически рассчитывает оценку APDEX (Application Performance Index). APDEX — это отраслевой стандарт метрики, переводящий время отклика в оценку удовлетворённости пользователей от 0 до 1.

Как рассчитывается APDEX

APDEX использует порог удовлетворённости T (по умолчанию: 500 мс) для классификации каждого запроса:

Зона Условие Описание
Удовлетворён время_ответа <= T Пользователи удовлетворены производительностью.
Терпит T < время_ответа <= 4T Пользователи замечают задержки, но терпят их.
Разочарован время_ответа > 4T или ошибка Пользователи разочарованы. Все ошибки считаются разочарованием.

Формула:

APDEX = (Удовлетворённые + Терпящие * 0.5) / Всего

Шкала оценок

Оценка Рейтинг
>= 0.94 Отлично
>= 0.85 Хорошо
>= 0.70 Удовлетворительно
>= 0.50 Плохо
< 0.50 Неприемлемо

APDEX в отчётах

APDEX рассчитывается как глобально, так и по маршрутам:

{
  "summary": {
    "apdex": {
      "score": 0.92,
      "rating": "Good",
      "satisfied": 4500,
      "tolerating": 350,
      "frustrated": 150,
      "total": 5000,
      "thresholdT": 500
    },
    "per_route_stats": [
      {
        "route": "GET /users/{id}",
        "count": 2500,
        "apdex": {
          "score": 0.98,
          "rating": "Excellent"
        }
      },
      {
        "route": "POST /orders",
        "count": 2500,
        "apdex": {
          "score": 0.86,
          "rating": "Good"
        }
      }
    ]
  }
}

APDEX по маршрутам помогает определить, какие конкретные эндпоинты снижают общую оценку.


Распределённое выполнение

Для масштабных тестов Mockarty поддерживает распределённое выполнение через Runner Agent’ы. Вместо выполнения теста на узле администрирования координатор направляет задачи производительности на удалённые runner-агенты.

Как это работает

  1. Узел администрирования получает запрос на тест и создаёт задачу типа performance.
  2. Координатор (gRPC-сервис на порту 5773) сопоставляет задачу с доступным runner-агентом, имеющим возможность performance.
  3. Runner-агент принимает задачу, выполняет скрипт с помощью perfengine и передаёт обновления прогресса обратно.
  4. Узел администрирования собирает итоговый отчёт и сохраняет его в базе данных.

Настройка Runner Agent

Скачайте бинарь mockarty-runner для вашей платформы со страницы releases и запустите:

COORDINATOR_ADDR=mockarty:5773 \
API_TOKEN=mki_your_integration_token \
RUNNER_NAME=perf-runner-1 \
CAPABILITIES=performance \
PERF_ENABLED=true \
MAX_CONCURRENT_TASKS=4 \
./mockarty-runner
Переменная окружения По умолчанию Описание
COORDINATOR_ADDR gRPC-адрес административной ноды (host:5773) — обязателен для режима grpc
API_TOKEN Токен интеграции типа test_runner (создайте через UI администратора > Интеграции)
RUNNER_NAME mockarty-runner Понятное имя для этого runner’а
CAPABILITIES api_test,performance Возможности через запятую. Должно включать performance, чтобы runner принимал задачи нагрузочного тестирования
PERF_ENABLED true Флаг движка нагрузочного тестирования. Установите false, чтобы отключить load testing на этом runner даже если performance включён в CAPABILITIES
MAX_CONCURRENT_TASKS 3 Максимум одновременных выполнений тестов на этом runner
SHARED true Если true, принимает задачи из всех пространств имён
NAMESPACE Обязателен при SHARED=false. Принимает задачи только для этого пространства имён

Важно. Runner, у которого performance есть в CAPABILITIES, но который запущен с PERF_ENABLED=false, будет отклонять нагрузочные задачи. Оба флага должны совпадать. Используйте PERF_ENABLED=false как быстрый runtime-переключатель, чтобы вывести runner из работы по нагрузочным задачам без перезапуска с другим списком capabilities.

Полный справочник переменных runner (TLS, режим polling, reverse, метки, drain timeout) см. в Интеграции. Подробнее об архитектуре развёртывания — в разделе Архитектура масштабирования.


Распределённые нагрузочные кампании (флот)

Один runner ограничен мощностью одной машины. Нагрузочная кампания распределяет один логический нагрузочный тест по флоту runner-агентов — делит целевую нагрузку на доли по раннерам, запускает их по единым часам и сворачивает частичные результаты в один отчёт. Так создаётся нагрузка, недостижимая для одного раннера.

Как кампания делит нагрузку

Вы задаёте цель на весь флот (суммарные VU, суммарный RPS или план стейджей). Кампания делит каждое количественное измерение между раннерами, сохраняя таймлайн одинаковым на каждом:

  • VU / RPS / итерации делятся — 1 000 000 RPS на 20 раннеров это по 50 000 RPS на каждого, и доли всегда точно суммируются в цель.
  • Длительность, длительности стейджей, пороги общие — поэтому весь флот разгоняется по одним часам, и всплеск в t=30s происходит везде одновременно.

Барьер согласованного старта удерживает каждый раннер (скрипт уже скомпилирован) до общего момента старта, чтобы флот разгонялся синхронно, а не размазывался по задержкам диспетчеризации.

Создание кампании

curl -X POST https://mockarty/api/v1/perf/campaign \
  -H "Authorization: Bearer mki_your_token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "пик оформления заказа",
    "script": "module.exports.default = () => { http.get(\"https://api.example.com/\"); }",
    "options": { "rps": 1000000, "duration": "5m" },
    "fleet": {
      "requiredLabels": ["region=eu"],
      "requestedShards": 20
    }
  }'

Селектор fleet использует те же метки раннеров, что и обычная диспетчеризация (requiredLabels, плюс необязательный labelExpr). requestedShards — на сколько раннеров распределять; кампания деградирует мягко — если из запрошенных N раннеров свободны только M, нагрузка переразбивается на M, и в ответе будет effectiveShards: M, а не ошибка.

Поле Значение
options Цель на весь флот (суммарные vus / rps / stages / duration).
fleet.requiredLabels Во флот входят только раннеры со всеми этими метками.
fleet.labelExpr Необязательное выражение по меткам (например, `linux
fleet.requestedShards На сколько раннеров распределять нагрузку.

Живое управление — меняем И нагрузку, И размер флота на лету

Запущенная кампания полностью динамична. Обе ручки заново прогоняют ту же логику распределения по текущим живым раннерам:

  • Сменить профиль — поднять/опустить суммарные VU или RPS, продлить длительность или заменить весь план стейджей:

    curl -X POST https://mockarty/api/v1/perf/campaign/<id>/load \
      -H "Authorization: Bearer mki_your_token" \
      -d '{ "rps": 2000000, "duration": "10m" }'
    
  • Сменить размер флота — добавить или убрать раннеров прямо во время теста; нагрузка мгновенно перераспределяется на новое число (добавленные раннеры входят по короткому старт-барьеру, чтобы не дать холодный всплеск):

    curl -X POST https://mockarty/api/v1/perf/campaign/<id>/scale \
      -H "Authorization: Bearer mki_your_token" \
      -d '{ "shards": 40 }'
    

Остановить весь флот — POST /api/v1/perf/campaign/<id>/stop.

Наблюдение за кампанией в реальном времени

GET /api/v1/perf/campaign/<id> возвращает текущий снимок кампании + шардов,
а GET /api/v1/perf/campaign/<id>/live стримит тот же payload как
Server-Sent Events — фрейм snapshot каждые пару секунд пока кампания
идёт, затем фрейм terminal на финальном статусе:

curl -N "https://mockarty/api/v1/perf/campaign/<id>/live" \
  -H "Authorization: Bearer mki_your_token"
# event: snapshot
# data: {"campaign":{...},"shards":[...]}
# event: terminal
# data: {"status":"completed"}

Флот-дашборд в UI использует этот стрим автоматически (с откатом на поллинг,
если соединение не устанавливается), поэтому графики и сетка шардов реагируют
в момент завершения шарда.

Чтение отчёта по флоту

GET /api/v1/perf/campaign/<id>/report возвращает единый сводный отчёт:

  • Счётчики (запросы, итерации, байты, счётчики по маршрутам) суммируются.
  • Процент ошибок, средняя задержка, APDEX считаются со взвешиванием по числу запросов, а не простым средним.
  • Перцентили (p50/p90/p95/p99) вычисляются слиянием гистограмм задержек раннеров — единственный статистически корректный способ получить p95 по флоту (усреднять p95 отдельных раннеров нельзя). percentilesExact: true подтверждает, что свою гистограмму прислал каждый раннер.

Общее состояние во флоте (важное ограничение)

Каждый раннер в кампании выполняет тот же скрипт как независимый процесс. Всё, что скрипт держит в памяти, — по раннеру, не общее:

  • SharedArray (и любые данные, загруженные в память при инициализации скрипта) копируются в каждый раннер — каждый получает полный набор данных, а не его срез. Учитывайте это при размере тестовых данных.
  • Внутрискриптовые счётчики, последовательности и логика «использовать строку один раз» локальны для одного раннера и не координируются по флоту.

Чтобы разделять состояние по флоту, используйте внешнее хранилище — поддерживаемый паттерн это Redis. Пусть скрипт читает/инкрементирует ключ Redis (через модуль mockarty/redis), тогда последовательность, пул токенов или множество «занять один раз» будут консистентны для всех раннеров. Слой кампаний намеренно не синхронизирует состояние скрипта в памяти; держите межраннерную координацию в Redis (или другом общем бэкенде) — так модель остаётся простой и масштабируется линейно.


Планирование

Нагрузочные тесты можно запускать по расписанию cron для непрерывного мониторинга производительности.

Создание расписания

Расписания связывают сохранённую конфигурацию производительности с cron-выражением:

// POST /api/v1/perf-schedules
{
  "name": "Nightly Load Test",
  "configId": "config-uuid-here",
  "cronExpression": "0 2 * * *",
  "namespace": "default",
  "enabled": true,
  "options": {
    "vus": 50,
    "duration": "5m"
  }
}

Cron-выражение использует стандартный 5-полевой формат: минута час день-месяца месяц день-недели.

Как работает планирование

  1. Фоновый цикл проверяет расписания каждые 60 секунд.
  2. Когда время nextRunAt расписания наступило, координатор создаёт задачу performance.
  3. Задача направляется на доступный runner-агент (или выполняется локально, если runner’ы не настроены).
  4. После выполнения nextRunAt пересчитывается по cron-выражению.
  5. При некорректном cron-выражении расписание автоматически отключается для предотвращения бесконечных повторных попыток.

Параметры, специфичные для расписания, переопределяют параметры сохранённой конфигурации, позволяя запускать один и тот же скрипт с разными параметрами по разным расписаниям.


Чтение результатов

Сводные метрики

Каждый отчёт теста включает сводку со следующими ключевыми метриками:

Метрика Описание
http_req_duration.avg Среднее время ответа (мс)
http_req_duration.med Медиана (p50) времени ответа
http_req_duration.p90 90-й перцентиль времени ответа
http_req_duration.p95 95-й перцентиль времени ответа
http_req_duration.p99 99-й перцентиль времени ответа
http_req_duration.min Самый быстрый ответ
http_req_duration.max Самый медленный ответ
http_reqs.count Общее количество запросов
http_reqs.rate Запросов в секунду (пропускная способность)
http_req_failed.rate Частота ошибок (0.0-1.0)
data_sent Всего байт отправлено
data_received Всего байт получено
vus_max Пиковое количество одновременных VU
iterations.count Всего итераций выполнено
checks.rate Процент пройденных проверок (0-100%)

Дополнительные показатели

Метрика Описание
stddev_ms Стандартное отклонение времени ответа
cv_pct Коэффициент вариации (%) — чем ниже, тем стабильнее
throughput_stability_cv Коэффициент вариации RPS во времени — чем ниже, тем стабильнее пропускная способность

Разбивка по маршрутам

Отчёт включает статистику по маршрутам для всех протоколов:

  • HTTP: GET /users/{id}, POST /orders
  • gRPC: gRPC mypackage.UserService/GetUser
  • SOAP: SOAP GetWeather
  • MCP: MCP tool get_weather
  • Kafka: Kafka produce orders, Kafka consume orders
  • RabbitMQ: RMQ publish /task_queue, RMQ consume task_queue
  • WebSocket: WS connect /ws, WS send /ws, WS recv /ws
  • SSE: SSE /events

Каждая запись маршрута включает количество, частоту ошибок, перцентили задержки и собственную оценку APDEX.

Данные временных рядов

Отчёт включает данные временных рядов, сэмплированные каждые 3 секунды для построения графиков:

  • Задержка (среднее и p95 во времени)
  • Пропускная способность (запросов в секунду во времени)
  • Активные VU во времени
  • Частота ошибок во времени

Сводки по протоколам

При использовании не-HTTP модулей отчёт включает дополнительные сводки:

{
  "grpc_reqs": { "count": 5000, "err_rate": 0.02, "duration": { "avg": 12.3, "p95": 45.6 } },
  "kafka_msgs": { "total": 10000, "produced": 5000, "consumed": 5000, "err_rate": 0.0 },
  "rmq_msgs": { "total": 8000, "produced": 4000, "consumed": 4000 },
  "ws_msgs": { "connections": 100, "sent": 5000, "received": 4800 },
  "sse_events": { "connections": 0, "events": 25000 }
}

Продвинутые паттерны

Нарастание / Снижение нагрузки (Стресс-тест)

Моделирование постепенного увеличения трафика для поиска точки отказа:

export const options = {
  stages: [
    { duration: '2m', target: 50 },    // Прогрев
    { duration: '3m', target: 200 },   // Нарастание до пика
    { duration: '5m', target: 200 },   // Удержание пика
    { duration: '2m', target: 50 },    // Снижение
    { duration: '1m', target: 0 },     // Охлаждение
  ],
  thresholds: {
    'http_req_duration': ['p(95) < 1000'],
    'http_req_failed': ['rate < 0.05'],
  },
};

Корреляция (Извлечение токенов из ответов)

Связывание запросов путём извлечения значений из одного ответа и использования их в следующем:

var http = require('mockarty/http');

module.exports.default = function () {
  // Шаг 1: Авторизация и извлечение токена
  var loginRes = http.post('https://api.example.com/auth/login',
    JSON.stringify({ username: 'testuser', password: 'testpass' }),
    { headers: { 'Content-Type': 'application/json' } }
  );

  var token = loginRes.json().token;

  // Шаг 2: Использование токена в последующих запросах
  var profileRes = http.get('https://api.example.com/profile', {
    headers: { 'Authorization': 'Bearer ' + token },
  });

  check(profileRes, {
    'profile loaded': function (r) { return r.status === 200; },
  });

  // Шаг 3: Создание заказа
  var orderRes = http.post('https://api.example.com/orders',
    JSON.stringify({ product: 'widget', quantity: 1 }),
    { headers: {
      'Authorization': 'Bearer ' + token,
      'Content-Type': 'application/json',
    }}
  );

  var orderId = orderRes.json().id;

  // Шаг 4: Проверка статуса заказа
  var statusRes = http.get('https://api.example.com/orders/' + orderId, {
    headers: { 'Authorization': 'Bearer ' + token },
  });

  check(statusRes, {
    'order exists': function (r) { return r.status === 200; },
    'order is pending': function (r) { return r.json().status === 'pending'; },
  });
};

Пользовательские метрики

Отслеживание метрик, специфичных для приложения, наряду со встроенными:

var http = require('mockarty/http');

var loginDuration = new Trend('login_duration');
var orderSuccess = new Rate('order_success_rate');
var totalOrders = new Counter('total_orders');

module.exports.options = {
  vus: 20,
  duration: '5m',
  thresholds: {
    'login_duration': ['avg < 300', 'p(95) < 1000'],
    'order_success_rate': ['rate > 0.95'],
  },
};

module.exports.default = function () {
  var start = Date.now();
  var res = http.post('https://api.example.com/login',
    JSON.stringify({ user: 'test', pass: 'test' }),
    { headers: { 'Content-Type': 'application/json' } }
  );
  loginDuration.add(Date.now() - start);

  var orderRes = http.post('https://api.example.com/orders',
    JSON.stringify({ item: 'widget' }),
    { headers: { 'Content-Type': 'application/json' } }
  );

  orderSuccess.add(orderRes.status === 201);
  totalOrders.add(1);

  sleep(1);
};

Data-driven тесты

Используйте SharedArray для загрузки тестовых данных, создаваемых один раз и разделяемых между всеми VU:

var http = require('mockarty/http');
var data = require('mockarty/data');
var faker = require('mockarty/faker');

// Создание 1000 тестовых пользователей один раз, разделяемых между всеми VU
var users = data.SharedArray('test-users', function () {
  var result = [];
  for (var i = 0; i < 1000; i++) {
    result.push({
      email: faker.email(),
      password: faker.password(),
      name: faker.name(),
    });
  }
  return result;
});

module.exports.options = {
  vus: 50,
  duration: '5m',
};

module.exports.default = function () {
  // Каждый VU выбирает случайного пользователя
  var idx = Math.floor(Math.random() * users.length);
  var user = users.get(idx);

  http.post('https://api.example.com/register',
    JSON.stringify(user),
    { headers: { 'Content-Type': 'application/json' } }
  );

  sleep(0.5);
};

Мультипротокольный тест

Тестирование HTTP, gRPC и Kafka в одном скрипте:

var http = require('mockarty/http');
var grpc = require('mockarty/grpc');
var kafka = require('mockarty/kafka');

var grpcClient = grpc.connect('localhost:4770', { reflect: true, plaintext: true });
var kafkaProducer = kafka.producer({ brokers: ['localhost:9092'] });

module.exports.options = {
  vus: 10,
  duration: '2m',
};

module.exports.default = function () {
  // HTTP: Создание заказа через REST
  var orderRes = http.post('http://localhost:8080/api/orders',
    JSON.stringify({ product: 'widget', qty: 1 }),
    { headers: { 'Content-Type': 'application/json' } }
  );

  check(orderRes, {
    'order created': function (r) { return r.status === 201; },
  });

  // gRPC: Проверка остатков через gRPC
  var invRes = grpcClient.invoke('inventory.Service/GetStock', {
    product_id: 'widget',
  });

  check(invRes, {
    'gRPC OK': function (r) { return r.status === 'OK'; },
    'stock available': function (r) { return r.message.quantity > 0; },
  });

  // Kafka: Публикация события заказа
  kafkaProducer.produce({
    topic: 'order-events',
    messages: [{ key: 'widget', value: JSON.stringify({ event: 'order_placed' }) }],
  });

  sleep(1);
};

Реальный пример: Поток оформления заказа в интернет-магазине

Комплексный нагрузочный тест, имитирующий реалистичный путь пользователя в интернет-магазине:

import http from 'k6/http';
import { check, sleep, group } from 'k6';

const BASE_URL = 'https://api.mystore.com';

export const options = {
  stages: [
    { duration: '1m', target: 25 },     // Прогрев
    { duration: '3m', target: 100 },    // Нарастание до стабильного состояния
    { duration: '5m', target: 100 },    // Удержание нагрузки
    { duration: '2m', target: 200 },    // Пиковый трафик
    { duration: '2m', target: 200 },    // Удержание пика
    { duration: '1m', target: 0 },      // Охлаждение
  ],
  thresholds: {
    'http_req_duration': ['p(95) < 800', 'p(99) < 2000'],
    'http_req_failed': ['rate < 0.02'],
    'checks': ['rate > 0.98'],
  },
  abortCriteria: [
    {
      name: 'Error rate too high',
      metric: 'http_req_failed',
      stat: 'rate',
      condition: '>',
      value: 0.3,
      duration: '1m',
      enabled: true,
    },
  ],
};

export default function () {
  const headers = { 'Content-Type': 'application/json' };

  // 1. Авторизация
  let token;
  group('login', function () {
    const loginRes = http.post(BASE_URL + '/auth/login',
      JSON.stringify({
        email: 'loadtest-' + Math.floor(Math.random() * 10000) + '@example.com',
        password: 'TestPass123!',
      }),
      { headers: headers }
    );

    check(loginRes, {
      'login status 200': (r) => r.status === 200,
      'login has token': (r) => r.json().token !== undefined,
    });

    token = loginRes.json().token;
  });

  sleep(1);

  const authHeaders = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + token,
  };

  // 2. Просмотр товаров
  group('browse', function () {
    const listRes = http.get(BASE_URL + '/products?page=1&limit=20', {
      headers: authHeaders,
    });

    check(listRes, {
      'products loaded': (r) => r.status === 200,
      'has products': (r) => r.json().items.length > 0,
    });

    // Просмотр случайного товара
    const products = listRes.json().items;
    const randomProduct = products[Math.floor(Math.random() * products.length)];

    const detailRes = http.get(BASE_URL + '/products/' + randomProduct.id, {
      headers: authHeaders,
    });

    check(detailRes, {
      'product detail loaded': (r) => r.status === 200,
    });
  });

  sleep(2);

  // 3. Добавление в корзину
  let cartId;
  group('add to cart', function () {
    const cartRes = http.post(BASE_URL + '/cart/items',
      JSON.stringify({
        productId: 'prod-001',
        quantity: 2,
      }),
      { headers: authHeaders }
    );

    check(cartRes, {
      'item added to cart': (r) => r.status === 200 || r.status === 201,
    });

    cartId = cartRes.json().cartId;
  });

  sleep(1);

  // 4. Оформление заказа
  group('checkout', function () {
    const checkoutRes = http.post(BASE_URL + '/checkout',
      JSON.stringify({
        cartId: cartId,
        paymentMethod: 'credit_card',
        shippingAddress: {
          street: '123 Load Test Ave',
          city: 'Performance City',
          zip: '12345',
        },
      }),
      { headers: authHeaders }
    );

    check(checkoutRes, {
      'checkout succeeded': (r) => r.status === 200 || r.status === 201,
      'has order ID': (r) => r.json().orderId !== undefined,
    });
  });

  sleep(3);
}

Этот скрипт:

  • Имитирует реалистичное время обдумывания с помощью sleep() между действиями
  • Использует блоки group() для пошаговой отчётности в результатах
  • Извлекает токены аутентификации и использует их между запросами
  • Наращивает трафик через фазы прогрева, стабильного состояния и пика
  • Определяет пороги для p95/p99 задержки и частоты ошибок
  • Включает критерий прерывания для досрочной остановки при превышении 30% ошибок

Как интерпретировать результаты

Запуски тестов – результаты с графиками

После завершения тестового запуска страница результатов отображает графики и сводные таблицы. Вот как их читать:

Начните с оценки APDEX. Это единственное число (от 0 до 1) показывает, были бы пользователи удовлетворены. Выше 0.94 — отлично; ниже 0.70 — сервис требует внимания. Если APDEX низкий, посмотрите разбивку по маршрутам, чтобы найти, какие эндпоинты его снижают.

Проверьте таблицу порогов. Каждый заданный порог отмечен как PASS или FAIL с фактическим значением. Если хотя бы один порог нарушен, общий результат теста — “провален”. Именно эту метрику стоит использовать для блокировки CI/CD-пайплайна.

Читайте график задержки. График отображает среднее и p95 время ответа на протяжении теста. Обращайте внимание на эти паттерны:

  • Ровная линия — здоровый сервис. Время ответа стабильно независимо от нагрузки.
  • Постепенный рост — сервис упирается в лимит ресурсов (CPU, соединения с БД, память). Точка, где задержка начинает расти — это потолок вашей мощности.
  • Внезапный всплеск — что-то сломалось в этот момент (исчерпание пула соединений, пауза сборщика мусора и т.д.). Сопоставьте временную метку с логами приложения.

Читайте график пропускной способности. Он показывает запросы в секунду во времени. Должен соответствовать вашему профилю нарастания VU. Если пропускная способность выходит на плато, а VU продолжают расти — сервис достиг максимальной мощности.

Проверьте частоту ошибок. Частота ошибок выше 1-2% при нормальной нагрузке обычно указывает на баг. Частота ошибок, растущая с нагрузкой, указывает на проблему мощности (таймауты, отказы соединений).

Посмотрите статистику по маршрутам. Некоторые эндпоинты могут быть в 10 раз медленнее других. Таблица по маршрутам показывает количество, перцентили задержки, частоту ошибок и APDEX для каждого маршрута отдельно, позволяя точно определить, какой API является узким местом.


Типовые паттерны тестирования

Smoke-тест (дымовой тест)

Smoke-тест использует минимальную нагрузку (1-2 VU на 30-60 секунд) для проверки корректности скрипта и доступности целевого сервиса. Запускайте его перед каждым полноценным нагрузочным тестом для раннего обнаружения ошибок скрипта или проблем окружения — без траты времени на долгий запуск.

Нагрузочный тест (Load Test)

Нагрузочный тест имитирует нормальный уровень продакшн-трафика (например, 50-200 VU на 5-15 минут) для измерения базовой производительности. Используйте его для установления нормальной пропускной способности и перцентилей задержки вашего сервиса. Запускайте нагрузочные тесты регулярно (ежедневно или при каждом деплое) для обнаружения регрессий производительности до того, как они затронут пользователей.

Стресс-тест (Stress Test)

Стресс-тест постепенно увеличивает нагрузку сверх нормального уровня для поиска точки отказа вашей системы. Используйте этапы нарастания для увеличения VU от нормального до 2-5x вашего ожидаемого пика и наблюдайте, где начинаются всплески задержки или ошибки. Это показывает потолок мощности и то, как сервис деградирует при перегрузке (плавно или катастрофически).

Spike-тест (тест на всплеск)

Spike-тест имитирует внезапный всплеск трафика — например, прыжок с 10 до 500 VU за несколько секунд, удержание 1-2 минуты, затем спад. Он выявляет, как система справляется с резкими изменениями нагрузки: flash-распродажи, вирусные моменты или DDoS-подобные паттерны. Обратите внимание, восстанавливается ли время ответа после прохождения всплеска.

Soak-тест (тест на выдержку)

Soak-тест подаёт умеренную нагрузку в течение длительного периода (от 30 минут до нескольких часов) для выявления проблем, проявляющихся только со временем: утечки памяти, исчерпание пула соединений, рост файлов логов, блокировки в базе данных или проблемы истечения кеша. Если задержка постепенно растёт во время soak-теста — в сервисе есть утечка ресурсов.


Использование CLI

В Mockarty CLI встроена команда нагрузочного тестирования, которая выполняется локально и не требует запущенного сервера:

# Базовый запуск
mockarty-cli perf run script.js

# С параметрами
mockarty-cli perf run script.js --vus 10 --duration 1m --rps 200

# Из HAR-файла
mockarty-cli perf run --har traffic.har --vus 5 --duration 30s

# С экспортом результатов
mockarty-cli perf run script.js --vus 50 --duration 2m --rps 500 \
  --out json:results.json --out html:report.html
Флаг По умолчанию Описание
--vus 1 Количество виртуальных пользователей
--duration 30s Длительность теста (формат Go duration)
--rps 0 Целевая частота запросов в секунду (0 — режим по VU)
--har HAR-файл для генерации сценария нагрузки
--out Формат и файл экспорта. Можно указать несколько раз. Форматы: json:FILE, html:FILE

Ограничения бесплатного тарифа: 5 VU, 100 RPS, максимум 2 минуты. Для снятия ограничений перейдите на PRO.

Live-отправка метрик (Prometheus / InfluxDB / ClickHouse)

Во время прогона CLI может потоково отправлять live-сэмплы метрик во внешнюю time-series БД, чтобы вы наблюдали нагрузочный тест в Grafana (или любом дашборде) в реальном времени. Добавьте один или несколько таргетов --metrics-push в формате <backend>:<url>:

# Prometheus Pushgateway
mockarty-cli perf run script.js --vus 50 --duration 5m \
  --metrics-push prometheus:http://pushgateway:9091

# InfluxDB v1
mockarty-cli perf run script.js \
  --metrics-push influxdb:http://influxdb:8086?db=loadtests

# InfluxDB v2 (org / bucket / token)
mockarty-cli perf run script.js \
  --metrics-push "influxdb2:http://influxdb:8086?org=myorg&bucket=perf&token=MYTOKEN"

# ClickHouse (HTTP-интерфейс)
mockarty-cli perf run script.js \
  --metrics-push clickhouse:http://clickhouse:8123?db=loadtests

# Отправка в несколько бэкендов сразу (флаг повторяемый)
mockarty-cli perf run script.js \
  --metrics-push prometheus:http://pushgateway:9091 \
  --metrics-push influxdb:http://influxdb:8086?db=loadtests
Флаг По умолчанию Описание
--metrics-push Таргет в формате <backend>:<url>. Бэкенды: prometheus, influxdb, influxdb2, clickhouse. Повторяемый.
--metrics-push-interval 10s Как часто отправляется снимок метрик.

Аутентификация. Укажите учётные данные в URL — http://user:pass@host:port?... (HTTP basic auth). InfluxDB v1 также принимает ?u=USER&p=PASS; InfluxDB v2 использует query-параметр token; ClickHouse также принимает ?user=&password=.

Кастомные теги / метки. Любой дополнительный query-параметр становится тегом (InfluxDB) или группирующей меткой (Prometheus). Например ...?db=loadtests&test_name=checkout&env=staging помечает каждую точку test_name=checkout и env=staging — так можно выделить один прогон среди многих в Grafana. ClickHouse пишет ?test_name= в колонку test_name.

Что отправляется. На каждом интервале отправляется текущий снимок: активные VU, запросов/сек, длительность HTTP-запроса (avg / p95 / p99 в мс), доля неуспешных HTTP-запросов, всего запросов и итераций, а также любые кастомные метрики из вашего скрипта. Доля неуспешных запросов (и кастомные метрики типа rate) отправляется как дробь 0–1 — конвенция k6 / Grafana, где 0.02 означает 2 % ошибок — поэтому существующие k6-дашборды читают её корректно. Финальный снимок всегда дописывается по завершении прогона.


Использование SDK

Все SDK предоставляют объект Perf() (или perf) со следующими методами:

Метод Описание
Run(config) Запустить новый нагрузочный тест
Stop(taskID) Остановить выполняющийся тест
Results() Список всех результатов тестов
GetResult(id) Получить отдельный результат по ID
Compare(ids) Сравнить несколько запусков
ListConfigs() Список сохранённых конфигураций
GetConfig(id) Получить конфигурацию по ID
CreateConfig(config) Создать новую конфигурацию
UpdateConfig(id, config) Обновить существующую конфигурацию
DeleteConfig(id) Удалить конфигурацию
DeleteResult(id) Удалить результат
GetResultHistory(configID) Получить историю результатов для конфигурации
GetResultTrend(configID) Получить тренд производительности для конфигурации
ListSchedules() Список всех расписаний
CreateSchedule(schedule) Создать периодическое расписание
UpdateSchedule(id, schedule) Обновить расписание
DeleteSchedule(id) Удалить расписание
RunCollection(req) Запустить тест из коллекции

Пример на Go:

task, err := client.Perf().Run(ctx, &mockarty.PerfConfig{
    Name:     "Load Test",
    Script:   scriptContent,
    VUs:      50,
    Duration: "2m",
})

// Ожидание и получение результатов
result, err := client.Perf().GetResult(ctx, task.ID)

// Сравнение двух запусков
comparison, err := client.Perf().Compare(ctx, []string{result1.ID, result2.ID})

См. также

  • Справочник API — REST API эндпоинты для управления конфигурациями производительности, запуска тестов и получения результатов
  • Интеграции — настройка Runner Agent’ов для распределённого выполнения тестов
  • Справочник Faker — все доступные генераторы фейковых данных для мок-ответов и тестовых данных
  • Архитектура масштабирования — распределённое развёртывание с Runner Agent и резолверами
  • Рекордер — запись трафика и экспорт в скрипты нагрузочного тестирования
  • Фаззинг — автоматизированное тестирование безопасности API
  • Контрактное тестирование — валидация моков против спецификаций и проверка обратной совместимости