From c30dac746fffb3aa8d4429c52cf0efd281f7ff26 Mon Sep 17 00:00:00 2001 From: song-tianyang Date: Wed, 13 Dec 2023 12:12:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=80=A7=E8=83=BD=E6=B5=8B=E8=AF=95):=20?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E6=B5=8B=E8=AF=95=E9=AB=98=E7=BA=A7=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=A2=9E=E5=8A=A0=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --story=1013936 --user=宋天阳 【性能测试】高级配置增加可配置项 https://www.tapd.cn/55049933/s/1443207 --- .../frontend/src/i18n/lang/en-US.js | 8 ++ .../frontend/src/i18n/lang/zh-CN.js | 8 ++ .../frontend/src/i18n/lang/zh-TW.js | 8 ++ .../xml/reader/JmeterDocumentParser.java | 128 +++++++++++++++++- .../report/components/TestConfiguration.vue | 9 +- .../src/business/test/DiffVersion.vue | 18 ++- .../src/business/test/EditPerformanceTest.vue | 10 +- .../components/PerformanceAdvancedConfig.vue | 73 +++++++++- .../frontend/src/i18n/lang/en-US.js | 1 + .../frontend/src/i18n/lang/zh-CN.js | 1 + .../frontend/src/i18n/lang/zh-TW.js | 1 + .../performance/TestConfiguration.vue | 9 +- .../load/PerformanceAdvancedConfig.vue | 47 ++++++- .../report/detail/load/TestConfiguration.vue | 2 +- 14 files changed, 301 insertions(+), 22 deletions(-) diff --git a/framework/sdk-parent/frontend/src/i18n/lang/en-US.js b/framework/sdk-parent/frontend/src/i18n/lang/en-US.js index a523a8540a..852c4bab60 100644 --- a/framework/sdk-parent/frontend/src/i18n/lang/en-US.js +++ b/framework/sdk-parent/frontend/src/i18n/lang/en-US.js @@ -1327,6 +1327,14 @@ const message = { "The file already exists in the project, please import it directly", project_file_update_type_error: "Updated file types must be consistent", csv_has_header: "Contains Title", + csv_file_end_recycle: "Recycle when file end", + csv_file_end_stop_thread: "Stop thread when file end", + csv_file_end_stop_thread_option: { + all: "All thread", + group: "Thread group", + thread: "Thread", + }, + thread_share: "Thread share", csv_split: "CSV Split", on_sample_error: "Sampler error", continue: "Continue", diff --git a/framework/sdk-parent/frontend/src/i18n/lang/zh-CN.js b/framework/sdk-parent/frontend/src/i18n/lang/zh-CN.js index 04468780b6..7b1610e0c6 100644 --- a/framework/sdk-parent/frontend/src/i18n/lang/zh-CN.js +++ b/framework/sdk-parent/frontend/src/i18n/lang/zh-CN.js @@ -1284,6 +1284,14 @@ const message = { load_api_automation_jmx: "引用接口自动化场景", project_file_exist: "项目中已存在该文件,请直接引用", csv_has_header: "包含表头", + csv_file_end_recycle: "遇到文件结束符再次循环", + csv_file_end_stop_thread: "遇到文件结束符停止线程", + csv_file_end_stop_thread_option: { + all: "所有线程", + group: "当前线程组", + thread: "当前线程", + }, + thread_share: "线程共享模式", csv_split: "CSV分割", on_sample_error: "取样器错误后", continue: "继续", diff --git a/framework/sdk-parent/frontend/src/i18n/lang/zh-TW.js b/framework/sdk-parent/frontend/src/i18n/lang/zh-TW.js index ca00311589..8a17b48b03 100644 --- a/framework/sdk-parent/frontend/src/i18n/lang/zh-TW.js +++ b/framework/sdk-parent/frontend/src/i18n/lang/zh-TW.js @@ -1283,6 +1283,14 @@ const message = { load_api_automation_jmx: "引用接口自動化場景", project_file_exist: "項目中已存在該文件,請直接引用", csv_has_header: "包含表頭", + csv_file_end_recycle: "遇到文件結束符再次循環", + csv_file_end_stop_thread: "遇到文件結束符停止線程", + csv_file_end_stop_thread_option: { + all: "所有線程", + group: "當前線程組", + thread: "當前線程", + }, + thread_share: "線程共享模式", csv_split: "CSV分割", on_sample_error: "取樣器錯誤後", continue: "繼續", diff --git a/performance-test/backend/src/main/java/io/metersphere/parse/xml/reader/JmeterDocumentParser.java b/performance-test/backend/src/main/java/io/metersphere/parse/xml/reader/JmeterDocumentParser.java index 605486ce49..3a9a1ab5ec 100644 --- a/performance-test/backend/src/main/java/io/metersphere/parse/xml/reader/JmeterDocumentParser.java +++ b/performance-test/backend/src/main/java/io/metersphere/parse/xml/reader/JmeterDocumentParser.java @@ -10,11 +10,13 @@ import io.metersphere.jmeter.utils.ScriptEngineUtils; import io.metersphere.parse.EngineSourceParser; import io.metersphere.parse.EngineSourceParserFactory; import io.metersphere.service.BaseTestResourcePoolService; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.Node; +import org.dom4j.tree.BaseElement; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -41,11 +43,24 @@ public class JmeterDocumentParser implements EngineSourceParser { private final static String HTTP_SAMPLER_PROXY = "HTTPSamplerProxy"; private final static String CSV_DATA_SET = "CSVDataSet"; private final static String THREAD_GROUP_AUTO_STOP = "io.metersphere.jmeter.reporters.ThreadGroupAutoStop"; + private final static List SCRIPTS = new ArrayList<>() {{ + add("JSR223PreProcessor"); + add("JSR223PostProcessor"); + add("JSR223Sampler"); + add("JSR223Assertion"); + add("JSR223Timer"); + add("JSR223Listener"); + }}; private EngineContext context; + private boolean cacheScript = false; @Override public byte[] parse(EngineContext context, InputStream source) throws Exception { this.context = context; + Object cacheScriptObj = context.getProperty("cacheScript"); + if (cacheScriptObj != null) { + cacheScript = BooleanUtils.toBoolean(cacheScriptObj.toString()); + } Document document = EngineSourceParserFactory.getDocument(source); @@ -56,7 +71,6 @@ public class JmeterDocumentParser implements EngineSourceParser { // jmeterTestPlan的子元素肯定是 parseHashTree(ele); } - return EngineSourceParserFactory.getBytes(document); } @@ -110,12 +124,31 @@ public class JmeterDocumentParser implements EngineSourceParser { } else if (ele.getName().endsWith(RESULT_COLLECTOR)) { // 处理结果收集器,性能测试不需要这些 processResultCollector(ele); + } else if (CollectionUtils.containsAny(SCRIPTS, ele.getName())) { + this.elementCacheScript(ele); } } } } + private void elementCacheScript(Element element) { + List childNodes = element.elements(); + Element cacheKeyNode = null; + for (Element item : childNodes) { + if (nodeNameEquals(item, STRING_PROP) && StringUtils.equalsIgnoreCase(item.attributeValue("name"), "cacheKey")) { + cacheKeyNode = item; + break; + } + } + if (cacheKeyNode == null) { + cacheKeyNode = new BaseElement(STRING_PROP); + cacheKeyNode.addAttribute("name", "cacheKey"); + element.add(cacheKeyNode); + } + cacheKeyNode.setText(cacheScript ? "true" : "false"); + + } private void processThreadType(Element ele) { Object threadType = context.getProperty("threadType"); if (threadType instanceof List) { @@ -238,20 +271,101 @@ public class JmeterDocumentParser implements EngineSourceParser { private void processCsvDataSet(Element element) { List childNodes = element.elements(); + String fileName = null; for (Element item : childNodes) { if (nodeNameEquals(item, STRING_PROP)) { - String filenameTag = item.attributeValue("name"); - if (StringUtils.equals(filenameTag, "filename")) { - // 截取文件名 - handleFilename(item); - // 切割CSV文件 - splitCsvFile(item); + if (StringUtils.equals(item.attributeValue("name"), "filename")) { + fileName = item.getText(); + String separator = "/"; + if (!StringUtils.contains(fileName, "/")) { + separator = "\\"; + } + fileName = fileName.substring(fileName.lastIndexOf(separator) + 1); break; } } } + + Element recycleNode = null; + Element stopThreadNode = null; + Element shareModeNode = null; + + for (Element item : childNodes) { + String filenameTag = item.attributeValue("name"); + if (nodeNameEquals(item, STRING_PROP)) { + if (StringUtils.equals(filenameTag, "filename")) { + // 截取文件名 + handleFilename(item); + splitCsvFile(item); + // 切割CSV文件 + } else if (StringUtils.equalsIgnoreCase(filenameTag, "shareMode")) { + shareModeNode = item; + } + } else if (nodeNameEquals(item, BOOL_PROP)) { + if (StringUtils.equalsIgnoreCase(filenameTag, "recycle")) { + recycleNode = item; + } else if (StringUtils.equalsIgnoreCase(filenameTag, "stopThread")) { + stopThreadNode = item; + } + } + } + + if (stopThreadNode == null) { + stopThreadNode = new BaseElement(BOOL_PROP); + stopThreadNode.addAttribute("name", "stopThread"); + element.add(stopThreadNode); + } + if (recycleNode == null) { + recycleNode = new BaseElement(BOOL_PROP); + recycleNode.addAttribute("name", "recycle"); + element.add(recycleNode); + } + if (shareModeNode == null) { + shareModeNode = new BaseElement(STRING_PROP); + shareModeNode.addAttribute("name", "shareMode"); + element.add(shareModeNode); + } + + setShareMode(shareModeNode, fileName); + setRecycle(recycleNode, fileName); + setStopThread(stopThreadNode, fileName); } + private void setShareMode(Node item, String fileName) { + Object csvConfig = context.getProperty("csvConfig"); + if (csvConfig == null) { + return; + } + Object config = ((Map) csvConfig).get(fileName); + if (config != null) { + String shareMode = String.valueOf(((Map) (config)).get("shareMode")); + item.setText(shareMode); + } + } + + private void setRecycle(Node item, String fileName) { + Object csvConfig = context.getProperty("csvConfig"); + if (csvConfig == null) { + return; + } + Object config = ((Map) csvConfig).get(fileName); + if (config != null) { + Boolean recycle = (Boolean) ((Map) (config)).get("recycle"); + item.setText(String.valueOf(BooleanUtils.toBoolean(recycle))); + } + } + + private void setStopThread(Node item, String fileName) { + Object csvConfig = context.getProperty("csvConfig"); + if (csvConfig == null) { + return; + } + Object config = ((Map) csvConfig).get(fileName); + if (config != null) { + Boolean recycle = (Boolean) ((Map) (config)).get("stopThread"); + item.setText(String.valueOf(BooleanUtils.toBoolean(recycle))); + } + } private void splitCsvFile(Node item) { String filename = item.getText(); filename = StringUtils.removeStart(filename, "/test/"); diff --git a/performance-test/frontend/src/business/report/components/TestConfiguration.vue b/performance-test/frontend/src/business/report/components/TestConfiguration.vue index 15196f5bcf..8beb50e0ee 100644 --- a/performance-test/frontend/src/business/report/components/TestConfiguration.vue +++ b/performance-test/frontend/src/business/report/components/TestConfiguration.vue @@ -37,7 +37,14 @@ export default { }); let csvFiles = []; for (const f of csvSet) { - csvFiles.push({name: f, csvSplit: false, csvHasHeader: true}); + csvFiles.push({ + name: f, + csvSplit: false, + csvHasHeader: true, + recycle: true, + stopThread: false, + shareMode: "shareMode.thread" + }); } this.$refs.advancedConfig.csvFiles = csvFiles; diff --git a/performance-test/frontend/src/business/test/DiffVersion.vue b/performance-test/frontend/src/business/test/DiffVersion.vue index 86b4b396da..38f7d124bf 100644 --- a/performance-test/frontend/src/business/test/DiffVersion.vue +++ b/performance-test/frontend/src/business/test/DiffVersion.vue @@ -162,7 +162,14 @@ export default { }); let csvFiles = []; for (const f of csvSet) { - csvFiles.push({name: f, csvSplit: false, csvHasHeader: true}); + csvFiles.push({ + name: f, + csvSplit: false, + csvHasHeader: true, + recycle: true, + stopThread: false, + shareMode: "shareMode.thread" + }); } this.$set(handler, "threadGroups", threadGroups); @@ -196,7 +203,14 @@ export default { }); let csvFiles = []; for (const f of csvSet) { - csvFiles.push({name: f, csvSplit: false, csvHasHeader: true}); + csvFiles.push({ + name: f, + csvSplit: false, + csvHasHeader: true, + recycle: true, + stopThread: false, + shareMode: "shareMode.thread" + }); } this.$set(handler, "threadGroups", threadGroups); diff --git a/performance-test/frontend/src/business/test/EditPerformanceTest.vue b/performance-test/frontend/src/business/test/EditPerformanceTest.vue index 9fe7b44af4..f35e668e8f 100644 --- a/performance-test/frontend/src/business/test/EditPerformanceTest.vue +++ b/performance-test/frontend/src/business/test/EditPerformanceTest.vue @@ -673,7 +673,14 @@ export default { }); let csvFiles = []; for (const f of csvSet) { - csvFiles.push({name: f, csvSplit: false, csvHasHeader: true}); + csvFiles.push({ + name: f, + csvSplit: false, + csvHasHeader: true, + recycle: true, + stopThread: false, + shareMode: "shareMode.thread" + }); } this.$set(handler, "threadGroups", threadGroups); @@ -681,7 +688,6 @@ export default { this.$refs.basicConfig.threadGroups = threadGroups; this.$refs.pressureConfig.threadGroups = threadGroups; this.$refs.advancedConfig.csvFiles = csvFiles; - this.$refs.pressureConfig.resourcePoolChange(); handler.calculateTotalChart(); }, diff --git a/performance-test/frontend/src/business/test/components/PerformanceAdvancedConfig.vue b/performance-test/frontend/src/business/test/components/PerformanceAdvancedConfig.vue index 498cc2614e..4551317768 100644 --- a/performance-test/frontend/src/business/test/components/PerformanceAdvancedConfig.vue +++ b/performance-test/frontend/src/business/test/components/PerformanceAdvancedConfig.vue @@ -2,7 +2,7 @@
- +
{{ $t('load_test.connect_timeout') }}
@@ -18,7 +18,7 @@
- +
{{ $t('load_test.response_timeout') }}
@@ -34,7 +34,7 @@
- +
@@ -64,7 +64,7 @@ - +
{{ $t('load_test.custom_http_code') }}
@@ -77,6 +77,13 @@
+ + + + + + + @@ -177,6 +184,30 @@ + + + + + + + + + + +
@@ -481,6 +512,7 @@ export default { csvFiles: [], csvConfig: [], statusCodeStr: '', + cacheScript: false, granularity: undefined, granularityData: [ {start: 0, end: 100, granularity: 1}, @@ -523,7 +555,11 @@ export default { } }, csvFiles() { - this.refreshCsv(); + if (this.testId) { + this.getAdvancedConfig(); + } else if (this.reportId) { + this.getAdvancedConfig('report'); + } } }, methods: { @@ -538,6 +574,7 @@ export default { this.domains = data.domains || []; this.params = data.params || []; this.granularity = data.granularity; + this.cacheScript = data.cacheScript; this.monitorParams = data.monitorParams || []; this.properties = data.properties || []; this.systemProperties = data.systemProperties || []; @@ -548,8 +585,23 @@ export default { refreshCsv() { if (this.csvConfig && this.csvFiles) { this.csvFiles.forEach(f => { - f.csvSplit = this.csvConfig[f.name]?.csvSplit; + f.csvSplit = this.csvConfig[f.name]?.csvSplit f.csvHasHeader = this.csvConfig[f.name]?.csvHasHeader; + f.recycle = this.csvConfig[f.name]?.recycle; + f.stopThread = this.csvConfig[f.name]?.stopThread; + f.shareMode = this.csvConfig[f.name]?.shareMode; + if (f.csvHasHeader === undefined) { + f.csvHasHeader = true; + } + if (f.stopThread === undefined) { + f.stopThread = false; + } + if (f.recycle === undefined) { + f.recycle = true; + } + if (f.shareMode === undefined) { + f.shareMode = "shareMode.thread"; + } }); } }, @@ -661,11 +713,18 @@ export default { properties: this.properties, systemProperties: this.systemProperties, csvConfig: this.csvFiles.reduce((result, curr) => { - result[curr.name] = {csvHasHeader: curr.csvHasHeader, csvSplit: curr.csvSplit}; + result[curr.name] = { + csvHasHeader: curr.csvHasHeader, + csvSplit: curr.csvSplit, + recycle: curr.recycle, + stopThread: curr.stopThread, + shareMode: curr.shareMode + }; return result; }, {}), domains: this.domains, granularity: this.granularity, + cacheScript: this.cacheScript, monitorParams: this.monitorParams }; }, diff --git a/performance-test/frontend/src/i18n/lang/en-US.js b/performance-test/frontend/src/i18n/lang/en-US.js index 1782e9ec8b..ceb4dedabd 100644 --- a/performance-test/frontend/src/i18n/lang/en-US.js +++ b/performance-test/frontend/src/i18n/lang/en-US.js @@ -15,6 +15,7 @@ const message = { error_samples: 'Error samples', all_samples: 'All samples', response_3_samples: 'The first three pieces of data', + cache_script: 'Cache script', } } export default { diff --git a/performance-test/frontend/src/i18n/lang/zh-CN.js b/performance-test/frontend/src/i18n/lang/zh-CN.js index 449c14af31..3ad1bc64bf 100644 --- a/performance-test/frontend/src/i18n/lang/zh-CN.js +++ b/performance-test/frontend/src/i18n/lang/zh-CN.js @@ -15,6 +15,7 @@ const message = { error_samples: '错误请求', all_samples: '所有请求', response_3_samples: '默认抽样前3个请求的响应数据', + cache_script: '缓存编译脚本', } } diff --git a/performance-test/frontend/src/i18n/lang/zh-TW.js b/performance-test/frontend/src/i18n/lang/zh-TW.js index f3e9c5428f..4fae0a2f15 100644 --- a/performance-test/frontend/src/i18n/lang/zh-TW.js +++ b/performance-test/frontend/src/i18n/lang/zh-TW.js @@ -15,6 +15,7 @@ const message = { error_samples: '錯誤請求', all_samples: '所有請求', response_3_samples: '默认抽样前3个请求的响应数据', + cache_script: '緩存編譯腳本', } } diff --git a/test-track/frontend/src/business/performance/TestConfiguration.vue b/test-track/frontend/src/business/performance/TestConfiguration.vue index b283bf4b0f..3847eef827 100644 --- a/test-track/frontend/src/business/performance/TestConfiguration.vue +++ b/test-track/frontend/src/business/performance/TestConfiguration.vue @@ -37,7 +37,14 @@ export default { }); let csvFiles = []; for (const f of csvSet) { - csvFiles.push({name: f, csvSplit: false, csvHasHeader: true}); + csvFiles.push({ + name: f, + csvSplit: false, + csvHasHeader: true, + recycle: true, + stopThread: false, + shareMode: "shareMode.thread" + }); } this.$refs.advancedConfig.csvFiles = csvFiles; diff --git a/test-track/frontend/src/business/plan/view/comonents/load/PerformanceAdvancedConfig.vue b/test-track/frontend/src/business/plan/view/comonents/load/PerformanceAdvancedConfig.vue index a82b8fbc0e..76df12e416 100644 --- a/test-track/frontend/src/business/plan/view/comonents/load/PerformanceAdvancedConfig.vue +++ b/test-track/frontend/src/business/plan/view/comonents/load/PerformanceAdvancedConfig.vue @@ -175,6 +175,30 @@ + + + + + + + + + + + @@ -439,6 +463,21 @@ export default { this.csvFiles.forEach(f => { f.csvSplit = this.csvConfig[f.name]?.csvSplit; f.csvHasHeader = this.csvConfig[f.name]?.csvHasHeader; + f.recycle = this.csvConfig[f.name]?.recycle; + f.stopThread = this.csvConfig[f.name]?.stopThread; + f.shareMode = this.csvConfig[f.name]?.shareMode; + if (f.csvHasHeader === undefined) { + f.csvHasHeader = true; + } + if (f.stopThread === undefined) { + f.stopThread = false; + } + if (f.recycle === undefined) { + f.recycle = true; + } + if (f.shareMode === undefined) { + f.shareMode = "shareMode.thread"; + } }); } } @@ -561,7 +600,13 @@ export default { params: this.params, properties: this.properties, csvConfig: this.csvFiles.reduce((result, curr) => { - result[curr.name] = {csvHasHeader: curr.csvHasHeader, csvSplit: curr.csvSplit}; + result[curr.name] = { + csvHasHeader: curr.csvHasHeader, + csvSplit: curr.csvSplit, + recycle: curr.recycle, + stopThread: curr.stopThread, + shareMode: curr.shareMode + }; return result; }, {}), domains: this.domains, diff --git a/test-track/frontend/src/business/plan/view/comonents/report/detail/load/TestConfiguration.vue b/test-track/frontend/src/business/plan/view/comonents/report/detail/load/TestConfiguration.vue index 5b5de28488..fb19f0b325 100644 --- a/test-track/frontend/src/business/plan/view/comonents/report/detail/load/TestConfiguration.vue +++ b/test-track/frontend/src/business/plan/view/comonents/report/detail/load/TestConfiguration.vue @@ -63,7 +63,7 @@ export default { }); let csvFiles = []; for (const f of csvSet) { - csvFiles.push({ name: f, csvSplit: false, csvHasHeader: true }); + csvFiles.push({ name: f, csvSplit: false, csvHasHeader: true, recycle: true, stopThread: false, shareMode: "shareMode.thread" }); } if (this.$refs.advancedConfig) { this.$refs.advancedConfig.csvFiles = csvFiles;