// k6 Load Testing Configuration // Comprehensive load test for Sankofa Phoenix API // Usage: k6 run k6-load-test.js import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate, Trend, Counter } from 'k6/metrics'; // Custom metrics const errorRate = new Rate('errors'); const apiLatency = new Trend('api_latency'); const graphqlLatency = new Trend('graphql_latency'); const requestCount = new Counter('requests_total'); // Configuration const API_URL = __ENV.API_URL || 'https://api.sankofa.nexus'; const TEST_DURATION = __ENV.TEST_DURATION || '5m'; const VUS = parseInt(__ENV.VUS || '10'); export const options = { stages: [ // Ramp up to 10 VUs over 1 minute { duration: '1m', target: 10 }, // Stay at 10 VUs for 2 minutes { duration: '2m', target: 10 }, // Ramp up to 50 VUs over 2 minutes { duration: '2m', target: 50 }, // Stay at 50 VUs for 2 minutes { duration: '2m', target: 50 }, // Ramp up to 100 VUs over 2 minutes { duration: '2m', target: 100 }, // Stay at 100 VUs for 2 minutes { duration: '2m', target: 100 }, // Ramp down to 0 VUs over 2 minutes { duration: '2m', target: 0 }, ], thresholds: { // 95% of requests should be below 200ms 'http_req_duration': ['p(95)<200', 'p(99)<500'], // Error rate should be less than 1% 'http_req_failed': ['rate<0.01'], 'errors': ['rate<0.01'], // API latency should be below 200ms 'api_latency': ['p(95)<200'], // GraphQL latency should be below 300ms 'graphql_latency': ['p(95)<300'], }, }; // Test data const graphqlQueries = [ { query: '{ __typename }', }, { query: ` query { me { id email name } } `, }, { query: ` query { sites { id name region status } } `, }, ]; // Helper function to get random query function getRandomQuery() { return graphqlQueries[Math.floor(Math.random() * graphqlQueries.length)]; } export default function () { // Test 1: Health endpoint const healthStart = Date.now(); const healthRes = http.get(`${API_URL}/health`, { tags: { name: 'HealthCheck' }, }); const healthDuration = Date.now() - healthStart; const healthCheck = check(healthRes, { 'health status is 200': (r) => r.status === 200, 'health response time < 100ms': (r) => r.timings.duration < 100, }); errorRate.add(!healthCheck); apiLatency.add(healthDuration); requestCount.add(1, { endpoint: 'health' }); sleep(0.5); // Test 2: GraphQL endpoint const graphqlQuery = getRandomQuery(); const graphqlStart = Date.now(); const graphqlRes = http.post( `${API_URL}/graphql`, JSON.stringify(graphqlQuery), { headers: { 'Content-Type': 'application/json' }, tags: { name: 'GraphQL' }, } ); const graphqlDuration = Date.now() - graphqlStart; const graphqlCheck = check(graphqlRes, { 'graphql status is 200': (r) => r.status === 200, 'graphql response time < 300ms': (r) => r.timings.duration < 300, 'graphql has data': (r) => { try { const body = JSON.parse(r.body); return body.data !== undefined || body.errors === undefined; } catch (e) { return false; } }, }); errorRate.add(!graphqlCheck); graphqlLatency.add(graphqlDuration); requestCount.add(1, { endpoint: 'graphql' }); sleep(1); } export function handleSummary(data) { return { 'stdout': textSummary(data, { indent: ' ', enableColors: true }), 'summary.json': JSON.stringify(data), }; } function textSummary(data, options) { const indent = options.indent || ''; const enableColors = options.enableColors || false; let summary = ''; summary += `${indent}Test Summary\n`; summary += `${indent}============\n\n`; // Metrics summary summary += `${indent}Metrics:\n`; summary += `${indent} - Total Requests: ${data.metrics.requests_total.values.count}\n`; summary += `${indent} - Failed Requests: ${data.metrics.http_req_failed.values.rate * 100}%\n`; summary += `${indent} - p95 Latency: ${data.metrics.http_req_duration.values['p(95)']}ms\n`; summary += `${indent} - p99 Latency: ${data.metrics.http_req_duration.values['p(99)']}ms\n`; return summary; }