162 lines
4.7 KiB
JavaScript
162 lines
4.7 KiB
JavaScript
|
|
// 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;
|
||
|
|
}
|
||
|
|
|