k6

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.

Get Started    Documentation

Search results for "{{ search.query }}"

No results found for "{{search.query}}". 
View All Results

Running k6

Running k6 the first time

To execute a very simple k6 test on the command line, you can do this:

k6 run github.com/loadimpact/k6/samples/http_get.js
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":

import http from "k6/http";
import { sleep } from "k6";

export default function() {
  http.get("http://test.loadimpact.com");
  sleep(1);
};

Then run k6 using this command line:

k6 run 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.

docker run -i loadimpact/k6 run - <script.js

Adding more VUs

Now we'll try running a real load test, with more than 1 virtual user and a slightly longer duration:

k6 run --vus 10 --duration 30s script.js
docker run -i loadimpact/k6 run --vus 10 --duration 30s - <script.js

Running a 30-second, 10-VU load test

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 inside default 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:

import http from "k6/http";
import { sleep } from "k6";

export let options = {
  vus: 10,
  duration: "30s"
};

export default function() {
  http.get("http://test.loadimpact.com");
  sleep(1);
};

Then you just run the script without those parameters on the command line:

k6 run script.js
docker run -i loadimpact/k6 run - <script.js

Stages: ramping up/down VUs

You can also have the VU level ramp up and down during the test. The options.stages property allows you to configure ramping behaviour.

import http from "k6/http";
import { check, sleep } from "k6";

export let options = {
 stages: [
    { duration: "30s", target: 20 },
    { duration: "1m30s", target: 10  },
    { duration: "20s", target: 0 },
  ]
};

export default function() {
  let res = http.get("https://httpbin.org/");
  check(res, {
    "status was 200": (r) => r.status == 200
  });
  sleep(1);
}

Using checks

Maybe we want to verify that an HTTP transaction worked and that the transaction time stayed below some acceptable value. We can use the check() function for this:

import http from "k6/http";
import { check, sleep } from "k6";

export let options = {
  vus: 10,
  duration: "30s"
};

export default function() {
  let res = http.get("http://test.loadimpact.com");
  check(res, {
    "status was 200": (r) => r.status == 200,
    "transaction time OK": (r) => r.timings.duration < 200
  });
  sleep(1);
};

Using check() to verify that transactions work and are fast enough

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:

Running k6