K6 Built-in Output Formats

7 min read

By default, K6 prints a text summary to the terminal when the test ends. That is useful during development but not enough for CI pipelines, stakeholder reports, or time-series analysis. This lesson covers every output format K6 supports and when to use each.

Choosing an output format

K6 output formats

Text summary (default)

  • Printed to stdout automatically

  • No flags required

  • Human-readable metric table

  • No time-series data — aggregate only

  • Best for: local development, quick checks

JSON (--out json)

  • One JSON event per line (NDJSON)

  • Every metric sample included

  • Full time-series data

  • Processable with jq, Python, custom scripts

  • Best for: custom analysis, CI artifact storage

CSV (--out csv)

  • One row per metric sample

  • time,metric_name,metric_type,tags,value columns

  • Importable into Excel or Google Sheets

  • Large files for long tests with many metrics

  • Best for: ad-hoc analysis, sharing with analysts

Custom (handleSummary)

  • Override the end-of-test summary

  • Write HTML, JSON, or any format

  • Access all aggregated metrics in data object

  • Best for: HTML reports, custom CI output, stakeholder artifacts

JSON output

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

The file contains one JSON object per line. Each line is a metric point:

{"type":"Point","metric":"http_req_duration","data":{"time":"2024-01-15T10:00:01.234Z","value":187.5,"tags":{"method":"GET","name":"Homepage","status":"200","url":"https://test.k6.io"}}}
{"type":"Point","metric":"http_req_failed","data":{"time":"2024-01-15T10:00:01.235Z","value":0,"tags":{"method":"GET","name":"Homepage","status":"200"}}}

Parse this with jq to extract specific data:

# 95th percentile of all http_req_duration values
jq -s '[.[] | select(.metric == "http_req_duration") | .data.value] | sort | .[length * 0.95 | floor]' results.json

CSV output

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

One row per metric sample, formatted as:

metric_name,timestamp,metric_value,check,error,error_code,expected_response,group,method,name,proto,scenario,service,status,subproto,tls_version,url,extra_tags
http_req_duration,1705312801234,187.5,,,,true,,GET,Homepage,HTTP/1.1,default,,200,,tls1.3,https://test.k6.io/,

Open in Excel and use pivot tables for ad-hoc percentile analysis, filtering by endpoint name or tag.

Multiple outputs simultaneously

K6 streams to all specified outputs at the same time — useful when you want both a local file and a real-time database:

k6 run --out json=results.json --out csv=results.csv --out influxdb=http://localhost:8086/k6 script.js

Each output receives the same metric data stream independently.

handleSummary — custom end-of-test output

handleSummary is an exported function that receives the aggregated summary data and returns an object mapping filenames (or stdout) to string content:

import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js';
 
export function handleSummary(data) {
  return {
    // Write JSON summary to file
    'summary.json': JSON.stringify(data, null, 2),
 
    // Print the default text summary to stdout (preserves normal terminal output)
    stdout: textSummary(data, { indent: ' ', enableColors: true }),
  };
}

The data object contains all metrics with their statistics:

data.metrics['http_req_duration'].values  // { avg, min, med, max, p(90), p(95), p(99) }
data.metrics['http_req_failed'].values    // { rate, passes, fails }
data.metrics['checks'].values            // { rate, passes, fails }

HTML reports with k6-reporter

The k6-reporter community library generates a self-contained HTML report with charts and tables:

import { htmlReport } from 'https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js';
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js';
 
export function handleSummary(data) {
  return {
    'report.html': htmlReport(data, { title: 'API Load Test — Sprint 42' }),
    stdout: textSummary(data, { indent: ' ', enableColors: true }),
  };
}

Open report.html in any browser. It includes:

  • Pass/fail status for each threshold
  • Metric summary tables with percentiles
  • Charts for request count and duration

This is the most useful format for non-engineers — QA leads, product managers, and developers who do not have Grafana access.

Console logging

K6 supports console.log(), console.warn(), and console.error() inside any function:

export default function () {
  const res = http.get('https://api.example.com/health');
  if (res.status !== 200) {
    console.warn(`Health check failed: ${res.status} — ${res.body.substring(0, 100)}`);
  }
}

Use console logging sparingly. At high VU counts — 200 VUs × 10 iterations each — logging on every iteration produces thousands of lines that slow down the K6 process and obscure useful output. Log only on unexpected conditions (non-200 status, slow responses above a threshold).

⚠️ Common mistakes

  • Relying on text summary for CI pass/fail. The text summary prints to stdout with no exit code information about individual metrics. Use thresholds for CI gates — K6 exits with code 108 when any threshold fails, which CI pipelines detect automatically.
  • Logging on every iteration at high VU counts. console.log() inside the default function at 500 VUs with a 30-second duration can produce 50,000+ log lines. This slows the runtime and obscures useful output. Gate logs with conditions: if (res.status !== 200) console.warn(...).
  • Using --out json for long soak tests without log rotation. A 24-hour soak test at 100 RPS generates millions of JSON lines — potentially gigabytes. Stream to InfluxDB instead, which compresses and aggregates the data, rather than writing raw samples to disk.

🎯 Practice task

Generate multiple output formats and build an HTML report. 30 minutes.

Use https://jsonplaceholder.typicode.com.

  1. Write a script with vus: 5, duration: '30s' that makes GET requests to /posts and /users. Add tags { name: 'ListPosts' } and { name: 'ListUsers' }.
  2. Run with --out json=results.json. Open the file and use grep http_req_duration results.json | wc -l to count how many duration samples were captured.
  3. Run with --out csv=results.csv. Open in a spreadsheet and filter for metric_name = http_req_duration. Sort by metric_value descending to find the 5 slowest requests.
  4. Add handleSummary to the script:
    import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js';
    export function handleSummary(data) {
      return {
        'summary.json': JSON.stringify(data, null, 2),
        stdout: textSummary(data, { indent: ' ', enableColors: true }),
      };
    }
    Run and confirm summary.json is created alongside the normal terminal output.
  5. Add the k6-reporter HTML report. Open report.html in a browser and identify where the p(95) for each tagged endpoint appears.

// tip to track lessons you complete and pick up where you left off across devices.