Skip to content

Load, Stress, and Benchmark Testing

AI-assisted performance testing uses a coding agent to write k6 load and stress scripts from latency targets a human sets, run them through the k6 MCP server, and rank bottlenecks from the k6 summary and database evidence. The pass or fail verdict comes from k6 thresholds and a baseline comparison in CI, never from the agent’s reading of the results.

This page is for developers and tech leads who own a service’s latency budget. Your checkout API holds 500 requests per second in staging. On launch day traffic triples, the database connection pool runs dry, p95 climbs from 120 ms to 4 s, and nobody can name the commit that removed the headroom. The workflow below finds that limit before your users do.

What you get from AI-assisted load testing

Section titled “What you get from AI-assisted load testing”
  • The k6 MCP server connected to Claude Code, Codex, and Cursor, so the agent validates and runs its own scripts
  • Five copy-paste prompts: a threshold-driven load test, a breaking-point stress test, a bottleneck ranking, a front-end check under load, and a baseline-comparison gate
  • A CI workflow with two gates: absolute thresholds through the k6 run exit code, and a relative gate that fails a pull request when p95 slides more than 20% against the stored baseline
  • A split of responsibilities: people own the thresholds and the baseline, the agent writes scripts and diagnoses, k6 decides pass or fail
  • The ways load-test numbers mislead, and how to recover from each one

k6 is a standalone Go binary that runs JavaScript test scripts. Install it from the k6 installation guide or a package manager (brew install k6). Do not run npm install k6: the k6 package on npm is an unrelated 0.0.0 placeholder.

On top of k6, install Grafana’s official k6 MCP server, grafana/mcp-k6. It gives the agent four tools: validate_script (one VU, one iteration), run_script, list_sections, and get_documentation. The server ships as the mcp-k6 binary, the grafana/mcp-k6 Docker image, or the k6 x mcp subcommand that k6 x agent init wires up, not as an npm package, so treat any npx “k6 MCP” as unofficial. Grafana marks mcp-k6 as experimental (README, checked 2026-09-26; latest release v0.6.1), so pin the version you tested.

The fastest setup is the k6 x agent subcommand, listed in the k6 v2.1.0 release notes as “Bootstrap an AI-assisted k6 testing workflow in any editor”. It requires k6 v2.1.0 or later (current: v2.3.0); on an older k6 you get an unknown-command error, and the mcp-k6 README still describes k6 x mcp as needing a custom xk6 build. Run k6 x to list the built-in subcommands and confirm that agent and mcp are present, then run it in the repository root:

Terminal window
# Preview the files it will write, then write them
k6 x agent init claude-code --dry-run
k6 x agent init claude-code # other targets: codex-cli, cursor, opencode, vscode-copilot, cline
k6 x agent status # show what is installed in this workspace
k6 x agent skills list # list the skills bundled in the k6 binary

It connects the editor to k6’s built-in k6 x mcp server, so no separate mcp-k6 binary is needed, and installs k6 skills (k6-load-test, k6-smoke-test, k6-trend-analysis, k6-test-maintenance and others). For Claude Code that is .mcp.json, .claude/settings.local.json and .claude/skills/; for Codex, .codex/mcp.json and .codex/skills/; for Cursor, .cursor/mcp.json and .cursor/rules/. Codex (checked against v0.157.1) reads MCP servers from config.toml, not .codex/mcp.json: run codex mcp list, and if k6 is missing, register it with codex mcp add k6 -- k6 x mcp (or -- mcp-k6). To register the standalone server by hand instead, install the binary once (brew tap grafana/grafana && brew install mcp-k6, or docker pull grafana/mcp-k6:latest) and add it per tool:

Terminal window
claude mcp add --scope user --transport stdio k6 -- mcp-k6
# Without a local binary:
claude mcp add --scope user --transport stdio k6 -- docker run --rm -i grafana/mcp-k6

For front-end performance, add the Chrome DevTools MCP server (chrome-devtools-mcp on npm). It records performance traces (performance_start_trace) and breaks them down into insights such as the LCP breakdown, which makes it a better fit for this job than Playwright MCP. Its lighthouse_audit tool covers accessibility, SEO and best practices but excludes performance, so measure with the trace. It sends usage statistics to Google by default; the --no-usage-statistics flag turns that off.

Terminal window
claude mcp add chrome-devtools --scope user -- npx chrome-devtools-mcp@latest --no-usage-statistics

Who decides what in the load-testing loop?

Section titled “Who decides what in the load-testing loop?”

The loop is the same in every tool. What matters is who owns each step, because an agent that writes the test, runs it, and judges the result can grade its own homework.

  1. You set the targets. p95 and p99 latency, error rate, and the traffic you must survive come from your SLO or the product owner, not from the agent. These numbers become the script’s thresholds.
  2. The agent writes the script from those targets, then calls validate_script so a syntax or import error surfaces in one iteration instead of eight minutes into a run.
  3. You review the script’s shape, not every line. Check four things: the endpoint and method, the stage durations, the thresholds match what you asked for, and the target host comes from an environment variable.
  4. The agent runs it against staging through run_script and reports which thresholds failed.
  5. The agent ranks bottlenecks from the k6 summary plus the database and APM evidence from the same time window.
  6. CI owns the verdict. k6 exits non-zero when a threshold fails, and a comparison script fails the build when p95 regresses against the baseline. A tech lead approves any change to thresholds or the baseline.

Generate a load test from your latency targets

Section titled “Generate a load test from your latency targets”

Start with one endpoint and numbers you supply. A script with your numbers in it can be checked in a minute; “write a load test” produces a plausible script with invented ones.

The result should look like this: concrete stages, your thresholds, and nothing hardcoded that belongs in the environment.

import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
const carts = new SharedArray('carts', () => JSON.parse(open('./data/carts.json')));
export const options = {
stages: [
{ duration: '2m', target: 200 },
{ duration: '5m', target: 200 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<300', 'p(99)<800'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const cart = carts[Math.floor(Math.random() * carts.length)];
const res = http.post(`${__ENV.BASE_URL}/api/checkout`, JSON.stringify(cart), {
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${__ENV.TOKEN}` },
});
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}

The invocation differs by tool; the prompt does not.

Start an interactive session with the stored prompt as the first message:

Terminal window
claude "$(cat prompts/perf-checkout.txt)"

Before the agent’s first run_script call, confirm that BASE_URL points at staging. A load test aimed at production by mistake is an outage you caused.

Store the prompt in prompts/perf-checkout.txt so the next person generates the same test. For realistic cart and user data, see test data strategies and fixtures.

Find the breaking point with a stress test

Section titled “Find the breaking point with a stress test”

A load test asks whether the service holds at expected traffic. A stress test asks at what rate it stops holding, and that number goes into the capacity plan. Define “broken” before the run so the test reports a number, not an impression.

Use an arrival-rate executor here. A VU ramp lets throughput fall as latency rises, so the test quietly sends less traffic exactly when the server struggles.

If dropped_iterations climbs before latency does, the load generator ran out of VUs or CPU, and the “breaking point” belongs to your laptop. Raise maxVUs or move the run to a larger machine before you trust the number.

A red threshold tells you the service is slow. It does not tell you whether the cause is a query, the connection pool, application CPU, or a third-party call. That diagnosis is where the agent earns its keep, provided you give it evidence from the same time window and ask it to cite that evidence.

The last line of the prompt matters: every fix comes with the re-run that proves it. Apply the change, rerun the same script, and compare p95. The measurement, not the agent’s explanation, closes the loop.

To correlate threshold failures with server-side errors and slow transactions, add the Sentry MCP server (remote, OAuth) and ask the agent to line up Sentry’s slowest transactions for the test window against the k6 failures:

Terminal window
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp

Avoid the npm package named sentry-mcp: it is an unrelated 0.0.1 stub. Sentry’s own stdio package is @sentry/mcp-server, aimed at self-hosted Sentry.

Most load-test cliffs trace back to a query that was fast at seed-data scale. When the ranking points at one, ask the agent to benchmark it with and without the proposed index at production row counts (for example 1M and 10M rows), using EXPLAIN (ANALYZE, BUFFERS) output as the evidence. For production-side tuning after release, continue in production performance optimization.

Check Core Web Vitals while the API is under load

Section titled “Check Core Web Vitals while the API is under load”

Server p95 can look healthy while the page still feels slow, because the browser waits on work the API never sees. Measure the front end during a load run, not on an idle staging box.

A lab trace does not measure INP, which needs real user interaction. Use field data from your RUM or analytics tool for INP, and use this check for LCP and layout shifts under load. For functional browser tests, see end-to-end test automation.

Two gates catch two different failures, and you need both.

The absolute gate is the script’s thresholds. k6 run exits non-zero when any threshold fails, so the CI step is the run itself. The relative gate catches what thresholds cannot: a change that doubles p95 from 90 ms to 180 ms still passes p(95)<300 while it eats half your headroom. For that you need a stored baseline and a comparison.

The workflow the prompt should produce looks like this. It tests the pull request’s own build on the runner, so the result belongs to that change:

.github/workflows/perf.yml
name: perf
on: pull_request
permissions:
contents: read
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: grafana/setup-k6-action@v1
- run: docker compose up -d --wait
- run: k6 run tests/perf/smoke.js
env:
BASE_URL: http://localhost:3000
- run: node scripts/perf-compare.mjs perf/baseline.json summary.json

The workflow above shows one run; the prompt asks for the median of three, which absorbs runner noise. The agent writes the gate, but the gate itself contains no agent. That is what makes the verdict reproducible: the same commit gives the same pass or fail, and nobody has to read the script to trust the red or green check.

Two ownership rules keep the gate honest:

  • Put perf/baseline.json and the thresholds blocks under CODEOWNERS for the tech lead. An agent asked to “make CI green” can loosen a threshold or refresh the baseline; a required review turns that into a visible decision. Own scripts/perf-compare.mjs and .github/workflows/perf.yml the same way, and turn on “Require review from Code Owners”: on pull_request the job runs the pull request’s own copy of both, so an unreviewed edit could make the comparison always exit 0. Protecting the oracle covers the pattern in depth.
  • Tier the runs by cost. The 30-second smoke test runs on every pull request. The full load test against staging runs nightly on main from a scheduled workflow, with a least-privilege staging token from your secret store. The stress test runs weekly and before launches. For wiring these into the pipeline, see CI/CD pipelines.

When a result looks impossible, give the agent the full k6 summary and the generator’s resource metrics, and ask it to decide between a real server bottleneck and a test artifact before you escalate.