Skip to content

Commit 82ad33f

Browse files
author
Mikalai Alimenkou
committed
Configured stress test, added metrics for Tomcat requests queue
1 parent 893d2e5 commit 82ad33f

File tree

7 files changed

+854
-5
lines changed

7 files changed

+854
-5
lines changed

compose.yaml

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,38 @@ services:
8888
ports:
8989
- "3000:3000"
9090
depends_on:
91-
- prometheus
91+
- prometheus
92+
93+
library-app:
94+
profiles: ["perftest"]
95+
image: xpinjection.com/library:perf-test
96+
container_name: library-app
97+
environment:
98+
- SPRING_PROFILES_ACTIVE=admin,perftest
99+
- SPRING_SECURITY_USER_NAME=admin
100+
- SPRING_SECURITY_USER_PASSWORD=xpinjection
101+
- SERVER_TOMCAT_THREADS_MAX=200
102+
- SERVER_TOMCAT_MAX_CONNECTIONS=1000
103+
- JAVA_TOOL_OPTIONS=-XX:InitialRAMPercentage=80.0 -XX:MaxRAMPercentage=80.0 -XX:MaxMetaspaceSize=128m
104+
ports:
105+
- "8080:8080"
106+
deploy:
107+
resources:
108+
limits:
109+
cpus: '2.0'
110+
memory: 2G
111+
reservations:
112+
memory: 2G
113+
healthcheck:
114+
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/admin/health/readiness"]
115+
interval: 10s
116+
timeout: 5s
117+
retries: 3
118+
start_period: 30s
119+
depends_on:
120+
db:
121+
condition: service_started
122+
jaeger:
123+
condition: service_started
124+
elastic:
125+
condition: service_healthy

k6/search-books-test.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import http from 'k6/http';
2+
import {check} from 'k6';
3+
4+
const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';
5+
const SCENARIO = __ENV.SCENARIO || 'load';
6+
const TARGET_RPS = parseInt(__ENV.TARGET_RPS || '50');
7+
const DURATION = __ENV.DURATION || '2m';
8+
9+
const AUTHORS = [
10+
'Martin Fowler',
11+
'Robert Martin',
12+
'Kent Beck',
13+
'Eric Evans',
14+
'Michael Feathers',
15+
'Joshua Bloch',
16+
'Erich Gamma',
17+
'Gang of Four',
18+
'Steve McConnell',
19+
'Andrew Hunt',
20+
];
21+
22+
export const options = {
23+
scenarios: {
24+
smoke: {
25+
executor: 'constant-vus',
26+
vus: 1,
27+
duration: '30s',
28+
exec: 'searchBooks',
29+
tags: {test_type: 'smoke'},
30+
gracefulStop: '5s',
31+
},
32+
load: {
33+
executor: 'constant-arrival-rate',
34+
rate: TARGET_RPS,
35+
timeUnit: '1s',
36+
duration: DURATION,
37+
preAllocatedVUs: 50,
38+
maxVUs: 500,
39+
exec: 'searchBooks',
40+
tags: {test_type: 'load'},
41+
gracefulStop: '10s',
42+
},
43+
ramp: {
44+
executor: 'ramping-arrival-rate',
45+
startRate: 10,
46+
timeUnit: '1s',
47+
preAllocatedVUs: 50,
48+
maxVUs: 500,
49+
exec: 'searchBooks',
50+
tags: {test_type: 'ramp'},
51+
gracefulStop: '10s',
52+
stages: [
53+
{duration: '1m', target: TARGET_RPS},
54+
{duration: '3m', target: TARGET_RPS},
55+
{duration: '1m', target: TARGET_RPS * 1.5},
56+
{duration: '2m', target: TARGET_RPS * 1.5},
57+
{duration: '1m', target: TARGET_RPS * 2},
58+
{duration: '1m', target: TARGET_RPS * 2},
59+
{duration: '1m', target: 10},
60+
],
61+
},
62+
stress: {
63+
executor: 'ramping-arrival-rate',
64+
startRate: 10,
65+
timeUnit: '1s',
66+
preAllocatedVUs: 20,
67+
maxVUs: 1000,
68+
exec: 'searchBooks',
69+
tags: {test_type: 'stress'},
70+
gracefulStop: '15s',
71+
stages: [
72+
{duration: '1m', target: TARGET_RPS},
73+
{duration: '2m', target: TARGET_RPS * 2},
74+
{duration: '2m', target: TARGET_RPS * 4},
75+
{duration: '2m', target: TARGET_RPS * 6},
76+
{duration: '1m', target: TARGET_RPS * 8},
77+
{duration: '2m', target: 10},
78+
],
79+
},
80+
},
81+
82+
thresholds: {
83+
'http_req_duration': ['p(95)<500', 'p(99)<1000', 'avg<300'],
84+
85+
'http_req_failed': ['rate<0.01'],
86+
'http_req_duration{test_type:load}': ['p(95)<500'],
87+
'http_req_duration{test_type:stress}': ['p(95)<1000'],
88+
},
89+
};
90+
91+
// Only run the scenario specified by SCENARIO env variable
92+
if (SCENARIO !== 'all') {
93+
for (const key of Object.keys(options.scenarios)) {
94+
if (key !== SCENARIO) {
95+
delete options.scenarios[key];
96+
}
97+
}
98+
}
99+
100+
export function setup() {
101+
console.log(`Starting performance test with scenario: ${SCENARIO}`);
102+
console.log(`Target RPS: ${TARGET_RPS}`);
103+
console.log(`Base URL: ${BASE_URL}`);
104+
console.log(`Duration: ${DURATION}`);
105+
106+
const healthCheck = http.get(`${BASE_URL}/admin/health`);
107+
if (healthCheck.status !== 200) {
108+
throw new Error(`Application health check failed: ${healthCheck.status}`);
109+
}
110+
console.log('Application health check passed');
111+
112+
return {startTime: new Date()};
113+
}
114+
115+
export function searchBooks() {
116+
const author = AUTHORS[Math.floor(Math.random() * AUTHORS.length)];
117+
118+
const response = http.get(`${BASE_URL}/books?author=${encodeURIComponent(author)}`, {
119+
headers: {
120+
'Accept': 'application/json',
121+
},
122+
tags: {
123+
endpoint: 'search-books',
124+
author: author
125+
},
126+
});
127+
128+
check(response, {
129+
'status is 200': (r) => r.status === 200,
130+
'response time < 500ms': (r) => r.timings.duration < 500,
131+
'response time < 1000ms': (r) => r.timings.duration < 1000,
132+
'content-type is JSON': (r) => r.headers['Content-Type']?.includes('application/json'),
133+
'response body is valid': (r) => {
134+
try {
135+
const body = JSON.parse(r.body);
136+
return Array.isArray(body);
137+
} catch (e) {
138+
return false;
139+
}
140+
},
141+
});
142+
}

0 commit comments

Comments
 (0)