Grafana k6: Load Tests Written as JavaScript Scripts, Executed by a Go Binary
A modern load testing tool, using Go and JavaScript
At a glance
- What is it?
- k6 turns load testing into version-controlled JavaScript that runs on an embedded JS engine inside a Go runtime. It is a strong fit for CI pipelines and protocol-level API testing, and a poor fit for teams that need real browser rendering at thousands of virtual users.
- Who is it for?
- Adopt k6 if your team already writes JavaScript, wants thresholds such as http_req_duration: ["p(99) < 3000"] enforced as pass or fail inside CI, and tests HTTP, WebSockets or gRPC endpoints rather than rendered pages.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Problem k6 Targets: Load Tests That Live Outside Version Control
Load testing has traditionally been split between GUI-driven tools where the test definition lives in a proprietary file, and hand-rolled scripts that generate traffic but report nothing structured. k6's stated design goal in the README is to provide "the best developer experience", and the mechanism for that is treating the test itself as source code. The README lists "Tests as code" as a core feature, with the explicit benefits being reuse, modularization, version control, and CI integration.
The audience is correspondingly narrow. This is for developers and testers who are comfortable writing JavaScript and who want performance assertions to fail a build the same way a unit test does. The README frames it as "Like unit testing, for performance", which is a fair summary of the intent: the test is a file, the pass or fail condition is declared in that file, and the runner is a command-line binary. Teams that want a point-and-click recorder are pointed at a separate project, k6 Studio, described in the README as a desktop application for generating k6 scripts without touching code. That separation is deliberate. The core tool assumes you can read and edit the script.
How the Go Binary and the Embedded JavaScript Engine Fit Together
The architecture is a Go program that embeds a JavaScript engine. The README states this directly: "An embedded JavaScript engine. The performance of Go, the scripting familiarity of JavaScript." The practical consequence is that a script is not sent to a separate interpreter process. It is evaluated inside the same runtime that schedules virtual users and collects metrics, which is why the README claims that even lower-end machines can simulate large amounts of traffic.
Data flow follows a fixed shape. A script exports an options object and a default function. The options object declares thresholds and stages. The default function is the body of a virtual user's iteration. In the README example, the default function issues one HTTP GET through the k6/http module, runs a check on the response status, and then calls sleep(1). The stages array ramps the virtual user count from zero to fifteen over thirty seconds, holds for one minute, and ramps back to zero over twenty seconds. Each iteration is independent and stateless unless the script explicitly shares state, which is the standard model for open and closed workload scenarios referenced in the documentation.
Metrics are produced by the runtime rather than by the script. The README example asserts on http_req_duration, a metric the tool generates automatically for HTTP requests, using the expression p(99) < 3000. That means the script author never writes timing code; they write assertions against counters and distributions the engine already maintains. Results can be viewed as summary statistics or exported in granular form to an external service, per the README's description of flexible metrics storage.
Running a Test: The CLI, the options Object, and the Threshold Expression
The concrete entry points are visible in the README example. A script imports from k6/http and from the k6 module itself:
import http from "k6/http"; import { check, sleep } from "k6";
Configuration is exported, not passed as flags, which is what makes the test portable between a laptop and a CI job:
export const options = { thresholds: { http_req_duration: ["p(99) < 3000"], }, stages: [ { duration: "30s", target: 15 }, { duration: "1m", target: 15 }, { duration: "20s", target: 0 }, ], };
The threshold value is a string expression evaluated against an aggregated metric. p(99) < 3000 means the 99th percentile of request duration must stay under 3000 milliseconds. If it does not, the run is a failure, which is the hook that makes the tool usable as a CI gate rather than a reporting dashboard.
Options beyond thresholds and stages are documented under k6 options, covering load, duration, and TLS certificates. Scenarios are the layer above stages, and the README describes them as the way to choose a workload model: open models, closed models, constant request rate, and fixed iteration counts. The README does not enumerate the exact config keys for each scenario type, so check the options and scenarios documentation pages before assuming a particular key name. The README also notes scripts can run on the CLI, in CI, or across a Kubernetes cluster, though the Kubernetes path is not shown in the README.
Where k6 Stops Being the Right Tool
The most consequential limitation is the JavaScript engine. It is embedded and purpose-built, not a browser and not Node.js. The README lists Browser as one of the supported protocols, but a browser protocol driven from a load generator is not the same thing as a rendering engine under real user conditions, and the resource cost per virtual user is far higher than for a plain HTTP request. If your application's failure mode only appears after client-side rendering, hydration, or third-party script execution, a script that issues HTTP calls will not reproduce it. The README's own example, a single GET to quickpizza.grafana.com with a status check, illustrates the level of abstraction: request and response, not pixels.
A second boundary is the extension ecosystem. The README presents extensions as a feature and links to a catalogue, but anything outside the core protocol set depends on code that is versioned separately from the k6 binary. That is a maintenance surface you inherit. A protocol supported only by a third-party extension can lag behind k6 releases, and the recent release list shows the project ships frequently: v1.8.1, v2.2.0, and v2.1.0 all appear within roughly three months.
A third point is licensing, discussed separately below, which rules k6 out for some commercial embedding scenarios regardless of technical fit.
The Honest Alternative: JMeter and the Difference in Approach
Apache JMeter is the natural comparison, and the difference is structural rather than cosmetic. JMeter is a Java application whose test plans are typically built in a graphical interface and stored as XML. Its logic is assembled from configurable elements: thread groups, samplers, listeners, and controllers. You extend it by writing Java classes or by installing plugins.
k6 inverts both halves of that. The test is a JavaScript module, so the control flow is ordinary code with imports, functions, and loops, and the whole file diffs cleanly in a pull request. The runner is a single Go binary rather than a JVM application with a heap to tune, which matters when the load generator itself is competing for CPU with the system under test. The cost of the inversion is that you cannot click your way to a test plan, and the README acknowledges this by pointing non-coders at k6 Studio.
Choose JMeter when your organisation already has GUI-authored plans, Java plugin expertise, or a protocol that only JMeter covers. Choose k6 when the test should be reviewed like application code and executed by a CI runner that installs a binary and nothing else.
Licence and Upgrade Costs Under AGPL-3.0
k6 is licensed AGPL-3.0. The practical question is not whether you may run it, which you may, but what happens when you distribute it or a modified version, or expose a modified version over a network. The AGPL's network clause is the part that differs from the GPL, and it is the part that catches teams who embed a tool inside a hosted product. If you only run the unmodified binary internally to generate load against your own systems, the obligation is minimal. If you fork it, link it into a service, or offer it to third parties, the analysis changes and belongs with your legal team, not with a review article. Nothing here is legal advice.
Upgrade cost is a function of release cadence. The supplied release list shows v2.1.0 in late June, v2.2.0 in early August, and v1.8.1 in mid August, which suggests parallel maintenance lines rather than a single linear track. That means a pinned version needs a deliberate decision about which line you are on. The README's contribution guidance asks contributors to discuss large changes with maintainers before coding, which is a signal that the project does not accept arbitrary patches silently. For consumers, the corresponding discipline is to read release notes before bumping, particularly if you depend on extensions that track the core binary.
Who Should Adopt k6, and What to Confirm First
The fit is clearest for a backend or platform team that already writes JavaScript, tests HTTP or gRPC endpoints, and wants a performance assertion to run in the same pipeline as the unit tests. The thresholds mechanism is the reason: p(99) < 3000 is a machine-checkable statement, and a threshold breach fails the run.
The fit is poor for a front-end team whose bugs only surface in a rendered page, and for anyone who needs a graphical test builder as the primary authoring surface, for whom k6 Studio is the relevant project rather than this repository.
Before adopting, confirm three things. First, whether AGPL-3.0 is acceptable given how you intend to deploy the binary. Second, whether every protocol you need is in the core modules or in an extension you are prepared to track across k6 releases. Third, whether the scenario model you need, open, closed, constant rate, or fixed iterations, maps to a documented config key, since the README names the models but does not list their keys. The README example is the smallest useful starting point: one GET, one check, one threshold, three stages.
Editorial conclusion
Adopt k6 if your team already writes JavaScript, wants thresholds such as http_req_duration: ["p(99) < 3000"] enforced as pass or fail inside CI, and tests HTTP, WebSockets or gRPC endpoints rather than rendered pages. Do not adopt it as a replacement for a browser-driven suite when the behaviour under test only appears after client-side rendering, and do not treat the free binary as interchangeable with the hosted Grafana Cloud execution service if you need distributed generation. Verify two things before committing: that the AGPL-3.0 obligations are acceptable to your legal team, and that your target protocol is covered by the core modules or by an extension you are willing to maintain.
Community notes