feat(benchmarks): add JMH benchmarks for performance testing and load scripts
Build & Test (NowChessSystems) TeamCity build failed
Build & Test (NowChessSystems) TeamCity build failed
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# NowChess Load Testing with k6
|
||||
|
||||
Performance testing suite for NowChess services using k6.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Install k6 (macOS)
|
||||
brew install k6
|
||||
|
||||
# Or download from https://k6.io/docs/getting-started/installation/
|
||||
```
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### 1. Ramp-Up Test
|
||||
Gradually increases load from 10 to 100 concurrent users.
|
||||
|
||||
```bash
|
||||
k6 run ramp-up.js
|
||||
```
|
||||
|
||||
Target: Identify system behavior under gradual load increase.
|
||||
|
||||
### 2. Stress Test
|
||||
Incremental load increase up to 500 concurrent users to find breaking point.
|
||||
|
||||
```bash
|
||||
k6 run stress-test.js
|
||||
```
|
||||
|
||||
Target: Determine system capacity and failure point.
|
||||
|
||||
### 3. Spike Test
|
||||
Sudden traffic surge (baseline 50 → 500 users instantly).
|
||||
|
||||
```bash
|
||||
k6 run spike-test.js
|
||||
```
|
||||
|
||||
Target: Test recovery and resilience to sudden spikes.
|
||||
|
||||
### 4. Constant Load Test
|
||||
Maintains 50 VUs for 10 minutes.
|
||||
|
||||
```bash
|
||||
k6 run constant-load.js
|
||||
```
|
||||
|
||||
Target: Check stability under sustained load.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# Override service endpoints
|
||||
export BASE_URL=http://localhost:8080
|
||||
export ACCOUNT_HOST=http://localhost:8083
|
||||
export STORE_HOST=http://localhost:8085
|
||||
|
||||
k6 run ramp-up.js
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Ensure services are running:
|
||||
- Core: `localhost:8080`
|
||||
- Account: `localhost:8083`
|
||||
- Store: `localhost:8085`
|
||||
- Redis: `localhost:6379` (with increased pool size)
|
||||
|
||||
## Metrics Interpretation
|
||||
|
||||
- `http_req_duration`: Response time (p95, p99 percentiles matter most)
|
||||
- `http_req_failed`: Failed requests (connection errors, errors, non-2xx responses)
|
||||
- `vus`: Virtual Users (concurrent connections)
|
||||
- `iterations`: Completed test cycles per VU
|
||||
|
||||
## Results
|
||||
|
||||
k6 generates HTML report output. Use with:
|
||||
|
||||
```bash
|
||||
k6 run --out=csv=results.csv ramp-up.js
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you see:
|
||||
- **"max pool wait timeout"** → Redis pool still too small or maxWaitTime too short
|
||||
- **"connection refused"** → Service not running
|
||||
- **"high p99 latency"** → System approaching capacity
|
||||
|
||||
Increase Redis pool settings in `modules/*/src/main/resources/application.yml`:
|
||||
```yaml
|
||||
quarkus:
|
||||
redis:
|
||||
pool:
|
||||
max-size: 128 # Increase if still hitting limits
|
||||
max-waiting: 256
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
export const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
|
||||
export const ACCOUNT_HOST = __ENV.ACCOUNT_HOST || 'http://localhost:8083';
|
||||
export const STORE_HOST = __ENV.STORE_HOST || 'http://localhost:8085';
|
||||
export const CORE_HOST = BASE_URL;
|
||||
|
||||
export const ENDPOINTS = {
|
||||
// Account endpoints
|
||||
accountCreateUser: `${ACCOUNT_HOST}/api/account`,
|
||||
accountLogin: `${ACCOUNT_HOST}/api/account/login`,
|
||||
accountProfile: `${ACCOUNT_HOST}/api/account/me`,
|
||||
accountPublicProfile: (username) => `${ACCOUNT_HOST}/api/account/${username}`,
|
||||
|
||||
// Store endpoints
|
||||
storeGame: `${STORE_HOST}/api/games`,
|
||||
storeGameById: (gameId) => `${STORE_HOST}/api/games/${gameId}`,
|
||||
|
||||
// Core endpoints (game operations)
|
||||
gameWebSocket: (gameId) => `ws://localhost:8080/api/games/${gameId}/ws`,
|
||||
};
|
||||
|
||||
export const TEST_USERS = [
|
||||
{ username: 'load-test-user-1', email: 'load1@example.com' },
|
||||
{ username: 'load-test-user-2', email: 'load2@example.com' },
|
||||
{ username: 'load-test-user-3', email: 'load3@example.com' },
|
||||
];
|
||||
@@ -0,0 +1,40 @@
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
import { ENDPOINTS } from './config.js';
|
||||
|
||||
export const options = {
|
||||
vus: 50,
|
||||
duration: '10m',
|
||||
thresholds: {
|
||||
http_req_duration: ['p(95)<500', 'p(99)<1000'],
|
||||
http_req_failed: ['rate<0.1'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
// Simulate consistent user traffic
|
||||
|
||||
// 1. Get account profile
|
||||
const profileRes = http.get(ENDPOINTS.accountProfile);
|
||||
check(profileRes, {
|
||||
'profile status is 200 or 401': (r) => r.status === 200 || r.status === 401,
|
||||
});
|
||||
|
||||
sleep(0.5);
|
||||
|
||||
// 2. Get public profile
|
||||
const publicRes = http.get(ENDPOINTS.accountPublicProfile('testuser'));
|
||||
check(publicRes, {
|
||||
'public profile status ok': (r) => r.status > 0,
|
||||
});
|
||||
|
||||
sleep(0.5);
|
||||
|
||||
// 3. List store games
|
||||
const gamesRes = http.get(ENDPOINTS.storeGame);
|
||||
check(gamesRes, {
|
||||
'store games status ok': (r) => r.status > 0,
|
||||
});
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
import { ENDPOINTS, ACCOUNT_HOST } from './config.js';
|
||||
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '1m', target: 10 },
|
||||
{ duration: '3m', target: 50 },
|
||||
{ duration: '5m', target: 100 },
|
||||
{ duration: '3m', target: 50 },
|
||||
{ duration: '1m', target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_duration: ['p(95)<500', 'p(99)<1000'],
|
||||
http_req_failed: ['rate<0.1'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
// Test account endpoints
|
||||
const accountRes = http.get(ENDPOINTS.accountPublicProfile('testuser'));
|
||||
check(accountRes, {
|
||||
'account endpoint status is 200': (r) => r.status === 200,
|
||||
});
|
||||
|
||||
sleep(1);
|
||||
|
||||
// Test store endpoints
|
||||
const storeRes = http.get(ENDPOINTS.storeGame);
|
||||
check(storeRes, {
|
||||
'store endpoint status is 200': (r) => r.status === 200 || r.status === 401,
|
||||
});
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Default values
|
||||
TEST_TYPE="${1:-ramp-up}"
|
||||
BASE_URL="${BASE_URL:-http://localhost:8080}"
|
||||
OUTPUT_FILE="${2:-results-$(date +%s).csv}"
|
||||
|
||||
echo -e "${BLUE}NowChess Load Test Runner${NC}"
|
||||
echo "Test Type: $TEST_TYPE"
|
||||
echo "Base URL: $BASE_URL"
|
||||
echo ""
|
||||
|
||||
# Check if k6 is installed
|
||||
if ! command -v k6 &> /dev/null; then
|
||||
echo -e "${RED}Error: k6 not found. Install from https://k6.io/docs/getting-started/installation/${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$TEST_TYPE" in
|
||||
ramp-up)
|
||||
echo -e "${GREEN}Running Ramp-Up Test (10->100 VUs over 13 minutes)${NC}"
|
||||
k6 run --out=csv=$OUTPUT_FILE ramp-up.js
|
||||
;;
|
||||
stress)
|
||||
echo -e "${GREEN}Running Stress Test (up to 500 VUs)${NC}"
|
||||
k6 run --out=csv=$OUTPUT_FILE stress-test.js
|
||||
;;
|
||||
spike)
|
||||
echo -e "${GREEN}Running Spike Test (sudden 50->500 spike)${NC}"
|
||||
k6 run --out=csv=$OUTPUT_FILE spike-test.js
|
||||
;;
|
||||
constant)
|
||||
echo -e "${GREEN}Running Constant Load Test (50 VUs for 10m)${NC}"
|
||||
k6 run --out=csv=$OUTPUT_FILE constant-load.js
|
||||
;;
|
||||
all)
|
||||
echo -e "${GREEN}Running All Tests${NC}"
|
||||
for test in ramp-up stress spike constant; do
|
||||
echo ""
|
||||
echo -e "${BLUE}Starting $test test...${NC}"
|
||||
$0 $test
|
||||
sleep 5
|
||||
done
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Usage: $0 {ramp-up|stress|spike|constant|all} [output-file]${NC}"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 ramp-up"
|
||||
echo " $0 stress results.csv"
|
||||
echo " $0 all"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo -e "${GREEN}Test complete. Results saved to $OUTPUT_FILE${NC}"
|
||||
@@ -0,0 +1,36 @@
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
import { ENDPOINTS } from './config.js';
|
||||
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '1m', target: 50 },
|
||||
{ duration: '30s', target: 500, ramp: 'fast' },
|
||||
{ duration: '2m', target: 500 },
|
||||
{ duration: '30s', target: 50, ramp: 'fast' },
|
||||
{ duration: '1m', target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_duration: ['p(95)<2000', 'p(99)<5000'],
|
||||
http_req_failed: ['rate<0.3'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
// Rapid account checks
|
||||
const urls = [
|
||||
ENDPOINTS.accountPublicProfile('testuser'),
|
||||
ENDPOINTS.storeGame,
|
||||
ENDPOINTS.accountProfile,
|
||||
];
|
||||
|
||||
urls.forEach((url) => {
|
||||
const res = http.get(url);
|
||||
check(res, {
|
||||
'response received': (r) => r.status > 0,
|
||||
'response time under 3s': (r) => r.timings.duration < 3000,
|
||||
});
|
||||
});
|
||||
|
||||
sleep(0.1);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
import { ENDPOINTS } from './config.js';
|
||||
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '2m', target: 100 },
|
||||
{ duration: '5m', target: 200 },
|
||||
{ duration: '5m', target: 300 },
|
||||
{ duration: '5m', target: 400 },
|
||||
{ duration: '5m', target: 500 },
|
||||
{ duration: '5m', target: 400 },
|
||||
{ duration: '5m', target: 200 },
|
||||
{ duration: '2m', target: 0 },
|
||||
],
|
||||
thresholds: {
|
||||
http_req_duration: ['p(95)<1000', 'p(99)<2000'],
|
||||
http_req_failed: ['rate<0.2'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const userIndex = Math.floor(Math.random() * 10);
|
||||
|
||||
// Burst account endpoint
|
||||
const accountRes = http.get(ENDPOINTS.accountPublicProfile(`user-${userIndex}`));
|
||||
check(accountRes, {
|
||||
'account endpoint responds': (r) => r.status > 0,
|
||||
});
|
||||
|
||||
// Burst store endpoint
|
||||
const storeRes = http.get(ENDPOINTS.storeGame);
|
||||
check(storeRes, {
|
||||
'store endpoint responds': (r) => r.status > 0,
|
||||
});
|
||||
|
||||
sleep(Math.random() * 0.5);
|
||||
}
|
||||
Reference in New Issue
Block a user