Đây là bài viết số 3 trong series K6 Performance Testing:

Trước đó chúng ta đã tìm hiểu và thực hiện các bài kiểm thử dựa trên công cụ K6. Sau mỗi lần chạy kiểm thử, K6 sẽ hiển thị kết quả trực tiếp trên console dưới dạng văn bản thô (plaintext) cùng các chỉ số phân tích khác nhau. Hiểu được kết quả của một bài kiểm thử là một bước để xác nhận rằng liệu ứng dụng được kiểm thử đã thực sự vượt qua các yêu cầu phi chức năng hay chưa. Nếu bạn thấy kết quả hiển thị trên console chưa thực sự trực quan, K6 có thể cung cấp các cách trích xuất và lưu lại kết quả ở các định dạng khác nhau cho mục đích phân tích, báo cáo.

Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu:

  • Ý nghĩa của các thông tin trong kết quả trả về của K6
  • Các định dạng xuất báo cáo mà K6 hỗ trợ như JSON, CSV, Prometheus,…

Lưu ý: trong bài viết sẽ sử dụng từ khóa output để thay thế cho cụm từ “kết quả”.

Bảng thuật ngữ

Thuật ngữGiải thích
OutputKết quả của bài kiểm thử bởi K6
Built-inĐề cập tới tính năng, thành phần có sẵn trong công cụ K6
MetricsThông số hoạt động, kết quả của cuộc kiểm thử

Thông tin trong K6 Output

Dưới đây là một output đã được tạo ra từ bài kiểm tra ở bài viết K6 Performance Testing - Nhập môn:

  
         /\      Grafana   /‾‾/  
    /\  /  \     |\  __   /  /   
   /  \/    \    | |/ /  /   ‾‾\ 
  /          \   |   (  |  (‾)  |
 / __________ \  |_|\_\  \_____/ 

     execution: local
        script: first_test.js
        output: -

     scenarios: (100.00%) 1 scenario, 50 max VUs, 3m30s max duration (incl. graceful stop):
              * default: 50 looping VUs for 3m0s (gracefulStop: 30s)

     ✓ status is 201

     checks.........................: 100.00% 7122 out of 7122
     data_received..................: 5.7 MB  31 kB/s
     data_sent......................: 800 kB  4.4 kB/s
     http_req_blocked...............: avg=794.73µs min=90ns     med=510ns    max=190.69ms p(90)=922ns    p(95)=1.16µs  
     http_req_connecting............: avg=344.43µs min=0s       med=0s       max=86.06ms  p(90)=0s       p(95)=0s      
   ✓ http_req_duration..............: avg=266.7ms  min=221.4ms  med=240.35ms max=785.97ms p(90)=303.18ms p(95)=314.48ms
       { expected_response:true }...: avg=266.7ms  min=221.4ms  med=240.35ms max=785.97ms p(90)=303.18ms p(95)=314.48ms
     http_req_failed................: 0.00%   0 out of 7122
     http_req_receiving.............: avg=631.32µs min=17.03µs  med=82.09µs  max=432.25ms p(90)=138.69µs p(95)=174.29µs
     http_req_sending...............: avg=74.19µs  min=18.96µs  med=69.29µs  max=3.48ms   p(90)=114.19µs p(95)=133.43µs
     http_req_tls_handshaking.......: avg=347.82µs min=0s       med=0s       max=90.19ms  p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=266ms    min=220.94ms med=240.15ms max=767.16ms p(90)=302.57ms p(95)=312.99ms
     http_reqs......................: 7122    39.291372/s
     iteration_duration.............: avg=1.26s    min=1.22s    med=1.24s    max=1.94s    p(90)=1.3s     p(95)=1.31s   
     iterations.....................: 7122    39.291372/s
     vus............................: 16      min=16           max=50
     vus_max........................: 50      min=50           max=50

running (3m01.3s), 00/50 VUs, 7122 complete and 0 interrupted iterations
default ✓ [======================================] 50 VUs  3m0s

Output của chúng ta được hiển thị theo định dạng mặc định trên console, bao gồm các dòng thông tin. Từng dòng một, chúng ta cùng nhau phân tích!

Các tham số cấu hình

5 dòng đầu tiên không bao gồm hình ảnh logo của K6 là những giá trị tham số cấu hình của bài kiểm thử.

execution: local

Tham số execution chỉ ra môi trường chạy kịch bản test. Vì chúng ta đang sử dụng bản K6 OSS (open source) nên giá trị hiển thị là local , nghĩa là kịch bản của test được thực thi trên máy người dùng. Đối với bản K6 Cloud, bạn sẽ thấy giá trị hiển thị là cloud .

script: first_test.js

Một tham số dễ hiểu, đây là tên tệp tin chứa kịch bản đã được thực thi.

output: -

Ký tự - chỉ ra hành vi mặc định về định dạng hiển thị kết quả. Ở phần sau của bài viết, chúng ta sẽ lưu trữ kết quả ở các định dạng khác nhau và cùng theo dõi giá trị của tham số output .

scenarios: (100.00%) 1 scenario, 50 max VUs, 3m30s max duration (incl. graceful stop):

Một scenario(kịch bản) là một tập các hướng dẫn về việc chạy kịch bản kiểm thử: đoạn code nào sẽ chạy, khi nào và tần suất đoạn code sẽ chạy, … Trong trường hợp của chúng ta, có:

  • Một scenario được cấu hình trong bài test
  • Tối đa 50 virtual users (VUs) - mỗi VU giả lập một người dùng chạy các vòng lặp kịch bản một cách đồng thời và riêng biệt, tương đương một người dùng thực của ứng dụng.
  • Thời gian chạy tối đa 3 phút 30 giây, trong đó, mặc định K6 sẽ sử dụng 30 giây cho hoạt động graceful stop - khoảng thời gian kết thúc của bài kiểm thử khi k6 hoàn thành các vòng lặp.
* default: 50 looping VUs for 3m0s (gracefulStop: 30s)
  • default đề cập tới tên mặc định cho scenario. Bởi vì chúng ta không có thiết lập về tên của kịch bản trong bài kiểm thử, K6 sử dụng giá trị mặc định.
  • iteration là một lần thực thi trong vòng lặp của bài kiểm thử. Trong load test, để giả lập lượng yêu cầu lớn, các request sẽ được thực thi trong một khoảng thời gian và lặp lại liên tục.

Kết quả tổng quan

Phần thông tin tiếp theo sẽ cho chúng ta biết về kết quả sơ bộ của bài kiểm thử.

✓ status is 201

Hãy nhìn lại một đoạn code trước khi giải thích chi tiết giá trị trên:

  check(response, {
    'status is 201': (r) => r.status === 201,
  });

Điều kiện mà chúng ta đưa vào trong bài kiểm thử là các request HTTP cần phản hồi với mã trạng thái 201. Kết quả kiểm thử của chúng ta đã xác nhận rằng check là hợp lệ, tương ứng là các request phát sinh đã thỏa mãn điều kiện.

checks.........................: 100.00% 7122 out of 7122

100%, tương ứng 7122 requests đáp ứng yêu cầu check.

K6 built-in metrics

Metrics là những thông số về hoạt động của K6 trong quá trình thực thi kiểm thử hiệu năng. Kết quả của một bài kiểm thử cho biết rất nhiều các metrics hữu ích.

     data_received..................: 5.7 MB  31 kB/s
     data_sent......................: 800 kB  4.4 kB/s
     http_req_blocked...............: avg=794.73µs min=90ns     med=510ns    max=190.69ms p(90)=922ns    p(95)=1.16µs  
     http_req_connecting............: avg=344.43µs min=0s       med=0s       max=86.06ms  p(90)=0s       p(95)=0s      
   ✓ http_req_duration..............: avg=266.7ms  min=221.4ms  med=240.35ms max=785.97ms p(90)=303.18ms p(95)=314.48ms
       { expected_response:true }...: avg=266.7ms  min=221.4ms  med=240.35ms max=785.97ms p(90)=303.18ms p(95)=314.48ms
     http_req_failed................: 0.00%   0 out of 7122
     http_req_receiving.............: avg=631.32µs min=17.03µs  med=82.09µs  max=432.25ms p(90)=138.69µs p(95)=174.29µs
     http_req_sending...............: avg=74.19µs  min=18.96µs  med=69.29µs  max=3.48ms   p(90)=114.19µs p(95)=133.43µs
     http_req_tls_handshaking.......: avg=347.82µs min=0s       med=0s       max=90.19ms  p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=266ms    min=220.94ms med=240.15ms max=767.16ms p(90)=302.57ms p(95)=312.99ms
     http_reqs......................: 7122    39.291372/s
     iteration_duration.............: avg=1.26s    min=1.22s    med=1.24s    max=1.94s    p(90)=1.3s     p(95)=1.31s   
     iterations.....................: 7122    39.291372/s

Dưới đây một những metrics có thể nói là quan trọng nhất cho quá trình phân tích bài test.

Thời gian phản hồi - Response time

Thời gian phản hồi là khoảng thời gian từ khi một request được gửi đi cho đến khi nhận được kết quả phản hồi. Một đoạn thời gian, nhưng trong đó là một loạt các hành động diễn ra:

  • Kết nối - tls_handshake: hành động mở kết nối tới máy chủ đích, sau khi kết nối được thiết lập, nó sẽ duy trì cho các request ở những lần tiếp theo.
  • Gửi - send: K6 tạo và gửi đi yêu cầu tới máy chủ tiếp nhận.
  • Chờ - wait: khoảng thời gian mà máy chủ xử lý và gửi ra phản hồi về K6
  • Nhận - receive: K6 nhận & xử lý phản hồi từ máy chủ.

Trong hầu hết tình huống, http_req_duration là metric mà chúng ta tìm kiếm:

http_req_duration..............: avg=266.7ms  min=221.4ms  med=240.35ms max=785.97ms p(90)=303.18ms p(95)=314.48ms

Và cả những chi tiết cho các thành phần thời gian phản hồi khác, chúng ta cũng sẽ thấy trong kết quả:

 http_req_receiving.............: avg=631.32µs min=17.03µs  med=82.09µs  max=432.25ms p(90)=138.69µs p(95)=174.29µs
 http_req_sending...............: avg=74.19µs  min=18.96µs  med=69.29µs  max=3.48ms   p(90)=114.19µs p(95)=133.43µs
 http_req_tls_handshaking.......: avg=347.82µs min=0s       med=0s       max=90.19ms  p(90)=0s       p(95)=0s      
 http_req_waiting...............: avg=266ms    min=220.94ms med=240.15ms max=767.16ms p(90)=302.57ms p(95)=312.99ms

Mỗi dòng đều có chung một định dạng:

  • avg : thời gian phản hồi trung bình của tất cả request, bao gồm cả các request thành công và thất bại.
  • min : thời gian phản hồi nhanh nhất của một request
  • med : thời gian phản hồi trung vị của tất cả request, bao gồm cả các request thành công và thất bại.
  • max : thời gian phản hồi chậm nhất của một request
  • p(90) : thời gian phản hồi của 90% lượng requests, ví dụ p(90)=303.18ms có nghĩa rằng 90% lượng requests có thời gian phản hồi ít hơn hoặc bằng 303.18 mili giây.
  • p(95) : thời gian phản hồi của 95% lượng requests, ví dụ p(95)=314.48ms có nghĩa rằng 95% lượng requests có thời gian phản hồi ít hơn hoặc bằng 314.48 mili giây.

Bài test K6 không chỉ gửi đi chỉ một request duy nhất, mà có rất nhiều requests được gửi đi. Thời gian phản hồi của những request này là biến thiên, có những request nhận phản hồi nhanh, có những request nhận phản hồi chậm, và có cả những request không nhận được phản hồi. Các giá trị p(xx) thể hiện mức thời gian phản hồi cho một lượng xx requests. Giá trị này sẽ hữu ích hơn so với avgmed khi chúng ta mô tả yêu cầu hệ thống rằng: xx tỉ lệ người dùng cần được đảm bảo thời gian phản hồi không quá lâu.

Tỉ lệ lỗi - Error rate

Tỉ lệ lỗi là số lượng request không thành công trong tổng số requests phát sinh trong bài test, đơn vị %. Trong K6, các HTTP response code trong khoảng 200 tới 300 được ghi nhận cho các request thành công. Điều này có nghĩa các mã 4xx và 5xx trong các phản hồi trả về bởi máy chủ sẽ được coi là lỗi. Tuy nhiên, nếu các HTTP response code của bạn khác với các giá trị mặc định của K6, hãy sử dụng tính năng **setResponseCallback(**https://grafana.com/docs/k6/latest/javascript-api/k6-http/set-response-callback/).

http_req_failed................: 0.00%   0 out of 7122

Trong kết quả của chúng ta, một kết quả đẹp với 0% tỉ lệ lỗi, hay nói các khác, tất cả các request đều có phản hồi thành công.

Số lượng request - Number of requests

http_reqs......................: 7122    39.291372/s

7112 là tổng số lượng requests được gửi đi bởi tất cả virtual users trong quá trình test. Thêm nữa, chúng ta có thông tin về số lượng requests trên 1 giây (requests per second - rps): 39.291372 requests trên 1 giây. Giá trị này giúp bạn định lượng về lượng tải mà hệ thống của bạn đã trải qua, góp phần giúp bạn xác định ngưỡng chịu tải của hệ thống.

Thời lượng mỗi vòng lặp - Iteration duration

iteration_duration.............: avg=1.26s    min=1.22s    med=1.24s    max=1.94s

Dòng thông tin này đưa cho chúng ta những lượng thời gian để hoàn thành một vòng lặp. Về định dạng, các giá trị avg, min, med, max tương tự như các giá trị đo lường thời gian http_req_duration, http_req_sending ,…

Một vòng lặp sẽ phức tạp hơn so với một một HTTP request. Ví dụ, một vòng lặp gồm các bước đăng nhập, tìm kiếm sản phẩm, thêm sản phẩm vào giỏ hàng, tiến thành thanh toán, nhập thông tin thanh toán, xác nhận đặt hàng. Hi vọng ví dụ trên gợi ý cho bạn rằng, người dùng sẽ cần một loạt các thao tác để hoàn tất một chức năng trong ứng dụng thay vì chỉ gửi duy nhất 1 HTTP request. Khi áp dụng điều này, chúng ta sẽ có một kịch bản kiểm tra cho đầy đủ luồng của một chức năng trong hệ thống.

Số lượng vòng lặp - Number of iteration

iterations.....................: 7122    39.291372/s

Trong bài kiểm thử của chúng ta, có 7122 vòng lặp cho kịch bản, con số này tính chung cho tất của các virtual users. Số 39.291372/s là số lượng vòng lặp thực hiện trong 1 giây. Tương tự như số lượng requests trên 1 giây, số lượng vòng lặp trong 1 giây nói về số lượng hành vi người dùng ứng dụng có thể đạt được trong thời gian 1 giây.

Tổng quan quá trình thực thi

running (3m01.3s), 00/50 VUs, 7122 complete and 0 interrupted iterations
default ✓ [======================================] 50 VUs  3m0s

Đây là tổng quan những gì đã xảy ra trong bài test.

  • Thời gian bài test kéo dài 3 phút 1.3 giây
  • 50 virtual user đã hoàn tất các vòng lặp và ngừng chạy khi kết thúc bài test.
  • 7122 vòng lặp đã hoàn thành, trong đó không xuất hiện vòng lặp lỗi.
  • default scenario đã thực thi thành công với 50 virtual users trong 3 phút.

Output formats

Trong quá trình kịch bản kiểm thử thực thi, K6 luôn liên tục phát ra metrics ở mỗi điểm của bài test. Dựa trên metrics này, có 2 phân loại chính trong K6 output:

  • end-of-test: các metrics được tổng hợp lại và kết quả được hình thành ở cuối bài test. Dạng output này chúng ta đã bắt gặp ở phần trước, kết quả tổng quan được hiển thị ở console.
  • realtime: các metrics được lưu trữ theo thời gian(timestamp), là nguồn dữ liệu xây dựng nên các biểu đồ theo thời gian thực trên các ứng dụng tích hợp như Prometheus, Grafana,…

Trong bài viết này, chúng ta đề cập tới 2 định dạng kết quả của end-of-test: CSV và JSON.

CSV

Kết quả của cuộc kiểm thử lưu trữ trong tệp tin CSV cho phép hiển thị dữ liệu trực quan thông qua các ứng dụng như Google Sheets, Excel,… Đồng thời, dữ liệu có cấu trúc trong CSV làm đơn giản hóa quá trình đọc và phân tích kết quả.

Để lưu trữ kết quả của bài test ở định dạng tệp tin CSV, hãy sử dụng thêm option --out trong câu lệnh khởi động bài kiểm thử như sau:

k6 run first_test.js --out csv=results.csv

Bạn có thể sử dụng option ở định dạng rút gọn -o thay cho --out .

Sau khi bài test hoàn tất, bạn sẽ nhận được tệp tin results.csv , chứa nội dung văn bản tương tự như sau:

metric_name,timestamp,metric_value,check,error,error_code,expected_response,group,method,name,proto,scenario,service,status,subproto,tls_version,url,extra_tags,metadata
http_reqs,1740503694,1.000000,,,,true,,POST,https://jsonplaceholder.typicode.com/posts,HTTP/2.0,default,,201,,tls1.3,https://jsonplaceholder.typicode.com/posts,,
http_req_duration,1740503694,230.209464,,,,true,,POST,https://jsonplaceholder.typicode.com/posts,HTTP/2.0,default,,201,,tls1.3,https://jsonplaceholder.typicode.com/posts,,
http_req_blocked,1740503694,97.038177,,,,true,,POST,https://jsonplaceholder.typicode.com/posts,HTTP/2.0,default,,201,,tls1.3,https://jsonplaceholder.typicode.com/posts,,
http_req_connecting,1740503694,32.280113,,,,true,,POST,https://jsonplaceholder.typicode.com/posts,HTTP/2.0,default,,201,,tls1.3,https://jsonplaceholder.typicode.com/posts,,
http_req_tls_handshaking,1740503694,32.649930,,,,true,,POST,https://jsonplaceholder.typicode.com/posts,HTTP/2.0,default,,201,,tls1.3,https://jsonplaceholder.typicode.com/posts,,
http_req_sending,1740503694,0.095231,,,,true,,POST,https://jsonplaceholder.typicode.com/posts,HTTP/2.0,default,,201,,tls1.3,https://jsonplaceholder.typicode.com/posts,,
http_req_waiting,1740503694,229.992251,,,,true,,POST,https://jsonplaceholder.typicode.com/posts,HTTP/2.0,default,,201,,tls1.3,https://jsonplaceholder.typicode.com/posts,,

Mỗi dòng trong kết quả đại diện cho một metric và các giá trị theo thời gian.

Nếu chúng ta mở tệp tin csv bằng excel, dữ liệu kiểm thị sẽ trực quan hơn.

JSON

Để lưu trữ kết quả tổng quan của bài kiểm thử ở định dạng tệp tin JSON, hãy sử dụng thêm option --out trong câu lệnh khởi động của K6 như sau:

k6 run first_test.js --out json=results.json

Nội dung tệp tin JSON sẽ tương tự như sau:

{"type":"Metric","data":{"name":"http_reqs","type":"counter","contains":"default","thresholds":[],"submetrics":null},"metric":"http_reqs"}
{"metric":"http_reqs","type":"Point","data":{"time":"2025-06-13T14:30:02.393739661+07:00","value":1,"tags":{"expected_response":"true","group":"","method":"POST","name":"https://jsonplaceholder.typicode.com/posts","proto":"HTTP/2.0","scenario":"default","status":"201","tls_version":"tls1.3","url":"https://jsonplaceholder.typicode.com/posts"}}}
{"type":"Metric","data":{"name":"http_req_duration","type":"trend","contains":"time","thresholds":["p(95)<500"],"submetrics":[{"name":"http_req_duration{expected_response:true}","suffix":"expected_response:true","tags":{"expected_response":"true"}}]},"metric":"http_req_duration"}
{"metric":"http_req_duration","type":"Point","data":{"time":"2025-06-13T14:30:02.393739661+07:00","value":248.205248,"tags":{"expected_response":"true","group":"","method":"POST","name":"https://jsonplaceholder.typicode.com/posts","proto":"HTTP/2.0","scenario":"default","status":"201","tls_version":"tls1.3","url":"https://jsonplaceholder.typicode.com/posts"}}}
{"type":"Metric","data":{"name":"http_req_blocked","type":"trend","contains":"time","thresholds":[],"submetrics":null},"metric":"http_req_blocked"}
{"metric":"http_req_blocked","type":"Point","data":{"time":"2025-06-13T14:30:02.393739661+07:00","value":74.821556,"tags":{"expected_response":"true","group":"","method":"POST","name":"https://jsonplaceholder.typicode.com/posts","proto":"HTTP/2.0","scenario":"default","status":"201","tls_version":"tls1.3","url":"https://jsonplaceholder.typicode.com/posts"}}}
{"type":"Metric","data":{"name":"http_req_connecting","type":"trend","contains":"time","thresholds":[],"submetrics":null},"metric":"http_req_connecting"}
{"metric":"http_req_connecting","type":"Point","data":{"time":"2025-06-13T14:30:02.393739661+07:00","value":28.75016,"tags":{"expected_response":"true","group":"","method":"POST","name":"https://jsonplaceholder.typicode.com/posts","proto":"HTTP/2.0","scenario":"default","status":"201","tls_version":"tls1.3","url":"https://jsonplaceholder.typicode.com/posts"}}}
{"type":"Metric","data":{"name":"http_req_tls_handshaking","type":"trend","contains":"time","thresholds":[],"submetrics":null},"metric":"http_req_tls_handshaking"}
{"metric":"http_req_tls_handshaking","type":"Point","data":{"time":"2025-06-13T14:30:02.393739661+07:00","value":32.372326,"tags":{"expected_response":"true","group":"","method":"POST","name":"https://jsonplaceholder.typicode.com/posts","proto":"HTTP/2.0","scenario":"default","status":"201","tls_version":"tls1.3","url":"https://jsonplaceholder.typicode.com/posts"}}}

Mỗi dòng là một metric hoặc một point.

  • Metric: định nghĩa cấu hình của metrics, ví dụ: tên metric, loại metric,… Một metric có thể là built-in metrics(https://k6.io/docs/using-k6/metrics/#built-in-metrics) hoặc là metric bổ sung định nghĩa bởi người dùng.
  • Point: sử dụng đo lường cho metric, một point bao gồm giá trị của một metric tại một thời điểm.

Trong phân loại end-of-test, ngoài định dạng CSV và JSON, chúng ta có thể tùy chỉnh định dạng lưu trữ theo ý muốn cho kết quả bài test thông qua chức năng Custom Summary(https://grafana.com/docs/k6/latest/results-output/end-of-test/custom-summary/).

Tổng kết

Yếu tố hình thành nên một bài kiểm thử thành công không thể thiếu phần trình bày kết quả. Kết quả có tính trực quan, có cấu trúc sẽ giúp kiểm thử viên dễ dàng hơn trong việc phân tích và đưa ra kết luận, báo cáo về hiệu năng của hệ thống.

Hi vọng, thông qua bài viết này, chúng ta sẵn sàng đọc bất kỳ kết quả của bài kiểm thử bằng công cụ K6, tiếp sau đó là hiểu và phân tích, xử lý các dữ liệu này. Và tùy thuộc vào tình huống, chúng ta có thể lưu trữ kết quả ở các dạng khác nhau cho các mục đích khác nhau.

Trong bài viết, chúng ta đã va chạm với định dạng output realtime về mặt khái niệm. Ở bài viết tiếp theo, sẽ hấp dẫn hơn khi chúng ta tiếp tục tìm hiểu thêm về định dạng kết quả của K6 và trực quan hóa chúng thông qua các nội dung thực hành.