diff --git a/docs/open-api.yaml b/docs/open-api.yaml index 2ea00e38..5b73897d 100644 --- a/docs/open-api.yaml +++ b/docs/open-api.yaml @@ -494,15 +494,15 @@ components: format: int32 sort: $ref: '#/components/schemas/SortObject' + first: + type: boolean + last: + type: boolean numberOfElements: type: integer format: int32 pageable: $ref: '#/components/schemas/PageableObject' - first: - type: boolean - last: - type: boolean empty: type: boolean PageableObject: @@ -640,15 +640,15 @@ components: format: int32 sort: $ref: '#/components/schemas/SortObject' + first: + type: boolean + last: + type: boolean numberOfElements: type: integer format: int32 pageable: $ref: '#/components/schemas/PageableObject' - first: - type: boolean - last: - type: boolean empty: type: boolean EventResponse: @@ -703,14 +703,14 @@ components: format: int32 sort: $ref: '#/components/schemas/SortObject' + first: + type: boolean + last: + type: boolean numberOfElements: type: integer format: int32 pageable: $ref: '#/components/schemas/PageableObject' - first: - type: boolean - last: - type: boolean empty: type: boolean diff --git a/src/performanceTest/README.md b/src/performanceTest/README.md index 345d12b5..34cfd12b 100644 --- a/src/performanceTest/README.md +++ b/src/performanceTest/README.md @@ -37,11 +37,11 @@ JWT_ISSUER= # Monitoring Configuration (Optional) GRAFANA_HOST= -K6_OUT= #experimental-prometheus-rw +K6_OUT= # experimental-prometheus-rw K6_PROMETHEUS_RW_SERVER_URL= K6_PROMETHEUS_RW_USERNAME= K6_PROMETHEUS_RW_PASSWORD= -K6_PROMETHEUS_RW_TREND_AS_NATIVE_HISTOGRAM= #true +K6_PROMETHEUS_RW_TREND_AS_NATIVE_HISTOGRAM=true ``` ### 4. Run the following commands @@ -53,11 +53,8 @@ You can change the `ENTRYPOINT` variable to run different test files: ```shell -# To run the `a_test.js` file -ENTRYPOINT=a_test.js docker-compose up - -# To run the `b_test.js` file -ENTRYPOINT=b_test.js docker-compose up +# To run the `smokeTest/healthCheck.js` file +ENTRYPOINT=smokeTest/healthCheck.js docker-compose up ``` diff --git a/src/performanceTest/dummy-data/src/schema.js b/src/performanceTest/dummy-data/src/schema.js index 1332f2ab..150edb73 100644 --- a/src/performanceTest/dummy-data/src/schema.js +++ b/src/performanceTest/dummy-data/src/schema.js @@ -30,7 +30,7 @@ const event = new Table('event', [ const user = new Table('user', [ new Field('id', (i) => `${i + 1}`), new Field('email', (i) => `K6-${i + 1}@email.com`), - new Field('name', (i) => `${i + 1}`), + new Field('name', (i) => `${i + 1}-name`), new Field('phone_number', (i) => "010-1234-1234"), new Field('pw', (i) => userpassword[i % 1000]), new Field('created_at', (i) => faker.date.recent()), diff --git a/src/performanceTest/initdb/sqls/cleanup.sql b/src/performanceTest/initdb/sqls/cleanup.sql index f8503e44..8510abb3 100644 --- a/src/performanceTest/initdb/sqls/cleanup.sql +++ b/src/performanceTest/initdb/sqls/cleanup.sql @@ -4,8 +4,13 @@ SET foreign_key_checks = 0; TRUNCATE bookmark; TRUNCATE reservation; -DELETE FROM event WHERE id > 10000000; + DELETE FROM user WHERE id > 1000000; --- UPDATE event SET current_reservation_count = 0; +ALTER TABLE user AUTO_INCREMENT=1000001; + +DELETE FROM event WHERE id > 10000000; +ALTER TABLE event AUTO_INCREMENT=10000001; + +UPDATE event SET total_attendees = 0 WHERE id IN (98); SET foreign_key_checks = 1; \ No newline at end of file diff --git a/src/performanceTest/scripts/breakdownTest/breakdownTest.js b/src/performanceTest/scripts/breakdownTest/breakdownTest.js new file mode 100644 index 00000000..b9ca53f6 --- /dev/null +++ b/src/performanceTest/scripts/breakdownTest/breakdownTest.js @@ -0,0 +1,59 @@ +import { check } from "k6"; +import Request from "../lib/request.js"; +import { encode } from "../lib/jwt.js"; +import hooks from "../lib/hooks.js"; +import generator from "../lib/generator.js"; +import { isSuccess, randomInt, isAlreadReservedAll } from "../lib/helpers.js"; + +export const setup = hooks.setup +export const handleSummary = hooks.handleSummary + +export const options = { + tags: { + testid: `${__ENV.ENTRYPOINT}` + }, + ext: { + loadimpact: { + apm: [ + { + includeTestRunId: true, + } + ] + } + }, + + executor: 'ramping-arrival-rate', //Assure load increase if the system slows + stages: [ + { duration: '30m', target: 10000 }, // just slowly ramp-up to a HUGE load + ], + + thresholds: { + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + http_req_duration: ['p(95)<300'], // 95% of requests should be below 300ms + }, +}; + + +export default function () { + const req = new Request() + + const ID = randomInt(1, 1000000) + req.setToken(encode(ID)) + + const query = { + size: 20, + page: 0, + sort: "id,asc" + } + for (let i = 0; i < 13; i++) { + check(req.getEvents(query), {"Success Get Events": isSuccess}); + query.page = query.page + randomInt(1, 10) + } + + const eventId = 98 // maxAttendees = 191 + check(req.getEvent(eventId), {"EVENT 98 maxAttendees = 191": (r) => r.json().data.maxAttendees === 191}) + + const res = req.createReservation(generator.Reservation(eventId)) + check(res, {"Success Reservation": isSuccess}); + check(res, {"Already reserved": isAlreadReservedAll}); +} \ No newline at end of file diff --git a/src/performanceTest/scripts/lib/generator.js b/src/performanceTest/scripts/lib/generator.js index cf944bad..4522a166 100644 --- a/src/performanceTest/scripts/lib/generator.js +++ b/src/performanceTest/scripts/lib/generator.js @@ -15,19 +15,32 @@ const User = (ID) => { let pw = Number(ID) % 1000 if (pw == 0) pw = 1000 return { - name: `${ID}`, + name: `${ID}-name`, email: `K6-${ID}@email.com`, password: `K6-${pw}-password`, + phoneNumber: '010-1234-1234', } } else { return { name: `${getPrefix()}-name`, email: `${getPrefix()}@email.com`, password: `${getPrefix()}-password`, + phoneNumber: '010-1234-1234', } } } +const Reservation = (eventId) => { + return { + eventId, + name: "name", + phoneNumber: "010-1234-1234", + postCode: 12345, + address: "address", + } +} + export default { - User + User, + Reservation, } \ No newline at end of file diff --git a/src/performanceTest/scripts/lib/helpers.js b/src/performanceTest/scripts/lib/helpers.js index 1b57a942..6dd7c289 100644 --- a/src/performanceTest/scripts/lib/helpers.js +++ b/src/performanceTest/scripts/lib/helpers.js @@ -3,10 +3,18 @@ import exec from 'k6/execution'; const getRandomByRange = (max) => Math.floor(Math.random() * max); +export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + export const getOneFromList = (list) => list[getRandomByRange(list.length)]; export const isSuccess = (r) => r.status >= 200 && r.status < 300; export const isFail = (r) => !isSuccess(r); + + +export const isAlreadReservedAll = (r) => r.status == 409 && r.json().errorCode == 50001; +export const isRunningQueueTicket = (r) => r.status == 200 && r.json().data.isWaiting == false; + export const randomInt = (start, end) => randomIntBetween(start, end); -export const getUserIDFromExec = (VU_COUNT) => exec.vu.idInTest + (VU_COUNT * exec.vu.iterationInScenario) \ No newline at end of file +export const getUserIDFromExec = (VU_COUNT) => exec.vu.idInTest + (VU_COUNT * exec.vu.iterationInScenario) + diff --git a/src/performanceTest/scripts/lib/hooks.js b/src/performanceTest/scripts/lib/hooks.js index 212a4b94..d9072a00 100644 --- a/src/performanceTest/scripts/lib/hooks.js +++ b/src/performanceTest/scripts/lib/hooks.js @@ -1,10 +1,12 @@ import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js"; import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js"; import config from "./config.js"; +import http from "k6/http"; + +http.setResponseCallback(http.expectedStatuses({ min: 200, max: 499 })); export default { setup: function() { - console.log(`setup: ${new Date().toISOString()}`) // https://stackoverflow.com/questions/73458542/k6-storing-data-between-setup-and-default-functions return { diff --git a/src/performanceTest/scripts/lib/request.js b/src/performanceTest/scripts/lib/request.js index 43d969df..ff86043b 100644 --- a/src/performanceTest/scripts/lib/request.js +++ b/src/performanceTest/scripts/lib/request.js @@ -2,7 +2,19 @@ import http from "k6/http"; import { sleep } from "k6"; import config from "./config.js"; - +function parseQuery(query) { + if (query == null) { + return '' + } + const keys = Object.keys(query) + const queryList = [] + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const value = query[key] + queryList.push(`${key}=${value}`) + } + return '?' + queryList.join('&') +} export default class Request { constructor(baseURL = config.HOST) { this.baseURL = baseURL @@ -56,9 +68,9 @@ export default class Request { return res } - getEvents() { + getEvents(query) { this.beforeHook() - const res = http.get(`${this.baseURL}/events/`); + const res = http.get(`${this.baseURL}/events` + parseQuery(query), this.getParams()); this.afterHook() return res } @@ -78,7 +90,7 @@ export default class Request { this.beforeHook() const res = http.post(`${this.baseURL}/users/signin`, JSON.stringify({ email: body.email, password: body.password }), this.getParams()); if (res.body) { - this.setToken(res.json()['Authorization']) + this.setToken(res.json().data.Authorization) } this.afterHook() return res @@ -98,4 +110,21 @@ export default class Request { this.afterHook() return res } + + + createQueueTicket(eventId, userId) { + this.beforeHook() + const res = http.post(`${this.baseURL}/ticket`, JSON.stringify({eventId, userId}), this.getParams()); + this.afterHook() + return res + } + + getQueueTicket(eventId, userId) { + this.beforeHook() + const res = http.get(`${this.baseURL}/ticket/${eventId}/${userId}`, this.getParams()); + this.afterHook() + this.afterHook() + this.afterHook() + return res + } } \ No newline at end of file diff --git a/src/performanceTest/scripts/loadTest.js b/src/performanceTest/scripts/loadTest/loadTest.js similarity index 76% rename from src/performanceTest/scripts/loadTest.js rename to src/performanceTest/scripts/loadTest/loadTest.js index 48ea85ff..abbf519d 100644 --- a/src/performanceTest/scripts/loadTest.js +++ b/src/performanceTest/scripts/loadTest/loadTest.js @@ -1,8 +1,8 @@ import { check } from "k6"; -import Request from "./lib/request.js"; -import generator from "./lib/generator.js"; -import hooks from "./lib/hooks.js"; -import { isSuccess, getOneFromList } from "./lib/helpers.js"; +import Request from "../lib/request.js"; +import generator from "../lib/generator.js"; +import hooks from "../lib/hooks.js"; +import { isSuccess, getOneFromList } from "../lib/helpers.js"; export const setup = hooks.setup export const handleSummary = hooks.handleSummary @@ -37,15 +37,6 @@ export default function () { const getAvaliableReservation = () => { const events = req.getEvents() return getOneFromList(events.json()) - - - // for (const event of events) { - // if (event.currentReservationCount < event.maxAttendees) { - // return event.id - // } - // } - - // return null } const user = generator.User() diff --git a/src/performanceTest/scripts/check/makeUsers.js b/src/performanceTest/scripts/makeUsers.js similarity index 67% rename from src/performanceTest/scripts/check/makeUsers.js rename to src/performanceTest/scripts/makeUsers.js index 4c5193c3..797a93ff 100644 --- a/src/performanceTest/scripts/check/makeUsers.js +++ b/src/performanceTest/scripts/makeUsers.js @@ -1,19 +1,19 @@ import { check } from "k6"; -import Request from "../lib/request.js"; -import hooks from "../lib/hooks.js"; -import generator from "../lib/generator.js"; -import { getUserIDFromExec, isSuccess } from "../lib/helpers.js"; +import Request from "./lib/request.js"; +import hooks from "./lib/hooks.js"; +import generator from "./lib/generator.js"; +import { getUserIDFromExec, isSuccess } from "./lib/helpers.js"; export const setup = hooks.setup export const handleSummary = hooks.handleSummary -const VU_COUNT = 100 +const VU_COUNT = 10 export const options = { scenarios: { contacts: { executor: 'per-vu-iterations', vus: VU_COUNT, - iterations: 20, + iterations: 10, maxDuration: '10m', }, }, diff --git a/src/performanceTest/scripts/check/healthCheck.js b/src/performanceTest/scripts/smokeTest/healthCheck.js similarity index 100% rename from src/performanceTest/scripts/check/healthCheck.js rename to src/performanceTest/scripts/smokeTest/healthCheck.js diff --git a/src/performanceTest/scripts/check/jwtCheck.js b/src/performanceTest/scripts/smokeTest/jwtCheck.js similarity index 86% rename from src/performanceTest/scripts/check/jwtCheck.js rename to src/performanceTest/scripts/smokeTest/jwtCheck.js index 15f4c6e8..986e878a 100644 --- a/src/performanceTest/scripts/check/jwtCheck.js +++ b/src/performanceTest/scripts/smokeTest/jwtCheck.js @@ -15,5 +15,5 @@ export default function () { const res = req.access_token_info() check(res, { "status == 200": (r) => r.status == 200 }); - check(res, { "res has userId key": (r) => r.json().userId > 0 }); + check(res, { "res has userId key": (r) => r.json().data.userId > 0 }); } diff --git a/src/performanceTest/scripts/smokeTest/queue_reservationCheck.js b/src/performanceTest/scripts/smokeTest/queue_reservationCheck.js new file mode 100644 index 00000000..d13853d7 --- /dev/null +++ b/src/performanceTest/scripts/smokeTest/queue_reservationCheck.js @@ -0,0 +1,28 @@ +import { check } from "k6"; +import Request from "../lib/request.js"; +import generator from "../lib/generator.js"; +import hooks from "../lib/hooks.js"; +import { getOneFromList, isRunningQueueTicket } from "../lib/helpers.js"; + +export const setup = hooks.setup +export const handleSummary = hooks.handleSummary + +export default function () { + const req = new Request() + + const USERID = 1 + const user = generator.User(USERID) + req.signup(user) + req.signin(user) + const event = getOneFromList(req.getEvents().json().data) + const res1 = req.createQueueTicket(event.id, USERID) + + check(res1, { "createQueueTicket status == 201": (r) => r.status == 201 }); + + while (!isRunningQueueTicket(req.getQueueTicket(event.id, USERID))) {} + + const res2 = req.createReservation(generator.Reservation(event.id)) + console.log(res2) + check(res2, { "createReservation status == 201": (r) => r.status == 201 }); + check(res2, { "createReservation.eventId == event.id": (r) => r.json().data.eventId == event.id }); +} \ No newline at end of file diff --git a/src/performanceTest/scripts/check/reservationCheck.js b/src/performanceTest/scripts/smokeTest/reservationCheck.js similarity index 59% rename from src/performanceTest/scripts/check/reservationCheck.js rename to src/performanceTest/scripts/smokeTest/reservationCheck.js index 97027d97..fc4c1598 100644 --- a/src/performanceTest/scripts/check/reservationCheck.js +++ b/src/performanceTest/scripts/smokeTest/reservationCheck.js @@ -13,10 +13,8 @@ export default function () { const user = generator.User() req.signup(user) req.signin(user) - - const event = getOneFromList(req.getEvents().json()) - - const res = req.createReservation({ eventId: event.id }) - check(res, { "status == 200": (r) => r.status == 200 }); - check(res, { "created.eventId == event.id": (r) => r.json().eventId == event.id }); -} + const event = getOneFromList(req.getEvents().json().data) + const res = req.createReservation(generator.Reservation(event.id)) + check(res, { "status == 201": (r) => r.status == 201 }); + check(res, { "created.eventId == event.id": (r) => r.json().data.eventId == event.id }); +} \ No newline at end of file diff --git a/src/performanceTest/scripts/check/signinCheck.js b/src/performanceTest/scripts/smokeTest/signinCheck.js similarity index 71% rename from src/performanceTest/scripts/check/signinCheck.js rename to src/performanceTest/scripts/smokeTest/signinCheck.js index 4ba21239..2dfef7d3 100644 --- a/src/performanceTest/scripts/check/signinCheck.js +++ b/src/performanceTest/scripts/smokeTest/signinCheck.js @@ -2,6 +2,7 @@ import { check } from "k6"; import Request from "../lib/request.js"; import generator from "../lib/generator.js"; import hooks from "../lib/hooks.js"; +import { isSuccess } from "../lib/helpers.js"; export const setup = hooks.setup export const handleSummary = hooks.handleSummary @@ -16,6 +17,6 @@ export default function () { const res = req.access_token_info() - check(res, { "status == 200": (r) => r.status == 200 }); - check(res, { "res has userId key": (r) => r.json().userId > 0 }); + check(res, { "isSuccess": isSuccess }); + check(res, { "res has userId key": (r) => r.json().data.userId > 0 }); } diff --git a/src/performanceTest/scripts/spikeTest.js b/src/performanceTest/scripts/spikeTest.js deleted file mode 100644 index e073d5fe..00000000 --- a/src/performanceTest/scripts/spikeTest.js +++ /dev/null @@ -1,61 +0,0 @@ -import { check } from "k6"; -import Request from "./lib/request.js"; -import { encode } from "./lib/jwt.js"; -import hooks from "./lib/hooks.js"; -import { isSuccess, getOneFromList, randomInt } from "./lib/helpers.js"; -import exec from 'k6/execution'; - -export const setup = hooks.setup -export const handleSummary = hooks.handleSummary - -export const options = { - tags: { - testid: `${__ENV.ENTRYPOINT}` - }, - ext: { - loadimpact: { - apm: [ - { - includeTestRunId: true, - } - ] - } - }, - scenarios: { - contacts: { - executor: 'per-vu-iterations', - vus: 200, - iterations: 1, - maxDuration: '1m', - }, - }, - - thresholds: { - http_req_failed: ['rate<0.01'], // http errors should be less than 1% - http_req_duration: ['p(95)<300'], // 95% of requests should be below 300ms - }, -}; - -export default function () { - const req = new Request() - - const getAvaliableReservation = () => { - let count = 0 - while (count < 10) { - req.getEvents() - count++ - } - const events = req.getEvents() - return getOneFromList(events.json()) - } - - req.setToken(encode(exec.vu.idInTest)) - - const event = getAvaliableReservation() - if (event) { - const res = req.createReservation({ - eventId: event.id - }) - check(res, {"Success Reservation": isSuccess}); - } -} \ No newline at end of file diff --git a/src/performanceTest/scripts/spikeTest/queue_spikeTest.js b/src/performanceTest/scripts/spikeTest/queue_spikeTest.js new file mode 100644 index 00000000..9959b331 --- /dev/null +++ b/src/performanceTest/scripts/spikeTest/queue_spikeTest.js @@ -0,0 +1,68 @@ +import { check } from "k6"; +import Request from "../lib/request.js"; +import { encode } from "../lib/jwt.js"; +import hooks from "../lib/hooks.js"; +import generator from "../lib/generator.js"; +import { isSuccess, randomInt, isAlreadReservedAll, isRunningQueueTicket } from "../lib/helpers.js"; + +export const setup = hooks.setup +export const handleSummary = hooks.handleSummary + +export const options = { + tags: { + testid: `${__ENV.ENTRYPOINT}` + }, + ext: { + loadimpact: { + apm: [ + { + includeTestRunId: true, + } + ] + } + }, + scenarios: { + contacts: { + executor: 'per-vu-iterations', + vus: 200, + iterations: 1, + maxDuration: '1m', + }, + }, + + thresholds: { + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + http_req_duration: ['p(95)<300'], // 95% of requests should be below 300ms + }, +}; + + +export default function () { + const req = new Request() + + const ID = randomInt(1, 1000000) + req.setToken(encode(ID)) + + const query = { + size: 20, + page: 0, + sort: "id,asc" + } + for (let i = 0; i < 13; i++) { + check(req.getEvents(query), {"Success Get Events": isSuccess}); + query.page = query.page + randomInt(1, 10) + } + + const eventId = 98 // maxAttendees = 191 + check(req.getEvent(eventId), {"EVENT 98 maxAttendees = 191": (r) => r.json().data.maxAttendees === 191}) + + const res1 = req.createQueueTicket(eventId, ID) + check(res1, { "createQueueTicket status == 201": (r) => r.status == 201 }); + + while (!isRunningQueueTicket(req.getQueueTicket(eventId, ID))) {} + + + const res = req.createReservation(generator.Reservation(eventId)) + check(res, {"Success Reservation": isSuccess}); + check(res, {"Already reserved": isAlreadReservedAll}); +} \ No newline at end of file diff --git a/src/performanceTest/scripts/signinTest.js b/src/performanceTest/scripts/spikeTest/signinTest.js similarity index 80% rename from src/performanceTest/scripts/signinTest.js rename to src/performanceTest/scripts/spikeTest/signinTest.js index 561bf2e0..ef49c52d 100644 --- a/src/performanceTest/scripts/signinTest.js +++ b/src/performanceTest/scripts/spikeTest/signinTest.js @@ -1,8 +1,8 @@ import { check } from "k6"; -import Request from "./lib/request.js"; -import generator from "./lib/generator.js"; -import hooks from "./lib/hooks.js"; -import { isSuccess, randomInt } from "./lib/helpers.js"; +import Request from "../lib/request.js"; +import generator from "../lib/generator.js"; +import hooks from "../lib/hooks.js"; +import { isSuccess, randomInt } from "../lib/helpers.js"; export const setup = hooks.setup export const handleSummary = hooks.handleSummary @@ -43,7 +43,7 @@ export default function () { const user = generator.User(ID) const res = req.signin(user) if (!isSuccess(res)) { - console.log(user, res.body) + console.log(user, res.status, res.body) } check(res, {"Success SignIn": (r) => isSuccess(r) && r.json().Authorization}); } \ No newline at end of file diff --git a/src/performanceTest/scripts/spikeTest/spikeTest.js b/src/performanceTest/scripts/spikeTest/spikeTest.js new file mode 100644 index 00000000..104fdd86 --- /dev/null +++ b/src/performanceTest/scripts/spikeTest/spikeTest.js @@ -0,0 +1,62 @@ +import { check } from "k6"; +import Request from "../lib/request.js"; +import { encode } from "../lib/jwt.js"; +import hooks from "../lib/hooks.js"; +import generator from "../lib/generator.js"; +import { isSuccess, randomInt, isAlreadReservedAll } from "../lib/helpers.js"; + +export const setup = hooks.setup +export const handleSummary = hooks.handleSummary + +export const options = { + tags: { + testid: `${__ENV.ENTRYPOINT}` + }, + ext: { + loadimpact: { + apm: [ + { + includeTestRunId: true, + } + ] + } + }, + scenarios: { + contacts: { + executor: 'per-vu-iterations', + vus: 200, + iterations: 1, + maxDuration: '1m', + }, + }, + + thresholds: { + http_req_failed: ['rate<0.01'], // http errors should be less than 1% + http_req_duration: ['p(95)<300'], // 95% of requests should be below 300ms + }, +}; + + +export default function () { + const req = new Request() + + const ID = randomInt(1, 1000000) + req.setToken(encode(ID)) + + const query = { + size: 20, + page: 0, + sort: "id,asc" + } + for (let i = 0; i < 13; i++) { + check(req.getEvents(query), {"Success Get Events": isSuccess}); + query.page = query.page + randomInt(1, 10) + } + + const eventId = 98 // maxAttendees = 191 + check(req.getEvent(eventId), {"EVENT 98 maxAttendees = 191": (r) => r.json().data.maxAttendees === 191}) + + const res = req.createReservation(generator.Reservation(eventId)) + check(res, {"Success Reservation": isSuccess}); + check(res, {"Already reserved": isAlreadReservedAll}); +} \ No newline at end of file