Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ out/

### VS Code ###
.vscode/

### Performance Testing ###
# Performance test build outputs
performance/dist/
performance/node_modules/
performance/package-lock.json
performance/.env
performance/*.log
performance/reports/
performance/summary.*

# Log files
logs/

# Compiled class file
*.class

Expand Down
70 changes: 70 additions & 0 deletions performance/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json

# Build outputs
dist/
build/
*.tsbuildinfo

# Environment files
.env
.env.*
!.env.example

# Test reports and outputs
reports/
summary.html
summary.json
summary.txt
*.log

# k6 specific
k6-*-report.html
k6-results.json

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Temporary files
*.tmp
*.temp

# Coverage reports
coverage/
.nyc_output/

# Dependency directories
jspm_packages/

# Optional npm cache directory
.npm

# ESLint cache
.eslintcache

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
200 changes: 200 additions & 0 deletions performance/200vu-simulation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
import { Trend } from 'k6/metrics';

const BASE_URL = 'http://localhost:8080/api/v1';
const USERS_ID = new SharedArray('users', function () {
const arr = [];
for(let i = 1; i< 51; i++) {
arr.push(i);
}
return arr;
});

let tokens = [];

const requestCount = new Trend('request_count', true);

export const options = {
thresholds: {
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<1000'],
},

scenarios: {
normal: {
executor: 'per-vu-iterations',
vus: 50, // 50명의 가상 사용자
iterations: 400, // 각 가상 사용자가 400번의 요청을 수행
maxDuration: '2m', // 테스트 전체 지속 시간
exec: 'normal' // 기록 범위 조회 API
},

mypage: {
executor: 'constant-arrival-rate',
rate: 10,
timeUnit: '4s', // 4초 당 10회 요청
duration: '2m', // 2분 지속
preAllocatedVUs: 4,
maxVUs: 50, // 최대 50명의 가상 사용자
exec: 'mypage', // 마이페이지 조회 API
},
};

export function setup() {
console.log('테스트 셋업 시작: 사용자 로그인 및 토큰 획득');

for (const user of USERS_ID) {
const res = http.post(`${BASE_URL}/auth/test/login`, JSON.stringify({ user_id: user }), {
headers: { 'Content-Type': 'application/json' },
});

const success = check(res, {
'logged in successfully': (r) => r.status === 200 && r.json('payload.tokens.access_token'),
});

if (success) {
tokens.push({
user_id: user,
accessToken: res.json('payload.tokens.access_token'),
refreshToken: res.json('payload.tokens.refresh_token'),
});
} else {
console.error(`사용자 ${user} 로그인 실패: ${res.status}`);
}
}

console.log(`셋업 완료: ${tokens.length}개의 토큰 획득`);
return { tokens };
}

export default function (data) {

}



export function mypage (data) {
const vuIndex = (__VU - 1) % data.tokens.length;
let { user_id, accessToken } = data.tokens[vuIndex];
let res = http.get(`${BASE_URL}/users/me`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (res.status === 401) {
const newToken = refreshAccessToken(data.tokens[vuIndex].refreshToken);
if (newToken) {
accessToken = newToken;
res = http.get(`${BASE_URL}/records/me${queryString}`, {
headers: { Authorization: `Bearer ${newToken}` },
});
data.tokens[vuIndex].accessToken = newToken;
requestCount.add(1);
}
}
check(res, {
'status is 200': (r) => r.status === 200,
});
}




export function normal(data ) {
const vuIndex = (__VU - 1) % data.tokens.length;
let { user_id, accessToken } = data.tokens[vuIndex];
const page = randomIntBetween(0, 5);
const size = randomIntBetween(1, 20);
let startDate = getRandomDateInPast(30);
let endDate = getRandomDateInPast(30);

if (startDate > endDate) {
const tmp = startDate;
startDate = endDate;
endDate = tmp;
}

const queryString = `?page=${page}&size=${size}&startDate=${formatDate(startDate)}&endDate=${formatDate(endDate)}`;

let res = http.get(`${BASE_URL}/records/me${queryString}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});


requestCount.add(1);

if (res.status === 401) {
const newToken = refreshAccessToken(data.tokens[vuIndex].refreshToken);
if (newToken) {
accessToken = newToken;
res = http.get(`${BASE_URL}/records/me${queryString}`, {
headers: { Authorization: `Bearer ${newToken}` },
});
data.tokens[vuIndex].accessToken = newToken;
requestCount.add(1);
}
}
check(res, {
'status is 200': (r) => r.status === 200,
});
}

function runStressScenario(user_id, access_token, vuIndex) {

let res = http.get(`${BASE_URL}/users/me`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (res.status === 401) {
const newToken = refreshAccessToken(data.tokens[vuIndex].refreshToken);
if (newToken) {
accessToken = newToken;
res = http.get(`${BASE_URL}/records/me${queryString}`, {
headers: { Authorization: `Bearer ${newToken}` },
});
data.tokens[vuIndex].accessToken = newToken;
requestCount.add(1);
}
}
check(res, {
'status is 200': (r) => r.status === 200,
});
}

export function teardown() {
const totalDurationInSeconds = 10 + 60 + 30;
const totalRequests = requestCount._sum;

const throughput = totalRequests / totalDurationInSeconds;
console.log(`\n---\n Throughput (requests/sec): ${throughput.toFixed(2)}\n---`);
}

// Utils
function randomIntBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

function formatDate(date) {
return date.toISOString().slice(0, 10);
}

function getRandomDateInPast(daysAgo = 30) {
const date = new Date();
date.setDate(date.getDate() - randomIntBetween(0, daysAgo));
return date;
}

function refreshAccessToken(refreshToken) {
const res = http.post(`${BASE_URL}/auth/refresh`, JSON.stringify({
refresh_token: refreshToken
}), {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${refreshToken}`
},
});
if (res.status === 200 && res.json('payload.access_token')) {
return res.json('payload.access_token');
}

return null;
}
59 changes: 59 additions & 0 deletions performance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## 🚀 설치 및 실행

### 1. 의존성 설치
```bash
cd performance
npm install
```

### 2. 환경 설정
```bash
cp .env.example .env
# .env 파일을 편집하여 실제 API URL 및 설정값 입력
```

### 3. 빌드
```bash
npm run build
```

### 4. 테스트 실행

#### 개별 테스트 실행
```bash
# 기본 부하 테스트
npm run test:basic-load

# 간단한 반복 테스트
npm run test:simple

# 실제 사용자 시뮬레이션 (22분 소요)
npm run test:real-user

# 다중 시나리오 테스트
npm run test:multi
```

#### 환경변수와 함께 실행
```bash
API_BASE_URL=https://your-api.com/v1 MAX_USERS=100 k6 run dist/basic-load.js
```

#### 모든 테스트 실행
```bash
npm run test:all
```

### 개발 모드 (파일 변경 감지)
```bash
npm run build:watch
```

### 새로운 테스트 추가
1. `src/tests/` 디렉토리에 새 TypeScript 파일 생성
2. `webpack.config.js`의 entry에 새 파일 추가
3. `package.json`의 scripts에 실행 명령 추가

### 새로운 시나리오 추가
1. `src/scenarios/` 디렉토리에 새 시나리오 클래스 생성
2. 기존 테스트에서 import하여 사용
25 changes: 25 additions & 0 deletions performance/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "k6-performance-tests",
"version": "1.0.0",
"description": "K6 performance tests written in TypeScript",
"scripts": {
"build": "webpack",
"build:watch": "webpack --watch",
"clean": "rm -rf dist",
"test:basic-load": "k6 run dist/basic-load.js",
"test:simple": "k6 run dist/simple-iteration.js",
"test:real-user": "k6 run dist/real-user-simulation.js",
"test:multi": "k6 run dist/multi-scenario.js",
"test:all": "npm run build && npm run test:basic-load && npm run test:simple && npm run test:real-user && npm run test:multi"
Copy link

@coderabbitai coderabbitai bot Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add missing test script for 200vu-simulation.js.

The test:all script and individual test scripts don't include the 200vu-simulation.js file that was added to the performance directory. Consider adding a script for this test.

    "test:multi": "k6 run dist/multi-scenario.js",
+    "test:200vu": "k6 run 200vu-simulation.js",
-    "test:all": "npm run build && npm run test:basic-load && npm run test:simple && npm run test:real-user && npm run test:multi"
+    "test:all": "npm run build && npm run test:basic-load && npm run test:simple && npm run test:real-user && npm run test:multi && npm run test:200vu"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"test:all": "npm run build && npm run test:basic-load && npm run test:simple && npm run test:real-user && npm run test:multi"
"test:multi": "k6 run dist/multi-scenario.js",
"test:200vu": "k6 run 200vu-simulation.js",
"test:all": "npm run build && npm run test:basic-load && npm run test:simple && npm run test:real-user && npm run test:multi && npm run test:200vu"
🤖 Prompt for AI Agents
In performance/package.json at line 13, the test:all script does not include a
test command for the new 200vu-simulation.js file. Add a new npm script to run
the 200vu-simulation.js test and include this script in the test:all sequence to
ensure it is executed with the other tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저건 js로 기존 레거시를 그냥 둔거라서 참고용이야

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekgns33 Thanks for the clarification! I understand now that the 200vu-simulation.js file is legacy JavaScript code kept for reference purposes and shouldn't be integrated into the new TypeScript-based test suite structure. I'll note this for future reference.


✏️ Learnings added
Learnt from: ekgns33
PR: Run-Us/Runimo#105
File: performance/package.json:13-13
Timestamp: 2025-06-30T04:22:51.529Z
Learning: In the performance testing directory of the Run-Us/Runimo repository, the file `200vu-simulation.js` is legacy JavaScript code kept for reference purposes only and should not be integrated into the npm scripts or main test suite. The project uses TypeScript-based K6 performance tests as the active testing approach.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

},
"devDependencies": {
"@types/k6": "^0.47.0",
"typescript": "^5.0.0",
"webpack": "^5.88.0",
"webpack-cli": "^5.1.0",
"ts-loader": "^9.4.0"
},
Comment on lines +15 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify dependency versions for security and compatibility.

The dependency versions should be checked for security vulnerabilities and compatibility with the latest k6 features.


🌐 Web query:

What are the latest stable versions of @types/k6, typescript, webpack, webpack-cli, and ts-loader? Are there any known security vulnerabilities in the specified versions?

💡 Result:

As of June 30, 2025, the latest stable versions of the specified packages are:

  • @types/k6: Version 0.45.0
  • TypeScript: Version 5.0.4
  • webpack: Version 5.88.2
  • webpack-cli: Version 4.6.0
  • ts-loader: Version 9.5.2

Regarding security vulnerabilities, as of the latest available information:

  • @types/k6: No known security vulnerabilities have been reported.
  • TypeScript: No known security vulnerabilities have been reported.
  • webpack: No known security vulnerabilities have been reported.
  • webpack-cli: No known security vulnerabilities have been reported.
  • ts-loader: No direct vulnerabilities have been found in the latest version (9.5.2). (security.snyk.io)

Please note that security landscapes can change over time. It's advisable to regularly check official sources or security advisories for the most current information.

Citations:


Dependencies mostly up to date; confirm unstable versions

No known security vulnerabilities were found in the specified versions, but a couple of entries merit your attention in performance/package.json (lines 15–21):

  • @types/k6 is set to ^0.47.0 but the latest stable release is 0.45.0 – please confirm you need the newer (potentially pre-release) version.
  • webpack-cli is set to ^5.1.0 while the latest stable is 4.6.0 – either lock to 4.x or verify v5.x is fully compatible with your webpack setup.
  • typescript (^5.0.0), webpack (^5.88.0) and ts-loader (^9.4.0) will automatically pick up the latest patches (5.0.4, 5.88.2 and 9.5.2 respectively) and have no reported vulnerabilities.

Please adjust or confirm these versions to ensure compatibility.

🤖 Prompt for AI Agents
In performance/package.json around lines 15 to 21, review the versions of
@types/k6 and webpack-cli dependencies. Confirm if you intentionally want to use
the potentially unstable @types/k6 version ^0.47.0 instead of the latest stable
0.45.0, and verify that webpack-cli version ^5.1.0 is fully compatible with your
webpack setup or consider downgrading it to the latest stable 4.x version.
Adjust the version numbers accordingly to ensure stability and compatibility.

"keywords": ["k6", "performance", "testing", "typescript"],
"author": "",
"license": "MIT"
}
17 changes: 17 additions & 0 deletions performance/src/config/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const config = {
baseUrl: __ENV.API_BASE_URL || 'http://localhost:8080/api/v1',
maxUsers: parseInt(__ENV.MAX_USERS || '1'),
testDuration: __ENV.TEST_DURATION || '2m',

// Thresholds
httpReqDurationP95: parseInt(__ENV.HTTP_REQ_DURATION_P95 || '1000'),
httpReqFailedRate: parseFloat(__ENV.HTTP_REQ_FAILED_RATE || '0.01'),

// Report settings
enableHtmlReport: (__ENV.ENABLE_HTML_REPORT || 'true') === 'true',
};

export const thresholds = {
http_req_failed: [`rate<${config.httpReqFailedRate}`],
http_req_duration: [`p(95)<${config.httpReqDurationP95}`],
};
Loading