From atum-stack-backend
Load testing and performance benchmarking pattern library for backend services — k6 (Grafana's modern load tester with JavaScript scenarios, thresholds, cloud runs via k6 Cloud), Gatling (Scala-based high-throughput simulations with detailed reports), Locust (Python distributed load testing with web UI), Apache JMeter (legacy but still widely used for enterprise), Artillery (Node.js YAML-configured scenarios), and wrk (minimal CLI for quick benchmarks). Covers test planning (baseline / load / stress / spike / soak tests), virtual users modeling, ramp-up strategies, think time, custom metrics (latency percentiles p50/p95/p99, throughput RPS, error rate), SLO-based thresholds, CI integration (GitHub Actions with k6 + threshold-based pass/fail), and result interpretation (bottleneck identification CPU/memory/DB/network). Use when benchmarking a new API before launch, validating capacity planning, identifying performance bottlenecks, setting SLOs, or testing auto-scaling triggers. Differentiates from unit/integration tests by focusing on performance under concurrent load, not functional correctness.
npx claudepluginhub arnwaldn/atum-plugins-collection --plugin atum-stack-backendThis skill uses the workspace's default tool permissions.
Le load testing mesure **comment un système se comporte sous charge concurrente**. Ne pas confondre avec unit/integration tests (fonctionnel) ou stress tests (casser le système).
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides implementation of event-driven hooks in Claude Code plugins using prompt-based validation and bash commands for PreToolUse, Stop, and session events.
Le load testing mesure comment un système se comporte sous charge concurrente. Ne pas confondre avec unit/integration tests (fonctionnel) ou stress tests (casser le système).
| Type | Objectif | Durée | Charge |
|---|---|---|---|
| Baseline | Mesurer la perf de référence | 5 min | 1 user |
| Load test | Valider la perf à charge attendue | 15-30 min | X users (capacity cible) |
| Stress test | Trouver le point de rupture | 30-60 min | X × 2 à X × 5 |
| Spike test | Résilience à un pic soudain | 5-10 min | 0 → X × 3 en 30s |
| Soak test (endurance) | Memory leaks, resource leaks | 2-24h | X users (stable) |
# Install
brew install k6 # macOS
// loadtest.js
import http from 'k6/http'
import { sleep, check } from 'k6'
export const options = {
stages: [
{ duration: '30s', target: 10 },
{ duration: '2m', target: 100 },
{ duration: '30s', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
http_req_failed: ['rate<0.01'],
},
}
export default function () {
const res = http.get('https://api.example.com/products')
check(res, {
'status is 200': (r) => r.status === 200,
'body contains items': (r) => r.body.includes('"items"'),
})
sleep(1)
}
k6 run loadtest.js
import http from 'k6/http'
import { sleep, check, group } from 'k6'
export const options = {
scenarios: {
browse: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '1m', target: 50 },
{ duration: '5m', target: 50 },
{ duration: '1m', target: 0 },
],
gracefulRampDown: '30s',
},
},
thresholds: {
'http_req_duration{group:::browse homepage}': ['p(95)<200'],
'http_req_duration{group:::view product}': ['p(95)<300'],
http_req_failed: ['rate<0.01'],
},
}
const BASE_URL = 'https://api.example.com'
export default function () {
group('browse homepage', () => {
const res = http.get(`${BASE_URL}/`)
check(res, { 'home 200': (r) => r.status === 200 })
sleep(Math.random() * 3 + 1)
})
group('view product', () => {
const res = http.get(`${BASE_URL}/products/p1`)
check(res, { 'product 200': (r) => r.status === 200 })
sleep(Math.random() * 5 + 2)
})
group('add to cart', () => {
const res = http.post(
`${BASE_URL}/cart/add`,
JSON.stringify({ productId: 'p1', qty: 1 }),
{ headers: { 'Content-Type': 'application/json' } }
)
check(res, { 'cart 200': (r) => r.status === 200 })
sleep(1)
})
}
thresholds: {
'http_req_duration': [
{ threshold: 'p(95)<500', abortOnFail: true },
{ threshold: 'p(99)<1500', abortOnFail: false },
],
'http_req_failed': ['rate<0.01'],
'checks': ['rate>0.99'],
}
Quand un threshold échoue, k6 exit avec un code non-zero → utile en CI.
pip install locust
# locustfile.py
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 5)
def on_start(self):
self.client.post("/login", json={"email": "test@example.com", "password": "xxx"})
@task(3)
def view_homepage(self):
self.client.get("/")
@task(2)
def view_product(self):
self.client.get("/products/p1")
@task(1)
def checkout(self):
self.client.post("/cart/add", json={"productId": "p1"})
locust -f locustfile.py --host https://api.example.com
# Ouvre http://localhost:8089 avec UI
Avantages Locust :
Gatling utilise Scala pour définir les simulations avec une DSL fluent. Un scénario enchaîne des actions HTTP (http("home").get("/")) avec des pauses, et une setUp configure l'injection profile (rampUsers(100).during(30.seconds), constantUsersPerSec(10).during(5.minutes)). Les assertions définissent les SLO (p95 < 500ms, error rate < 1%).
Avantages Gatling :
Inconvénient : Scala = learning curve.
# loadtest.yml
config:
target: https://api.example.com
phases:
- duration: 60
arrivalRate: 10
rampTo: 50
name: Ramp up
- duration: 300
arrivalRate: 50
name: Steady
ensure:
p95: 500
maxErrorRate: 1
scenarios:
- name: Browse
flow:
- get:
url: /
- think: 2
- get:
url: /products/p1
- think: 3
- post:
url: /cart/add
json:
productId: p1
qty: 1
npm install -g artillery
artillery run loadtest.yml
Avantage : YAML minimal, idéal pour des tests rapides sans écrire de code.
brew install wrk
# 100 connexions, 4 threads, 30 secondes
wrk -c 100 -t 4 -d 30s https://api.example.com/
# Avec un script Lua custom
wrk -c 100 -t 4 -d 30s -s custom.lua https://api.example.com/
Ultra simple, parfait pour un quick benchmark en 10 secondes.
| Métrique | Cible typique |
|---|---|
| Latency p50 (median) | < 100ms |
| Latency p95 | < 500ms |
| Latency p99 | < 1000ms |
| Latency p99.9 | < 3000ms |
| Throughput (RPS) | Varié selon use case |
| Error rate | < 1 % |
| Connections active | < capacity limit |
| Métrique | Alerte si |
|---|---|
| CPU | > 70 % sustained |
| Memory | > 80 % |
| Disk I/O | > 70 % utilization |
| Network out | > 80 % bandwidth |
| DB query time | p95 > 100ms |
| DB connections | > 80 % pool |
| GC pauses | > 100ms |
# .github/workflows/loadtest.yml
name: Load test
on:
pull_request:
paths:
- 'api/**'
jobs:
loadtest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to preview env
run: echo "Deploy script here"
- name: Run k6 load test
uses: grafana/k6-action@v0.3.1
with:
filename: loadtest.js
flags: --out json=results.json
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: k6-results
path: results.json
Quand un load test révèle un problème, chercher le bottleneck :
node --prof, py-spy, async-profiler Java)Process type :
e2e-testing dans atum-stack-web*-testing par langage dans les plugins stackperformance-optimizerdatabase-optimizerposthog-instrumentation, sentry-*