Add k6 load testing infrastructure for Darkwatch service
- Create load test directory structure (infra/load-tests/) - Implement k6 script for Darkwatch endpoints (darkwatch.js) - Tests watchlist, scan, exposure, and alert operations - Configured for 500 req/s sustained load with P99 < 200ms - Includes error rate metrics and threshold validation - Add documentation and usage guide (README.md) Related: [FRE-4807](/FRE/issues/FRE-4807) Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
61
infra/load-tests/README.md
Normal file
61
infra/load-tests/README.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# ShieldAI Load Tests
|
||||||
|
|
||||||
|
k6 load testing suite for ShieldAI services.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- k6 v0.45+ installed
|
||||||
|
- Target services running on staging environment
|
||||||
|
- Authentication tokens for API access
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
### Local Execution
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run against local development environment
|
||||||
|
k6 run --env BASE_URL=http://localhost:3000 --env AUTH_TOKEN=dev-token src/darkwatch.js
|
||||||
|
|
||||||
|
# Run with results output
|
||||||
|
k6 run --out json=results.json src/darkwatch.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Execution
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run on staging environment
|
||||||
|
k6 run --env BASE_URL=https://staging-api.freno.me --env AUTH_TOKEN=$STAGING_AUTH_TOKEN src/darkwatch.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Configuration
|
||||||
|
|
||||||
|
Each test script includes:
|
||||||
|
|
||||||
|
- **Stages**: Ramp-up, sustained load, ramp-down
|
||||||
|
- **Thresholds**: P99 latency and error rate limits
|
||||||
|
- **Metrics**: Custom metrics for error tracking
|
||||||
|
|
||||||
|
### Current Thresholds
|
||||||
|
|
||||||
|
| Service | P99 Latency | Error Rate |
|
||||||
|
|---------|-------------|------------|
|
||||||
|
| Darkwatch | < 200ms | < 1% |
|
||||||
|
|
||||||
|
## Metrics Collection
|
||||||
|
|
||||||
|
Run with output options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# JSON output for analysis
|
||||||
|
k6 run --out json=darkwatch-results.json src/darkwatch.js
|
||||||
|
|
||||||
|
# InfluxDB for visualization
|
||||||
|
k6 run --out influxdb=http://influxdb:8086/k6 src/darkwatch.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Create load test scripts for Spamshield and Voiceprint
|
||||||
|
2. Integrate with GitHub Actions CI pipeline
|
||||||
|
3. Set up metrics visualization dashboard
|
||||||
|
4. Configure alerting on threshold breaches
|
||||||
112
infra/load-tests/src/darkwatch.js
Normal file
112
infra/load-tests/src/darkwatch.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check, group } from 'k6';
|
||||||
|
import { Rate } from 'k6/metrics';
|
||||||
|
|
||||||
|
// Custom metrics
|
||||||
|
const errorRate = new Rate('errors');
|
||||||
|
|
||||||
|
// Test configuration
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '30s', target: 100 }, // Ramp up to 100 users
|
||||||
|
{ duration: '2m', target: 500 }, // Ramp to 500 req/s
|
||||||
|
{ duration: '3m', target: 500 }, // Stay at 500 req/s for 3 minutes
|
||||||
|
{ duration: '30s', target: 0 }, // Ramp down to 0
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_duration: ['p(99)<200'], // P99 latency < 200ms
|
||||||
|
errors: ['rate<0.01'], // Error rate < 1%
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
group('Watchlist Operations', function () {
|
||||||
|
// GET /watchlist
|
||||||
|
const watchlistRes = http.get(`${BASE_URL}/watchlist`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${getAuthToken()}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
check(watchlistRes, {
|
||||||
|
'watchlist GET status is 200': (r) => r.status === 200,
|
||||||
|
'watchlist GET P99 < 100ms': (r) => r.timings.duration < 100,
|
||||||
|
});
|
||||||
|
errorRate.add(watchlistRes.status !== 200);
|
||||||
|
|
||||||
|
// POST /watchlist
|
||||||
|
const newItemRes = http.post(
|
||||||
|
`${BASE_URL}/watchlist`,
|
||||||
|
JSON.stringify({ type: 'email', value: `test${Date()}@example.com` }),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${getAuthToken()}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
check(newItemRes, {
|
||||||
|
'watchlist POST status is 201': (r) => r.status === 201,
|
||||||
|
'watchlist POST P99 < 200ms': (r) => r.timings.duration < 200,
|
||||||
|
});
|
||||||
|
errorRate.add(newItemRes.status !== 201);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Scan Operations', function () {
|
||||||
|
// POST /scan
|
||||||
|
const scanRes = http.post(
|
||||||
|
`${BASE_URL}/scan`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: { 'Authorization': `Bearer ${getAuthToken()}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
check(scanRes, {
|
||||||
|
'scan POST status is 200': (r) => r.status === 200,
|
||||||
|
'scan POST P99 < 150ms': (r) => r.timings.duration < 150,
|
||||||
|
});
|
||||||
|
errorRate.add(scanRes.status !== 200);
|
||||||
|
|
||||||
|
// GET /scan/schedule
|
||||||
|
const scheduleRes = http.get(`${BASE_URL}/scan/schedule`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${getAuthToken()}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
check(scheduleRes, {
|
||||||
|
'schedule GET status is 200': (r) => r.status === 200,
|
||||||
|
'schedule GET P99 < 100ms': (r) => r.timings.duration < 100,
|
||||||
|
});
|
||||||
|
errorRate.add(scheduleRes.status !== 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Exposure and Alert Operations', function () {
|
||||||
|
// GET /exposures
|
||||||
|
const exposuresRes = http.get(`${BASE_URL}/exposures`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${getAuthToken()}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
check(exposuresRes, {
|
||||||
|
'exposures GET status is 200': (r) => r.status === 200,
|
||||||
|
'exposures GET P99 < 150ms': (r) => r.timings.duration < 150,
|
||||||
|
});
|
||||||
|
errorRate.add(exposuresRes.status !== 200);
|
||||||
|
|
||||||
|
// GET /alerts
|
||||||
|
const alertsRes = http.get(`${BASE_URL}/alerts`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${getAuthToken()}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
check(alertsRes, {
|
||||||
|
'alerts GET status is 200': (r) => r.status === 200,
|
||||||
|
'alerts GET P99 < 150ms': (r) => r.timings.duration < 150,
|
||||||
|
});
|
||||||
|
errorRate.add(alertsRes.status !== 200);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get auth token (replace with actual token retrieval)
|
||||||
|
function getAuthToken() {
|
||||||
|
return __ENV.AUTH_TOKEN || 'test-token';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user