Skip to content

Commit 0dedaa8

Browse files
LeeJeongGighkdqhrbalsseungminyi
authored
feat : banchmark Performance add (#41)
* dev : template entity create * dev : Developed template service feature * chore : 탬플릿 관련 에러 메세지 추가 * chore : Entity 클래스 update 기능 추가 * chore : test 코드 실행 시 sql 콘솔에 보여주는 옵션 변경 (false -> true) * test : service CUD 테스트 코드 작성 * chore : README.md API 명세 추가 * Test : 템플릿 조회 하는 테스트 추가 * chore : 반환 형태 entity class 가 아닌 responseDto 생성해서 넘겨주는 방식으로 수정 * develop : 탬플릿 api 개발 * chore: change some frontend * dev: 계층구조 ROLE 적용 * chore: line seperate * dev: 모듈화 및 테스트 & 리팩토링, 권한구조 변경 * fix: AccessDeniedException Handling in Security Tests * chore: remove unused test * dev: change userInfo get method with role based branch state * chore: add comments * dev: split getUser method into admin, user priv method * dev: add admin role check method * dev: add getUserInSameGroup method * dev: add USER_NOT_SAME_GROUP enum * dev: change all mocks and add more user test * dev: remove unused GET /user/me method * chore: lombok setter added * dev: jacoco test coverage set * dev: set builder in constructor method * dev: change fromString method to static * chore: line clean * dev: remove unused test * dev: security test added * dev: role enum testing * dev: implement random string generator util * dev: add login service tests * dev: add tests for token validation & creation process * dev: properties for token test * dev: 401, 403 security exception test * dev: JWT token filter test added * dev: Jacoco test coverage exclude lombok setting * dev: add Builder default * chore: change 500 status error code * fix: GlobalErrorResponse casting method * chore: line clean * dev: Builder default set with Lombok:1.18.2 * dev: Builder default set with Lombok:1.18.2 * dev: GlobalException & resp & ErrorCode test added * dev: usergroup eq & hash set * dev: test for getUser method * dev: implement GlobalException test * dev: add getter for app info * dev: add app info in test * dev: cookie util tests * dev: test formatted DateUtils * dev: add test for login api controller * dev: change logout roles * dev: implement jacoco class exclude method * dev: change overriding eq & hashcode to lombok * chore: remove unused mock HttpServletRequest * chore: group add test * dev: userGroup json property set * dev: user group repo test * dev: user group optional save * dev: remove pre groupRepository save process * dev: create new group thymeleaf added * dev: remove @tostring * dev: GROUP_ALREADY_EXIST errorCode added * feat: CI with test coverage commenting (#13) * dev: add workflow for CI * chore: allow gradle-wrapper.jar push * chore: add gradle-wrapper for build * chore: remove unused test * fix: #14 move config from main to test * feat: jacoco ci report (#15) * dev: add workflow for CI * chore: allow gradle-wrapper.jar push * chore: add gradle-wrapper for build * chore: remove unused test * fix: #14 move config from main to test * test: change jacoco reporter * test: remove upload artifact * test: remove action permission and auth in team * dev: add pass-emoji and remove tests * chore: update title of comment * chore: change pass emoji * chore: change job name * chore : Entity 클래스 update 기능 추가 * chore : 호출 api 수정, response 객체 jsonProperty 설정 * Test : 탬플릿 CRUD 테스트 추가 * chore : 코드 merge * chore : 코드 merge * chore : Constructor annotation change. * chore : add controller authorization * chore : Constructor annotation change * chore : Field validation add, exceptionHandler method add * test : Field Validation test code add * dev : Test result save entity add * dev : 비동기 처리 위한 webClient 라이브러리 추가 * chore : not use import delete * dev : Test result save entity add * chore : Template field modify (userGroupName -> userGroupId) * dev : Result save repository add * chore : webClient Dns server 접근 위한 netty 라이브러리 추가 * chore : convert method add * chore : test result utils class create * chore : error code modify and add * dev - temp save * chore - TestResult save logic update * chore - code refactor * chore - code refactor * chore - not use class del * chore - entity update * chore - modify baseurl * chore - 에러 처리 수정 * chore - test 추가 * dev: mock server open and webClient bean replace * chore - service logic update * test - test code modify * dev - controller add * chore - del class * chore - 임시 주석 * dev: MockServer setup for webClient API request test * dev: DI testResultService with made WebClient, extends MockServer * fix: gradlew build change * fix: module import * chore - 임시 주석 해제 * chore - 임시 주석 해제 * test : controller test add * dev: reset all package structure * dev: incorrect external API test remove * dev: gradle ci reset * chore: set agent module test coverage to 0.60 --------- Co-authored-by: Hwangbo <ghkdqhrbals@naver.com> Co-authored-by: seungminyi <seungminyi95@gmail.com>
1 parent 1ed9ba0 commit 0dedaa8

36 files changed

+1440
-50
lines changed

bm-agent/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ version = '0.0.1-SNAPSHOT'
1111

1212
def excludeJacocoTestCoverageReport = [
1313
// bmagent
14-
'org/benchmarker/bmagent/**',
14+
// 'org/benchmarker/bmagent/**',
1515
]
1616

1717
java {
@@ -41,7 +41,6 @@ dependencies {
4141
testImplementation 'org.springframework.boot:spring-boot-starter-test'
4242
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
4343
testImplementation 'org.testcontainers:junit-jupiter'
44-
4544
implementation project(':bm-common')
4645
}
4746

bm-agent/src/test/java/org/benchmarker/bmagent/BmAgentApplicationTests.java

Lines changed: 0 additions & 13 deletions
This file was deleted.

bm-controller/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ dependencies {
6565
implementation project(":bm-common")
6666
implementation project(":bm-agent")
6767
jacocoAggregation project(":bm-agent")
68+
implementation 'com.squareup.okhttp3:mockwebserver:4.9.1'
69+
70+
6871

6972
implementation 'org.springframework.boot:spring-boot-starter-validation'
7073
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'

bm-controller/src/main/java/org/benchmarker/bmcontroller/common/beans/JsonConfig.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.benchmarker.bmcontroller.common.beans;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import com.fasterxml.jackson.databind.SerializationFeature;
54
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
65
import org.springframework.context.annotation.Bean;
76
import org.springframework.context.annotation.Configuration;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.benchmarker.bmcontroller.common.beans;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.springframework.http.HttpStatusCode;
5+
import org.springframework.stereotype.Component;
6+
7+
import java.util.concurrent.atomic.AtomicInteger;
8+
9+
@Slf4j
10+
@Component
11+
public class RequestCounter {
12+
13+
private final AtomicInteger totalRequests = new AtomicInteger(0);
14+
private final AtomicInteger totalSuccesses = new AtomicInteger(0);
15+
private final AtomicInteger totalErrors = new AtomicInteger(0);
16+
17+
// 요청이 발생할 때마다 호출하여 요청 카운터를 증가시키는 메서드
18+
public void incrementTotalRequests() {
19+
totalRequests.incrementAndGet();
20+
}
21+
22+
public void incrementTotalSuccesses() {
23+
totalSuccesses.incrementAndGet();
24+
}
25+
26+
public void incrementTotalErrors() {
27+
totalErrors.incrementAndGet();
28+
}
29+
30+
public int getTotalRequests() {
31+
return totalRequests.get();
32+
}
33+
34+
public int getTotalSuccesses() {
35+
return totalSuccesses.get();
36+
}
37+
38+
public int getTotalErrors() {
39+
return totalErrors.get();
40+
}
41+
42+
public void reset() {
43+
this.totalRequests.set(0);
44+
this.totalSuccesses.set(0);
45+
this.totalErrors.set(0);
46+
}
47+
48+
public void updateTotalResult(HttpStatusCode statusCode) {
49+
50+
this.incrementTotalRequests();
51+
52+
if (statusCode.is2xxSuccessful()) {
53+
this.incrementTotalSuccesses();
54+
} else if (statusCode.is4xxClientError() || statusCode.is5xxServerError()) {
55+
this.incrementTotalErrors();
56+
}
57+
}
58+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.benchmarker.bmcontroller.common.beans;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.springframework.beans.factory.annotation.Autowired;
5+
import org.springframework.scheduling.annotation.Scheduled;
6+
import org.springframework.stereotype.Component;
7+
8+
@Slf4j
9+
@Component
10+
public class RequestCounterResetScheduler {
11+
12+
private final RequestCounter requestCounter;
13+
14+
@Autowired
15+
public RequestCounterResetScheduler(RequestCounter requestCounter) {
16+
this.requestCounter = requestCounter;
17+
}
18+
19+
@Scheduled(fixedRate = 5000) // 5초마다 실행
20+
public void resetRequestCounter() {
21+
int currentRequestCount = requestCounter.getTotalRequests();
22+
int currentSuccessCount = requestCounter.getTotalSuccesses();
23+
int currentErrorCount = requestCounter.getTotalErrors();
24+
25+
// 요청 카운터를 초기화합니다.
26+
requestCounter.reset();
27+
28+
log.info("Reset request counter. Current request count: " + currentRequestCount);
29+
log.info("Total successes: " + currentSuccessCount);
30+
log.info("Total errors: " + currentErrorCount);
31+
}
32+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.benchmarker.bmcontroller.common.beans;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.web.reactive.function.client.WebClient;
6+
7+
@Configuration
8+
public class WebClientConfig {
9+
10+
@Bean
11+
public WebClient webClient() {
12+
return WebClient.builder()
13+
.baseUrl("http://localhost:8080")
14+
.build();
15+
}
16+
}

bm-controller/src/main/java/org/benchmarker/bmcontroller/common/error/ErrorCode.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,39 @@ public enum ErrorCode {
1414
/**
1515
* 400
1616
*/
17-
BAD_REQUEST(400, "잘못된 요청입니다"),
18-
PASSWORD_NOT_MATCH(400, "패스워드 불일치"),
19-
USER_NOT_SAME_GROUP(400, "그룹이 다른 사용자입니다"),
20-
GROUP_ALREADY_EXIST(400, "그룹이 이미 존재합니다"),
21-
USER_NOT_IN_GROUP(400, "그룹에 속해있지 않은 사용자입니다"),
22-
USER_ALREADY_IN_GROUP(400, "그룹에 이미 속해있는 사용자입니다"),
17+
BAD_REQUEST(400, "잘못된 요청입니다."),
18+
PASSWORD_NOT_MATCH(400, "패스워드를 정확히 입력해주세요."),
19+
USER_NOT_SAME_GROUP(400, "그룹이 다른 사용자입니다."),
20+
GROUP_ALREADY_EXIST(400, "그룹이 이미 존재합니다."),
21+
USER_NOT_IN_GROUP(400, "그룹에 속해있지 않은 사용자입니다."),
22+
USER_ALREADY_IN_GROUP(400, "그룹에 이미 속해있는 사용자입니다."),
23+
HTTP_METHOD_NOT_FOUND(400, "지원하지 않는 HTTP METHOD 입니다."),
2324

2425
/**
2526
* 401
2627
*/
27-
UNAUTHORIZED(401, "인증되지 않은 사용자입니다"),
28+
UNAUTHORIZED(401, "인증되지 않은 사용자입니다."),
2829

2930
/**
3031
* 403
3132
*/
32-
FORBIDDEN(403, "권한이 없는 사용자입니다"),
33+
FORBIDDEN(403, "권한이 없는 사용자입니다."),
3334

3435
/**
3536
* 404
3637
*/
37-
USER_NOT_FOUND(404, "유저가 존재하지 않습니다"),
38-
USER_ALREADY_EXIST(404, "유저가 이미 존재합니다"),
39-
GROUP_NOT_FOUND(404, "그룹이 존재하지 않습니다"),
40-
TEMPLATE_NOT_FOUND(404, "탬플릿이 존재하지 않습니다"),
38+
USER_NOT_FOUND(404, "유저가 존재하지 않습니다."),
39+
USER_ALREADY_EXIST(404, "유저가 이미 존재합니다."),
40+
GROUP_NOT_FOUND(404, "그룹이 존재하지 않습니다."),
41+
TEMPLATE_NOT_FOUND(404, "탬플릿이 존재하지 않습니다."),
42+
TEMPLATE_RESULT_NOT_FOUND(404, "탬플릿에 대한 결과가 존재하지 않습니다."),
4143

4244
/**
4345
* 500
4446
*/
4547
INTERNAL_SERVER_ERROR(500, "서버 내부 오류"),
4648
;
47-
48-
49+
4950
private final int httpStatus;
5051
private final String message;
5152

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package org.benchmarker.bmcontroller.template.common;
2+
3+
import java.util.Locale;
4+
import java.util.Map;
5+
import org.benchmarker.bmcontroller.template.model.TestTemplate;
6+
import org.springframework.http.MediaType;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.reactive.function.client.WebClient;
9+
import reactor.core.publisher.Mono;
10+
11+
public class TemplateUtils {
12+
13+
public static double calculateTPS(long startTime, long endTime, int totalRequests) {
14+
// 경과 시간 계산 (밀리초 단위)
15+
long elapsedTime = endTime - startTime;
16+
17+
// 경과 시간이 0보다 작거나 같으면 0을 반환하여 나누기 오류를 방지합니다.
18+
if (elapsedTime <= 0) {
19+
return 0;
20+
}
21+
22+
// 초당 트랜잭션 수 계산 (총 요청 수를 경과 시간(초)으로 나눔)
23+
return (double) totalRequests / (elapsedTime / 1000.0);
24+
}
25+
26+
public static double calculateAvgResponseTime(long startTime, long endTime, int totalRequests) {
27+
// 경과 시간 계산 (밀리초 단위)
28+
long elapsedTime = endTime - startTime;
29+
30+
// 경과 시간이 0보다 작거나 같으면 0을 반환하여 나누기 오류를 방지합니다.
31+
if (elapsedTime <= 0 || totalRequests == 0) {
32+
return 0;
33+
}
34+
35+
// 평균 응답 시간 계산 (총 경과 시간을 총 요청 수로 나눔)
36+
return (double) elapsedTime / totalRequests;
37+
}
38+
39+
public static Mono<ResponseEntity<String>> createRequest(WebClient webClient, TestTemplate testTemplate) {
40+
String method = testTemplate.getMethod().toUpperCase(Locale.ENGLISH);
41+
return switch (method) {
42+
case "GET" -> webClient.get()
43+
.uri(testTemplate.getUrl())
44+
.retrieve()
45+
.toEntity(String.class);
46+
case "POST" -> webClient.post()
47+
.uri(testTemplate.getUrl())
48+
.contentType(MediaType.APPLICATION_JSON)
49+
.bodyValue(testTemplate.getBody())
50+
.retrieve()
51+
.toEntity(String.class);
52+
case "PATCH", "PUT" -> webClient.patch()
53+
.uri(testTemplate.getUrl())
54+
.contentType(MediaType.APPLICATION_JSON)
55+
.bodyValue(testTemplate.getBody())
56+
.retrieve()
57+
.toEntity(String.class);
58+
case "DELETE" -> webClient.delete()
59+
.uri(testTemplate.getUrl())
60+
.retrieve()
61+
.toEntity(String.class);
62+
default -> throw new IllegalArgumentException("Unsupported HTTP method: " + method);
63+
};
64+
}
65+
66+
public static String getStatusCodeCategory(int statusCode) {
67+
if (statusCode >= 200 && statusCode < 300) {
68+
return "2xx";
69+
} else if (statusCode >= 300 && statusCode < 400) {
70+
return "3xx";
71+
} else if (statusCode >= 400 && statusCode < 500) {
72+
return "4xx";
73+
} else if (statusCode >= 500 && statusCode < 600) {
74+
return "5xx";
75+
}
76+
return "";
77+
}
78+
79+
public static void addPercentile(Map<String, Double> tpsPercentiles, Map<String, Double> mttfbPercentiles, String key, long startTime, long finishTime, int totalRequest) {
80+
double tpsPercentile = calculateTPS(startTime, finishTime, totalRequest);
81+
double mttfbPercentile = calculateAvgResponseTime(startTime, finishTime, totalRequest);
82+
83+
tpsPercentiles.put(key, tpsPercentile);
84+
mttfbPercentiles.put(key, mttfbPercentile);
85+
}
86+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.benchmarker.bmcontroller.template.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.benchmarker.bmcontroller.template.controller.dto.TestResultResponseDto;
6+
import org.benchmarker.bmcontroller.template.service.ITestResultService;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.security.access.prepost.PreAuthorize;
9+
import org.springframework.web.bind.annotation.*;
10+
11+
@Slf4j
12+
@RestController
13+
@RequestMapping("/api")
14+
@RequiredArgsConstructor
15+
public class TestResultTemplateApiController {
16+
17+
private final ITestResultService testResultService;
18+
19+
@PostMapping("/groups/{group_id}/templates/{template_id}?action={action}")
20+
@PreAuthorize("hasAnyRole('USER')")
21+
public ResponseEntity<TestResultResponseDto> measurePerformance(
22+
@PathVariable String group_id,
23+
@PathVariable Integer template_id,
24+
@RequestParam(required = true) String action) throws InterruptedException {
25+
return ResponseEntity.ok(testResultService.measurePerformance(group_id, template_id, action));
26+
}
27+
28+
}

0 commit comments

Comments
 (0)