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