Custom метрики и бизнес-KPI
Зачем нужны custom метрики
Стандартные метрики k6 (http_req_duration, http_req_failed) показывают техническое состояние, но не отвечают на бизнес-вопросы:
- Сколько заказов создано за тест?
- Какой средний чек?
- Какой процент отказов на этапе оплаты?
- Сколько пользователей добавили товар в корзину, но не оформили заказ?
Custom метрики позволяют измерять бизнес-KPI прямо в нагрузочных тестах.
Бизнес-метрики ближе к деньгам, чем технические. Stakeholder'ам понятнее «conversion rate упал на 5%», чем «p95 вырос на 200ms».
Типы custom метрик
1. Trend — распределение значений
Используется для: latency, размеров payload, времени операций, стоимости заказов.
import { Trend } from "k6/metrics";
const orderValue = new Trend("order_value");
const checkoutTime = new Trend("checkout_time");
export const options = {
thresholds: {
order_value: ["p(50)>40", "p(95)>100"], // Средний чек > $40, p95 > $100
checkout_time: ["p(95)<2000"], // Checkout < 2s
},
};
export default function () {
const startTime = Date.now();
const res = http.post(
`${BASE_URL}/api/orders`,
JSON.stringify({
items: [
{ sku: "PROD-001", price: 99.99, qty: 1 },
{ sku: "PROD-002", price: 29.99, qty: 2 },
],
}),
{ headers: { "Content-Type": "application/json" } }
);
const duration = Date.now() - startTime;
checkoutTime.add(duration);
if (res.status === 201) {
const order = res.json();
orderValue.add(order.totalAmount); // Записываем сумму заказа
}
}Результат в summary:
order_value.............: avg=78.45 min=15.99 med=65.00 max=299.99 p(95)=150.00 p(99)=199.99
checkout_time...........: avg=1245ms min=800ms med=1200ms max=2100ms p(95)=1800ms p(99)=2000ms2. Counter — счетчик событий
Используется для: количество заказов, количество ошибок, количество успешных операций.
import { Counter } from "k6/metrics";
const ordersCreated = new Counter("orders_created");
const ordersRejected = new Counter("orders_rejected");
const itemsAddedToCart = new Counter("items_added_to_cart");
export const options = {
thresholds: {
orders_created: ["count>100"], // Минимум 100 заказов за тест
orders_rejected: ["count<10"], // Меньше 10 отказов
},
};
export default function () {
// Add to cart
let res = http.post(
`${BASE_URL}/api/cart`,
JSON.stringify({ sku: "PROD-001" }),
{
headers: { "Content-Type": "application/json" },
}
);
if (res.status === 200) {
itemsAddedToCart.add(1);
}
// Checkout
res = http.post(
`${BASE_URL}/api/orders`,
JSON.stringify({ cartId: "cart-123" }),
{
headers: { "Content-Type": "application/json" },
}
);
if (res.status === 201) {
ordersCreated.add(1);
} else {
ordersRejected.add(1);
}
}Результат в summary:
orders_created..........: 458
orders_rejected.........: 12
items_added_to_cart.....: 5123. Rate — доля событий (0..1)
Используется для: error rate, success rate, conversion rate, abandonment rate.
import { Rate } from "k6/metrics";
const checkoutSuccessRate = new Rate("checkout_success_rate");
const paymentFailureRate = new Rate("payment_failure_rate");
const cartAbandonmentRate = new Rate("cart_abandonment_rate");
export const options = {
thresholds: {
checkout_success_rate: ["rate>0.95"], // > 95% успешных checkout
payment_failure_rate: ["rate<0.02"], // < 2% неудач оплаты
cart_abandonment_rate: ["rate<0.30"], // < 30% брошенных корзин
},
};
export default function () {
// Add to cart
let res = http.post(
`${BASE_URL}/api/cart`,
JSON.stringify({ sku: "PROD-001" }),
{
headers: { "Content-Type": "application/json" },
}
);
const addedToCart = res.status === 200;
// Checkout (не все, кто добавил, оформляют заказ)
if (Math.random() > 0.25) {
// 75% оформляют заказ
res = http.post(
`${BASE_URL}/api/orders`,
JSON.stringify({ cartId: "cart-123" }),
{
headers: { "Content-Type": "application/json" },
}
);
const checkoutSuccess = res.status === 201;
checkoutSuccessRate.add(checkoutSuccess);
if (!checkoutSuccess) {
paymentFailureRate.add(1); // Payment failed
} else {
paymentFailureRate.add(0); // Payment success
}
cartAbandonmentRate.add(0); // Completed checkout
} else {
cartAbandonmentRate.add(1); // Abandoned cart
}
}Результат в summary:
checkout_success_rate...: 97.5% ✓ 438 ✗ 12
payment_failure_rate....: 1.2% ✓ 5 ✗ 445
cart_abandonment_rate...: 24.8% ✓ 125 ✗ 3794. Gauge — текущее значение
Используется для: размер очереди, количество активных сессий, current balance.
import { Gauge } from "k6/metrics";
const activeCartSize = new Gauge("active_cart_size");
const currentBalance = new Gauge("current_balance");
export default function () {
const res = http.get(`${BASE_URL}/api/cart`, {
headers: { Authorization: `Bearer ${token}` },
});
if (res.status === 200) {
const cart = res.json();
activeCartSize.add(cart.items.length); // Текущий размер корзины
const balance = res.json("balance");
currentBalance.add(balance); // Текущий баланс
}
}Результат в summary:
active_cart_size........: 3 (последнее значение)
current_balance.........: 150.45 (последнее значение)Продвинутые примеры
Conversion rate funnel
import { Rate, Counter } from "k6/metrics";
const viewedProduct = new Counter("funnel_viewed_product");
const addedToCart = new Counter("funnel_added_to_cart");
const startedCheckout = new Counter("funnel_started_checkout");
const completedOrder = new Counter("funnel_completed_order");
const addToCartConversion = new Rate("conversion_add_to_cart");
const checkoutConversion = new Rate("conversion_checkout");
const overallConversion = new Rate("conversion_overall");
export default function () {
// Step 1: View product
let res = http.get(`${BASE_URL}/products/PROD-001`);
if (res.status === 200) {
viewedProduct.add(1);
// Step 2: Add to cart (70% conversion)
if (Math.random() < 0.7) {
res = http.post(
`${BASE_URL}/api/cart`,
JSON.stringify({ sku: "PROD-001" }),
{
headers: { "Content-Type": "application/json" },
}
);
if (res.status === 200) {
addedToCart.add(1);
addToCartConversion.add(1);
// Step 3: Start checkout (60% из add to cart)
if (Math.random() < 0.6) {
res = http.post(
`${BASE_URL}/api/checkout/start`,
{},
{
headers: { "Content-Type": "application/json" },
}
);
if (res.status === 200) {
startedCheckout.add(1);
// Step 4: Complete order (90% из started checkout)
if (Math.random() < 0.9) {
res = http.post(
`${BASE_URL}/api/orders`,
{},
{
headers: { "Content-Type": "application/json" },
}
);
if (res.status === 201) {
completedOrder.add(1);
checkoutConversion.add(1);
overallConversion.add(1);
return; // Success
}
}
}
}
}
}
// Failed to convert
addToCartConversion.add(0);
checkoutConversion.add(0);
overallConversion.add(0);
}
}Результат:
funnel_viewed_product......: 1000
funnel_added_to_cart.......: 700 (70% от viewed)
funnel_started_checkout....: 420 (60% от added)
funnel_completed_order.....: 378 (90% от started)
conversion_add_to_cart.....: 70.0%
conversion_checkout........: 90.0%
conversion_overall.........: 37.8% (378/1000)Cart abandonment с таймингом
import { Rate, Trend } from "k6/metrics";
const cartAbandonmentRate = new Rate("cart_abandonment_rate");
const timeToAbandon = new Trend("time_to_abandon");
const timeToCheckout = new Trend("time_to_checkout");
export default function () {
const cartStartTime = Date.now();
// Add to cart
let res = http.post(
`${BASE_URL}/api/cart`,
JSON.stringify({ sku: "PROD-001" }),
{
headers: { "Content-Type": "application/json" },
}
);
if (res.status === 200) {
sleep(2 + Math.random() * 3); // Browse time 2-5s
// Decision: checkout or abandon?
if (Math.random() < 0.7) {
// 70% checkout
res = http.post(
`${BASE_URL}/api/orders`,
{},
{
headers: { "Content-Type": "application/json" },
}
);
const duration = Date.now() - cartStartTime;
timeToCheckout.add(duration);
cartAbandonmentRate.add(0); // Completed
} else {
// 30% abandon
const duration = Date.now() - cartStartTime;
timeToAbandon.add(duration);
cartAbandonmentRate.add(1); // Abandoned
}
}
}Результат:
cart_abandonment_rate......: 30.5%
time_to_abandon............: avg=3.2s min=2.1s max=5.4s p(95)=4.8s
time_to_checkout...........: avg=4.5s min=2.3s max=7.8s p(95)=6.9sИнтеграция с tags
import { Counter } from "k6/metrics";
const ordersCreated = new Counter("orders_created");
export default function () {
const res = http.post(
`${BASE_URL}/api/orders`,
JSON.stringify({ payment: "credit_card" }),
{
headers: { "Content-Type": "application/json" },
tags: {
payment_method: "credit_card",
region: "us-east",
},
}
);
if (res.status === 201) {
ordersCreated.add(1, {
payment_method: "credit_card",
region: "us-east",
});
}
}
export const options = {
thresholds: {
"orders_created{payment_method:credit_card}": ["count>50"],
"orders_created{region:us-east}": ["count>80"],
},
};✅ Чек-лист завершения урока
После этого урока вы должны уметь:
Типы метрик:
- Понимать, когда использовать Trend (распределение значений)
- Понимать, когда использовать Counter (количество событий)
- Понимать, когда использовать Rate (доля событий 0..1)
- Понимать, когда использовать Gauge (текущее значение)
Бизнес-метрики:
- Создавать метрики для order value, средний чек
- Измерять conversion rate на разных этапах воронки
- Отслеживать cart abandonment rate
- Считать количество успешных/неудачных операций
Thresholds для бизнес-метрик:
- Устанавливать пороги для conversion rate:
rate>0.95 - Устанавливать пороги для order value:
p(50)>40 - Устанавливать пороги для счетчиков:
count>100
Интеграция:
- Добавлять tags к custom метрикам для фильтрации
- Использовать метрики в thresholds с tags
- Выводить метрики в Grafana для визуализации
Практическое задание:
- Добавьте custom метрику для ключевой бизнес-операции
- Создайте conversion funnel для вашего user journey
- Настройте thresholds для бизнес-KPI
- Запустите тест и проанализируйте бизнес-метрики в summary
Если чек-лист пройден — переходите к уроку 11: научимся анализировать результаты и триангулировать проблемы через observability.