v0.27.0
k6 v0.27.0 is here! 🎉
This is a milestone release containing a major overhaul to the execution subsystem of k6, along with many improvements and bug fixes.
New features and enhancements!
New execution engine (#1007)
After 1.5 years in the making, the k6 team is proud to release the first public version of the new execution engine, offering users new ways of modeling advanced load testing scenarios that can more closely represent real-world traffic patterns.
These new scenarios are entirely optional, and the vast majority of existing k6 scripts and options should continue to work the same as before. There are several minor breaking changes and fixes of previously undefined behavior, but please create a new issue if you find some issue we haven't explicitly noted as a breaking change.
See the documentation for details and examples, or keep reading for the summary.
New executors
Some of the currently possible script execution patterns were formalized into standalone executors:
shared-iterations
: a fixed number of iterations are "shared" by all VUs, and the test ends once all iterations are executed. This executor is equivalent to the globalvus
anditerations
(plus optionalduration
) options.constant-vus
: a fixed number of VUs execute as many iterations as possible for a specified amount of time. This executor is equivalent to the globalvus
andduration
options.ramping-vus
: a variable number of VUs execute as many iterations as possible for a specified amount of time. This executor is equivalent to the globalstages
option.externally-controlled
: control and scale execution at runtime via k6's REST API or the CLI.
You'd still be able to use the global vus
, iterations
, duration
, and stages
options, they are not deprecated! They are just transparently converted to one of the above executors underneath the hood. And if your test run needs just a single, simple scenario, you may never need to use more than these shortcut options. For more complicated use cases however, you can now fine-tune any of these executors with additional options, and use multiple different executors in the same test run, via the new scenarios
option, described below.
Additionally, besides the 4 "old" executor types, there are 3 new executors, added to support some of the most frequently requested load testing scenarios that were previously difficult or impossible to model in k6:
per-vu-iterations
: each VU executes a fixed number of iterations (#381).constant-arrival-rate
: iterations are started at a specified fixed rate, for a specified duration. This allows k6 to dynamically change the amount of active VUs during a test run, to achieve the specified amount of iterations per period. This can be very useful for a more accurate representation of RPS (requests per second), for example. See #550 for details.ramping-arrival-rate
: a variable number of iterations are executed in a specified period of time. This is similar to the ramping VUs executor, but instead of specifying how many VUs should loop through the script at any given point in time, the iterations per second k6 should execute at that point in time can be specified.
It's important to also note that all of these executors, except the externally-controlled
one, can be used both in local k6 execution with k6 run
, and in the distributed cloud execution with k6 cloud
. This even includes "old" executors that were previously unavailable in the cloud, like the shared-iterations
one. Now, you can execute something like k6 cloud --iterations 10000 --vus 100 script.js
without any issues.
Execution scenarios and executor options
Multiple execution scenarios can now be configured in a single test run via the new scenarios
option. These scenarios can run both sequentially and in parallel, and can independently execute different script functions, have different executor types and execution options, and have custom environment variables and metrics tags.
An example using 3 scenarios:
import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
scenarios: {
my_web_test: { // some arbitrary scenario name
executor: 'constant-vus',
vus: 50,
duration: '5m',
gracefulStop: '0s', // do not wait for iterations to finish in the end
tags: { test_type: 'website' }, // extra tags for the metrics generated by this scenario
exec: 'webtest', // the function this scenario will execute
},
my_api_test_1: {
executor: 'constant-arrival-rate',
rate: 90, timeUnit: '1m', // 90 iterations per minute, i.e. 1.5 RPS
duration: '5m',
preAllocatedVUs: 10, // the size of the VU (i.e. worker) pool for this scenario
maxVUs: 10, // we don't want to allocate more VUs mid-test in this scenario
tags: { test_type: 'api' }, // different extra metric tags for this scenario
env: { MY_CROC_ID: '1' }, // and we can specify extra environment variables as well!
exec: 'apitest', // this scenario is executing different code than the one above!
},
my_api_test_2: {
executor: 'ramping-arrival-rate',
startTime: '30s', // the ramping API test starts a little later
startRate: 50, timeUnit: '1s', // we start at 50 iterations per second
stages: [
{ target: 200, duration: '30s' }, // go from 50 to 200 iters/s in the first 30 seconds
{ target: 200, duration: '3m30s' }, // hold at 200 iters/s for 3.5 minutes
{ target: 0, duration: '30s' }, // ramp down back to 0 iters/s over the last 30 second
],
preAllocatedVUs: 50, // how large the initial pool of VUs would be
maxVUs: 100, // if the preAllocatedVUs are not enough, we can initialize more
tags: { test_type: 'api' }, // different extra metric tags for this scenario
env: { MY_CROC_ID: '2' }, // same function, different environment variables
exec: 'apitest', // same function as the scenario above, but with different env vars
},
},
discardResponseBodies: true,
thresholds: {
// we can set different thresholds for the different scenarios because
// of the extra metric tags we set!
'http_req_duration{test_type:api}': ['p(95)<250', 'p(99)<350'],
'http_req_duration{test_type:website}': ['p(99)<500'],
// we can reference the scenario names as well
'http_req_duration{scenario:my_api_test_2}': ['p(99)<300'],
}
};
export function webtest() {
http.get('https://test.k6.io/contacts.php');
sleep(Math.random() * 2);
}
export function apitest() {
http.get(`https://test-api.k6.io/public/crocodiles/${__ENV.MY_CROC_ID}/`);
// no need for sleep() here, the iteration pacing will be controlled by the
// arrival-rate executors above!
}
As shown in the example above and the documentation, all executors have some additional options that improve their flexibility and facilitate code reuse, especially in multi-scenario test runs:
- Each executor has a
startTime
property, which defines at what time, relative to the beginning of the whole test run, the scenario will start being executed. - Executors have a new
gracefulStop
property that allows for iterations to complete gracefully for some amount of time after the normal executor duration is over (#879, #1033). Theramping-vus
executor additionally also hasgracefulRampDown
, to give iterations time to finish when VUs are ramped down. The default value for both options is30s
, so it's a slight breaking change, but the old behavior of immediately interrupting iterations can easily be restored by setting these options to0s
. - Different executors can execute different functions other than the
default
exported one. This can be specified by theexec
option in eachscenarios
config, and allows for more flexibility in organizing your tests, easier code reuse, building test suites, etc. - To allow for even greater script flexibility and code reuse, you can specify different environment variables and tags in each scenario, via the new
env
andtags
executor options respectively. - k6 may now emit a new
dropped_iterations
metric in theshared-iterations
,per-vu-iterations
,constant-arrival-rate
andramping-arrival-rate
executors; this is done if it can't run an iteration on time, depending on the configured rates (for the arrival-rate executors) or scenariomaxDuration
(for the iteration-based executors), so it's generally a sign of a poor config or an overloaded system under test (#1529).
We've also introduced new --execution-segment
and --execution-segment-sequence
options, which allow for relatively easy partitioning of test runs across multiple k6 instances. Initially this applies to the test execution (all new executor types are supported!), but opens the door to test data partitioning, an often requested feature. See #997 for more details.
UX
- CLI: There are separate descriptions and real-time thread-safe progress bars for each individual executor.
- CLI: Improve module import error message (#1439).
- JS: The
__VU
variable is now available in the script init context, allowing easier splitting of test input data per VU and reducing RAM usage (#889). - A new method to stop engine execution via the REST API (#1352). Thanks @hynd!
Bugs fixed!
- CLI: Stop
--http-debug
from exiting k6 on request dump error (#1402). Thanks @berndhartzer! - CLI: JSON output is now less noisy (#1469). Thanks @michiboo!
- CLI: k6 doesn't exit when using
iterations
withstages
(#812). - CLI: Mismatch in check counts in the end-of-test summary (#1033).
- Config: Better validation of
stages
(#875). - JS: Rare panics in goja (#867,#1552).
- HTTP: Fix request timeout and wrong context used for pushing metrics (#1260).
- Execution: Fix sometimes skipping iterations with a
context cancelled
error when rapidly ramping up and down (#1283). - WebSockets: Fix possible connection hanging when the main context is cancelled (#1260).
- WebSockets: Avoid leaking goroutines (#1513).
- WebSockets: Emit WS metrics as soon as possible instead of when the connection is closed (#885).
Internals
- As mentioned above, #1007 was almost a complete re-write of the execution scheduling parts of k6. This involved deep architectural changes in how test runs are executed and improved overall code coverage by around 2%.
- Switched to Go 1.14 for building and testing k6, bringing some fixes and performance improvements.
- WebSockets: Updated gorilla/websocket library bringing minor performance improvements (#1515).
- Code cleanup and formatting. Thanks @thinkerou!
Breaking changes
-
Execution config options (
scenarios
,stages
,iterations
,duration
) from "upper" config layers overwrite execution options from "lower" (i.e. CLI flags > environment variables > JS options > JSON options) config layers. For example, the--iterations
CLI flag will overwrite any execution options set as environment variables (e.g.K6_DURATION
,K6_STAGES
, etc.) or script options (stages: [ /* ... */]
,scenarios: { /* ... */ }
, etc. ). -
Previously, if the
iterations
andvus
script options were specified, butduration
was not, the script could have ran practically indefinitely, given a sufficiently large number or length of the used iterations. There was no implicit or explicit time limit, one of the reasons this execution pattern was not allowed ink6 cloud
test runs before. From k6 v0.27.0, by default, if the specified iterations haven't finished, these scripts will abort after 10 minutes, the defaultmaxDuration
value ofshared-iterations
executors. This default value can easily be changed by either setting themaxDuration
option in the correspondingscenarios
entry, or, if just the execution shortcuts were used to run the script, by setting theduration
/--duration
/K6_DURATION
script option. -
Previously, all iterations were interruptible - as soon as the specified
duration
expired, or when VUs were ramped down in a stage, any running iterations were interrupted. Now all executors besides theexternally-controlled
one have agracefulStop
period of30s
by default (#898). Additionally, theramping-vus
executor has agracefulRampDown
parameter that configures the ramp-down grace period. For those periods, no new iterations will be started by the executors, but any currently running iterations will be allowed up to the specified periods to finish their execution. -
Using different execution config options on the same level is now a configuration conflict error and will abort the script. For example, executing
k6 run --duration 10s --stages 5s:20 script.js
won't work (#812). The only exception is combiningduration
anditerations
, which will result in ashared-iterations
executor with the specified non-defaultmaxDuration
(#1058). -
The k6 REST API for controlling script execution (i.e. the
k6 pause
,k6 scale
commands) now only works when aexternally-controlled
executor is configured in thescenarios
config. The initial pausing of a test (i.e.k6 run --paused script.js
) still works with all executor types, but once the test is started withk6 resume
(or the corresponding REST API call), it can't be paused again unless only theexternally-controlled
executor is used. -
Previously, running a script with
k6 run --paused script.js
would have still executed the script'ssetup()
function (if it was present and wasn't explicitly disabled with--no-setup
) and paused immediately after. Now, k6 will pause before it executessetup()
. -
Previously, if you ramped down and ramped up VUs via
stages
, the__VU
global variables would have been incremented on the ramp-ups. This will no longer happen, the max value of__VU
across a test run will never exceed the number of initialized VUs. -
The
vusMax
/K6_VUS_MAX
/-m
/--max
option is deprecated - it was previously used for the control of the initialized VUs by the REST API. Since that has now been restricted to theexternally-controlled
executor, the equivalent option there is calledmaxVUs
. -
Tests with infinite duration are now only possible via the
externally-controlled
executor. -
k6 will now exit with an error if
--address
is specified but the API server is unable to start. Previously this would've resulted in a warning message. -
The format returned by
Date.prototype.toUTCString()
was changed fromThu Jan 01 1970 00:00:00 GMT+0000 (UTC)
toThu, 01 Jan 1970 00:00:00 GMT
. This is a fix that aligns it with the ECMAScript spec. See dop251/goja#119 . -
The default
setupTimeout
andteardownTimeout
values were changed from 10s to 60s (#1356).