k6 Telemetry Metrics & Threshold Gates

Tran Van Ngoc|

TL;DR

  • Bản chất: k6 tích hợp telemetry engine thu thập các mốc thời gian TCP/HTTP mặc định kết hợp 4 loại Custom Metric (Counter, Trend, Rate, Gauge).
  • Mục đích: Thiết lập cổng kiểm soát chất lượng (Quality Gate) tự động đánh giá SLA độ trễ và các bất biến nghiệp vụ.
  • Điểm mấu chốt: check() chỉ ghi nhận tỷ lệ thành công (Soft Assertion); thresholds là Hard Assertion quyết định mã thoát CI/CD (Exit Code≠0Exit\ Code \ne 0) và có thể kích hoạt dừng sớm khẩn cấp qua abortOnFail: true.

Core Concept

1. Phân loại 4 Custom Metric Types (k6/metrics)

Metric TypeÝ nghĩa & Hành viTrường hợp sử dụng chuẩn
CounterSố nguyên tích lũy tăng dần (add(n)).Đếm số lượng đơn 201 Created, xung đột 409, lỗi 500.
TrendThu thập thống kê phân phối (min, max, avg, p90,p95,p99p90, p95, p99).Đo lường độ trễ xử lý lock, thời gian phản hồi API.
RateTỷ lệ phần trăm điều kiện đúng/sai (0.0→1.00.0 \rightarrow 1.0).Tỷ lệ request không lỗi (rate > 0.99), cache hit rate.
GaugeLưu giữ giá trị tức thời gần nhất.Đo lường số kết nối DB Pool đang mở, queue backlog.

2. Giải phẫu Độ trễ Mạng (HTTP Timings Anatomy)

[Tổng thời gian http_req_duration]
┌─────────┬────────────┬──────────────────┬─────────────────┬───────────┐
│ blocked │ connecting │ tls_handshaking  │ waiting (TTFB)  │ receiving │
└─────────┴────────────┴──────────────────┴─────────────────┴───────────┘
  • blocked: Chờ socket rảnh trong connection pool hoặc phân giải DNS.
  • connecting + tls_handshaking: Thiết lập TCP 3-way handshake và mã hóa TLS.
  • waiting (Time to First Byte - TTFB): Thời gian server xử lý nghiệp vụ (Distributed Lock, DB Transaction). Chỉ số quyết định hiệu năng backend.
  • receiving: Thời gian đọc dữ liệu phản hồi từ socket mạng.

3. So sánh check() vs thresholds

Tiêu chícheck() (Soft Assertion)thresholds (Hard Assertion / Quality Gate)
Phạm viTừng HTTP response trong VU contextAggregated Metrics sau toàn bộ bài test
Tác động CI/CDKhông làm fail pipeline (Exit Code = 0)Làm fail pipeline (Exit Code = 99 hoặc 1)
Mục đíchBáo cáo tỷ lệ đạt/hỏng, debug payloadThiết lập chốt chặn SLA và bất biến toán học

4. Cấu hình Ngưỡng Nâng cao (abortOnFail & summaryTrendStats)

  • abortOnFail: true: Dừng ngay lập tức toàn bộ bài test khi một ngưỡng quan trọng bị vi phạm (ví dụ: phát hiện lỗi 500 hàng loạt), bảo vệ server khỏi bị quá tải kéo dài.
  • delayAbortEval: "1s": Khoảng trễ trước khi bắt đầu đánh giá điều kiện dừng sớm, tránh abort giả trong 1-2 request khởi động đầu tiên.
  • summaryTrendStats: Tùy chỉnh danh sách các cột phân vị hiển thị trên CLI report (ví dụ: ["min", "avg", "p(95)", "p(99)", "max"]).

Practical Implementation

import { post } from "k6/http";
import { check } from "k6";
import { Counter, Trend } from "k6/metrics";

export const reserve201 = new Counter("reserve_success_201");
export const reserve409 = new Counter("reserve_conflict_409");
export const reserve500 = new Counter("reserve_unexpected_errors");
export const hotSeatDuration = new Trend("hot_seat_duration_ms");

export const options = {
  summaryTrendStats: ["min", "med", "avg", "p(90)", "p(95)", "p(99)", "max"],
  thresholds: {
    // Invariant: Duy nhất 1 ghế đặt thành công
    reserve_success_201: ["count==1"],
    // Invariant: 99 requests còn lại nhận 409
    reserve_conflict_409: ["count==99"],
    // Fail-Fast: Dừng test ngay lập tức nếu xuất hiện bất kỳ lỗi 500 nào
    reserve_unexpected_errors: [
      { threshold: "count==0", abortOnFail: true, delayAbortEval: "1s" },
    ],
    // SLA phân vị p95 cho kịch bản có tag
    "hot_seat_duration_ms{scenario:hot_seat}": ["p(95)<=700", "p(99)<=800"],
  },
};

export default function (): void {
  const params = {
    headers: { "Content-Type": "application/json" },
    tags: { scenario: "hot_seat" },
  };

  const res = post(
    "http://127.0.0.1:3000/reserve",
    JSON.stringify({ seatId: "A1" }),
    params,
  );

  hotSeatDuration.add(res.timings.duration);
  if (res.status === 201) reserve201.add(1);
  else if (res.status === 409) reserve409.add(1);
  else reserve500.add(1);

  check(res, {
    "status is 201 or 409": (r) => r.status === 201 || r.status === 409,
    "no 500 error": (r) => r.status !== 500,
  });
}