Skip to content

Commit 0b56fb3

Browse files
committed
chore: add publish wiki
1 parent 7112bf9 commit 0b56fb3

3 files changed

Lines changed: 283 additions & 0 deletions

File tree

.github/workflows/publish-wiki.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: 📚 Publish Wiki
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
paths:
7+
- "wiki/**"
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: write
12+
13+
concurrency:
14+
group: "wiki-publish"
15+
cancel-in-progress: true
16+
17+
jobs:
18+
publish:
19+
if: ${{ !contains(toJson(github.event.commits), '[skip ci]') && !contains(toJson(github.event.commits), '[ci skip]') }}
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Publish wiki
26+
run: |
27+
set -euo pipefail
28+
29+
git config --global user.name "github-actions[bot]"
30+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
31+
32+
WIKI_REPO="https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.wiki.git"
33+
WIKI_DIR=$(mktemp -d)
34+
35+
git clone "$WIKI_REPO" "$WIKI_DIR"
36+
cp wiki/*.md "$WIKI_DIR/"
37+
38+
cd "$WIKI_DIR"
39+
git add -A
40+
41+
if git diff --cached --quiet; then
42+
echo "No wiki changes."
43+
exit 0
44+
fi
45+
46+
git commit -m "docs: sync wiki from main (${{ github.sha }})"
47+
git push

wiki/Home.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# sandbox-load-test Wiki
2+
3+
부하 테스트 도구 모음 프로젝트의 문서 홈.
4+
5+
## 모듈
6+
7+
| 모듈 | 설명 |
8+
|------|------|
9+
| [gatling-runner](gatling-runner) | 어노테이션 기반 Gatling 실행 + Prometheus 메트릭 노출 |
10+
| perf-backend | 시나리오 관리 및 테스트 실행 API 서버 |
11+
12+
## 관련 링크
13+
14+
- [GitHub Repository](https://github.com/eottabom/sandbox-load-test)
15+
- [GitHub Packages](https://github.com/eottabom/sandbox-load-test/packages)

wiki/gatling-runner.md

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# gatling-runner
2+
3+
어노테이션 기반으로 Gatling 부하 테스트를 실행하고, 실행 중 메트릭을 Prometheus 형식으로 노출하는 실행 JAR 모듈.
4+
5+
---
6+
7+
## 왜 만들었나
8+
9+
Gatling은 기본적으로 테스트가 **끝난 뒤** HTML 리포트를 생성한다.
10+
실행 중 실시간 메트릭을 Prometheus/Grafana로 보려면 별도 설정이 필요하고, 시나리오 클래스도 Gatling의 `Simulation`을 직접 상속해야 해서 템플릿이 무거워진다.
11+
12+
`gatling-runner`는 두 가지 문제를 동시에 해결한다.
13+
14+
1. **어노테이션만 붙이면 실행**`@LoadTest`, `@Protocol`, `@Scenario`, `@Injection` 4개 어노테이션으로 시나리오를 선언하면 `GatlingRunner`가 나머지를 처리한다. `Simulation`을 상속하거나 `setUp()`을 직접 호출할 필요가 없다.
15+
2. **실행 중 Prometheus 메트릭 노출** — 테스트가 시작되는 순간 HTTP 엔드포인트(기본 `:9102/metrics`)가 열리고, 응답 시간·요청 수·에러 수·활성 사용자 수가 실시간으로 스크레이핑된다.
16+
17+
---
18+
19+
## 목표
20+
21+
- Gatling 시나리오 작성 진입 장벽 낮추기 (Simulation 상속 제거)
22+
- 테스트 실행 중 실시간 메트릭 가시성 확보 (Prometheus + Grafana)
23+
- `perf-backend`에서 시나리오 소스를 받아 **런타임 컴파일 → 실행**하는 파이프라인 지원
24+
- 단일 Fat JAR로 배포 — 의존성 없이 `java -jar`로 즉시 실행 가능
25+
26+
---
27+
28+
## 전체 플로우
29+
30+
```
31+
[ 시나리오 클래스 (.java) ]
32+
33+
│ java -jar gatling-runner.jar <ClassName | /path/to/File.java>
34+
35+
[ GatlingRunner (main) ]
36+
├─ .java 파일이면 RuntimeCompiler로 런타임 컴파일
37+
├─ 클래스패스에 있는 클래스면 Class.forName으로 로드
38+
└─ DynamicPrometheusSimulation.setTargetClass(className)
39+
40+
│ Gatling 엔진이 DynamicPrometheusSimulation 인스턴스 생성
41+
42+
[ DynamicPrometheusSimulation (생성자) ]
43+
├─ SimulationAnnotationScanner → @Protocol / @Scenario / @Injection 필드 스캔
44+
├─ PrometheusProtocolWrapper → HttpProtocol에 메트릭 수집 래퍼 적용
45+
└─ setUp(scenario.injectOpen(injection)).protocols(wrappedProtocol)
46+
47+
│ 테스트 실행 시작
48+
49+
[ GatlingPrometheusMetrics (싱글톤) ]
50+
├─ 응답마다 Histogram / Counter 기록
51+
└─ PrometheusServerManager → HTTP 서버 :9102 기동
52+
53+
54+
[ :9102/metrics ] ←─ Prometheus 스크레이핑
55+
56+
57+
[ Grafana 대시보드 ]
58+
```
59+
60+
---
61+
62+
## 패키지 구조
63+
64+
```
65+
io.github.eottabom.gatling
66+
├── annotation
67+
│ ├── LoadTest # 시나리오 클래스 마커 어노테이션
68+
│ ├── Protocol # HttpProtocolBuilder 필드 마커
69+
│ ├── Scenario # ScenarioBuilder 필드 마커
70+
│ └── Injection # OpenInjectionStep[] 필드 마커
71+
├── core
72+
│ ├── DynamicPrometheusSimulation # Gatling이 실행하는 Simulation 구현체
73+
│ ├── SimulationAnnotationScanner # 어노테이션 스캔 → SimulationDescriptor 반환
74+
│ ├── SimulationDescriptor # 스캔 결과 값 객체 (record)
75+
│ └── PrometheusSimulation # 공통 Simulation 베이스 (metrics 초기화)
76+
├── metrics
77+
│ ├── GatlingPrometheusMetrics # 메트릭 기록 싱글톤
78+
│ ├── PrometheusServerManager # HTTPServer 생명주기 관리
79+
│ ├── PrometheusProtocolWrapper # HttpProtocol에 메트릭 래핑
80+
│ └── PrometheusHttpDsl # 수동 메트릭 기록용 DSL (선택적)
81+
├── runner
82+
│ ├── GatlingRunner # 진입점 main 클래스
83+
│ └── RuntimeCompiler # .java 파일 런타임 컴파일
84+
└── simulations
85+
├── SampleScenario # 어노테이션 방식 예제
86+
└── SampleSimulation # 기존 방식 예제 (참고용)
87+
```
88+
89+
---
90+
91+
## 전제 조건
92+
93+
| 항목 | 버전 | 비고 |
94+
|------|------|------|
95+
| Java | 21 이상 | RuntimeCompiler는 `javac`를 PATH에서 탐색 |
96+
| Gatling | 3.14.x | Fat JAR에 포함 (별도 설치 불필요) |
97+
| Prometheus | 2.x | 메트릭 스크레이핑용 (선택) |
98+
| Grafana | 10.x 이상 | 대시보드 시각화 (선택) |
99+
100+
> Prometheus/Grafana는 테스트 실행에 필수가 아니다. 없어도 테스트는 정상 실행된다.
101+
> 메트릭은 `:9102/metrics`에 노출되므로 Prometheus 없이 `curl`로도 확인 가능하다.
102+
103+
---
104+
105+
## 어노테이션 기반 시나리오 작성
106+
107+
### 4개 어노테이션
108+
109+
| 어노테이션 | 대상 | 설명 |
110+
|-----------|------|------|
111+
| `@LoadTest` | 클래스 | 시나리오 클래스임을 선언. `name` 속성으로 시뮬레이션 이름 지정 가능 |
112+
| `@Protocol` | 필드 (`HttpProtocolBuilder`) | 대상 URL, 헤더 등 HTTP 프로토콜 설정 |
113+
| `@Scenario` | 필드 (`ScenarioBuilder`) | 실행할 HTTP 요청 시나리오 체인 |
114+
| `@Injection` | 필드 (`OpenInjectionStep[]`) | 가상 사용자 주입 전략 (ramp-up, constant 등) |
115+
116+
### 예제
117+
118+
```java
119+
import io.gatling.javaapi.core.*;
120+
import io.gatling.javaapi.http.*;
121+
import io.github.eottabom.gatling.annotation.*;
122+
123+
import java.time.Duration;
124+
125+
import static io.gatling.javaapi.core.CoreDsl.*;
126+
import static io.gatling.javaapi.http.HttpDsl.*;
127+
128+
@LoadTest(name = "My Load Test") // name 생략 시 클래스명 사용
129+
public class MyScenario {
130+
131+
@Protocol
132+
HttpProtocolBuilder protocol = http
133+
.baseUrl("https://api.example.com")
134+
.acceptHeader("application/json");
135+
136+
@Scenario
137+
ScenarioBuilder scenario = scenario("Main Flow")
138+
.exec(http("Health Check").get("/health"))
139+
.pause(Duration.ofMillis(500))
140+
.exec(http("Get Items").get("/items"));
141+
142+
@Injection
143+
OpenInjectionStep[] injection = {
144+
rampUsers(50).during(Duration.ofSeconds(30)), // 30초 동안 50명 ramp-up
145+
constantUsersPerSec(10).during(Duration.ofMinutes(2)) // 이후 2분 동안 초당 10명
146+
};
147+
}
148+
```
149+
150+
> **규칙**
151+
> - `@LoadTest`가 없으면 `GatlingRunner`가 실행을 거부한다.
152+
> - `@Protocol`, `@Scenario`, `@Injection` 3개가 모두 있어야 한다.
153+
> - 필드 접근 제어자는 무관하다 (리플렉션으로 스캔).
154+
155+
---
156+
157+
## 실행 방법
158+
159+
### 클래스패스에 있는 클래스 실행
160+
161+
```bash
162+
# Fat JAR 빌드
163+
./gradlew shadowJar
164+
165+
# 클래스명만 지정 (기본 패키지 io.github.eottabom.gatling.simulations 자동 적용)
166+
java -jar build/libs/gatling-runner.jar SampleScenario
167+
168+
# 패키지 포함 전체 클래스명
169+
java -jar build/libs/gatling-runner.jar io.github.eottabom.gatling.simulations.SampleScenario
170+
```
171+
172+
### 외부 .java 파일 런타임 컴파일 후 실행
173+
174+
```bash
175+
# 시나리오 파일을 작성한 뒤 경로 지정
176+
java -jar gatling-runner.jar /tmp/MyScenario.java
177+
```
178+
179+
런타임 컴파일 시 `javac`가 PATH에 있어야 하고, `gatling-runner.jar`가 컴파일 classpath에 포함된다.
180+
181+
### Prometheus 포트 변경
182+
183+
기본 포트는 `9102`. 환경변수로 변경 가능하다 (구현 확장 시 참고).
184+
185+
---
186+
187+
---
188+
189+
## 노출되는 Prometheus 메트릭
190+
191+
테스트 실행 중 `http://localhost:9102/metrics`에서 확인 가능하다.
192+
193+
| 메트릭 이름 | 타입 | 설명 | 라벨 |
194+
|------------|------|------|------|
195+
| `gatling_response_time_milliseconds` | Histogram | 응답 시간 분포 (ms) | `simulation`, `scenario`, `request`, `status` |
196+
| `gatling_requests_total` | Counter | 총 요청 수 (성공/실패 포함) | `simulation`, `scenario`, `request`, `status` |
197+
| `gatling_errors_total` | Counter | 총 에러 수 | `simulation`, `scenario`, `request`, `error` |
198+
| `gatling_active_users` | Gauge | 현재 활성 사용자 수 | `simulation`, `scenario` |
199+
| `gatling_users_started_total` | Counter | 시나리오를 시작한 총 사용자 수 | `simulation`, `scenario` |
200+
| `gatling_users_finished_total` | Counter | 시나리오를 완료한 총 사용자 수 | `simulation`, `scenario` |
201+
202+
---
203+
204+
## 빌드 및 배포
205+
206+
### 로컬 빌드
207+
208+
```bash
209+
cd gatling-runner
210+
./gradlew shadowJar
211+
# → build/libs/gatling-runner.jar
212+
```
213+
214+
### GitHub Packages 배포
215+
216+
`main` 브랜치의 `gatling-runner/**` 경로에 변경이 푸시되면 GitHub Actions가 자동으로 빌드·배포한다.
217+
218+
- `version.properties`의 버전이 `X.Y.Z-SNAPSHOT`이면 패키지만 업데이트, 태그 없음
219+
- `X.Y.Z`(SNAPSHOT 없음)이면 `gatling-runner-vX.Y.Z` 태그 생성 후 다음 SNAPSHOT으로 자동 범프
220+
221+
배포된 패키지는 [GitHub Packages](https://github.com/eottabom/sandbox-load-test/packages)에서 확인할 수 있다.

0 commit comments

Comments
 (0)