Load Testing and Performance Benchmarking
Load testing validates application performance under stress. Learn to design and execute load tests and interpret results for optimization.
Challenge
Section titled “Challenge”Conduct comprehensive load testing to identify bottlenecks, validate performance targets, and ensure system reliability under peak load.
Explanation
Section titled “Explanation”Load Testing Types
Section titled “Load Testing Types”- Smoke Testing: Basic functionality
- Load Testing: Normal peak load
- Stress Testing: Beyond capacity
- Endurance Testing: Long-duration testing
- Spike Testing: Sudden load changes
Key Metrics
Section titled “Key Metrics”- Throughput: Requests per second
- Response Time: Average, P95, P99
- Error Rate: Failed requests percentage
- Resource Usage: CPU, memory, connections
Code Examples
Section titled “Code Examples”1. Basic Load Testing with Artillery
Section titled “1. Basic Load Testing with Artillery”# Install Artillerynpm install -g artillery
# Simple load testartillery quick --count 100 --num 10 https://api.example.com
# Config-based testartillery run load-test-config.yml --target https://api.example.com2. Artillery Configuration
Section titled “2. Artillery Configuration”config: target: "https://api.example.com" phases: - duration: 60 arrivalRate: 10 name: "Warm up" - duration: 120 arrivalRate: 50 name: "Sustained load" - duration: 60 arrivalRate: 100 name: "Peak load" - duration: 60 arrivalRate: 10 name: "Cool down" processor: "./load-test-processor.js" variables: userIds: []
scenarios: - name: "Get users" flow: - get: url: "/api/users" - think: 3 - get: url: "/api/users/{{ id }}"
- name: "Create user" flow: - post: url: "/api/users" json: email: "user{{ $randomNumber(1, 10000) }}@example.com" name: "Test User" expect: - statusCode: 201
- name: "Update user" flow: - put: url: "/api/users/1" json: name: "Updated Name" expect: - statusCode: 2003. Custom Performance Benchmark
Section titled “3. Custom Performance Benchmark”const axios = require('axios');
class PerformanceBenchmark { constructor(options = {}) { this.baseURL = options.baseURL || 'http://localhost:3000'; this.duration = options.duration || 60000; // 1 minute this.concurrency = options.concurrency || 10; this.results = { requests: [], errors: [], totalRequests: 0, totalErrors: 0, startTime: null, endTime: null, }; }
async runBenchmark(endpoints) { console.log(`Starting benchmark: ${this.duration}ms with ${this.concurrency} concurrency`);
this.results.startTime = Date.now(); const endTime = this.results.startTime + this.duration;
const tasks = []; for (let i = 0; i < this.concurrency; i++) { tasks.push(this.worker(endpoints, endTime)); }
await Promise.all(tasks); this.results.endTime = Date.now();
this.printResults(); }
async worker(endpoints, endTime) { while (Date.now() < endTime) { const endpoint = endpoints[Math.floor(Math.random() * endpoints.length)];
try { const startTime = Date.now();
if (endpoint.method === 'POST') { await axios.post(`${this.baseURL}${endpoint.path}`, endpoint.data); } else { await axios.get(`${this.baseURL}${endpoint.path}`); }
const duration = Date.now() - startTime; this.results.requests.push({ endpoint: endpoint.path, duration, timestamp: new Date(), statusCode: 200, }); this.results.totalRequests++; } catch (error) { this.results.errors.push({ endpoint: endpoint.path, error: error.message, statusCode: error.response?.status, }); this.results.totalErrors++; } } }
printResults() { const durations = this.results.requests.map(r => r.duration); const avg = durations.reduce((a, b) => a + b, 0) / durations.length; const sorted = durations.sort((a, b) => a - b); const p95 = sorted[Math.floor(sorted.length * 0.95)]; const p99 = sorted[Math.floor(sorted.length * 0.99)]; const max = Math.max(...durations); const min = Math.min(...durations);
const totalTime = this.results.endTime - this.results.startTime; const throughput = (this.results.totalRequests / (totalTime / 1000)).toFixed(2);
console.log(`Benchmark Results:==================Total Requests: ${this.results.totalRequests}Total Errors: ${this.results.totalErrors}Error Rate: ${((this.results.totalErrors / this.results.totalRequests) * 100).toFixed(2)}%Throughput: ${throughput} req/s
Response Times (ms):- Average: ${avg.toFixed(2)}- Min: ${min}- Max: ${max}- P95: ${p95}- P99: ${p99} `); }}
// Usageconst benchmark = new PerformanceBenchmark({ baseURL: 'http://localhost:3000', duration: 60000, concurrency: 10,});
const endpoints = [ { path: '/api/users', method: 'GET' }, { path: '/api/posts', method: 'GET' }, { path: '/api/users', method: 'POST', data: { name: 'Test', email: 'test@example.com' } },];
await benchmark.runBenchmark(endpoints);4. Node.js Built-in Benchmarking
Section titled “4. Node.js Built-in Benchmarking”const { performance } = require('perf_hooks');
class LocalBenchmark { static async compare(name, fn1, fn2, iterations = 10000) { // Benchmark function 1 const start1 = performance.now(); for (let i = 0; i < iterations; i++) { await fn1(); } const time1 = performance.now() - start1;
// Benchmark function 2 const start2 = performance.now(); for (let i = 0; i < iterations; i++) { await fn2(); } const time2 = performance.now() - start2;
console.log(`${name}:- Function 1: ${(time1 / iterations).toFixed(4)}ms per call- Function 2: ${(time2 / iterations).toFixed(4)}ms per call- Difference: ${Math.abs(time1 - time2).toFixed(2)}ms (${((Math.max(time1, time2) / Math.min(time1, time2)).toFixed(2))}x) `); }}
// Example: Compare array methodsLocalBenchmark.compare( 'Array methods', async () => { const arr = Array(1000).fill(0).map((_, i) => i); return arr.map(x => x * 2); }, async () => { const arr = Array(1000).fill(0).map((_, i) => i); return arr.reduce((acc, x) => [...acc, x * 2], []); }, 1000);5. Load Test Report Generation
Section titled “5. Load Test Report Generation”class LoadTestReportGenerator { constructor(results) { this.results = results; }
generateHTML() { const stats = this.calculateStats();
return `<!DOCTYPE html><html><head> <title>Load Test Report</title> <style> body { font-family: Arial; margin: 20px; } table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #4CAF50; color: white; } </style></head><body> <h1>Load Test Report</h1>
<h2>Summary</h2> <table> <tr> <th>Metric</th> <th>Value</th> </tr> <tr> <td>Total Requests</td> <td>${stats.totalRequests}</td> </tr> <tr> <td>Successful Requests</td> <td>${stats.totalRequests - stats.totalErrors}</td> </tr> <tr> <td>Failed Requests</td> <td>${stats.totalErrors}</td> </tr> <tr> <td>Error Rate</td> <td>${stats.errorRate.toFixed(2)}%</td> </tr> <tr> <td>Throughput</td> <td>${stats.throughput.toFixed(2)} req/s</td> </tr> </table>
<h2>Response Times</h2> <table> <tr> <th>Metric</th> <th>Value (ms)</th> </tr> <tr> <td>Average</td> <td>${stats.avg.toFixed(2)}</td> </tr> <tr> <td>P50</td> <td>${stats.p50}</td> </tr> <tr> <td>P95</td> <td>${stats.p95}</td> </tr> <tr> <td>P99</td> <td>${stats.p99}</td> </tr> <tr> <td>Min</td> <td>${stats.min}</td> </tr> <tr> <td>Max</td> <td>${stats.max}</td> </tr> </table></body></html> `; }
calculateStats() { const durations = this.results.requests.map(r => r.duration); const sorted = durations.sort((a, b) => a - b);
const totalTime = (this.results.endTime - this.results.startTime) / 1000;
return { totalRequests: this.results.totalRequests, totalErrors: this.results.totalErrors, errorRate: (this.results.totalErrors / this.results.totalRequests) * 100, throughput: this.results.totalRequests / totalTime, avg: durations.reduce((a, b) => a + b, 0) / durations.length, p50: sorted[Math.floor(sorted.length * 0.5)], p95: sorted[Math.floor(sorted.length * 0.95)], p99: sorted[Math.floor(sorted.length * 0.99)], min: Math.min(...durations), max: Math.max(...durations), }; }
saveReport(filename) { const fs = require('fs'); fs.writeFileSync(filename, this.generateHTML()); console.log(`Report saved to ${filename}`); }}Best Practices
Section titled “Best Practices”-
Test Design
- Use realistic scenarios
- Test different endpoints
- Vary request types
- Simulate real user behavior
-
Environment
- Test in production-like environment
- Isolate from other traffic
- Control external dependencies
- Document test conditions
-
Analysis
- Focus on P95/P99 latency
- Monitor error rates
- Track resource usage
- Compare against baselines
-
Interpretation
- Identify bottlenecks
- Find breaking points
- Understand bottleneck causes
- Plan improvements
-
Automation
- Automate test execution
- Generate reports
- Track trends
- Integrate with CI/CD
Real-World Applications
Section titled “Real-World Applications”- Capacity Planning: Determine infrastructure needs
- Regression Testing: Detect performance regressions
- Optimization: Identify improvement areas
- Release Validation: Verify performance before deployment
- SLA Verification: Ensure service meets commitments