diff --git a/backend/src/main/java/io/metersphere/commons/utils/MsJMeterUtils.java b/backend/src/main/java/io/metersphere/commons/utils/MsJMeterUtils.java new file mode 100644 index 0000000000..85a9b638f4 --- /dev/null +++ b/backend/src/main/java/io/metersphere/commons/utils/MsJMeterUtils.java @@ -0,0 +1,44 @@ +package io.metersphere.commons.utils; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; + +import java.io.InputStream; +import java.util.Properties; + +public class MsJMeterUtils { + /** + * Load the JMeter properties file; if not found, then + * default to "org/apache/jmeter/jmeter.properties" from the classpath + * + *

+ * c.f. loadProperties + * + * @param file Name of the file from which the JMeter properties should be loaded + */ + public static void loadJMeterProperties(String file) { + InputStream is = null; + try { + JMeterUtils.loadJMeterProperties(file); + } catch (Exception e) { + try { + Properties p = new Properties(System.getProperties()); + // In jar file classpath is + is = ClassLoader.getSystemResourceAsStream( + "BOOT-INF/classes/org/apache/jmeter/jmeter.properties"); // $NON-NLS-1$ + if (is == null) { + throw new RuntimeException("Could not read JMeter properties file:" + file); + } + p.load(is); + + FieldUtils.writeStaticField(JMeterUtils.class, "appProperties", p, true); + } catch (Exception ex) { + throw new RuntimeException("Could not read JMeter properties file:" + file); + } + } finally { + JOrphanUtils.closeQuietly(is); + } + } + +} diff --git a/backend/src/main/java/io/metersphere/report/JtlResolver.java b/backend/src/main/java/io/metersphere/report/JtlResolver.java index 87082b87c5..e4b79a1c79 100644 --- a/backend/src/main/java/io/metersphere/report/JtlResolver.java +++ b/backend/src/main/java/io/metersphere/report/JtlResolver.java @@ -3,7 +3,7 @@ package io.metersphere.report; import com.opencsv.bean.CsvToBean; import com.opencsv.bean.CsvToBeanBuilder; import com.opencsv.bean.HeaderColumnNameMappingStrategy; -import io.metersphere.base.domain.LoadTestReportWithBLOBs; +import io.metersphere.commons.utils.MsJMeterUtils; import io.metersphere.report.base.*; import io.metersphere.report.dto.ErrorsTop5DTO; import io.metersphere.report.dto.RequestStatisticsDTO; @@ -11,15 +11,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.report.core.Sample; import org.apache.jmeter.report.core.SampleMetadata; import org.apache.jmeter.report.dashboard.JsonizerVisitor; -import org.apache.jmeter.report.processor.ListResultData; -import org.apache.jmeter.report.processor.MapResultData; -import org.apache.jmeter.report.processor.ResultData; -import org.apache.jmeter.report.processor.SampleContext; +import org.apache.jmeter.report.processor.*; import org.apache.jmeter.report.processor.graph.AbstractOverTimeGraphConsumer; import org.apache.jmeter.report.processor.graph.impl.ActiveThreadsGraphConsumer; import org.apache.jmeter.report.processor.graph.impl.HitsPerSecondGraphConsumer; import org.apache.jmeter.report.processor.graph.impl.ResponseTimeOverTimeGraphConsumer; -import org.apache.jmeter.util.JMeterUtils; import java.io.Reader; import java.io.StringReader; @@ -346,61 +342,6 @@ public class JtlResolver { return testOverview; } - -// public static List getLoadChartData(String jtlString) { -// List chartsDataList = new ArrayList<>(); -// List totalMetricList = JtlResolver.resolver(jtlString); -// -// if (totalMetricList != null) { -// for (Metric metric : totalMetricList) { -// metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp())); -// } -// } -// Map> collect = Objects.requireNonNull(totalMetricList).stream().collect(Collectors.groupingBy(Metric::getTimestamp)); -// List>> entries = new ArrayList<>(collect.entrySet()); -// -// for (Map.Entry> entry : entries) { -// int failSize = 0; -// List metrics = entry.getValue(); -// Map> metricsMap = metrics.stream().collect(Collectors.groupingBy(Metric::getThreadName)); -// int maxUsers = metricsMap.size(); -// for (Metric metric : metrics) { -// String isSuccess = metric.getSuccess(); -// if (!"true".equals(isSuccess)) { -// failSize++; -// } -// } -// -// String timeStamp = ""; -// try { -// timeStamp = formatDate(entry.getKey()); -// } catch (ParseException e) { -// e.printStackTrace(); -// } -// -// ChartsData chartsData = new ChartsData(); -// chartsData.setxAxis(timeStamp); -// chartsData.setGroupName("hits"); -// chartsData.setyAxis(new BigDecimal(metrics.size() * 1.0 / maxUsers)); -// chartsDataList.add(chartsData); -// -// chartsData = new ChartsData(); -// chartsData.setxAxis(timeStamp); -// chartsData.setGroupName("users"); -// chartsData.setyAxis(new BigDecimal(maxUsers)); -// chartsDataList.add(chartsData); -// -// chartsData = new ChartsData(); -// chartsData.setxAxis(timeStamp); -// chartsData.setGroupName("errors"); -// chartsData.setyAxis(new BigDecimal(failSize)); -// chartsDataList.add(chartsData); -// -// } -// -// return chartsDataList; -// } - public static List getLoadChartData(String jtlString) { Map activeThreadMap = getResultDataMap(jtlString, new ActiveThreadsGraphConsumer()); Map hitsMap = getResultDataMap(jtlString, new HitsPerSecondGraphConsumer()); @@ -412,45 +353,6 @@ public class JtlResolver { return activeThreadList; } - // public static List getResponseTimeChartData(String jtlString) { -// List chartsDataList = new ArrayList<>(); -// List totalMetricList = JtlResolver.resolver(jtlString); -// -// totalMetricList.forEach(metric -> { -// metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp())); -// }); -// -// Map> metricMap = totalMetricList.stream().collect(Collectors.groupingBy(Metric::getTimestamp)); -// List>> entries = new ArrayList<>(metricMap.entrySet()); -// -// for (Map.Entry> entry : entries) { -// List metricList = entry.getValue(); -// Map> metricsMap = metricList.stream().collect(Collectors.groupingBy(Metric::getThreadName)); -// int maxUsers = metricsMap.size(); -// int sumElapsedTime = metricList.stream().mapToInt(metric -> Integer.parseInt(metric.getElapsed())).sum(); -// String timeStamp = ""; -// try { -// timeStamp = formatDate(entry.getKey()); -// } catch (ParseException e) { -// e.printStackTrace(); -// } -// -// ChartsData chartsData = new ChartsData(); -// chartsData.setxAxis(timeStamp); -// chartsData.setGroupName("users"); -// chartsData.setyAxis(new BigDecimal(maxUsers)); -// chartsDataList.add(chartsData); -// -// ChartsData chartsData2 = new ChartsData(); -// chartsData2.setxAxis(timeStamp); -// chartsData2.setGroupName("responseTime"); -// chartsData2.setyAxis(new BigDecimal(sumElapsedTime * 1.0 / metricList.size())); -// chartsDataList.add(chartsData2); -// -// } -// -// return chartsDataList; -// } public static List getResponseTimeChartData(String jtlString) { Map activeThreadMap = getResultDataMap(jtlString, new ActiveThreadsGraphConsumer()); Map responseTimeMap = getResultDataMap(jtlString, new ResponseTimeOverTimeGraphConsumer()); @@ -464,44 +366,56 @@ public class JtlResolver { } public static void mapResolver(Map map, List list, String seriesName) { + // ThreadGroup-1 for (String key : map.keySet()) { MapResultData mapResultData = (MapResultData) map.get(key); ResultData maxY = mapResultData.getResult("maxY"); ListResultData series = (ListResultData) mapResultData.getResult("series"); if (series.getSize() > 0) { - MapResultData resultData = (MapResultData) series.get(0); - ListResultData data = (ListResultData) resultData.getResult("data"); - if (data.getSize() > 0) { - for (int i = 0; i < data.getSize(); i++) { - ListResultData listResultData = (ListResultData) data.get(i); - String result = listResultData.accept(new JsonizerVisitor()); - result = result.substring(1, result.length() - 1); - String[] split = result.split(","); - ChartsData chartsData = new ChartsData(); - BigDecimal bigDecimal = new BigDecimal(split[0]); - String timeStamp = bigDecimal.toPlainString(); - String time = null; - try { - time = formatDate(stampToDate(DATE_TIME_PATTERN, timeStamp)); - } catch (ParseException e) { - e.printStackTrace(); + for (int j = 0; j < series.getSize(); j++) { + MapResultData resultData = (MapResultData) series.get(j); + // data, isOverall, label, isController + ListResultData data = (ListResultData) resultData.getResult("data"); + ValueResultData label = (ValueResultData) resultData.getResult("label"); + + if (data.getSize() > 0) { + for (int i = 0; i < data.getSize(); i++) { + ListResultData listResultData = (ListResultData) data.get(i); + String result = listResultData.accept(new JsonizerVisitor()); + result = result.substring(1, result.length() - 1); + String[] split = result.split(","); + ChartsData chartsData = new ChartsData(); + BigDecimal bigDecimal = new BigDecimal(split[0]); + String timeStamp = bigDecimal.toPlainString(); + String time = null; + try { + time = formatDate(stampToDate(DATE_TIME_PATTERN, timeStamp)); + } catch (ParseException e) { + e.printStackTrace(); + } + chartsData.setxAxis(time); + chartsData.setyAxis(new BigDecimal(split[1].trim())); + if (series.getSize() == 1) { + chartsData.setGroupName(seriesName); + } else { + chartsData.setGroupName((String) label.getValue()); + } + list.add(chartsData); } - chartsData.setxAxis(time); - chartsData.setyAxis(new BigDecimal(split[1].trim())); - chartsData.setGroupName(seriesName); - list.add(chartsData); } } + } } } public static Map getResultDataMap(String jtlString, AbstractOverTimeGraphConsumer timeGraphConsumer) { + int row = 0; AbstractOverTimeGraphConsumer abstractOverTimeGraphConsumer = timeGraphConsumer; abstractOverTimeGraphConsumer.setGranularity(60000); - // 这个路径不存在 - JMeterUtils.loadJMeterProperties("jmeter.properties"); + // 使用反射获取properties + MsJMeterUtils.loadJMeterProperties("jmeter.properties"); // 这个路径不存在 SampleMetadata sampleMetaData = createTestMetaData(); SampleContext sampleContext = new SampleContext(); abstractOverTimeGraphConsumer.setSampleContext(sampleContext); @@ -513,7 +427,7 @@ public class JtlResolver { while (tokenizer.hasMoreTokens()) { String line = tokenizer.nextToken(); String[] data = line.split(",", -1); - Sample sample = new Sample(0, sampleMetaData, data); + Sample sample = new Sample(row++, sampleMetaData, data); abstractOverTimeGraphConsumer.consume(sample, 0); } abstractOverTimeGraphConsumer.stopConsuming(); diff --git a/backend/src/main/java/io/metersphere/service/PerformanceTestService.java b/backend/src/main/java/io/metersphere/service/PerformanceTestService.java index 850b1eafcf..ef3ce7006f 100644 --- a/backend/src/main/java/io/metersphere/service/PerformanceTestService.java +++ b/backend/src/main/java/io/metersphere/service/PerformanceTestService.java @@ -93,8 +93,6 @@ public class PerformanceTestService { loadTest.setProjectId(request.getProjectId()); loadTest.setCreateTime(System.currentTimeMillis()); loadTest.setUpdateTime(System.currentTimeMillis()); - loadTest.setScenarioDefinition("todo"); - loadTest.setDescription("todo"); loadTest.setTestResourcePoolId(request.getTestResourcePoolId()); loadTest.setLoadConfiguration(request.getLoadConfiguration()); loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration()); @@ -133,6 +131,14 @@ public class PerformanceTestService { } public String edit(EditTestPlanRequest request, List files) { + // + LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId()); + if (loadTest == null) { + MSException.throwException(Translator.get("edit_load_test_not_found") + request.getId()); + } + if (StringUtils.containsAny(loadTest.getStatus(), PerformanceTestStatus.Running.name(), PerformanceTestStatus.Starting.name())) { + MSException.throwException(Translator.get("cannot_edit_load_test_running")); + } // 新选择了一个文件,删除原来的文件 List updatedFiles = request.getUpdatedFileList(); List originFiles = fileService.getFileMetadataByTestId(request.getId()); @@ -152,22 +158,14 @@ public class PerformanceTestService { }); } - final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId()); - if (loadTest == null) { - MSException.throwException(Translator.get("edit_load_test_not_found") + request.getId()); - } else { - loadTest.setName(request.getName()); - loadTest.setProjectId(request.getProjectId()); - loadTest.setUpdateTime(System.currentTimeMillis()); - loadTest.setScenarioDefinition("todo"); - loadTest.setDescription("todo"); - loadTest.setLoadConfiguration(request.getLoadConfiguration()); - loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration()); - loadTest.setTestResourcePoolId(request.getTestResourcePoolId()); - // todo 修改 load_test 的时候排除状态,这里存在修改了 Running 的测试状态的风险 -// loadTest.setStatus(PerformanceTestStatus.Saved.name()); - loadTestMapper.updateByPrimaryKeySelective(loadTest); - } + loadTest.setName(request.getName()); + loadTest.setProjectId(request.getProjectId()); + loadTest.setUpdateTime(System.currentTimeMillis()); + loadTest.setLoadConfiguration(request.getLoadConfiguration()); + loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration()); + loadTest.setTestResourcePoolId(request.getTestResourcePoolId()); + loadTest.setStatus(PerformanceTestStatus.Saved.name()); + loadTestMapper.updateByPrimaryKeySelective(loadTest); return request.getId(); } diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index 634a2c073f..22315563d4 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -19,3 +19,4 @@ duplicate_node_ip=Duplicate IPs only_one_k8s=Only one K8s can be added organization_id_is_null=Organization ID cannot be null max_thread_insufficient=The number of concurrent users exceeds +cannot_edit_load_test_running=Cannot modify the running test \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index 6a6daf57a4..bc77486217 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -18,4 +18,5 @@ no_nodes_message=\u6CA1\u6709\u8282\u70B9\u4FE1\u606F duplicate_node_ip=\u8282\u70B9 IP \u91CD\u590D only_one_k8s=\u53EA\u80FD\u6DFB\u52A0\u4E00\u4E2A K8s organization_id_is_null=\u7EC4\u7EC7 ID \u4E0D\u80FD\u4E3A\u7A7A -max_thread_insufficient=\u5E76\u53D1\u7528\u6237\u6570\u8D85\u989D \ No newline at end of file +max_thread_insufficient=\u5E76\u53D1\u7528\u6237\u6570\u8D85\u989D +cannot_edit_load_test_running=不能修改正在运行的测试 \ No newline at end of file diff --git a/frontend/src/business/components/api/test/ApiScenarioConfig.vue b/frontend/src/business/components/api/test/ApiScenarioConfig.vue index 193d2f7926..fd46816c96 100644 --- a/frontend/src/business/components/api/test/ApiScenarioConfig.vue +++ b/frontend/src/business/components/api/test/ApiScenarioConfig.vue @@ -26,13 +26,14 @@ - +

- - + +
@@ -49,6 +50,7 @@ import MsApiRequest from "./components/ApiRequest"; import MsApiRequestForm from "./components/ApiRequestForm"; import MsApiScenarioForm from "./components/ApiScenarioForm"; + import {Scenario, Request} from "./model/APIModel"; export default { name: "MsApiScenarioConfig", @@ -64,6 +66,17 @@ }, methods: { + createScenario: function () { + let scenario = new Scenario({name: "Scenario"}); + this.scenarios.push(scenario); + }, + deleteScenario: function (index) { + this.scenarios.splice(index, 1); + if (this.scenarios.length === 0) { + this.createScenario(); + this.select(this.scenarios[0]); + } + }, handleChange: function (index) { this.select(this.scenarios[index]); }, @@ -74,35 +87,23 @@ break; } }, - createScenario: function () { - return { - type: "Scenario", - name: "Scenario", - address: "", - file: "", - variables: [], - headers: [], - requests: [] - } - }, - deleteScenario: function (index) { - this.scenarios.splice(index, 1); - if (this.scenarios.length === 0) { - this.create(); - } - }, - create: function () { - let scenario = this.createScenario(); - this.scenarios.push(scenario); - }, select: function (obj) { this.selected = obj; } }, + computed: { + isScenario() { + return this.selected instanceof Scenario; + }, + isRequest() { + return this.selected instanceof Request; + } + }, + created() { if (this.scenarios.length === 0) { - this.create(); + this.createScenario(); this.select(this.scenarios[0]); } } @@ -137,6 +138,9 @@ } .scenario-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; font-size: 14px; width: 100%; } diff --git a/frontend/src/business/components/api/test/components/ApiAssertionRegex.vue b/frontend/src/business/components/api/test/components/ApiAssertionRegex.vue new file mode 100644 index 0000000000..5dd59c026b --- /dev/null +++ b/frontend/src/business/components/api/test/components/ApiAssertionRegex.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/frontend/src/business/components/api/test/components/ApiAssertionResponseTime.vue b/frontend/src/business/components/api/test/components/ApiAssertionResponseTime.vue new file mode 100644 index 0000000000..d3f3e505cf --- /dev/null +++ b/frontend/src/business/components/api/test/components/ApiAssertionResponseTime.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/frontend/src/business/components/api/test/components/ApiAssertionText.vue b/frontend/src/business/components/api/test/components/ApiAssertionText.vue new file mode 100644 index 0000000000..5f807c5e68 --- /dev/null +++ b/frontend/src/business/components/api/test/components/ApiAssertionText.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/frontend/src/business/components/api/test/components/ApiAssertions.vue b/frontend/src/business/components/api/test/components/ApiAssertions.vue new file mode 100644 index 0000000000..10c46cc7f7 --- /dev/null +++ b/frontend/src/business/components/api/test/components/ApiAssertions.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend/src/business/components/api/test/components/ApiAssertionsEdit.vue b/frontend/src/business/components/api/test/components/ApiAssertionsEdit.vue new file mode 100644 index 0000000000..f07eefcdae --- /dev/null +++ b/frontend/src/business/components/api/test/components/ApiAssertionsEdit.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend/src/business/components/api/test/components/ApiBody.vue b/frontend/src/business/components/api/test/components/ApiBody.vue index a6074a6e65..4869609197 100644 --- a/frontend/src/business/components/api/test/components/ApiBody.vue +++ b/frontend/src/business/components/api/test/components/ApiBody.vue @@ -1,15 +1,15 @@