-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from Tiketeer/feat/DEV-190
[DEV-190] 로드테스트 반복 스크립트 작성 (PLOCK)
- Loading branch information
Showing
9 changed files
with
212 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#!/bin/bash | ||
|
||
# Endpoint and resource | ||
url="localhost:4000/api/stress-test" | ||
|
||
# Send DELETE request | ||
response=$(curl -s -X DELETE "$url" -w "%{http_code}") | ||
|
||
http_status=$(echo "$response" | tail -n1) | ||
|
||
# Check if the DELETE was successful | ||
if [ "$http_status" -eq 200 ]; then | ||
echo "Delete successful." | ||
else | ||
echo "Failed to delete resource. HTTP Status: $http_status" | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
version: "3.8" | ||
|
||
services: | ||
k6: | ||
image: grafana/k6 | ||
volumes: | ||
- ./output:/output | ||
- ./k6-scripts:/scripts | ||
command: > | ||
run | ||
-e VSR=${VSR} | ||
-e TICKETS=${TICKETS} | ||
-e ITERATION=${ITERATION} | ||
-e LOCKTYPE=${LOCKTYPE} | ||
/scripts/stress.js | ||
extra_hosts: | ||
- "host.docker.internal:host-gateway" | ||
|
||
# TODO: 서버, DB 서비스 함께 올리기 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,140 +1,173 @@ | ||
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.4.0/index.js"; | ||
import {uuidv4} from "https://jslib.k6.io/k6-utils/1.4.0/index.js"; | ||
import http from "k6/http"; | ||
|
||
import { check } from "k6"; | ||
import {check} from "k6"; | ||
|
||
const setupVar = { | ||
userNum: 300, // 구매자 수 (vUser), | ||
ticketStock: 50, // 티켓 재고 | ||
ticketPrice: 1000, | ||
setupTimeout: "120s", | ||
userNum: __ENV.VSR || 10, // 구매자 수 (vUser), | ||
ticketStock: __ENV.TICKETS || 50, // 티켓 재고 | ||
backoff: __ENV.BACKOFF || 0, | ||
retry: __ENV.RETRY || 0, | ||
waitTime: __ENV.WAITTIME || 0, | ||
leaseTime: __ENV.LEASETIME || 0, | ||
iteration: __ENV.ITERATION || 1, | ||
lockType: __ENV.LOCKTYPE || 'plock', | ||
ticketPrice: 1000, | ||
setupTimeout: "120s", | ||
}; | ||
|
||
export const options = { | ||
setupTimeout: setupVar.setupTimeout, | ||
scenarios: { | ||
contacts: { | ||
executor: "per-vu-iterations", | ||
vus: setupVar.userNum, | ||
iterations: 1, | ||
|
||
setupTimeout: setupVar.setupTimeout, | ||
scenarios: { | ||
contacts: { | ||
executor: "per-vu-iterations", | ||
vus: setupVar.userNum, | ||
iterations: 1, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
const commonHeader = { "Content-Type": "application/json" }; | ||
const commonHeader = {"Content-Type": "application/json"}; | ||
|
||
const domain = "http://host.docker.internal:4000"; | ||
|
||
const commonPwd = "1q2w3e4r@@Q"; | ||
|
||
export function setup() { | ||
// 멤버 생성 (1: seller, n: buyer) | ||
const buyerEmailList = Array(setupVar.userNum) | ||
.fill() | ||
.map(() => `test-buyer-${uuidv4()}@test.com`); | ||
// 멤버 생성 (1: seller, n: buyer) | ||
|
||
const buyerEmailList = Array.from({length: setupVar.userNum}, () => | ||
`test-buyer-${uuidv4()}@test.com` | ||
); | ||
|
||
const sellerEmail = `test-seller-${uuidv4()}@test.com`; | ||
|
||
wrapWithTimeLogging("유저 생성", () => { | ||
createUsers({ | ||
emailList: [...buyerEmailList, sellerEmail], | ||
const sellerEmail = `test-seller-${uuidv4()}@test.com`; | ||
|
||
wrapWithTimeLogging("유저 생성", () => { | ||
createUsers({ | ||
emailList: [...buyerEmailList, sellerEmail], | ||
}); | ||
}); | ||
}); | ||
|
||
// 티케팅 생성 | ||
const now = new Date(); | ||
const saleStart = new Date( | ||
now.getFullYear(), | ||
now.getMonth(), | ||
now.getDate(), | ||
now.getHours() + 9, | ||
now.getMinutes(), | ||
now.getSeconds() + 5 | ||
); | ||
|
||
const ticketingId = wrapWithTimeLogging("티케팅 생성", () => { | ||
return createTicketing({ | ||
title: "Stress Test", | ||
location: "서울", | ||
category: "IT", | ||
runningMinutes: 100, | ||
price: setupVar.ticketPrice, | ||
saleStart, | ||
saleEnd: new Date( | ||
new Date().setFullYear(now.getFullYear() + 1) | ||
).toISOString(), | ||
eventTime: new Date( | ||
new Date().setFullYear(now.getFullYear() + 2) | ||
).toISOString(), | ||
stock: setupVar.ticketStock, | ||
email: sellerEmail, | ||
|
||
// 티케팅 생성 | ||
const now = new Date(); | ||
const saleStart = new Date( | ||
now.getFullYear(), | ||
now.getMonth(), | ||
now.getDate(), | ||
now.getHours() + 9, | ||
now.getMinutes(), | ||
now.getSeconds() + 5 | ||
); | ||
|
||
const ticketingId = wrapWithTimeLogging("티케팅 생성", () => { | ||
return createTicketing({ | ||
title: "Stress Test", | ||
location: "서울", | ||
category: "IT", | ||
runningMinutes: 100, | ||
price: setupVar.ticketPrice, | ||
saleStart, | ||
saleEnd: new Date( | ||
new Date().setFullYear(now.getFullYear() + 1) | ||
).toISOString(), | ||
eventTime: new Date( | ||
new Date().setFullYear(now.getFullYear() + 2) | ||
).toISOString(), | ||
stock: setupVar.ticketStock, | ||
email: sellerEmail, | ||
}); | ||
}); | ||
}); | ||
|
||
return { buyerEmailList, ticketingId }; | ||
return {buyerEmailList, ticketingId}; | ||
} | ||
|
||
export default function ({ buyerEmailList, ticketingId }) { | ||
const userCounter = __VU; | ||
export default function ({buyerEmailList, ticketingId}) { | ||
const userCounter = __VU; | ||
|
||
const email = buyerEmailList[userCounter]; | ||
const email = buyerEmailList[userCounter - 1]; | ||
|
||
// TODO: 락 방법론 마다 분리된 EP 잘 찔러보기 | ||
puchase(ticketingId, 1, email); | ||
|
||
// TODO: 락 방법론 마다 분리된 EP 잘 찔러보기 | ||
purchase(ticketingId, 1, email); | ||
} | ||
|
||
function createUsers(users) { | ||
const response = http.post(domain + "/api/members", JSON.stringify(users), { | ||
headers: commonHeader, | ||
}); | ||
const response = http.post(domain + "/api/members", JSON.stringify(users), { | ||
headers: commonHeader, | ||
}); | ||
|
||
check(response, { | ||
"status check after create user": (r) => r.status === 200, | ||
}); | ||
check(response, { | ||
"status check after create user": (r) => r.status === 200, | ||
}); | ||
} | ||
|
||
function createTicketing(ticketingMetadata) { | ||
const response = http.post( | ||
domain + "/api/ticketings", | ||
JSON.stringify(ticketingMetadata), | ||
{ | ||
headers: commonHeader, | ||
cookies: { accessToken }, | ||
} | ||
); | ||
|
||
check(response, { | ||
"status check after create ticketing": (r) => r.status === 201, | ||
"ticketing id check after create ticketing": (r) => { | ||
const ticketingId = r.json().data["ticketingId"]; | ||
return typeof ticketingId === "string" && ticketingId !== ""; | ||
}, | ||
}); | ||
const response = http.post( | ||
domain + "/api/ticketings", | ||
JSON.stringify(ticketingMetadata), | ||
{ | ||
headers: commonHeader, | ||
} | ||
); | ||
|
||
|
||
check(response, { | ||
"status check after create ticketing": (r) => r.status === 201, | ||
"ticketing id check after create ticketing": (r) => { | ||
const ticketingId = r.json().data["ticketingId"]; | ||
return typeof ticketingId === "string" && ticketingId !== ""; | ||
}, | ||
}); | ||
|
||
return response.json("data")["ticketingId"]; | ||
return response.json("data")["ticketingId"]; | ||
} | ||
|
||
function puchase(ticketingId, count, buyerEmail) { | ||
const response = http.post( | ||
domain + "/api/purchases", | ||
JSON.stringify({ | ||
ticketingId, | ||
count, | ||
}), | ||
{ | ||
headers: commonHeader, | ||
} | ||
); | ||
|
||
check(response, { | ||
"status check after create purchase": (r) => r.status === 201, | ||
}); | ||
function purchase(ticketingId, count, buyerEmail) { | ||
|
||
const response = http.post( | ||
domain + "/api/purchases/p-lock", | ||
JSON.stringify({ | ||
ticketingId, | ||
count, | ||
email: buyerEmail | ||
}), | ||
{ | ||
headers: commonHeader, | ||
} | ||
); | ||
|
||
|
||
check(response, { | ||
"status check after create purchase": (r) => r.status === 201, | ||
}); | ||
} | ||
|
||
function wrapWithTimeLogging(tag, callback) { | ||
const start = new Date(); | ||
const result = callback(); | ||
const end = new Date(); | ||
console.log(`${tag}: 소요시간 - ${end.getTime() - start.getTime()}ms`); | ||
return result; | ||
const start = new Date(); | ||
const result = callback(); | ||
const end = new Date(); | ||
console.log(`${tag}: 소요시간 - ${end.getTime() - start.getTime()}ms`); | ||
return result; | ||
} | ||
|
||
export function handleSummary(data) { | ||
|
||
const filenameParts = [ | ||
setupVar.lockType, | ||
`vus_${setupVar.userNum}`, | ||
`tickets_${setupVar.ticketStock}`, | ||
`backoff_${setupVar.backoff}`, | ||
`retry_${setupVar.retry}`, | ||
`waitTime_${setupVar.waitTime}`, | ||
`leaseTime_${setupVar.leaseTime}`, | ||
`${setupVar.iteration}` | ||
]; | ||
|
||
const filename = `/output/${filenameParts.join('_')}.json`; | ||
|
||
return { | ||
[filename]: JSON.stringify(data) | ||
}; | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#!/bin/bash | ||
|
||
declare -a vsrs=(30 60) | ||
declare -a tickets=(30 60) | ||
|
||
if [[ -z "$1" ]] || ! [[ "$1" =~ ^[0-9]+$ ]]; then | ||
echo "Usage: $0 <number of iterations>" | ||
exit 1 | ||
fi | ||
|
||
for vsr in "${vsrs[@]}"; do | ||
for ticket in "${tickets[@]}"; do | ||
for ((i = 1; i <= $1; i++)); do | ||
sh ./run_plock.sh ${vsr} ${ticket} ${i} | ||
sleep 1 | ||
sh ./cleanup.sh | ||
done | ||
done | ||
done |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#!/bin/bash | ||
# ${1} : VSR | ||
# ${2} : TICKETS | ||
# ${3} : ITERATION | ||
echo ${1} ${2} ${3} | ||
|
||
echo "Run with VSR:${1} TICKETS:${2} ITERATION:${3}" | ||
|
||
VSR=${1} | ||
TICKETS=${2} | ||
ITERATION=${3} | ||
LOCKTYPE="dlock" | ||
|
||
export VSR | ||
export TICKETS | ||
export ITERATION | ||
export LOCKTYPE | ||
|
||
docker compose -f docker-compose.stress-k6-only.yml up | ||
|