Skip to content

Software Testing & QA Tools

k6

k6 is an open-source load testing tool, now maintained by Grafana Labs, where a test is a JavaScript (or TypeScript) file rather than a GUI project: virtual user behavior, checks, and thresholds are all written as code and run from the command line. Its execution engine is written in Go and runs each virtual user as a lightweight goroutine through the goja JavaScript interpreter, rather than as an OS thread, which keeps its resource footprint per virtual user low. Native protocol support covers HTTP/1.1, HTTP/2, WebSocket, and gRPC, with other protocols added through compiled extensions.

Why it matters

Tests live as code, next to the code they test
A k6 script is a .js or .ts file that checks into git, gets code-reviewed, and diffs like any other source file, instead of living as a binary or XML project file.
Built for CI/CD gating, not just producing a report
Thresholds defined in the script make k6 exit with a non-zero status when a performance budget is breached, which is what lets a pipeline fail a build automatically instead of relying on someone reading a report.
A lighter resource footprint per virtual user
The Go-based engine can generate meaningful concurrent load from a single machine, since each virtual user is a lightweight goroutine rather than a full OS thread.
Backed by Grafana's observability stack
k6 metrics stream natively into Prometheus and Grafana dashboards, and into Grafana Cloud k6, which matters for teams already using that stack for monitoring elsewhere.

A test is a JavaScript file, not a GUI project

The k6 engine itself has no GUI for assembling or running a test the way JMeter's tree does — a test is a script with a default exported function that k6 calls repeatedly for each virtual user, plus an `options` object controlling how many virtual users run and for how long. (Grafana does ship a separate desktop app, k6 Studio, that can record a browser or API session and generate a starting script for you, but what it produces is still a JavaScript file that runs through the k6 CLI, not a GUI-native test plan.) Checks act like per-request assertions (did this response return status 200?) without stopping the test on failure, and thresholds are aggregate pass/fail criteria evaluated across the whole run.

JavaScript
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 50,
  duration: '30s',
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
    http_req_failed: ['rate<0.01'],   // error rate under 1%
  },
};

export default function () {
  const res = http.get('https://api.example.com/orders/42');
  check(res, { 'status is 200': (r) => r.status === 200 });
  sleep(1);
}

Virtual users as goroutines, not one-thread-per-user

k6's engine is written in Go and runs each virtual user's script through goja, a Go implementation of a JavaScript interpreter. Modeling a virtual user as a goroutine rather than a full OS or JVM thread is why a single machine running k6 can typically sustain a larger number of concurrent virtual users than a one-thread-per-user model, for the same hardware.

Protocols, extensions, and the newer browser module

Out of the box, k6 natively supports HTTP/1.1 (upgrading automatically to HTTP/2 when the server offers it), WebSocket, and gRPC. Anything outside that set — SQL, Kafka, Redis, MQTT, and other protocols contributed by the community — requires building a custom k6 binary with the xk6 extension toolchain, which is more setup than dropping a plugin into an existing install. k6 also has a newer browser-testing module for driving an actual browser and measuring things like Core Web Vitals, which sits closer to a tool like Playwright than to k6's original protocol-level model.

Where results go

By default, k6 prints a summary to the terminal when a run ends — useful for a quick local check, but not a persistent record. For trends and dashboards over time, results are typically streamed in real time to a backend like Prometheus or InfluxDB, or to Grafana Cloud k6, or exported as JSON for custom processing. The open-source CLI doesn't generate a polished HTML report the way JMeter's dashboard generator does; that gap is filled by the external backends or by Grafana Cloud.

Mistakes people make here

Treating the terminal summary as the only output that matters
The default end-of-run summary is fine for a one-off local check, but the open-source CLI doesn't keep historical results on its own — teams that need trends or dashboards have to stream metrics to a time-series backend or Grafana Cloud, or export JSON.
Using a flat vus/duration for every kind of test
A constant virtual-user count for a fixed duration models a step load, not the ramp-up-hold-ramp-down pattern most real traffic looks like; k6's staged or scenario-based execution options exist specifically to shape load over time, and are usually the more honest model of production traffic.
Assuming k6 covers a protocol out of the box
Only HTTP/1.1, HTTP/2, WebSocket, and gRPC are native. Anything else — a message queue, a database driver, MQTT — needs an xk6-built custom binary first, which is more setup than JMeter's drop-in plugin model for the same kind of protocol.
Setting thresholds so loose they always pass
A threshold like a 95th-percentile response time under five seconds technically 'passes' almost any run and gives false confidence in a CI pipeline; a threshold is only useful when it's set at the performance budget the team actually cares about, not a number chosen to never fail.

Strengths and trade-offs

Where it is strong

  • Test scripts are plain JavaScript or TypeScript, so they diff, get reviewed, and version-control the same as any other source file.
  • Checks and thresholds give an automatic pass/fail signal, making k6 a natural fit for CI/CD gating rather than a report a human has to read.
  • The goroutine-based engine generates load with a lighter per-virtual-user footprint than a one-thread-per-user model.
  • Native, real-time integration with Prometheus and Grafana for metrics and dashboards, on top of the same open-source engine.

The trade-offs

  • Native protocol support stops at HTTP/1.1, HTTP/2, WebSocket, and gRPC; anything else needs an xk6-built custom binary.
  • k6's core engine has no GUI for building or running a test the way JMeter's tree does; Grafana's separate k6 Studio app can record a session and generate a starting script, but the output is still a JavaScript file executed through the CLI — there's no way to build and run a full test purely by pointing and clicking.
  • The default output is a terminal summary; persistent historical reporting or dashboards require wiring up an external backend or Grafana Cloud k6.
  • Large-scale distributed load generation across many machines is built into Grafana Cloud k6, but self-hosting an equivalent distributed run with pure open-source k6 means orchestrating it yourself (for example with the separate k6-operator project on Kubernetes) rather than getting it as a built-in mode.

Who needs this

Developers and SREs who want load tests written and reviewed as code and wired directly into CI/CD, especially teams already using Prometheus or Grafana elsewhere. A team that needs to load-test JDBC, JMS, or mail protocols without building a custom binary will find JMeter's built-in coverage a more direct fit.

Questions about k6

Do I need to know JavaScript to use k6?
Yes — test scripts are written in JavaScript or TypeScript, since that's how k6 defines virtual user behavior, checks, and thresholds. Grafana's k6 Studio can record a session and generate a starting script for you, but the output is still a JavaScript file you run through the k6 CLI, not a no-code alternative to writing one.
Is k6 free and open source?
The core k6 CLI is open source and free to run yourself. Grafana Labs, which maintains the project, also sells Grafana Cloud k6, a hosted service for distributed load generation and long-term result storage and dashboards on top of the same open-source engine.
Can k6 test WebSocket or gRPC APIs, not just REST over HTTP?
Yes, both are supported natively alongside HTTP/1.1 and HTTP/2. Protocols outside that set need a custom k6 binary built with the xk6 extension toolchain.
How does k6 decide whether a test passed or failed?
Through thresholds defined in the script's `options` — for example, a rule that 95% of requests must finish under a given time, or that the error rate must stay under some percentage. If a threshold is breached, k6 exits with a non-zero status, which is what lets a CI pipeline fail the build automatically.

The primary source

Related concepts

← All concept guides