diff --git a/test-common/src/main/java/com/test/common/core/domain/model/ErrorCodeStat.java b/test-common/src/main/java/com/test/common/core/domain/model/ErrorCodeStat.java new file mode 100644 index 0000000..7770705 --- /dev/null +++ b/test-common/src/main/java/com/test/common/core/domain/model/ErrorCodeStat.java @@ -0,0 +1,36 @@ +package com.test.common.core.domain.model; + +/** + * @author liangdaliang + * @Description:Http错误编码统计结果 + * @date 2025-04-17 19:05 + */ +public class ErrorCodeStat { + private final String responseCode; + private final Long count; + private final Double errorRatio; // 占所有错误的比率 + private final Double totalRatio; // 占所有请求的比率 + + public ErrorCodeStat(String responseCode, long count, double errorRatio, double totalRatio) { + this.responseCode = responseCode; + this.count = count; + this.errorRatio = errorRatio; + this.totalRatio = totalRatio; + } + + public String getResponseCode() { + return responseCode; + } + + public Long getCount() { + return count; + } + + public Double getErrorRatio() { + return errorRatio; + } + + public Double getTotalRatio() { + return totalRatio; + } +} diff --git a/test-common/src/main/java/com/test/common/core/domain/model/ErrorTotalStats.java b/test-common/src/main/java/com/test/common/core/domain/model/ErrorTotalStats.java new file mode 100644 index 0000000..6c2d9f4 --- /dev/null +++ b/test-common/src/main/java/com/test/common/core/domain/model/ErrorTotalStats.java @@ -0,0 +1,47 @@ +package com.test.common.core.domain.model; + +import java.util.List; + +/** + * @author liangdaliang + * @Description:总体错误信息统计 + * @date 2025-04-17 19:50 + */ +public class ErrorTotalStats { + /** + * 错误响应状态码统计列表 + */ + private List errorCodeStatList; + /** + * Top 5错误接口列表 + */ + private List top5InterfaceErrorStatList; + /** + * 每个接口的错误码明细列表 + */ + private List interfaceErrorDetailList; + + public List getErrorCodeStatList() { + return errorCodeStatList; + } + + public void setErrorCodeStatList(List errorCodeStatList) { + this.errorCodeStatList = errorCodeStatList; + } + + public List getTop5InterfaceErrorStatList() { + return top5InterfaceErrorStatList; + } + + public void setTop5InterfaceErrorStatList(List top5InterfaceErrorStatList) { + this.top5InterfaceErrorStatList = top5InterfaceErrorStatList; + } + + public List getInterfaceErrorDetailList() { + return interfaceErrorDetailList; + } + + public void setInterfaceErrorDetailList(List interfaceErrorDetailList) { + this.interfaceErrorDetailList = interfaceErrorDetailList; + } +} diff --git a/test-common/src/main/java/com/test/common/core/domain/model/HttpSample.java b/test-common/src/main/java/com/test/common/core/domain/model/HttpSample.java new file mode 100644 index 0000000..44b7962 --- /dev/null +++ b/test-common/src/main/java/com/test/common/core/domain/model/HttpSample.java @@ -0,0 +1,42 @@ +package com.test.common.core.domain.model; + +/** + * @author liangdaliang + * @Description:Http采样 + * @date 2025-04-17 19:03 + */ +public class HttpSample { + private String lb; // 接口名称 + private Boolean s; // true为请求成功 + private String rc; // 请求响应编码 + + public HttpSample(String label, Boolean s, String rc) { + this.lb = label; + this.s = s; + this.rc = rc; + } + + public String getLb() { + return lb; + } + + public void setLb(String lb) { + this.lb = lb; + } + + public Boolean getS() { + return s; + } + + public void setS(Boolean s) { + this.s = s; + } + + public String getRc() { + return rc; + } + + public void setRc(String rc) { + this.rc = rc; + } +} diff --git a/test-common/src/main/java/com/test/common/core/domain/model/InterfaceErrorDetail.java b/test-common/src/main/java/com/test/common/core/domain/model/InterfaceErrorDetail.java new file mode 100644 index 0000000..c8f0673 --- /dev/null +++ b/test-common/src/main/java/com/test/common/core/domain/model/InterfaceErrorDetail.java @@ -0,0 +1,27 @@ +package com.test.common.core.domain.model; + +import java.util.Map; + +/** + * @author liangdaliang + * @Description:接口错误明细 + * @date 2025-04-17 19:18 + */ +public class InterfaceErrorDetail { + private String interfaceName; + private Map errorCodeCounts; + + public InterfaceErrorDetail(String interfaceName, Map errorCodeCounts) { + this.interfaceName = interfaceName; + this.errorCodeCounts = errorCodeCounts; + } + + public String getInterfaceName() { + return interfaceName; + } + + public Map getErrorCodeCounts() { + return errorCodeCounts; + } + +} diff --git a/test-common/src/main/java/com/test/common/core/domain/model/InterfaceErrorStat.java b/test-common/src/main/java/com/test/common/core/domain/model/InterfaceErrorStat.java new file mode 100644 index 0000000..e13bf3b --- /dev/null +++ b/test-common/src/main/java/com/test/common/core/domain/model/InterfaceErrorStat.java @@ -0,0 +1,36 @@ +package com.test.common.core.domain.model; + +/** + * @author liangdaliang + * @Description:接口错误统计 + * @date 2025-04-17 19:08 + */ +public class InterfaceErrorStat { + private String interfaceName; + private Long totalSamples; + private Long errorCount; + private Double errorRate; + + public InterfaceErrorStat(String interfaceName, long totalSamples, long errorCount) { + this.interfaceName = interfaceName; + this.totalSamples = totalSamples; + this.errorCount = errorCount; + } + + public String getInterfaceName() { + return interfaceName; + } + + public Long getTotalSamples() { + return totalSamples; + } + + public Long getErrorCount() { + return errorCount; + } + + public Double getErrorRate() { + errorRate = (totalSamples == 0 ? 0 : (double) errorCount / totalSamples * 100); + return errorRate; + } +} diff --git a/test-common/src/main/java/com/test/common/core/domain/model/JmeterGlobalStatEntity.java b/test-common/src/main/java/com/test/common/core/domain/model/JmeterGlobalStatEntity.java new file mode 100644 index 0000000..12d5ef2 --- /dev/null +++ b/test-common/src/main/java/com/test/common/core/domain/model/JmeterGlobalStatEntity.java @@ -0,0 +1,36 @@ +package com.test.common.core.domain.model; + +import java.util.List; + +/** + * @author liangdaliang + * @Description:Jmeter性能测试总体报告实体类 + * @date 2025-04-18 08:55 + */ +public class JmeterGlobalStatEntity { + /** + * Jmeter汇总报告实体对象列表 + */ + private List statsEntityList; + + /** + * Jmeter总体错误信息统计 + */ + private ErrorTotalStats errorTotalStats; + + public List getStatsEntityList() { + return statsEntityList; + } + + public void setStatsEntityList(List statsEntityList) { + this.statsEntityList = statsEntityList; + } + + public ErrorTotalStats getErrorTotalStats() { + return errorTotalStats; + } + + public void setErrorTotalStats(ErrorTotalStats errorTotalStats) { + this.errorTotalStats = errorTotalStats; + } +} diff --git a/test-common/src/main/java/com/test/common/utils/HttpSampleAnalyzer.java b/test-common/src/main/java/com/test/common/utils/HttpSampleAnalyzer.java new file mode 100644 index 0000000..e430d88 --- /dev/null +++ b/test-common/src/main/java/com/test/common/utils/HttpSampleAnalyzer.java @@ -0,0 +1,127 @@ +package com.test.common.utils; + +import com.test.common.core.domain.model.ErrorCodeStat; +import com.test.common.core.domain.model.HttpSample; +import com.test.common.core.domain.model.InterfaceErrorDetail; +import com.test.common.core.domain.model.InterfaceErrorStat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author liangdaliang + * @Description:Http采样分析工具类 + * @date 2025-04-17 19:01 + */ +public class HttpSampleAnalyzer { + private final List samples; + private final int totalRequests; + + public HttpSampleAnalyzer(List samples) { + this.samples = samples; + this.totalRequests = samples.size(); + } + + /** + * 需求1: 错误响应状态码统计 + * @return 错误状态码统计列表 + */ + public List analyzeErrorCodes() { + // 过滤出所有失败的请求 + List errorSamples = samples.stream() + .filter(s -> !s.getS()) + .collect(Collectors.toList()); + + int totalErrors = errorSamples.size(); + + // 按状态码分组统计 + Map codeCountMap = errorSamples.stream() + .collect(Collectors.groupingBy( + HttpSample::getRc, + Collectors.counting() + )); + + // 转换为包含比率的统计对象 + List result = new ArrayList<>(); + codeCountMap.forEach((code, count) -> { + double errorRatio = totalErrors == 0 ? 0 : (double) count / totalErrors * 100; + double totalRatio = (double) count / totalRequests * 100; + result.add(new ErrorCodeStat(code, count, errorRatio, totalRatio)); + }); + + // 按错误数量降序排序 + result.sort((a, b) -> Long.compare(b.getCount(), a.getCount())); + + return result; + } + + /** + * 需求2: Top 5错误接口统计 + * @return Top 5错误接口列表 + */ + public List getTop5ErrorInterfaces() { + // 获取所有接口的错误统计 + Map allInterfaceStats = getAllInterfacesErrorStats(); + + // 按错误数量降序排序并取前5 + return allInterfaceStats.values().stream() + .sorted((a, b) -> Long.compare(b.getErrorCount(), a.getErrorCount())) + .limit(5) + .collect(Collectors.toList()); + } + + /** + * 需求3: 每个接口的错误码明细 + * @return 接口错误明细列表 + */ + public List getInterfaceErrorDetails() { + // 按接口名称分组 + Map> samplesByInterface = samples.stream() + .collect(Collectors.groupingBy(HttpSample::getLb)); + + List result = new ArrayList<>(); + + samplesByInterface.forEach((interfaceName, interfaceSamples) -> { + // 过滤出该接口的错误请求 + List errorSamples = interfaceSamples.stream() + .filter(s -> !"true".equals(s.getS())) + .collect(Collectors.toList()); + + // 按错误码统计 + Map errorCodeCounts = errorSamples.stream() + .collect(Collectors.groupingBy( + HttpSample::getRc, + Collectors.counting() + )); + + if (!errorCodeCounts.isEmpty()) { + result.add(new InterfaceErrorDetail(interfaceName, errorCodeCounts)); + } + }); + + return result; + } + + // 辅助方法:获取所有接口的错误统计 + private Map getAllInterfacesErrorStats() { + // 按接口名称分组 + Map> samplesByInterface = samples.stream() + .collect(Collectors.groupingBy(HttpSample::getLb)); + + Map result = new HashMap<>(); + + samplesByInterface.forEach((interfaceName, interfaceSamples) -> { + long total = interfaceSamples.size(); + long errors = interfaceSamples.stream() + .filter(s -> !"true".equals(s.getS())) + .count(); + + result.put(interfaceName, new InterfaceErrorStat(interfaceName, total, errors)); + }); + + return result; + } +} diff --git a/test-common/src/main/java/com/test/common/utils/JMeterGroupUtil.java b/test-common/src/main/java/com/test/common/utils/JMeterGroupUtil.java index c154ca4..d1f69d0 100644 --- a/test-common/src/main/java/com/test/common/utils/JMeterGroupUtil.java +++ b/test-common/src/main/java/com/test/common/utils/JMeterGroupUtil.java @@ -4,9 +4,7 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.PathNotFoundException; -import com.test.common.core.domain.model.JmeterGroupRequest; -import com.test.common.core.domain.model.JmeterRequest; -import com.test.common.core.domain.model.LabelStatsEntity; +import com.test.common.core.domain.model.*; import org.apache.jmeter.config.Arguments; import org.apache.jmeter.config.gui.ArgumentsPanel; import org.apache.jmeter.control.LoopController; @@ -64,7 +62,7 @@ public class JMeterGroupUtil { * @param jmeterGroupRequest * @return */ - public static List getJmeterResult(JmeterGroupRequest jmeterGroupRequest) { + public static JmeterGlobalStatEntity getJmeterResult(JmeterGroupRequest jmeterGroupRequest) { Long id = jmeterGroupRequest.getTestCaseId(); Integer concurrentThreads = jmeterGroupRequest.getConcurrentThreads(); Integer loopCount = jmeterGroupRequest.getLoopCount(); @@ -75,7 +73,7 @@ public class JMeterGroupUtil { Integer rpsStatus = jmeterGroupRequest.getRpsStatus(); Integer rpsLimit = jmeterGroupRequest.getRpsLimit(); String jmeterHomePath = jmeterGroupRequest.getJmeterHomePath(); - List result = null; + JmeterGlobalStatEntity result = null; try { // 1. 初始化 JMeter JMeterUtils.loadJMeterProperties(jmeterHomePath + "/bin/jmeter.properties"); @@ -533,7 +531,7 @@ public class JMeterGroupUtil { * @param filePath * @return */ - private static List getResultMessageFromFile(String filePath) { + private static JmeterGlobalStatEntity getResultMessageFromFile(String filePath) { File file = new File(filePath); if (!file.exists()) { return null; @@ -550,6 +548,7 @@ public class JMeterGroupUtil { NodeList httpSampleList = document.getElementsByTagName("httpSample"); // 初始化统计变量 Map labelStatsMap = new LinkedHashMap<>(); + List samples = new ArrayList<>(); long startTime = Long.MAX_VALUE; long endTime = Long.MIN_VALUE; long totalRequests = 0; @@ -570,6 +569,7 @@ public class JMeterGroupUtil { long receivedBytes = Long.parseLong(httpSampleElement.getAttribute("by")); long sentBytes = Long.parseLong(httpSampleElement.getAttribute("sby")); String responseCode = httpSampleElement.getAttribute("rc"); + samples.add(new HttpSample(label, success, responseCode)); // 更新全局统计 totalRequests++; @@ -644,7 +644,21 @@ public class JMeterGroupUtil { labelStatsEntity.setReceivedKBPerSec(globalReceivedKBPerSec); labelStatsEntity.setSentKBPerSec(globalSentKBPerSec); statsEntityList.add(labelStatsEntity); - return statsEntityList; + + // 错误报告汇总 + HttpSampleAnalyzer analyzer = new HttpSampleAnalyzer(samples); + List errorCodeStatList = analyzer.analyzeErrorCodes(); + List top5InterfaceErrorStatList = analyzer.getTop5ErrorInterfaces(); + List interfaceErrorDetailList = analyzer.getInterfaceErrorDetails(); + ErrorTotalStats errorTotalStats = new ErrorTotalStats(); + errorTotalStats.setErrorCodeStatList(errorCodeStatList); + errorTotalStats.setTop5InterfaceErrorStatList(top5InterfaceErrorStatList); + errorTotalStats.setInterfaceErrorDetailList(interfaceErrorDetailList); + + JmeterGlobalStatEntity globalStatEntity = new JmeterGlobalStatEntity(); + globalStatEntity.setStatsEntityList(statsEntityList); + globalStatEntity.setErrorTotalStats(errorTotalStats); + return globalStatEntity; } catch (Exception e) { e.printStackTrace(); } diff --git a/test-test/src/main/java/com/test/test/controller/PerformanceReportController.java b/test-test/src/main/java/com/test/test/controller/PerformanceReportController.java index c01ad98..88758ec 100644 --- a/test-test/src/main/java/com/test/test/controller/PerformanceReportController.java +++ b/test-test/src/main/java/com/test/test/controller/PerformanceReportController.java @@ -1,7 +1,17 @@ package com.test.test.controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import com.test.common.annotation.Log; +import com.test.common.core.controller.BaseController; +import com.test.common.core.domain.AjaxResult; +import com.test.common.core.page.TableDataInfo; +import com.test.common.enums.BusinessType; +import com.test.test.domain.PerformanceTestCaseReport; +import com.test.test.service.IPerformanceTestCaseReportService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; +import java.util.List; /** * @author liangdaliang @@ -10,5 +20,43 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping("/test/performanceReport") -public class PerformanceReportController { +public class PerformanceReportController extends BaseController { + + @Autowired + private IPerformanceTestCaseReportService performanceTestCaseReportService; + + @PostMapping("/list") + public TableDataInfo list(@RequestBody PerformanceTestCaseReport input) + { + startPage(); + input.setDelFlag("0"); + List list = performanceTestCaseReportService.selectPerformanceTestCaseReportList(input); + return getDataTable(list); + } + + /** + * 获取单个性能测试报告详细信息 + */ + @GetMapping(value = "/getInfo/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(performanceTestCaseReportService.selectPerformanceTestCaseReportById(id)); + } + + /** + * 删除性能测试报告详细信息 + */ + @Log(title = "性能测试", businessType = BusinessType.UPDATE) + @PutMapping("/delete/{id}") + public AjaxResult remove(@PathVariable Long id) + { + PerformanceTestCaseReport report = performanceTestCaseReportService.selectPerformanceTestCaseReportById(id); + if (report != null) { + report.setDelFlag("1"); + report.setUpdateTime(new Date()); + return toAjax(performanceTestCaseReportService.updatePerformanceTestCaseReport(report)); + } else { + return null; + } + } } diff --git a/test-test/src/main/java/com/test/test/domain/PerformanceTestCaseReport.java b/test-test/src/main/java/com/test/test/domain/PerformanceTestCaseReport.java index 00010cc..586eb0e 100644 --- a/test-test/src/main/java/com/test/test/domain/PerformanceTestCaseReport.java +++ b/test-test/src/main/java/com/test/test/domain/PerformanceTestCaseReport.java @@ -80,6 +80,9 @@ public class PerformanceTestCaseReport extends BaseEntity /** 删除标记:0:正常;1:删除 */ private String delFlag; + /** 用例名称 */ + private String name; + public Long getId() { return id; } @@ -207,4 +210,12 @@ public class PerformanceTestCaseReport extends BaseEntity public void setDelFlag(String delFlag) { this.delFlag = delFlag; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/test-test/src/main/java/com/test/test/service/IPerformanceTestCaseReportService.java b/test-test/src/main/java/com/test/test/service/IPerformanceTestCaseReportService.java index 6386bd1..e6fc12c 100644 --- a/test-test/src/main/java/com/test/test/service/IPerformanceTestCaseReportService.java +++ b/test-test/src/main/java/com/test/test/service/IPerformanceTestCaseReportService.java @@ -17,8 +17,9 @@ public interface IPerformanceTestCaseReportService * @param id * @param jmeterHomePath * @param triggerType 触发方式:1-定时任务;2-手动 + * @return 返回本次性能测试的批次id */ - public void executePerformanceTestAndReport(Long id, String jmeterHomePath, Integer triggerType); + public Long executePerformanceTestAndReport(Long id, String jmeterHomePath, Integer triggerType); /** * 查询性能测试用例报告 diff --git a/test-test/src/main/java/com/test/test/service/impl/PerformanceTestCaseReportServiceImpl.java b/test-test/src/main/java/com/test/test/service/impl/PerformanceTestCaseReportServiceImpl.java index f599d05..f46ea7c 100644 --- a/test-test/src/main/java/com/test/test/service/impl/PerformanceTestCaseReportServiceImpl.java +++ b/test-test/src/main/java/com/test/test/service/impl/PerformanceTestCaseReportServiceImpl.java @@ -1,10 +1,8 @@ package com.test.test.service.impl; import com.google.gson.Gson; -import com.test.common.core.domain.model.JmeterGroupRequest; -import com.test.common.core.domain.model.JmeterRequest; -import com.test.common.core.domain.model.LabelStatsEntity; -import com.test.common.core.domain.model.LoginUser; +import com.google.gson.GsonBuilder; +import com.test.common.core.domain.model.*; import com.test.common.utils.DateUtils; import com.test.common.utils.JMeterGroupUtil; import com.test.common.utils.SecurityUtils; @@ -49,7 +47,7 @@ public class PerformanceTestCaseReportServiceImpl implements IPerformanceTestCas private ITestCaseStepService testCaseStepService; @Override - public void executePerformanceTestAndReport(Long id, String jmeterHomePath, Integer triggerType) { + public Long executePerformanceTestAndReport(Long id, String jmeterHomePath, Integer triggerType) { Long sid = System.currentTimeMillis(); PerformanceTest performanceTest = performanceTestMapper.selectPerformanceTestById(id); PerformanceTestCase performanceTestCase = new PerformanceTestCase(); @@ -74,9 +72,13 @@ public class PerformanceTestCaseReportServiceImpl implements IPerformanceTestCas List jmeterRequestList = dealAddTestCaseHttpStep(testCaseStepList); jmeterGroupRequest.setJmeterRequestList(jmeterRequestList); Date startTime = new Date(); - List jmeterResultList = JMeterGroupUtil.getJmeterResult(jmeterGroupRequest); + JmeterGlobalStatEntity jmeterGlobalStatEntity = JMeterGroupUtil.getJmeterResult(jmeterGroupRequest); + List jmeterResultList = jmeterGlobalStatEntity.getStatsEntityList(); + ErrorTotalStats errorTotalStats = jmeterGlobalStatEntity.getErrorTotalStats(); Date endTime = new Date(); - Gson gson = new Gson(); + Gson gson = new GsonBuilder() + .serializeSpecialFloatingPointValues() + .create(); if (!CollectionUtils.isEmpty(jmeterResultList)) { LabelStatsEntity lastElement = jmeterResultList.get(jmeterResultList.size() - 1); PerformanceTestCaseReport performanceTestCaseReport = new PerformanceTestCaseReport(); @@ -91,7 +93,7 @@ public class PerformanceTestCaseReportServiceImpl implements IPerformanceTestCas performanceTestCaseReport.setCostTime(endTime.getTime() - startTime.getTime()); performanceTestCaseReport.setTriggerType(triggerType); performanceTestCaseReport.setSummaryReport(gson.toJson(jmeterResultList)); - performanceTestCaseReport.setErrorReport(""); + performanceTestCaseReport.setErrorReport(gson.toJson(errorTotalStats)); performanceTestCaseReport.setStatus("1"); LoginUser user = null; try { @@ -110,6 +112,7 @@ public class PerformanceTestCaseReportServiceImpl implements IPerformanceTestCas performanceTest.setStatus("1"); performanceTest.setUpdateTime(new Date()); performanceTestMapper.updatePerformanceTest(performanceTest); + return sid; } /** diff --git a/test-test/src/main/resources/mapper/test/PerformanceTestCaseReportMapper.xml b/test-test/src/main/resources/mapper/test/PerformanceTestCaseReportMapper.xml index 44f363b..c031395 100644 --- a/test-test/src/main/resources/mapper/test/PerformanceTestCaseReportMapper.xml +++ b/test-test/src/main/resources/mapper/test/PerformanceTestCaseReportMapper.xml @@ -19,6 +19,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -32,22 +33,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"