A modern load testing tool, using Go and JavaScript

"like unit testing, for performance"

k6 is a modern load testing tool, building on Load Impact's years of experience. It provides a clean, approachable JavaScript scripting API, distributed and cloud execution, and orchestration via a REST API.

Running k6

Running k6 the first time

docker run loadimpact/k6 run github.com/loadimpact/k6/samples/http_get.js

Note that you can click the "Docker image" tab in the example above, to see what the same command would look like using our docker image

The above command is actually more complicated than it seems. What happens is k6 fetches the http_get.js file from Github and only then fires up 1 virtual user (VU) which gets to execute the script once. As you can see here, k6 assumes that the file is a remote file that needs to be fetched over HTTP(s) before it can be executed by k6.

Executing local scripts

But k6 can also execute local files, of course. Try copying the code below, paste it into your favourite editor, and save it as "script.js":

Docker syntax

When using the k6 docker image, the script file name given should be -. This tells k6 to read from stdin. Then you use < before the actual file name, to redirect the file to stdin for the docker process.

Note: reading from stdin doesn't work if you want to use JS modules as those won't be found by k6 (running inside of Docker) at time of import. To use modules you need to first mount your host/local directory into the Docker container, see Modules with Docker.

k6 works with the concept of virtual users (VUs), which run scripts - they're essentially glorified, parallel while(true) loops. Scripts are written using JavaScript, as ES6 modules, which allows you to break larger tests into smaller pieces, or make reusable pieces as you like.

Scripts must contain, at the very least, a default function - this defines the entry point for your VUs, similar to the main() function in many other languages:

export default function() {
// do things here...
}

The init context and the default function

"Why not just run my script normally, from top to bottom", you might ask - the answer is: we do, but code inside and outside your default function can do different things.

Code insidedefault is called "VU code", and is run over and over for as long as the test is running. Code outside of it is called "init code", and is run only once per VU.

VU code can make HTTP requests, emit metrics, and generally do everything you'd expect a load test to do - with a few important exceptions: you can't load anything from your local filesystem, or import any other modules. This all has to be done from init code.

There are two reasons for this. The first is, of course: performance.

If you read a file from disk on every single script iteration, it'd be needlessly slow; even if you cache the contents of the file and any imported modules, it'd mean the first run of the script would be much slower than all the others. Worse yet, if you have a script that imports or loads things based on things that can only be known at runtime, you'd get slow iterations thrown in every time you load something new.

But there's another, more interesting reason. By forcing all imports and file reads into the init context, we design for distributed execution. We know which files will be needed, so we distribute only those files. We know which modules will be imported, so we can bundle them up from the get-go. And, tying into the performance point above, the other nodes don't even need writable filesystems - everything can be kept in-memory.

As an added bonus, you can use this to reuse data between iterations (but only for the same VU):

var counter = 0;
export default function() {
counter++;
}

Using options

If you want to avoid having to type --vus 10 and --duration 30s all the time, you can include those settings inside your JavaScript file also:

The above will generate a couple of extra output lines after the test, telling you if your check conditions succeeded or failed during the test. If the check conditions never failed, you will see this:

And if a check condition failed a few times, it will instead look like this:

If you are using Load Impact Insights you can also see how a given check has failed or passed during the test run: