Performance Testing — k6, Locust & JMeter (2026)
Performance testing evaluates how a system behaves under various load conditions — measuring response times, throughput, resource utilization, and identifying bottlenecks before they affect users.
What You'll Learn
You'll understand load, stress, spike, and soak testing types, compare k6, Locust, and JMeter tools, learn how to write performance tests, and interpret results to identify bottlenecks.
Why Performance Testing Matters
A slow application loses users. Research shows that a one-second delay in page load time reduces conversions by 7%. Performance testing catches bottlenecks before they reach production. At DodaTech, DodaZIP's compression API is load-tested to handle 10,000 concurrent requests before every release.
Performance Testing Learning Path
flowchart LR A[Testing Basics] --> B[Performance Testing] B --> C[k6 Scripting] B --> D[Load Testing k6] style B fill:#f90,color:#fff
Types of Performance Tests
| Type | Purpose | Load Pattern | Duration |
|---|---|---|---|
| Load test | Normal expected traffic | Steady ramp to target | 10-30 min |
| Stress test | Breaking point | Ramp until failure | 10-20 min |
| Spike test | Sudden traffic bursts | Instant spike | 1-5 min spike |
| Soak test | Long-term stability | Sustained load | Hours to days |
Load Test
Simulates expected traffic to verify the system meets performance targets.
// k6 load test
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 100 }, // Ramp up
{ duration: '5m', target: 100 }, // Stay at 100 users
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
},
};
export default function () {
const res = http.get('https://api.example.com/users');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 300ms': (r) => r.timings.duration < 300,
});
sleep(1);
}
Stress Test
Finds the system's breaking point by increasing load until it fails.
export const options = {
stages: [
{ duration: '2m', target: 200 },
{ duration: '5m', target: 500 },
{ duration: '2m', target: 1000 },
{ duration: '2m', target: 0 },
],
};
Spike Test
Simulates sudden traffic surges — like a product launch or flash sale.
export const options = {
stages: [
{ duration: '10s', target: 1000 }, // Instant spike
{ duration: '3m', target: 1000 }, // Stay at peak
{ duration: '10s', target: 0 }, // Drop off
],
};
Soak Test
Runs sustained load for hours to detect memory leaks and resource degradation.
export const options = {
stages: [
{ duration: '10m', target: 200 },
{ duration: '4h', target: 200 }, // 4-hour soak
{ duration: '10m', target: 0 },
],
};
Tools Comparison
| Tool | Language | GUI | Scripting | Best For |
|---|---|---|---|---|
| k6 | JavaScript | No | Code-based | CI/CD, API testing |
| Locust | Python | Yes | Code-based | Distributed testing |
| JMeter | Java | Yes | XML-based | Complex scenarios, GUI lovers |
k6
Pros: JavaScript scripting, CI/CD friendly, built-in metrics, open-source.
k6 run load-test.js
Output:
http_req_duration.........: avg=245ms p(95)=480ms
http_reqs..................: 12000 200/s
vus........................: 100 min=100 max=100
Locust
Pros: Python scripting, distributed by default, real-time web UI.
# locustfile.py
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 5)
@task
def get_users(self):
self.client.get("/users")
@task(3)
def view_items(self):
self.client.get("/items")
locust -f locustfile.py --headless -u 100 -r 10 --run-time 5m
JMeter
Pros: Extensive protocol support, mature ecosystem, GUI test builder.
Cons: XML-based scripts are hard to version control, steep learning curve.
Interpreting Results
Key metrics to analyze:
| Metric | What It Measures | Good Target |
|---|---|---|
| Response time | Time to complete request | p95 < 500ms |
| Throughput | Requests per second | Depends on system |
| Error rate | Percentage of failed requests | < 1% |
| CPU usage | Server CPU utilization | < 80% |
| Memory usage | RAM consumption | < 80% |
| Concurrent users | Active users at once | Depends on architecture |
Analyzing a Slow Response
- Check p95 vs p99 — large gap indicates inconsistent performance
- Check error rate during high load — errors spike before throughput drops
- Check resource utilization — CPU bottleneck or memory pressure?
- Check database query times — slow queries under load?
Common Mistakes
1. Testing Only the Happy Path
Performance issues often appear under error conditions. Test degraded modes.
2. Ignoring Warm-Up Time
JIT compilation and cache warming take time. Include ramp-up in your plan.
3. Testing from the Same Network
Network latency from the test machine masks real user experience.
4. Insufficient Think Time
Real users pause between actions. Instant requests create unrealistic load.
5. Not Monitoring Server Resources
Client-side metrics alone can't identify where the bottleneck is.
6. Single-Endpoint Testing
Real users hit multiple endpoints. Test realistic workflows.
7. Running Tests Too Briefly
Performance issues like memory leaks only surface after extended runtime.
Practice Questions
1. What are the four main types of performance tests? Load test (expected traffic), stress test (breaking point), spike test (sudden bursts), soak test (long-term stability).
2. What is the difference between k6 and JMeter? k6 is JavaScript-based and CI/CD friendly. JMeter is Java-based with a GUI but harder to integrate into pipelines.
3. What does p95 response time mean? 95% of requests are faster than this value. It excludes the slowest 5% as outliers.
4. How long should a soak test run? Typically 4-24 hours to detect memory leaks and resource degradation over time.
5. Challenge: Design a performance test for an e-commerce checkout API. Consider adding items to cart, applying a discount, processing payment, and confirming the order. What load pattern makes sense?
Mini Project: k6 Performance Test Suite
// checkout-test.js
import http from 'k6/http';
import { check, sleep, group } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 },
{ duration: '3m', target: 50 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<1000'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
group('checkout flow', () => {
// Browse products
let res = http.get('https://api.example.com/products');
check(res, { 'products loaded': (r) => r.status === 200 });
// Add to cart
res = http.post('https://api.example.com/cart', JSON.stringify({
productId: 'prod_123', quantity: 1,
}), { headers: { 'Content-Type': 'application/json' } });
check(res, { 'item added': (r) => r.status === 200 });
// Checkout
res = http.post('https://api.example.com/checkout', JSON.stringify({
paymentMethod: 'card',
}), { headers: { 'Content-Type': 'application/json' } });
check(res, { 'checkout complete': (r) => r.status === 200 });
});
sleep(2);
}
FAQ
What's Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro