Skip to content

Commit badf258

Browse files
authored
feat: perf result page (#91)
1 parent c4a01f9 commit badf258

File tree

26 files changed

+564
-127
lines changed

26 files changed

+564
-127
lines changed

bm-controller/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ dependencies {
121121

122122

123123
implementation 'org.postgresql:postgresql'
124-
compileOnly 'org.projectlombok:lombok'
124+
implementation 'org.projectlombok:lombok'
125125
annotationProcessor 'org.projectlombok:lombok'
126126
testImplementation 'org.springframework.boot:spring-boot-starter-test'
127127
testImplementation 'org.springframework.security:spring-security-test'

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public enum ErrorCode {
3737
/**
3838
* 404
3939
*/
40+
TEST_RESULT_NOT_FOUND(404, "테스트 결과가 존재하지 않습니다."),
4041
USER_NOT_FOUND(404, "유저가 존재하지 않습니다."),
4142
USER_ALREADY_EXIST(404, "유저가 이미 존재합니다."),
4243
GROUP_NOT_FOUND(404, "그룹이 존재하지 않습니다."),
@@ -47,7 +48,7 @@ public enum ErrorCode {
4748
/**
4849
* 500
4950
*/
50-
INTERNAL_SERVER_ERROR(500, "서버 내부 오류"),;
51+
INTERNAL_SERVER_ERROR(500, "서버 내부 오류"), ;
5152

5253
private final int httpStatus;
5354
private final String message;

bm-controller/src/main/java/org/benchmarker/bmcontroller/preftest/controller/ResultController.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import java.util.concurrent.atomic.AtomicReference;
77
import lombok.RequiredArgsConstructor;
88
import lombok.extern.slf4j.Slf4j;
9+
import org.benchmarker.bmcommon.dto.CommonTestResult;
910
import org.benchmarker.bmcommon.dto.MTTFBInfo;
1011
import org.benchmarker.bmcommon.dto.TPSInfo;
12+
import org.benchmarker.bmcontroller.common.controller.annotation.GlobalControllerModel;
1113
import org.benchmarker.bmcontroller.common.model.BaseTime;
1214
import org.benchmarker.bmcontroller.template.model.TestExecution;
1315
import org.benchmarker.bmcontroller.template.model.TestMttfb;
@@ -27,6 +29,7 @@
2729
@Controller
2830
@Slf4j
2931
@RequiredArgsConstructor
32+
@GlobalControllerModel
3033
public class ResultController {
3134

3235
private final TestExecutionService testExecutionService;
@@ -55,7 +58,7 @@ public String showChart(
5558
List<MTTFBInfo> mttfbInfoList = new ArrayList<>();
5659
List<TPSInfo> tpsInfoList = new ArrayList<>();
5760

58-
AtomicReference<TestResult> lastTestResult = new AtomicReference<TestResult>();
61+
AtomicReference<TestResult> resultSets = new AtomicReference<TestResult>();
5962

6063
// TODO : N+1 prob. n.n
6164
testResults.stream().forEach(testResult -> {
@@ -68,9 +71,12 @@ public String showChart(
6871
TPSInfo.builder().timestamp(testTps.getCreatedAt()).tps(testTps.getTransaction())
6972
.build());
7073
assert false;
71-
lastTestResult.set(testResult);
74+
resultSets.set(testResult);
7275
});
7376

77+
CommonTestResult lastTestResult = testExecutionService.getLastTestResult(testId);
78+
79+
model.addAttribute("commonTestResult",lastTestResult);
7480
model.addAttribute("mttfbInfoList", mttfbInfoList);
7581
model.addAttribute("tpsInfoList", tpsInfoList);
7682

bm-controller/src/main/java/org/benchmarker/bmcontroller/template/model/TestResult.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
package org.benchmarker.bmcontroller.template.model;
22

3-
import jakarta.persistence.*;
4-
import lombok.*;
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.EnumType;
6+
import jakarta.persistence.Enumerated;
7+
import jakarta.persistence.FetchType;
8+
import jakarta.persistence.GeneratedValue;
9+
import jakarta.persistence.GenerationType;
10+
import jakarta.persistence.Id;
11+
import jakarta.persistence.JoinColumn;
12+
import jakarta.persistence.ManyToOne;
13+
import jakarta.persistence.OneToMany;
14+
import java.time.LocalDateTime;
15+
import java.util.List;
16+
import lombok.AllArgsConstructor;
17+
import lombok.Builder;
18+
import lombok.Getter;
19+
import lombok.NoArgsConstructor;
20+
import lombok.Setter;
521
import lombok.extern.slf4j.Slf4j;
622
import org.benchmarker.bmagent.AgentStatus;
723
import org.benchmarker.bmcontroller.common.model.BaseTime;
824
import org.benchmarker.bmcontroller.template.controller.dto.TestResultResponseDto;
925

10-
import java.time.LocalDateTime;
11-
import java.util.List;
12-
1326

1427
@Slf4j
1528
@Getter
@@ -24,10 +37,6 @@ public class TestResult extends BaseTime {
2437
@GeneratedValue(strategy = GenerationType.IDENTITY)
2538
private Integer id;
2639

27-
// @ManyToOne(fetch = FetchType.EAGER)
28-
// @JoinColumn(name = "test_template_id", referencedColumnName = "id", nullable = false)
29-
// private TestTemplate testTemplate;
30-
3140
private Integer totalRequest;
3241

3342
private Integer totalError;
@@ -71,4 +80,5 @@ public TestResultResponseDto convertToResponseDto() {
7180
.build();
7281
}
7382

83+
7484
}

bm-controller/src/main/java/org/benchmarker/bmcontroller/template/repository/TestResultRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.benchmarker.bmcontroller.template.repository;
22

3+
import java.util.List;
4+
import java.util.UUID;
35
import org.benchmarker.bmcontroller.template.model.TestResult;
46
import org.springframework.data.jpa.repository.JpaRepository;
57
import org.springframework.data.jpa.repository.Query;
@@ -11,4 +13,7 @@ public interface TestResultRepository extends JpaRepository<TestResult, Integer>
1113

1214
@Query("select tr from TestResult tr where tr.testExecution.testTemplate.id = :templateId")
1315
TestResult findByTestTemplate(@Param("templateId") Integer templateId);
16+
17+
@Query("select tr from TestResult tr where tr.testExecution.id = :testExecutionId order by tr.createdAt desc")
18+
List<TestResult> findAllByTestExecutionId(@Param("testExecutionId") UUID testExecutionId);
1419
}

bm-controller/src/main/java/org/benchmarker/bmcontroller/template/service/TestExecutionService.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
import java.util.UUID;
55
import lombok.RequiredArgsConstructor;
66
import org.benchmarker.bmagent.AgentStatus;
7+
import org.benchmarker.bmcommon.dto.CommonTestResult;
78
import org.benchmarker.bmcontroller.common.error.ErrorCode;
89
import org.benchmarker.bmcontroller.common.error.GlobalException;
910
import org.benchmarker.bmcontroller.preftest.common.TestInfo;
1011
import org.benchmarker.bmcontroller.template.model.TestExecution;
12+
import org.benchmarker.bmcontroller.template.model.TestResult;
1113
import org.benchmarker.bmcontroller.template.model.TestTemplate;
1214
import org.benchmarker.bmcontroller.template.repository.TestExecutionRepository;
15+
import org.benchmarker.bmcontroller.template.repository.TestResultRepository;
1316
import org.benchmarker.bmcontroller.template.repository.TestTemplateRepository;
17+
import org.benchmarker.bmcontroller.template.service.utils.CommonDtoConversion;
1418
import org.springframework.data.domain.Page;
1519
import org.springframework.data.domain.Pageable;
1620
import org.springframework.stereotype.Service;
@@ -22,6 +26,7 @@ public class TestExecutionService {
2226

2327
private final TestExecutionRepository testExecutionRepository;
2428
private final TestTemplateRepository testTemplateRepository;
29+
private final TestResultRepository testResultRepository;
2530

2631
/**
2732
* 테스트 시작 전 TestExecution 을 DB 에 저장합니다.
@@ -47,6 +52,18 @@ public TestExecution getTest(String id) {
4752
.orElseThrow(() -> new GlobalException(ErrorCode.TEST_NOT_FOUND));
4853
}
4954

55+
@Transactional(readOnly = true)
56+
public CommonTestResult getLastTestResult(String testId) {
57+
List<TestResult> findResults = testResultRepository.findAllByTestExecutionId(
58+
UUID.fromString(testId));
59+
if (findResults.isEmpty()) {
60+
throw new GlobalException(ErrorCode.TEST_RESULT_NOT_FOUND);
61+
}
62+
// get latest test result
63+
TestResult testResult = findResults.get(0);
64+
return CommonDtoConversion.convertToCommonTestResult(testResult);
65+
}
66+
5067
@Transactional(readOnly = true)
5168
public Page<TestExecution> getTestsPageable(Pageable pageable, Integer templateId) {
5269
return testExecutionRepository.findAllByTestTemplateId(templateId, pageable);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.benchmarker.bmcontroller.template.service.utils;
2+
3+
import java.time.Duration;
4+
import java.util.stream.Collectors;
5+
import org.benchmarker.bmcommon.dto.CommonTestResult;
6+
import org.benchmarker.bmcontroller.template.model.TestResult;
7+
import org.springframework.transaction.annotation.Propagation;
8+
import org.springframework.transaction.annotation.Transactional;
9+
10+
/**
11+
* convert Entity to DTO
12+
*/
13+
@Transactional(propagation = Propagation.REQUIRED, readOnly = true, rollbackFor = Exception.class)
14+
public class CommonDtoConversion {
15+
16+
// TODO : 다량의 정보를 불러오는 만큼 캐싱 필요
17+
public static CommonTestResult convertToCommonTestResult(TestResult testResult) {
18+
// check parent transaction or exception
19+
ParentTXCheck.IsParentTransactionActive();
20+
21+
return CommonTestResult.builder()
22+
.groupId(testResult.getTestExecution().getTestTemplate().getUserGroup().getId())
23+
.testStatus(testResult.getAgentStatus())
24+
.totalRequests(testResult.getTotalRequest())
25+
.totalErrors(testResult.getTotalError())
26+
.totalUsers(testResult.getTestExecution().getTestTemplate().getVuser())
27+
.totalSuccess(testResult.getTotalSuccess())
28+
.url(testResult.getTestExecution().getTestTemplate().getUrl())
29+
.tpsAverage(testResult.getTpsAvg())
30+
.mttfbAverage(testResult.getMttbfbAvg())
31+
.startedAt(testResult.getStartedAt().toString())
32+
.finishedAt(testResult.getFinishedAt().toString())
33+
.totalDuration(
34+
Duration.between(testResult.getStartedAt(), testResult.getFinishedAt()).toString())
35+
.statusCodeCount(testResult.getTestStatuses().stream().collect(
36+
Collectors.toMap((status) -> status.getCode().toString(),
37+
(status) -> status.getCount())))
38+
// TODO : 현재 mttfb, tps percentile 정보가 저장되어있지 않고 그때그때 연산필요.
39+
// TODO : TestStatus 가 GET, POST 와 같은 정보를 저장하고 있음. 이는 맞지 않는 스키마이며 HTTP Status Code 를 저장해야함.
40+
// HTTP Status Code 는 200, 201, 400, 404, 500 등이 있음. 따라서 엔티티 내부 필드에서 int 로 저장해야함.
41+
.build();
42+
}
43+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.benchmarker.bmcontroller.template.service.utils;
2+
3+
import org.springframework.transaction.support.TransactionSynchronizationManager;
4+
5+
/**
6+
* check parent transaction
7+
*/
8+
public class ParentTXCheck {
9+
10+
/**
11+
* check parent transaction
12+
*
13+
* @throws IllegalStateException if parent transaction is not active or not exist
14+
*/
15+
public static void IsParentTransactionActive() {
16+
boolean actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
17+
if (!actualTransactionActive) {
18+
throw new IllegalStateException("Parent transaction is not active");
19+
}
20+
}
21+
}

bm-controller/src/main/resources/static/css/styles.css

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,15 +188,19 @@ button[type="submit"]:hover {
188188

189189
#agentInfoContainer {
190190
display: flex;
191-
flex-wrap: wrap; /* 요소들이 넘칠 경우 줄 바꿈 */
192-
background-color: #f8f9fa; /* 배경색 설정 */
193-
padding: 10px; /* 내부 여백 설정 */
194-
align-items: center; /* 요소들을 수직 가운데 정렬 */
191+
flex-wrap: wrap;
192+
flex-direction: row;
193+
background-color: #f8f9fa;
194+
padding: 10px;
195+
align-items: center;
195196
align-self: center;
196-
border-radius: 5px; /* 테두리 모서리를 둥글게 만듦 */
197+
border-radius: 5px;
197198
margin-bottom: 0px;
198-
border: 1px solid #ced4da; /* 테두리 설정 */
199-
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* 그림자 효과 추가 */
199+
border: 1px solid #ced4da;
200+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
201+
height: 40px; /* Set a specific height */
202+
overflow-y: auto; /* Add scrollbar if content exceeds container height */
203+
visibility: hidden;
200204
}
201205

202206
.agent-info {

bm-controller/src/main/resources/templates/fragments/agentStatus.html

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
<meta charset="UTF-8">
55
<link rel="stylesheet" type="text/css" href="/css/bar.css">
66
<link rel="stylesheet" type="text/css" href="/css/styles.css">
7+
78
</head>
89
<body>
910

1011
<!-- Current user status bar -->
11-
<div th:fragment="agentStatus" id="agentInfoContainer">
12+
<div th:fragment="agentStatus" id="agentInfoContainer" style="visibility: hidden;">
1213

1314
<script>
14-
console.log("agentStatus");
15-
1615
var sockJs = new SockJS("/gs-guide-websocket");
1716
var stompClient = Stomp.over(sockJs);
1817
stompClient.debug = null;
@@ -97,13 +96,20 @@
9796
console.error('Error with websocket', error);
9897
};
9998

100-
console.log(stompClient);
99+
const toggleButton = document.getElementById('toggleButton');
100+
const agentContainer = document.getElementById('agentInfoContainer');
101+
102+
toggleButton.addEventListener('click', function() {
103+
// Toggle the display property of the agent status container
104+
if (agentContainer.style.visibility === 'hidden') {
105+
agentContainer.style.visibility = 'visible';
106+
} else {
107+
agentContainer.style.visibility = 'hidden';
108+
}
109+
});
101110

102111
</script>
103112
</div>
104113

105-
106114
</body>
107115
</html>
108-
109-

bm-controller/src/main/resources/templates/fragments/commonHeader.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@
99
<link rel="stylesheet" type="text/css" href="/css/styles.css">
1010
<link rel="stylesheet" type="text/css" href="/css/bar.css">
1111
<!-- <script src="/js/connectStomp.js"></script>-->
12+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
13+
<style>
14+
.charts-container {
15+
display: grid;
16+
grid-template-columns: 1fr 1fr; /* Two equal columns */
17+
grid-gap: 20px; /* Gap between charts */
18+
padding: 20px;
19+
}
20+
.chart-container {
21+
width: 100%;
22+
}
23+
canvas {
24+
width: 100%;
25+
padding: 20px;
26+
height: auto !important;
27+
}
28+
</style>
1229
</head>
1330
<body>
1431

bm-controller/src/main/resources/templates/fragments/currentUserFragment.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<div th:fragment="currentUser" class="status-bar" th:classappend="${currentUser} ? 'logged-in' : ''">
1111
<link rel="stylesheet" type="text/css" href="/css/bar.css">
1212
<a th:href="@{/}" class="home-link">Benchmark</a>
13+
<button id="toggleButton">see agents</button>
1314
<p th:if="${currentUser}" class="user-text">
1415
Logged in as:
1516
<a th:href="@{'/users/' + ${currentUser.id}}" class="user-link" th:text="${currentUser.id}"></a>

bm-controller/src/main/resources/templates/group/list.html

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
<!DOCTYPE html>
22
<html xmlns:th="http://www.thymeleaf.org">
3-
<head>
4-
<meta charset="UTF-8">
5-
<title>Group Information</title>
6-
<link rel="stylesheet" type="text/css" href="../css/styles.css">
7-
</head>
8-
<body>
3+
<header th:replace="~{fragments/commonHeader :: common('My Groups')}"></header>
4+
95
<div th:replace="fragments/currentUserFragment :: currentUser"></div>
6+
<div th:replace="~{fragments/agentStatus :: agentStatus}"></div>
7+
108
<h1>Groups</h1>
119

1210
<div class="container">

bm-controller/src/main/resources/templates/group/register.html

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
<!DOCTYPE html>
22
<html xmlns:th="http://www.thymeleaf.org">
3-
<head>
4-
<meta charset="UTF-8">
5-
<title>Register Group</title>
6-
<link rel="stylesheet" type="text/css" href="../css/styles.css">
7-
</head>
8-
<body>
3+
<header th:replace="~{fragments/commonHeader :: common('Register Group')}"></header>
94
<div th:replace="fragments/currentUserFragment :: currentUser"></div>
5+
<div th:replace="~{fragments/agentStatus :: agentStatus}"></div>
106

117
<div class="container">
128
<h1>Register Group</h1>

0 commit comments

Comments
 (0)