Load Testing Tools: k6, Locust, and Artillery for Performance Testing
Compare k6, Locust, and Artillery for load testing — scripting, distributed testing, CI integration, and reading results. Includes real test scripts and perform
Load Testing Tools: k6, Locust, and Artillery for Performance Testing
Load testing answers the question: "How does this system behave when N users hit it simultaneously?" Without it, you find out the answer in production — usually during a product launch or viral moment, at the worst possible time.
The three tools covered here — k6, Locust, and Artillery — are the most actively maintained open-source options in 2026. Each has a different scripting model and integration story.
What to Measure
Before writing a single test script, define what "good" means for your system:
| Metric | Definition | Typical Target |
|---|---|---|
| Throughput | Requests per second the system handles | Varies by use case |
| Latency p50 | 50% of requests complete in this time | < 100ms for API |
| Latency p95 | 95% of requests complete in this time | < 500ms for API |
| Latency p99 | 99% of requests complete in this time | < 1s for API |
| Error rate | % of requests that return 4xx/5xx | < 0.1% under load |
| Saturation point | Load level where latency starts climbing | Know before prod |
The saturation point is the most important finding from a load test. Every system has one — the point where adding more load causes disproportionate latency increases. Your headroom above current peak traffic tells you how long until you need to scale.
k6
k6 (by Grafana Labs) is JavaScript-scripted, integrates with CI/CD, and has excellent output formatting. It's the most popular choice for teams already in the JavaScript ecosystem.
Basic k6 script:
// load-tests/api-checkout.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Trend, Rate, Counter } from 'k6/metrics';
// Custom metrics
const checkoutDuration = new Trend('checkout_duration', true);
const checkoutErrors = new Rate('checkout_errors');
const ordersCreated = new Counter('orders_created');
// Test configuration
export const options = {
stages: [
{ duration: '2m', target: 10 }, // Ramp up to 10 VUs over 2 minutes
{ duration: '5m', target: 50 }, // Ramp up to 50 VUs
{ duration: '10m', target: 50 }, // Hold at 50 VUs for 10 minutes
{ duration: '3m', target: 100 }, // Spike to 100 VUs
{ duration: '5m', target: 100 }, // Hold the spike
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests < 500ms
http_req_failed: ['rate<0.01'], // Error rate < 1%
checkout_duration: ['p(95)<2000'], // Checkout flow < 2s at p95
checkout_errors: ['rate<0.005'], // Checkout errors < 0.5%
},
};
const BASE_URL = __ENV.BASE_URL || 'https://api.yourdomain.com';
const API_TOKEN = __ENV.API_TOKEN;
export function setup() {
// Runs once before all VUs — use for test data setup
return {
products: [
{ id: 'prod_001', price: 1999 },
{ id: 'prod_002', price: 4999 },
],
};
}
export default function (data) {
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_TOKEN}`,
};
// Step 1: Get product list
const productsRes = http.get(`${BASE_URL}/products?limit=10`, { headers });
check(productsRes, {
'products 200': (r) => r.status === 200,
'products has items': (r) => r.json('data').length > 0,
});
sleep(1); // Think time between actions
// Step 2: Add to cart
const product = data.products[Math.floor(Math.random() * data.products.length)];
const cartRes = http.post(
`${BASE_URL}/cart`,
JSON.stringify({ productId: product.id, quantity: 1 }),
{ headers }
);
check(cartRes, { 'cart 201': (r) => r.status === 201 });
sleep(2);
// Step 3: Checkout (the critical path)
const checkoutStart = Date.now();
const checkoutRes = http.post(
`${BASE_URL}/orders`,
JSON.stringify({
cartId: cartRes.json('id'),
paymentMethod: 'pm_card_visa', // Stripe test card
}),
{ headers, timeout: '10s' }
);
const checkoutMs = Date.now() - checkoutStart;
checkoutDuration.add(checkoutMs);
const checkoutOk = check(checkoutRes, {
'checkout 201': (r) => r.status === 201,
'checkout has orderId': (r) => r.json('orderId') !== undefined,
});
checkoutErrors.add(!checkoutOk);
if (checkoutOk) ordersCreated.add(1);
sleep(3);
}
Running k6:
# Local run
k6 run load-tests/api-checkout.js
# With environment variables
BASE_URL=https://staging.yourdomain.com API_TOKEN=xxx k6 run load-tests/api-checkout.js
# Output to InfluxDB for Grafana visualization
k6 run --out influxdb=http://localhost:8086/k6 load-tests/api-checkout.js
# CI integration — fails if thresholds are breached
k6 run --exit-on-running --out json=results.json load-tests/api-checkout.js
GitHub Actions CI integration:
# .github/workflows/load-test.yml
name: Load Test (Staging)
on:
workflow_dispatch: # Manual trigger
schedule:
- cron: '0 2 * * 1' # Every Monday at 2 AM
jobs:
load-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: grafana/setup-k6-action@v1
with:
k6-version: 0.50.0
- name: Run load tests
run: k6 run load-tests/api-checkout.js
env:
BASE_URL: ${{ secrets.STAGING_URL }}
API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: load-test-results
path: results.json
🌐 Looking for a Dev Team That Actually Delivers?
Most agencies sell you a project manager and assign juniors. Viprasol is different — senior engineers only, direct Slack access, and a 5.0★ Upwork record across 100+ projects.
- React, Next.js, Node.js, TypeScript — production-grade stack
- Fixed-price contracts — no surprise invoices
- Full source code ownership from day one
- 90-day post-launch support included
Locust
Locust is Python-based, which makes it natural for teams with Python expertise. Its distributed mode scales to millions of simulated users by adding worker nodes.
Locust test script:
# locustfile.py
from locust import HttpUser, task, between, events
from locust.exception import StopUser
import random
import json
class APIUser(HttpUser):
wait_time = between(1, 3) # Think time between tasks
token = None
def on_start(self):
"""Called when a simulated user starts — authenticate"""
response = self.client.post("/auth/login", json={
"email": f"testuser{random.randint(1, 1000)}@loadtest.com",
"password": "TestPassword123!"
})
if response.status_code != 200:
raise StopUser()
self.token = response.json()["token"]
self.headers = {"Authorization": f"Bearer {self.token}"}
@task(3) # Weight: this runs 3x more often than task weight 1
def browse_products(self):
with self.client.get(
"/products",
params={"page": random.randint(1, 5), "limit": 20},
headers=self.headers,
name="/products [list]", # Group similar URLs in results
catch_response=True
) as response:
if response.status_code == 200:
data = response.json()
if len(data.get("data", [])) == 0:
response.failure("Empty product list")
else:
response.failure(f"HTTP {response.status_code}")
@task(1)
def create_order(self):
# Step 1: Get a product
products_res = self.client.get("/products?limit=5", headers=self.headers)
if products_res.status_code != 200:
return
products = products_res.json().get("data", [])
if not products:
return
product = random.choice(products)
# Step 2: Create order
with self.client.post(
"/orders",
json={"productId": product["id"], "quantity": 1},
headers=self.headers,
name="/orders [create]",
catch_response=True
) as response:
if response.status_code == 201:
response.success()
else:
response.failure(f"Order creation failed: {response.status_code}")
# Run: locust -f locustfile.py --host=https://staging.yourdomain.com
# Web UI at http://localhost:8089 to configure and start the test
Distributed Locust for high load:
# Start master
locust -f locustfile.py --master --host=https://staging.yourdomain.com
# Start workers (on separate machines or containers)
locust -f locustfile.py --worker --master-host=master.internal
# Headless distributed run
locust -f locustfile.py \
--master \
--headless \
--users 1000 \
--spawn-rate 50 \
--run-time 10m \
--host https://staging.yourdomain.com \
--html report.html
Artillery
Artillery is YAML/JSON-configured (no scripting required for basic tests) but supports JavaScript plugins for complex scenarios. Best for teams that want tests-as-config rather than tests-as-code.
Artillery config (load-test.yml):
config:
target: "https://staging.yourdomain.com"
phases:
- duration: 60
arrivalRate: 5
name: "Warm up"
- duration: 300
arrivalRate: 50
name: "Sustained load"
- duration: 60
arrivalRate: 200
name: "Spike"
defaults:
headers:
Content-Type: "application/json"
plugins:
expect: {} # Response validation plugin
scenarios:
- name: "User journey: Browse and purchase"
weight: 70 # 70% of users follow this flow
flow:
- post:
url: "/auth/login"
json:
email: "{{ $randomString() }}@test.com"
password: "TestPass123!"
capture:
- json: "$.token"
as: "authToken"
- get:
url: "/products"
qs:
limit: 10
headers:
Authorization: "Bearer {{ authToken }}"
expect:
- statusCode: 200
- contentType: json
- think: 2
- post:
url: "/orders"
json:
productId: "prod_001"
quantity: 1
headers:
Authorization: "Bearer {{ authToken }}"
expect:
- statusCode: 201
- name: "Browse only"
weight: 30
flow:
- get:
url: "/products"
- think: 3
- get:
url: "/products/{{ $randomString() }}"
# Run Artillery
artillery run load-test.yml
# Run with HTML report
artillery run --output results.json load-test.yml
artillery report results.json
🚀 Senior Engineers. No Junior Handoffs. Ever.
You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.
- MVPs in 4–8 weeks, full platforms in 3–5 months
- Lighthouse 90+ performance scores standard
- Works across US, UK, AU timezones
- Free 30-min architecture review, no commitment
Tool Comparison
| Factor | k6 | Locust | Artillery |
|---|---|---|---|
| Script language | JavaScript | Python | YAML / JS |
| Learning curve | Low (for JS devs) | Low (for Python devs) | Very low |
| Distributed load | k6 Cloud or InfluxDB | Native worker model | Built-in cluster mode |
| CI integration | Excellent (GitHub Actions) | Good | Good |
| Real browser support | No (protocol-level) | No | Via Playwright plugin |
| Grafana integration | Native | Plugin | Plugin |
| Performance overhead | Low (Go runtime) | Medium (Python) | Medium (Node.js) |
| Open source | ✅ | ✅ | ✅ |
| Managed cloud | k6 Cloud ($25+/mo) | No | Artillery Cloud |
Quick decision:
- JavaScript team, CI-first → k6
- Python team, need distributed → Locust
- Want YAML config, quick start → Artillery
Performance Baselines by Tier
| System Type | p95 Target | Throughput |
|---|---|---|
| REST API (read-heavy) | < 200ms | 1,000+ req/s per instance |
| REST API (write-heavy) | < 500ms | 200–500 req/s per instance |
| GraphQL API | < 500ms | 300–800 req/s |
| File upload endpoint | < 2,000ms | 50–100 req/s |
| Payment processing | < 3,000ms | 20–50 req/s |
| Report generation | < 10,000ms | 5–10 req/s |
Working With Viprasol
We run load tests as part of production readiness reviews — before major launches, infrastructure changes, or traffic events. Our test suites cover the critical user journeys, include realistic think times, and produce reports that tell you exactly where the saturation point is and what to do about it.
→ Talk to our team about performance testing your application.
See Also
- Software Testing Strategies — where load testing fits in the test pyramid
- PostgreSQL Performance — diagnosing DB bottlenecks found in load tests
- Caching Strategies — the fastest fix for load test findings
- Kubernetes vs ECS — auto-scaling to handle load test peaks
- Cloud Solutions — infrastructure sizing and scaling
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need a Modern Web Application?
From landing pages to complex SaaS platforms — we build it all with Next.js and React.
Free consultation • No commitment • Response within 24 hours
Need a custom web application built?
We build React and Next.js web applications with Lighthouse ≥90 scores, mobile-first design, and full source code ownership. Senior engineers only — from architecture through deployment.