k6 Concepts
Table of Contents
- Why a mental model first
- How: the lifecycle
- How: VU vs. iteration
- How: options vs. CLI vs.
-e - How:
__ENV,__VU,__ITER - The first script, in full
- JMeter → k6 cheat sheet
- Tips
Why a mental model first
k6 scripts look like plain JavaScript, but they don't run like a normal Node program: the same file gets loaded once per VU, a few functions run at fixed points regardless of how many VUs there are, and load shape lives in a config object rather than in a loop you write yourself. Knowing which part of the script runs when — and how many times — is what makes the rest of this track's scripts readable instead of magic.
How: the lifecycle
| Stage | Runs | Typically used for |
|---|---|---|
| Init code | Once per VU, before that VU's iterations start | imports, SharedArray test data, constants — nothing that touches the network |
setup() | Once total, before any VU iterates | logging in as owner@playground.local, resetting the playground, computing data to hand to every VU |
| default function | Once per iteration, for every VU | the actual business flow — requests, checks |
teardown() | Once total, after every VU has finished | cleanup, or asserting something about the final state |
setup() and teardown() are optional — 01-first-test.js uses neither. 03-order-flow.js in the sample uses setup() to reset the playground once before the whole run.
How: VU vs. iteration
A VU (virtual user) is one of the parallel "workers" k6 spins up to run your default function — think of it as one simulated user's execution thread. An iteration is a single run of the default function, start to end, by one VU. options.vus: 1, duration: '10s' means one VU looping its default function for 10 seconds — however many iterations fit in that time, which is exactly what 01-first-test.js reports: 0/1 VUs, 10 complete ... iterations (the 0 is VUs still active at that instant — the final summary line prints after the one VU has finished).
How: options vs. CLI vs. -e
Three ways to configure a run, in order of precedence (highest wins):
- CLI flags —
k6 run --vus 10 --duration 30s script.js - Environment variables —
K6_VUS=10 K6_DURATION=30s k6 run script.js(built-in options only; these map to the same names as the CLI flags, prefixedK6_) - The script's
optionsobject —export const options = { vus: 1, duration: '10s' }, the fallback when nothing else is set
-e NAME=value is a different mechanism: it does not set a built-in option, it sets a custom variable your script reads yourself via __ENV.NAME. That's how every script in k6/sample/ reads BASE_URL, USERS, SCALE and the rest — see k6/README.md for the full list.
How: __ENV, __VU, __ITER
Three globals k6 injects into every script:
__ENV— an object of everything passed with-e, plus the process environment.__ENV.BASE_URL || 'http://127.0.0.1:8080'is the pattern used throughout the sample (seelib/config.js).__VU— the current VU's number, starting at 1.lib/data.jsuses(vu - 1) % customers.lengthto give each VU a customer login, wrapping around once VUs outnumber rows.__ITER— the current iteration number for this VU, starting at 0. Useful for varying behaviour across a VU's own iterations (e.g. only doing something expensive on__ITER === 0).
The first script, in full
k6/sample/01-first-test.js:
import http from 'k6/http';
import { check, sleep } from 'k6';
import { API } from './lib/config.js';
export const options = {
vus: 1,
duration: '10s',
thresholds: {
checks: ['rate==1'], // every check must pass
http_req_duration: ['p(95)<500'],
},
};
export default function () {
const res = http.get(`${API}/menu`);
check(res, {
'status is 200': (r) => r.status === 200,
'menu has pizzas': (r) => r.json('pizzas').length > 0,
});
sleep(1);
}
One VU, one default function, no setup() or teardown() — the smallest complete k6 script. check() records pass/fail without stopping the run; the thresholds block is what actually fails the test (and the process exit code) if either condition is not met.
JMeter → k6 cheat sheet
| JMeter | k6 |
|---|---|
| Thread Group | a scenario / executor in options.scenarios (or vus/stages for the simple case) |
| HTTP Request sampler | http.get(...) / http.post(...) |
| Assertion | check(res, { ... }) |
| JSON Extractor | res.json('path') |
| CSV Data Set Config | SharedArray (see lib/data.js) |
| Timer | sleep(seconds) |
| Transaction Controller | group('name', () => { ... }) |
| Listener | --out <destination> while running, or the end-of-test summary |
User Defined Variables / -J properties | -e KEY=value, read back via __ENV.KEY |
Tips
- k6 is not Node. Scripts run on Sobek, Grafana's fork of the
gojaJavaScript engine, not on Node or V8. It implements the JS spec, not Node's runtime APIs — so an npm package that needsfs, native modules, or other Node-only APIs will not load in a k6 script, no matter how small it looks. Stick to thek6/*modules and plain JS; if you need an npm-style package, check whether Grafana ships an equivalentk6/x/...extension first.