What is a Virtual User?
A VU is a virtualized user that runs your test script. Each VU executes independently and concurrently, running thedefault function in a loop.
VU Lifecycle
Based on the k6 source code inlib/execution.go, VUs have a sophisticated lifecycle:
Initialization
Fromlib/execution.go, k6 tracks VU initialization:
initializedVUs- Total number of initialized VUsactiveVUs- VUs currently executing the test script- VU IDs start from 1 (for backwards compatibility)
- Each VU gets unique local and global identifiers
VU States
A VU transitions through these states:1
Initialized
VU is created and ready to use, stored in the VU buffer.
2
Active
VU is retrieved from the buffer and executing the default function.
3
Returned
VU completes an iteration and returns to the buffer for reuse.
Planned vs Unplanned VUs
k6 distinguishes between two types of VUs based on the source code inlib/execution.go:
Planned VUs
Planned VUs are pre-initialized before the test starts. They’re allocated based on your executor configuration.Unplanned VUs
Unplanned VUs are initialized dynamically when arrival-rate executors need more VUs than pre-allocated.When k6 initializes an unplanned VU, you’ll see: “Initializing an unplanned VU, this may affect test results”
VU Execution Model
Fromlib/executor/shared_iterations.go, k6 implements a sophisticated VU execution model:
VU Retrieval
TheGetPlannedVU() function retrieves VUs from a shared buffer channel:
- VUs wait in a buffer channel when not in use
- Executors borrow VUs when needed
- VUs are returned to the buffer after use
- If retrieval takes longer than 400ms, k6 logs a warning
VU Activation
When a VU starts an iteration:- It’s retrieved from the buffer
- The
activeVUscounter increments - It executes the default function
- The
activeVUscounter decrements - It returns to the buffer
Configuring VUs
Constant VUs
Maintain a fixed number of VUs for the entire test:Ramping VUs
Gradually increase or decrease VUs using stages fromexamples/stages.js:
Per-Scenario VUs
Different scenarios can use different VU configurations:VU Metrics
k6 automatically emits VU-related metrics defined inmetrics/builtin.go:
vus
Current number of active VUs (Gauge metric):
vus_max
Maximum number of VUs initialized during the test (Gauge metric).
VU Iterations
Fromlib/execution.go, k6 tracks two types of iterations:
Full Iterations
Iterations that complete normally are counted infullIterationsCount:
Interrupted Iterations
Iterations cut short by:- Test duration ending
- VU ramping down
- Manual test interruption (Ctrl+C)
- Threshold failures with
abortOnFail
interruptedIterationsCount and reflected in the dropped_iterations metric.
VU Resources and Memory
Each VU maintains its own:- JavaScript runtime (Sobek/goja)
- Execution context
- Variable scope
- Iteration state
VU Execution Patterns
Shared Iterations
Fromlib/executor/shared_iterations.go, VUs share a total iteration count:
Per-VU Iterations
Each VU executes a specific number of iterations:Advanced VU Concepts
VU Identifiers
Fromlib/execution.go, each VU receives unique identifiers:
VU Tags
All metrics emitted by a VU are automatically tagged:Graceful Stop
When ramping down, VUs complete their current iteration before stopping:Best Practices
1
Start Small
Begin with a small number of VUs and gradually increase to find limits.
2
Use Stages for Realistic Load
Ramp up VUs gradually to simulate realistic traffic patterns.
3
Monitor VU Metrics
Watch
vus and vus_max to ensure your test runs as expected.4
Consider Arrival Rates
For consistent throughput, use arrival-rate executors instead of VU-based executors.