diff --git a/backend/pom.xml b/backend/pom.xml index 561fe81516..ab143b2730 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -110,7 +110,6 @@ commons-codec commons-codec - 1.12 junit @@ -128,43 +127,12 @@ slf4j-simple - - - org.apache.jmeter - ApacheJMeter_http - ${jmeter.version} - - - org.apache.logging.log4j - log4j-slf4j-impl - - - - - - kg.apc - jmeter-plugins-tst - 2.5 - - - - kg.apc - jmeter-plugins-casutg - 2.9 - - com.opencsv opencsv 5.1 - - org.apache.httpcomponents - httpclient - 4.5.3 - - diff --git a/backend/src/main/java/io/metersphere/Application.java b/backend/src/main/java/io/metersphere/Application.java index 20ac59d613..693b24040f 100644 --- a/backend/src/main/java/io/metersphere/Application.java +++ b/backend/src/main/java/io/metersphere/Application.java @@ -6,8 +6,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.ServletComponentScan; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestTemplate; @SpringBootApplication(exclude = {QuartzAutoConfiguration.class}) @ServletComponentScan @@ -17,8 +15,5 @@ public class Application { SpringApplication.run(Application.class, args); } - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } + } diff --git a/backend/src/main/java/io/metersphere/config/WebConfig.java b/backend/src/main/java/io/metersphere/config/WebConfig.java index 35acc14672..1546184637 100644 --- a/backend/src/main/java/io/metersphere/config/WebConfig.java +++ b/backend/src/main/java/io/metersphere/config/WebConfig.java @@ -1,7 +1,9 @@ package io.metersphere.config; import io.metersphere.interceptor.TestInterceptor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -12,4 +14,9 @@ public class WebConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TestInterceptor()); } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/backend/src/main/java/io/metersphere/controller/ReportController.java b/backend/src/main/java/io/metersphere/controller/ReportController.java index 6167c9b8d4..57a62f5c32 100644 --- a/backend/src/main/java/io/metersphere/controller/ReportController.java +++ b/backend/src/main/java/io/metersphere/controller/ReportController.java @@ -9,8 +9,9 @@ import io.metersphere.commons.utils.Pager; import io.metersphere.controller.request.ReportRequest; import io.metersphere.dto.ReportDTO; import io.metersphere.report.base.Errors; -import io.metersphere.report.base.RequestStatistics; -import io.metersphere.report.base.RequestStatisticsDTO; +import io.metersphere.report.base.TestOverview; +import io.metersphere.report.dto.ErrorsTop5DTO; +import io.metersphere.report.dto.RequestStatisticsDTO; import io.metersphere.service.ReportService; import io.metersphere.user.SessionUtils; import org.apache.shiro.authz.annotation.Logical; @@ -65,5 +66,15 @@ public class ReportController { return reportService.getReportErrors(reportId); } + @GetMapping("/content/errors_top5/{reportId}") + public ErrorsTop5DTO getReportErrorsTop5(@PathVariable String reportId) { + return reportService.getReportErrorsTOP5(reportId); + } + + @GetMapping("/content/testoverview/{reportId}") + public TestOverview getTestOverview(@PathVariable String reportId) { + return reportService.getTestOverview(reportId); + } + } diff --git a/backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java b/backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java index af7cb5b7f0..b08cb4676c 100644 --- a/backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java +++ b/backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java @@ -6,11 +6,12 @@ import io.metersphere.base.domain.TestResource; import io.metersphere.commons.constants.ResourceStatusEnum; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; -import io.metersphere.controller.request.TestRequest; import io.metersphere.dto.NodeDTO; import io.metersphere.engine.AbstractEngine; import io.metersphere.engine.EngineContext; import io.metersphere.engine.EngineFactory; +import io.metersphere.engine.docker.request.DockerLoginRequest; +import io.metersphere.engine.docker.request.TestRequest; import io.metersphere.engine.kubernetes.registry.RegistryService; import org.apache.commons.lang3.StringUtils; import org.springframework.web.client.RestTemplate; @@ -85,10 +86,12 @@ public class DockerTestEngine extends AbstractEngine { testRequest.setFileString(content); testRequest.setImage(registryService.getRegistry() + JMETER_IMAGE); testRequest.setTestData(context.getTestData()); - + testRequest.setRegistry(registryService.getRegistryUrl()); + testRequest.setPassword(registryService.getRegistryPassword()); + testRequest.setUsername(registryService.getRegistryUsername()); // todo 判断测试状态 String taskStatusUri = String.format(BASE_URL + "/jmeter/task/status/" + testId, nodeIp, port); - List containerList = restTemplate.getForObject(taskStatusUri, List.class); + List containerList = restTemplate.postForObject(taskStatusUri, testRequest, List.class); for (int i = 0; i < containerList.size(); i++) { HashMap h = (HashMap) containerList.get(i); if (StringUtils.equals((String) h.get("State"), "running")) { @@ -103,13 +106,14 @@ public class DockerTestEngine extends AbstractEngine { public void stop() { // TODO 停止运行测试 String testId = loadTest.getId(); + DockerLoginRequest request = new DockerLoginRequest(); this.resourceList.forEach(r -> { NodeDTO node = JSON.parseObject(r.getConfiguration(), NodeDTO.class); String ip = node.getIp(); Integer port = node.getPort(); String uri = String.format(BASE_URL + "/jmeter/container/stop/" + testId, ip, port); - restTemplate.postForObject(uri, "", String.class); + restTemplate.postForObject(uri, request, String.class); }); diff --git a/backend/src/main/java/io/metersphere/engine/docker/request/DockerLoginRequest.java b/backend/src/main/java/io/metersphere/engine/docker/request/DockerLoginRequest.java new file mode 100644 index 0000000000..db6ab045a8 --- /dev/null +++ b/backend/src/main/java/io/metersphere/engine/docker/request/DockerLoginRequest.java @@ -0,0 +1,31 @@ +package io.metersphere.engine.docker.request; + +public class DockerLoginRequest { + private String username; + private String password; + private String registry; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getRegistry() { + return registry; + } + + public void setRegistry(String registry) { + this.registry = registry; + } +} diff --git a/backend/src/main/java/io/metersphere/controller/request/TestRequest.java b/backend/src/main/java/io/metersphere/engine/docker/request/TestRequest.java similarity index 90% rename from backend/src/main/java/io/metersphere/controller/request/TestRequest.java rename to backend/src/main/java/io/metersphere/engine/docker/request/TestRequest.java index 22061bdaec..3f4131886b 100644 --- a/backend/src/main/java/io/metersphere/controller/request/TestRequest.java +++ b/backend/src/main/java/io/metersphere/engine/docker/request/TestRequest.java @@ -1,9 +1,9 @@ -package io.metersphere.controller.request; +package io.metersphere.engine.docker.request; import java.util.HashMap; import java.util.Map; -public class TestRequest { +public class TestRequest extends DockerLoginRequest { private int size; private String fileString; diff --git a/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java b/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java index 69f154816a..dcfdef07a5 100644 --- a/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java +++ b/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java @@ -105,15 +105,14 @@ public class KubernetesTestEngine extends AbstractEngine { public void stop() { resourceList.forEach(r -> { try { - EngineContext context = EngineFactory.createContext(loadTest, threadNum); String configuration = r.getConfiguration(); ClientCredential clientCredential = JSON.parseObject(configuration, ClientCredential.class); KubernetesProvider provider = new KubernetesProvider(JSON.toJSONString(clientCredential)); - provider.confirmNamespace(context.getNamespace()); + provider.confirmNamespace(loadTest.getProjectId()); Jmeter jmeter = new Jmeter(); jmeter.setMetadata(new ObjectMeta() {{ - setName(context.getTestId()); - setNamespace(context.getNamespace()); + setName(loadTest.getId()); + setNamespace(loadTest.getProjectId()); }}); jmeter.setSpec(new JmeterSpec() {{ setReplicas(1); diff --git a/backend/src/main/java/io/metersphere/parse/xml/reader/jmx/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/parse/xml/reader/jmx/JmeterDocumentParser.java index 2f457f543d..cccc050a7a 100644 --- a/backend/src/main/java/io/metersphere/parse/xml/reader/jmx/JmeterDocumentParser.java +++ b/backend/src/main/java/io/metersphere/parse/xml/reader/jmx/JmeterDocumentParser.java @@ -422,7 +422,8 @@ public class JmeterDocumentParser implements DocumentParser { stringPropCount++; } else { stringPropCount = 0; - prop.getFirstChild().setNodeValue(context.getProperty("duration").toString()); + Integer duration = (Integer) context.getProperty("duration");// 传入的是分钟数, 需要转化成秒数 + prop.getFirstChild().setNodeValue(String.valueOf(duration * 60)); continue; } prop.getFirstChild().setNodeValue(context.getProperty("rpsLimit").toString()); diff --git a/backend/src/main/java/io/metersphere/report/JtlResolver.java b/backend/src/main/java/io/metersphere/report/JtlResolver.java index b5e07fa585..30f631f045 100644 --- a/backend/src/main/java/io/metersphere/report/JtlResolver.java +++ b/backend/src/main/java/io/metersphere/report/JtlResolver.java @@ -3,12 +3,10 @@ package io.metersphere.report; import com.opencsv.bean.CsvToBean; import com.opencsv.bean.CsvToBeanBuilder; import com.opencsv.bean.HeaderColumnNameMappingStrategy; -import io.metersphere.report.base.Errors; -import io.metersphere.report.base.Metric; -import io.metersphere.report.base.RequestStatistics; -import io.metersphere.report.base.RequestStatisticsDTO; +import io.metersphere.report.base.*; +import io.metersphere.report.dto.ErrorsTop5DTO; +import io.metersphere.report.dto.RequestStatisticsDTO; import org.apache.commons.lang3.StringUtils; - import java.io.Reader; import java.io.StringReader; import java.text.DecimalFormat; @@ -21,7 +19,6 @@ public class JtlResolver { HeaderColumnNameMappingStrategy ms = new HeaderColumnNameMappingStrategy<>(); ms.setType(Metric.class); try (Reader reader = new StringReader(jtlString)) { - CsvToBean cb = new CsvToBeanBuilder(reader) .withType(Metric.class) .withSkipLines(0) @@ -40,36 +37,33 @@ public class JtlResolver { List requestStatisticsList = new ArrayList<>(); Iterator>> iterator = map.entrySet().iterator(); List allelapse = new ArrayList<>(); - Integer totalAverage = 0; + DecimalFormat df = new DecimalFormat("0.00"); + int totalAverage = 0; while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); String label = entry.getKey(); List list = entry.getValue(); List timestampList = list.stream().map(Metric::getTimestamp).collect(Collectors.toList()); int index=0; - //总的响应时间 int sumElapsed=0; - Integer failSize = 0; - Float totalBytes = 0f; + int failSize = 0; + float totalBytes = 0f; List elapsedList = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { try { Metric row = list.get(i); - //响应时间 String elapsed = row.getElapsed(); - sumElapsed += Integer.valueOf(elapsed); - totalAverage += Integer.valueOf(elapsed); + sumElapsed += Integer.parseInt(elapsed); + totalAverage += Integer.parseInt(elapsed); elapsedList.add(Integer.valueOf(elapsed)); allelapse.add(Integer.valueOf(elapsed)); - //成功与否 String success = row.getSuccess(); if (!"true".equals(success)){ failSize++; } - //字节 String bytes = row.getBytes(); - totalBytes += Float.valueOf(bytes); + totalBytes += Float.parseFloat(bytes); index++; }catch (Exception e){ System.out.println("exception i:"+i); @@ -78,17 +72,19 @@ public class JtlResolver { Collections.sort(elapsedList); - Integer tp90 = elapsedList.size()*90/100; - Integer tp95 = elapsedList.size()*95/100; - Integer tp99 = elapsedList.size()*99/100; - Long l = Long.valueOf(timestampList.get(timestampList.size()-1)) - Long.valueOf(timestampList.get(0)); + int tp90 = elapsedList.size()*90/100; + int tp95 = elapsedList.size()*95/100; + int tp99 = elapsedList.size()*99/100; + long l = Long.valueOf(timestampList.get(timestampList.size()-1)) - Long.valueOf(timestampList.get(0)); RequestStatistics requestStatistics = new RequestStatistics(); requestStatistics.setRequestLabel(label); requestStatistics.setSamples(index); - DecimalFormat df = new DecimalFormat("0.00"); + + String s = df.format((float)sumElapsed/index); requestStatistics.setAverage(s+""); + /** * TP90的计算 * 1,把一段时间内全部的请求的响应时间,从小到大排序,获得序列A @@ -99,6 +95,7 @@ public class JtlResolver { requestStatistics.setTp90(elapsedList.get(tp90)+""); requestStatistics.setTp95(elapsedList.get(tp95)+""); requestStatistics.setTp99(elapsedList.get(tp99)+""); + requestStatistics.setMin(elapsedList.get(0)+""); requestStatistics.setMax(elapsedList.get(index-1)+""); requestStatistics.setErrors(String.format("%.2f",failSize*100.0/index)+"%"); @@ -112,16 +109,15 @@ public class JtlResolver { } Collections.sort(allelapse); - Integer totalTP90 = allelapse.size()*90/100; - Integer totalTP95 = allelapse.size()*95/100; - Integer totalTP99 = allelapse.size()*99/100; + int totalTP90 = allelapse.size()*90/100; + int totalTP95 = allelapse.size()*95/100; + int totalTP99 = allelapse.size()*99/100; Integer min = allelapse.get(0); Integer max = allelapse.get(allelapse.size() - 1); - Integer allSamples = requestStatisticsList.stream().mapToInt(RequestStatistics::getSamples).sum(); - Integer failSize = requestStatisticsList.stream().mapToInt(RequestStatistics::getKo).sum(); - DecimalFormat df = new DecimalFormat("0.00"); + int allSamples = requestStatisticsList.stream().mapToInt(RequestStatistics::getSamples).sum(); + int failSize = requestStatisticsList.stream().mapToInt(RequestStatistics::getKo).sum(); double errors = (double)failSize / allSamples * 100; String totalerrors = df.format(errors); double average = (double)totalAverage / allSamples; @@ -142,20 +138,21 @@ public class JtlResolver { return statisticsDTO; } - // Aggregate Report + // report - Aggregate Report public static RequestStatisticsDTO getRequestStatistics(String jtlString) { List totalLines = resolver(jtlString); Map> map = totalLines.stream().collect(Collectors.groupingBy(Metric::getLabel)); return getOneRpsResult(map); } - // Errors + // report - Errors public static List getErrorsList(String jtlString) { List totalLines = resolver(jtlString); List falseList = totalLines.stream().filter(metric -> StringUtils.equals("false", metric.getSuccess())).collect(Collectors.toList()); List errorsList = new ArrayList<>(); Map> collect = falseList.stream().collect(Collectors.groupingBy(JtlResolver::getResponseCodeAndFailureMessage)); Iterator>> iterator = collect.entrySet().iterator(); + DecimalFormat df = new DecimalFormat("0.00"); while (iterator.hasNext()) { Map.Entry> next = iterator.next(); String key = next.getKey(); @@ -163,10 +160,9 @@ public class JtlResolver { Errors errors = new Errors(); errors.setErrorType(key); errors.setErrorNumber(String.valueOf(value.size())); - Integer errorSize = value.size(); - Integer errorAllSize = falseList.size(); - Integer allSamples = totalLines.size(); - DecimalFormat df = new DecimalFormat("0.00"); + int errorSize = value.size(); + int errorAllSize = falseList.size(); + int allSamples = totalLines.size(); errors.setPrecentOfErrors(df.format((double)errorSize / errorAllSize * 100) + "%"); errors.setPrecentOfAllSamples(df.format((double)errorSize / allSamples * 100) + "%"); errorsList.add(errors); @@ -178,4 +174,106 @@ public class JtlResolver { return metric.getResponseCode() + "/" + metric.getResponseMessage(); } + // report - Errors Top 5 + public static ErrorsTop5DTO getErrorsTop5DTO(String jtlString) { + List totalLines = resolver(jtlString); + ErrorsTop5DTO top5DTO = new ErrorsTop5DTO(); + List falseList = totalLines.stream().filter(metric -> StringUtils.equals("false", metric.getSuccess())).collect(Collectors.toList()); + Map> collect = falseList.stream().collect(Collectors.groupingBy(JtlResolver::getResponseCodeAndFailureMessage)); + Iterator>> iterator = collect.entrySet().iterator(); + List errorsTop5s = new ArrayList<>(); + while (iterator.hasNext()) { + Map.Entry> next = iterator.next(); + String key = next.getKey(); + List value = next.getValue(); + ErrorsTop5 errorsTop5 = new ErrorsTop5(); + List list = totalLines.stream() + .filter(metric -> StringUtils.equals(metric.getLabel(), value.get(0).getLabel())).collect(Collectors.toList()); + errorsTop5.setSamples(String.valueOf(list.size())); + errorsTop5.setSample(value.get(0).getLabel()); + errorsTop5.setErrors(String.valueOf(value.size())); + errorsTop5.setErrorsAllSize(value.size()); + errorsTop5.setError(key); + errorsTop5s.add(errorsTop5); + } + + errorsTop5s.sort((t0, t1) -> t1.getErrorsAllSize().compareTo(t0.getErrorsAllSize())); + + if (errorsTop5s.size() >= 5) { + errorsTop5s = errorsTop5s.subList(0, 5); + } + + top5DTO.setLabel("Total"); + top5DTO.setErrorsTop5List(errorsTop5s); + top5DTO.setTotalSamples(String.valueOf(totalLines.size())); + top5DTO.setTotalErrors(String.valueOf(falseList.size())); + int size = errorsTop5s.size(); + // Total行 信息 + top5DTO.setError1(size > 0 ? errorsTop5s.get(0).getError() : null); + top5DTO.setError1Size(size > 0 ? errorsTop5s.get(0).getErrors() : null); + top5DTO.setError2(size > 1 ? errorsTop5s.get(1).getError() : null); + top5DTO.setError2Size(size > 1 ? errorsTop5s.get(1).getErrors() : null); + top5DTO.setError3(size > 2 ? errorsTop5s.get(2).getError() : null); + top5DTO.setError3Size(size > 2 ? errorsTop5s.get(2).getErrors() : null); + top5DTO.setError4(size > 3 ? errorsTop5s.get(3).getError() : null); + top5DTO.setError4Size(size > 3 ? errorsTop5s.get(3).getErrors() : null); + top5DTO.setError5(size > 4 ? errorsTop5s.get(4).getError() : null); + top5DTO.setError5Size(size > 4 ? errorsTop5s.get(4).getErrors() : null); + + return top5DTO; + } + + // report - TestOverview + public static TestOverview getTestOverview(String jtlString) { + TestOverview testOverview = new TestOverview(); + List total = JtlResolver.resolver(jtlString); + Map> collect = total.stream().collect(Collectors.groupingBy(Metric::getTimestamp)); + Iterator>> iterator = collect.entrySet().iterator(); + int max = 0; + int totalElapsed = 0; + float totalBytes = 0f; + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + List list = entry.getValue(); + if (list.size() > max) { + max = list.size(); + } + for (int i = 0; i < list.size(); i++) { + Metric metric = list.get(i); + String elapsed = metric.getElapsed(); + totalElapsed += Integer.parseInt(elapsed); + String bytes = metric.getBytes(); + totalBytes += Float.parseFloat(bytes); + } + } + + total.sort(Comparator.comparing(t0 -> Long.valueOf(t0.getTimestamp()))); + DecimalFormat df = new DecimalFormat("0.00"); + + testOverview.setMaxUsers(String.valueOf(max)); + + List list90 = total.subList(0, total.size() * 9 / 10); + long sum = list90.stream().mapToLong(metric -> Long.parseLong(metric.getElapsed())).sum(); + double avg90 = (double)sum / 1000 / list90.size(); + testOverview.setResponseTime90(df.format(avg90)); + + Long timestamp1 = Long.valueOf(total.get(0).getTimestamp()); + Long timestamp2 = Long.valueOf(total.get(total.size()-1).getTimestamp()); + long seconds = (timestamp2 - timestamp1) / 1000; + double avgThroughput = (double)total.size() / seconds; + testOverview.setAvgThroughput(df.format(avgThroughput)); + + List falseList = total.stream().filter(metric -> StringUtils.equals("false", metric.getSuccess())).collect(Collectors.toList()); + double errors = falseList.size() * 1.0 / total.size() * 100; + testOverview.setErrors(df.format(errors)); + + double avg = totalElapsed * 1.0 / total.size() / 1000; // s + testOverview.setAvgResponseTime(df.format(avg)); + + double bandwidth = totalBytes * 1.0 / 1024 / seconds; + testOverview.setAvgBandwidth(df.format(bandwidth)); + + return testOverview; + } + } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/report/base/ErrorsTop5.java b/backend/src/main/java/io/metersphere/report/base/ErrorsTop5.java new file mode 100644 index 0000000000..311e1112fc --- /dev/null +++ b/backend/src/main/java/io/metersphere/report/base/ErrorsTop5.java @@ -0,0 +1,50 @@ +package io.metersphere.report.base; + +public class ErrorsTop5 { + + private String sample; + private String samples; + private Integer errorsAllSize; + private String error; + private String errors; + + public String getSample() { + return sample; + } + + public void setSample(String sample) { + this.sample = sample; + } + + public String getSamples() { + return samples; + } + + public void setSamples(String samples) { + this.samples = samples; + } + + public Integer getErrorsAllSize() { + return errorsAllSize; + } + + public void setErrorsAllSize(Integer errorsAllSize) { + this.errorsAllSize = errorsAllSize; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getErrors() { + return errors; + } + + public void setErrors(String errors) { + this.errors = errors; + } +} diff --git a/backend/src/main/java/io/metersphere/report/base/TestOverview.java b/backend/src/main/java/io/metersphere/report/base/TestOverview.java new file mode 100644 index 0000000000..9ef2b2c3dc --- /dev/null +++ b/backend/src/main/java/io/metersphere/report/base/TestOverview.java @@ -0,0 +1,59 @@ +package io.metersphere.report.base; + +public class TestOverview { + + private String maxUsers; + private String avgThroughput; // Hits/s + private String errors; + private String avgResponseTime; // s + private String responseTime90; + private String avgBandwidth; + + public String getMaxUsers() { + return maxUsers; + } + + public void setMaxUsers(String maxUsers) { + this.maxUsers = maxUsers; + } + + public String getAvgThroughput() { + return avgThroughput; + } + + public void setAvgThroughput(String avgThroughput) { + this.avgThroughput = avgThroughput; + } + + public String getErrors() { + return errors; + } + + public void setErrors(String errors) { + this.errors = errors; + } + + public String getAvgResponseTime() { + return avgResponseTime; + } + + public void setAvgResponseTime(String avgResponseTime) { + this.avgResponseTime = avgResponseTime; + } + + public String getResponseTime90() { + return responseTime90; + } + + public void setResponseTime90(String responseTime90) { + this.responseTime90 = responseTime90; + } + + public String getAvgBandwidth() { + return avgBandwidth; + } + + public void setAvgBandwidth(String avgBandwidth) { + this.avgBandwidth = avgBandwidth; + } +} diff --git a/backend/src/main/java/io/metersphere/report/dto/ErrorsTop5DTO.java b/backend/src/main/java/io/metersphere/report/dto/ErrorsTop5DTO.java new file mode 100644 index 0000000000..7bcf200855 --- /dev/null +++ b/backend/src/main/java/io/metersphere/report/dto/ErrorsTop5DTO.java @@ -0,0 +1,135 @@ +package io.metersphere.report.dto; + +import io.metersphere.report.base.ErrorsTop5; + +import java.util.List; + +public class ErrorsTop5DTO { + + private List errorsTop5List; + private String label; + private String totalSamples; + private String totalErrors; + private String error1; + private String error1Size; + private String error2; + private String error2Size; + private String error3; + private String error3Size; + private String error4; + private String error4Size; + private String error5; + private String error5Size; + + public List getErrorsTop5List() { + return errorsTop5List; + } + + public void setErrorsTop5List(List errorsTop5List) { + this.errorsTop5List = errorsTop5List; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getTotalSamples() { + return totalSamples; + } + + public void setTotalSamples(String totalSamples) { + this.totalSamples = totalSamples; + } + + public String getTotalErrors() { + return totalErrors; + } + + public void setTotalErrors(String totalErrors) { + this.totalErrors = totalErrors; + } + + public String getError1() { + return error1; + } + + public void setError1(String error1) { + this.error1 = error1; + } + + public String getError1Size() { + return error1Size; + } + + public void setError1Size(String error1Size) { + this.error1Size = error1Size; + } + + public String getError2() { + return error2; + } + + public void setError2(String error2) { + this.error2 = error2; + } + + public String getError2Size() { + return error2Size; + } + + public void setError2Size(String error2Size) { + this.error2Size = error2Size; + } + + public String getError3() { + return error3; + } + + public void setError3(String error3) { + this.error3 = error3; + } + + public String getError3Size() { + return error3Size; + } + + public void setError3Size(String error3Size) { + this.error3Size = error3Size; + } + + public String getError4() { + return error4; + } + + public void setError4(String error4) { + this.error4 = error4; + } + + public String getError4Size() { + return error4Size; + } + + public void setError4Size(String error4Size) { + this.error4Size = error4Size; + } + + public String getError5() { + return error5; + } + + public void setError5(String error5) { + this.error5 = error5; + } + + public String getError5Size() { + return error5Size; + } + + public void setError5Size(String error5Size) { + this.error5Size = error5Size; + } +} diff --git a/backend/src/main/java/io/metersphere/report/base/RequestStatisticsDTO.java b/backend/src/main/java/io/metersphere/report/dto/RequestStatisticsDTO.java similarity index 96% rename from backend/src/main/java/io/metersphere/report/base/RequestStatisticsDTO.java rename to backend/src/main/java/io/metersphere/report/dto/RequestStatisticsDTO.java index 3f93a5f8a2..a92e235a89 100644 --- a/backend/src/main/java/io/metersphere/report/base/RequestStatisticsDTO.java +++ b/backend/src/main/java/io/metersphere/report/dto/RequestStatisticsDTO.java @@ -1,4 +1,6 @@ -package io.metersphere.report.base; +package io.metersphere.report.dto; + +import io.metersphere.report.base.RequestStatistics; import java.util.List; diff --git a/backend/src/main/java/io/metersphere/service/LoadTestService.java b/backend/src/main/java/io/metersphere/service/LoadTestService.java index 1cdce5e7fb..a9396f4dd8 100644 --- a/backend/src/main/java/io/metersphere/service/LoadTestService.java +++ b/backend/src/main/java/io/metersphere/service/LoadTestService.java @@ -166,6 +166,9 @@ public class LoadTestService { if (loadTest == null) { MSException.throwException(Translator.get("run_load_test_not_found") + request.getId()); } + if (TestStatus.Running.name().equals(loadTest.getStatus())) { + MSException.throwException(Translator.get("load_test_is_running") + request.getId()); + } LogUtil.info("Load test started " + loadTest.getName()); // engine type (NODE|K8S) diff --git a/backend/src/main/java/io/metersphere/service/ReportService.java b/backend/src/main/java/io/metersphere/service/ReportService.java index a918b34516..76145cfced 100644 --- a/backend/src/main/java/io/metersphere/service/ReportService.java +++ b/backend/src/main/java/io/metersphere/service/ReportService.java @@ -8,8 +8,9 @@ import io.metersphere.controller.request.ReportRequest; import io.metersphere.dto.ReportDTO; import io.metersphere.report.JtlResolver; import io.metersphere.report.base.Errors; -import io.metersphere.report.base.RequestStatistics; -import io.metersphere.report.base.RequestStatisticsDTO; +import io.metersphere.report.base.TestOverview; +import io.metersphere.report.dto.ErrorsTop5DTO; +import io.metersphere.report.dto.RequestStatisticsDTO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -59,4 +60,18 @@ public class ReportService { List errors = JtlResolver.getErrorsList(content); return errors; } + + public ErrorsTop5DTO getReportErrorsTOP5(String id) { + LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(id); + String content = loadTestReport.getContent(); + ErrorsTop5DTO errors = JtlResolver.getErrorsTop5DTO(content); + return errors; + } + + public TestOverview getTestOverview(String id) { + LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(id); + String content = loadTestReport.getContent(); + TestOverview testOverview = JtlResolver.getTestOverview(content); + return testOverview; + } } diff --git a/backend/src/main/java/io/metersphere/service/TestResourcePoolService.java b/backend/src/main/java/io/metersphere/service/TestResourcePoolService.java index 058c555d34..e81cc22944 100644 --- a/backend/src/main/java/io/metersphere/service/TestResourcePoolService.java +++ b/backend/src/main/java/io/metersphere/service/TestResourcePoolService.java @@ -16,7 +16,7 @@ import io.metersphere.dto.TestResourcePoolDTO; import io.metersphere.engine.kubernetes.provider.KubernetesProvider; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpStatus; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -43,6 +43,8 @@ public class TestResourcePoolService { private TestResourceMapper testResourceMapper; @Resource private ExtTestReourcePoolMapper extTestReourcePoolMapper; + @Resource + private RestTemplate restTemplate; public TestResourcePoolDTO addTestResourcePool(TestResourcePoolDTO testResourcePool) { testResourcePool.setId(UUID.randomUUID().toString()); @@ -100,9 +102,8 @@ public class TestResourcePoolService { private boolean validateNode(NodeDTO node) { try { - RestTemplate restTemplate = new RestTemplate(); ResponseEntity entity = restTemplate.getForEntity(String.format(nodeControllerUrl, node.getIp(), node.getPort()), String.class); - return entity.getStatusCode().value() == HttpStatus.SC_OK; + return HttpStatus.OK.equals(entity.getStatusCode()); } catch (Exception e) { return false; } diff --git a/backend/src/main/resources/i18n/en-US.json b/backend/src/main/resources/i18n/en-US.json index 4d1a6251c1..04c61c9de6 100644 --- a/backend/src/main/resources/i18n/en-US.json +++ b/backend/src/main/resources/i18n/en-US.json @@ -12,5 +12,6 @@ "run_load_test_not_found": "Cannot run test, test not found:", "run_load_test_file_not_found": "Unable to run test, unable to get test file meta information, test ID:", "run_load_test_file_content_not_found": "Cannot run test, cannot get test file content, test ID:", - "run_load_test_file_init_error": "Failed to run test, failed to initialize run environment, test ID:" + "run_load_test_file_init_error": "Failed to run test, failed to initialize run environment, test ID:", + "load_test_is_running": "Load test is running, please wait." } \ No newline at end of file diff --git a/backend/src/main/resources/i18n/zh-CN.json b/backend/src/main/resources/i18n/zh-CN.json index 85ca41c996..1d085f8813 100644 --- a/backend/src/main/resources/i18n/zh-CN.json +++ b/backend/src/main/resources/i18n/zh-CN.json @@ -12,5 +12,6 @@ "run_load_test_not_found": "无法运行测试,未找到测试:", "run_load_test_file_not_found": "无法运行测试,无法获取测试文件元信息,测试ID:", "run_load_test_file_content_not_found": "无法运行测试,无法获取测试文件内容,测试ID:", - "run_load_test_file_init_error": "无法运行测试,初始化运行环境失败,测试ID:" + "run_load_test_file_init_error": "无法运行测试,初始化运行环境失败,测试ID:", + "load_test_is_running": "测试正在运行, 请等待" } \ No newline at end of file diff --git a/frontend/src/business/components/performance/plan/EditPerformanceTestPlan.vue b/frontend/src/business/components/performance/plan/EditPerformanceTestPlan.vue index a76ec27387..84d55c223a 100644 --- a/frontend/src/business/components/performance/plan/EditPerformanceTestPlan.vue +++ b/frontend/src/business/components/performance/plan/EditPerformanceTestPlan.vue @@ -82,6 +82,10 @@ return; } + if (to.name !== 'editPerTest') { + return; + } + let testId = to.path.split('/')[4]; // find testId if (testId) { this.result = this.$get('/testplan/get/' + testId, response => { diff --git a/frontend/src/business/components/performance/plan/components/PerformanceAdvancedConfig.vue b/frontend/src/business/components/performance/plan/components/PerformanceAdvancedConfig.vue index 716b23fea9..fd0d714d1a 100644 --- a/frontend/src/business/components/performance/plan/components/PerformanceAdvancedConfig.vue +++ b/frontend/src/business/components/performance/plan/components/PerformanceAdvancedConfig.vue @@ -218,8 +218,8 @@ } }, watch: { - '$route'(to, from) { - if (from.name !== 'createPerTest' && from.name !== 'editPerTest') { + '$route'(to) { + if (to.name !== 'createPerTest' && to.name !== 'editPerTest') { return; } let testId = to.path.split('/')[4]; diff --git a/frontend/src/business/components/performance/plan/components/PerformancePressureConfig.vue b/frontend/src/business/components/performance/plan/components/PerformancePressureConfig.vue index 07b08f2d8f..be8ffb0314 100644 --- a/frontend/src/business/components/performance/plan/components/PerformancePressureConfig.vue +++ b/frontend/src/business/components/performance/plan/components/PerformancePressureConfig.vue @@ -131,8 +131,8 @@ this.getResourcePools(); }, watch: { - '$route'(to, from) { - if (from.name !== 'createPerTest' && from.name !== 'editPerTest') { + '$route'(to) { + if (to.name !== 'createPerTest' && to.name !== 'editPerTest') { return; } let testId = to.path.split('/')[4]; diff --git a/frontend/src/business/components/performance/report/PerformanceReportView.vue b/frontend/src/business/components/performance/report/PerformanceReportView.vue index 522f861dce..c153d20254 100644 --- a/frontend/src/business/components/performance/report/PerformanceReportView.vue +++ b/frontend/src/business/components/performance/report/PerformanceReportView.vue @@ -35,7 +35,7 @@ - + @@ -44,7 +44,7 @@ - + diff --git a/frontend/src/business/components/performance/report/components/ErrorLog.vue b/frontend/src/business/components/performance/report/components/ErrorLog.vue index eb13535c5b..aa885f8322 100644 --- a/frontend/src/business/components/performance/report/components/ErrorLog.vue +++ b/frontend/src/business/components/performance/report/components/ErrorLog.vue @@ -34,37 +34,39 @@ Top 5 Errors by sampler @@ -126,7 +128,9 @@ name: "ErrorLog", data() { return { - tableData: [{},{},{},{},{}] + tableData: [{},{},{},{},{}], + errorTotal: {}, + errorTop5: [] } }, methods: { @@ -134,10 +138,32 @@ this.$get("/report/content/errors/" + this.id, res => { this.tableData = res.data; }) + this.$get("/report/content/errors_top5/" + this.id, res => { + this.errorTotal = res.data + this.errorTop5 = res.data.errorsTop5List; + }) + }, + getSummaries () { + const sums = [] + sums[0] = this.errorTotal.label; + sums[1] = this.errorTotal.totalSamples; + sums[2] = this.errorTotal.totalErrors; + sums[3] = this.errorTotal.error1; + sums[4] = this.errorTotal.error1Size; + sums[5] = this.errorTotal.error2; + sums[6] = this.errorTotal.error2Size; + sums[7] = this.errorTotal.error3; + sums[8] = this.errorTotal.error3Size; + sums[9] = this.errorTotal.error4; + sums[10] = this.errorTotal.error4Size; + sums[11] = this.errorTotal.error5; + sums[12] = this.errorTotal.error5Size; + return sums; } }, created() { this.initTableData(); + this.getSummaries() }, props: ['id'], watch: { @@ -145,9 +171,13 @@ if (to.name === "perReportView") { let reportId = to.path.split('/')[4]; if(reportId){ - this.$get("/report/content/errors/" + this.id, res => { + this.$get("/report/content/errors/" + reportId, res => { this.tableData = res.data; }) + this.$get("/report/content/errors_top5/" + reportId, res => { + this.errorTop5 = res.data.errorsTop5List; + this.errorTotal = res.data + }) } } } diff --git a/frontend/src/business/components/performance/report/components/TestOverview.vue b/frontend/src/business/components/performance/report/components/TestOverview.vue index 1745b0bca7..54414663b8 100644 --- a/frontend/src/business/components/performance/report/components/TestOverview.vue +++ b/frontend/src/business/components/performance/report/components/TestOverview.vue @@ -4,7 +4,7 @@ - 40 + {{maxUsers}} VU Max Users @@ -13,7 +13,7 @@ - 5.4 + {{avgThroughput}} Hits/s Avg.Throughput @@ -22,7 +22,7 @@ - 0.41 + {{errors}} % Errors @@ -31,7 +31,7 @@ - 1.28 + {{avgResponseTime}} s Avg.Response Time @@ -40,7 +40,7 @@ - 1.41 + {{responseTime90}} s 90% Response Time @@ -49,7 +49,7 @@ - 817.29 + {{avgBandwidth}} KiB/s Avg.Bandwidth @@ -73,6 +73,12 @@ name: "TestOverview", data() { return { + maxUsers: "0", + avgThroughput: "0", + errors: "0", + avgResponseTime: "0", + responseTime90: "0", + avgBandwidth: "0", option1: { legend: { top: 20, @@ -146,6 +152,41 @@ ] } } + }, + methods: { + initTableData() { + this.$get("/report/content/testoverview/" + this.id, res => { + let data = res.data; + this.maxUsers = data.maxUsers; + this.avgThroughput = data.avgThroughput; + this.errors = data.errors; + this.avgResponseTime = data.avgResponseTime; + this.responseTime90 = data.responseTime90; + this.avgBandwidth = data.avgBandwidth; + }) + } + }, + created() { + this.initTableData() + }, + props: ['id'], + watch: { + '$route'(to) { + if (to.name === "perReportView") { + let reportId = to.path.split('/')[4]; + if(reportId){ + this.$get("/report/content/testoverview/" + reportId, res => { + let data = res.data; + this.maxUsers = data.maxUsers; + this.avgThroughput = data.avgThroughput; + this.errors = data.errors; + this.avgResponseTime = data.avgResponseTime; + this.responseTime90 = data.responseTime90; + this.avgBandwidth = data.avgBandwidth; + }) + } + } + } } } diff --git a/pom.xml b/pom.xml index 14cce36261..7277be513f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.2.RELEASE + 2.2.6.RELEASE