feat(性能测试): 支持配置不同的线程组参数

This commit is contained in:
Captain.B 2020-11-06 18:11:21 +08:00
parent 1e5e19417e
commit 2d1a5a1995
14 changed files with 751 additions and 324 deletions

View File

@ -104,6 +104,12 @@ public class PerformanceTestController {
return performanceTestService.getLoadConfiguration(testId);
}
@GetMapping("/get-jmx-content/{testId}")
public String getJmxContent(@PathVariable String testId) {
checkOwnerService.checkPerformanceTestOwner(testId);
return performanceTestService.getJmxContent(testId);
}
@PostMapping("/delete")
public void delete(@RequestBody DeleteTestPlanRequest request) {
checkOwnerService.checkPerformanceTestOwner(request.getId());

View File

@ -18,6 +18,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public abstract class AbstractEngine implements Engine {
@ -81,9 +82,22 @@ public abstract class AbstractEngine implements Engine {
String loadConfiguration = t.getLoadConfiguration();
JSONArray jsonArray = JSON.parseArray(loadConfiguration);
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject o = jsonArray.getJSONObject(i);
if (StringUtils.equals(o.getString("key"), "TargetLevel")) {
s = o.getInteger("value");
if (jsonArray.get(i) instanceof Map) {
JSONObject o = jsonArray.getJSONObject(i);
if (StringUtils.equals(o.getString("key"), "TargetLevel")) {
s = o.getInteger("value");
break;
}
}
if (jsonArray.get(i) instanceof List) {
JSONArray o = jsonArray.getJSONArray(i);
for (int j = 0; j < o.size(); j++) {
JSONObject b = o.getJSONObject(j);
if (StringUtils.equals(b.getString("key"), "TargetLevel")) {
s += b.getInteger("value");
break;
}
}
}
}
return s;

View File

@ -10,7 +10,6 @@ public class EngineContext {
private String fileType;
private String content;
private String resourcePoolId;
private Long threadNum;
private Long startTime;
private String reportId;
private Integer resourceIndex;
@ -95,14 +94,6 @@ public class EngineContext {
this.resourcePoolId = resourcePoolId;
}
public Long getThreadNum() {
return threadNum;
}
public void setThreadNum(Long threadNum) {
this.threadNum = threadNum;
}
public Long getStartTime() {
return startTime;
}

View File

@ -9,6 +9,7 @@ import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.commons.constants.FileType;
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.config.KafkaProperties;
import io.metersphere.i18n.Translator;
import io.metersphere.performance.engine.docker.DockerTestEngine;
@ -22,6 +23,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -52,7 +54,7 @@ public class EngineFactory {
return null;
}
public static EngineContext createContext(LoadTestWithBLOBs loadTest, String resourceId, long threadNum, long startTime, String reportId, int resourceIndex) {
public static EngineContext createContext(LoadTestWithBLOBs loadTest, String resourceId, double ratio, long startTime, String reportId, int resourceIndex) {
final List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(loadTest.getId());
if (org.springframework.util.CollectionUtils.isEmpty(fileMetadataList)) {
MSException.throwException(Translator.get("run_load_test_file_not_found") + loadTest.getId());
@ -73,7 +75,6 @@ public class EngineFactory {
engineContext.setTestName(loadTest.getName());
engineContext.setNamespace(loadTest.getProjectId());
engineContext.setFileType(jmxFile.getType());
engineContext.setThreadNum(threadNum);
engineContext.setResourcePoolId(loadTest.getTestResourcePoolId());
engineContext.setStartTime(startTime);
engineContext.setReportId(reportId);
@ -90,8 +91,34 @@ public class EngineFactory {
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
for (int i = 0; i < jsonArray.size(); i++) {
final JSONObject jsonObject = jsonArray.getJSONObject(i);
engineContext.addProperty(jsonObject.getString("key"), jsonObject.get("value"));
if (jsonArray.get(i) instanceof Map) {
JSONObject o = jsonArray.getJSONObject(i);
String key = o.getString("key");
if ("TargetLevel".equals(key)) {
engineContext.addProperty(key, Math.round(((Integer) o.get("value")) * ratio));
} else {
engineContext.addProperty(key, o.get("value"));
}
}
if (jsonArray.get(i) instanceof List) {
JSONArray o = jsonArray.getJSONArray(i);
for (int j = 0; j < o.size(); j++) {
JSONObject b = o.getJSONObject(j);
String key = b.getString("key");
Object values = engineContext.getProperty(key);
if (values == null) {
values = new ArrayList<>();
}
if (values instanceof List) {
Object value = b.get("value");
if ("TargetLevel".equals(key)) {
value = Math.round(((Integer) b.get("value")) * ratio);
}
((List<Object>) values).add(value);
engineContext.addProperty(key, values);
}
}
}
}
}
/*
@ -112,8 +139,10 @@ public class EngineFactory {
String content = engineSourceParser.parse(engineContext, source);
engineContext.setContent(content);
} catch (MSException e) {
LogUtil.error(e);
throw e;
} catch (Exception e) {
LogUtil.error(e);
MSException.throwException(e);
}

View File

@ -6,6 +6,7 @@ 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.commons.utils.LogUtil;
import io.metersphere.controller.ResultHolder;
import io.metersphere.dto.NodeDTO;
import io.metersphere.i18n.Translator;
@ -52,19 +53,21 @@ public class DockerTestEngine extends AbstractEngine {
for (int i = 0, size = resourceList.size(); i < size; i++) {
int ratio = resourceRatio.get(i);
double realThreadNum = ((double) ratio / totalThreadNum) * threadNum;
runTest(resourceList.get(i), Math.round(realThreadNum), i);
// double realThreadNum = ((double) ratio / totalThreadNum) * threadNum;
runTest(resourceList.get(i), ((double) ratio / totalThreadNum), i);
}
}
private void runTest(TestResource resource, long realThreadNum, int resourceIndex) {
private void runTest(TestResource resource, double ratio, int resourceIndex) {
EngineContext context = null;
try {
context = EngineFactory.createContext(loadTest, resource.getId(), realThreadNum, this.getStartTime(), this.getReportId(), resourceIndex);
context = EngineFactory.createContext(loadTest, resource.getId(), ratio, this.getStartTime(), this.getReportId(), resourceIndex);
} catch (MSException e) {
LogUtil.error(e);
throw e;
} catch (Exception e) {
LogUtil.error(e);
MSException.throwException(e);
}

View File

@ -777,8 +777,22 @@ public class JmeterDocumentParser implements DocumentParser {
elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController");
threadGroup.appendChild(elementProp);
// 持续时长
String duration = context.getProperty("duration").toString();
String rampUp = context.getProperty("RampUp").toString();
Object durations = context.getProperty("duration");
String duration;
if (durations instanceof List) {
Object o = ((List<?>) durations).get(0);
duration = o.toString();
} else {
duration = durations.toString();
}
Object rampUps = context.getProperty("RampUp");
String rampUp;
if (rampUps instanceof List) {
Object o = ((List<?>) rampUps).get(0);
rampUp = o.toString();
} else {
rampUp = rampUps.toString();
}
int realHold = Integer.parseInt(duration) - Integer.parseInt(rampUp);
threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
threadGroup.appendChild(createStringProp(document, "TargetLevel", "2"));
@ -803,9 +817,18 @@ public class JmeterDocumentParser implements DocumentParser {
</collectionProp>
</kg.apc.jmeter.timers.VariableThroughputTimer>
*/
if (context.getProperty("rpsLimitEnable") == null || StringUtils.equals(context.getProperty("rpsLimitEnable").toString(), "false")) {
if (context.getProperty("rpsLimitEnable") == null) {
return;
}
Object rpsLimitEnables = context.getProperty("rpsLimitEnable");
if (rpsLimitEnables instanceof List) {
Object o = ((List<?>) rpsLimitEnables).get(0);
((List<?>) rpsLimitEnables).remove(0);
if (o == null || "false".equals(o.toString())) {
return;
}
}
Document document = element.getOwnerDocument();
@ -866,11 +889,6 @@ public class JmeterDocumentParser implements DocumentParser {
if (nodeNameEquals(ele, STRING_PROP)) {
parseStringProp(ele);
}
// 设置具体的线程数
if (nodeNameEquals(ele, STRING_PROP) && "TargetLevel".equals(ele.getAttribute("name"))) {
ele.getFirstChild().setNodeValue(context.getThreadNum().toString());
}
}
}
}
@ -902,11 +920,28 @@ public class JmeterDocumentParser implements DocumentParser {
stringPropCount++;
} else {
stringPropCount = 0;
Integer duration = (Integer) context.getProperty("duration");// 传入的是分钟数, 需要转化成秒数
Object durations = context.getProperty("duration");// 传入的是分钟数, 需要转化成秒数
Integer duration;
if (durations instanceof List) {
Object o = ((List<?>) durations).get(0);
duration = (Integer) o;
((List<?>) durations).remove(0);
} else {
duration = (Integer) durations;
}
prop.getFirstChild().setNodeValue(String.valueOf(duration * 60));
continue;
}
prop.getFirstChild().setNodeValue(context.getProperty("rpsLimit").toString());
Object rpsLimits = context.getProperty("rpsLimit");
String rpsLimit;
if (rpsLimits instanceof List) {
Object o = ((List<?>) rpsLimits).get(0);
((List<?>) rpsLimits).remove(0);
rpsLimit = o.toString();
} else {
rpsLimit = rpsLimits.toString();
}
prop.getFirstChild().setNodeValue(rpsLimit);
}
}
}
@ -920,8 +955,15 @@ public class JmeterDocumentParser implements DocumentParser {
}
private void parseStringProp(Element stringProp) {
if (stringProp.getChildNodes().getLength() > 0 && context.getProperty(stringProp.getAttribute("name")) != null) {
stringProp.getFirstChild().setNodeValue(context.getProperty(stringProp.getAttribute("name")).toString());
Object threadParams = context.getProperty(stringProp.getAttribute("name"));
if (stringProp.getChildNodes().getLength() > 0 && threadParams != null) {
if (threadParams instanceof List) {
Object o = ((List<?>) threadParams).get(0);
((List<?>) threadParams).remove(0);
stringProp.getFirstChild().setNodeValue(o.toString());
} else {
stringProp.getFirstChild().setNodeValue(threadParams.toString());
}
}
}

View File

@ -345,6 +345,17 @@ public class PerformanceTestService {
return Optional.ofNullable(loadTestWithBLOBs).orElse(new LoadTestWithBLOBs()).getLoadConfiguration();
}
public String getJmxContent(String testId) {
List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(testId);
for (FileMetadata metadata : fileMetadataList) {
if (FileType.JMX.name().equals(metadata.getType())) {
FileContent fileContent = fileService.getFileContent(metadata.getId());
return new String(fileContent.getFile());
}
}
return null;
}
public List<LoadTestWithBLOBs> selectByTestResourcePoolId(String resourcePoolId) {
LoadTestExample example = new LoadTestExample();
example.createCriteria().andTestResourcePoolIdEqualTo(resourcePoolId);

@ -1 +1 @@
Subproject commit 24047fea950a74f7848a9fdaa857a22b884c4ce2
Subproject commit 57d6f78efa4b0300be188e8b024511ceef0873ed

View File

@ -37,7 +37,8 @@
"nprogress": "^0.2.0",
"el-table-infinite-scroll": "^1.0.10",
"vue-pdf": "^4.2.0",
"diffable-html": "^4.0.0"
"diffable-html": "^4.0.0",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",

View File

@ -1,98 +1,81 @@
<template>
<div v-loading="result.loading" class="pressure-config-container">
<el-row>
<el-col :span="10">
<el-form :inline="true">
<el-form-item>
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
:placeholder="$t('load_test.input_thread_num')"
v-model="threadNumber"
@change="calculateChart"
:min="1"
size="mini"/>
</el-form-item>
</el-form>
<el-form :inline="true">
<el-form-item>
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
:placeholder="$t('load_test.duration')"
v-model="duration"
:min="1"
@change="calculateChart"
size="mini"/>
</el-form-item>
</el-form>
<el-form :inline="true">
<el-form-item>
<el-form-item>
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
</el-form-item>
<el-form-item>
<el-switch v-model="rpsLimitEnable" :disabled="true"/>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
:placeholder="$t('load_test.input_rps_limit')"
v-model="rpsLimit"
@change="calculateChart"
:min="1"
size="mini"/>
</el-form-item>
</el-form-item>
</el-form>
<el-form :inline="true" class="input-bottom-border">
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
placeholder=""
:min="1"
:max="duration"
v-model="rampUpTime"
@change="calculateChart"
size="mini"/>
</el-form-item>
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="true"
placeholder=""
:min="1"
:max="Math.min(threadNumber, rampUpTime)"
v-model="step"
@change="calculateChart"
size="mini"/>
</el-form-item>
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
</el-form-item>
</el-form>
</el-col>
<el-col :span="14">
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
<el-col>
<ms-chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></ms-chart>
</el-col>
</el-row>
<el-row>
<el-collapse v-model="activeNames">
<el-collapse-item :title="threadGroup.attributes.testname" :name="index"
v-for="(threadGroup, index) in threadGroups"
:key="index">
<el-col :span="10">
<el-form :inline="true">
<el-form-item :label="$t('load_test.thread_num')">
<el-input-number
:disabled="true"
:placeholder="$t('load_test.input_thread_num')"
v-model="threadGroup.threadNumber"
:min="1"
size="mini"/>
</el-form-item>
<br>
<el-form-item :label="$t('load_test.duration')">
<el-input-number
:disabled="true"
:placeholder="$t('load_test.duration')"
v-model="threadGroup.duration"
:min="1"
size="mini"/>
</el-form-item>
<br>
<el-form-item :label="$t('load_test.rps_limit')">
<el-switch v-model="rpsLimitEnable"/>
&nbsp;
<el-input-number
:disabled="true"
:placeholder="$t('load_test.input_rps_limit')"
v-model="threadGroup.rpsLimit"
:min="1"
size="mini"/>
</el-form-item>
<br>
<el-form-item :label="$t('load_test.ramp_up_time_within')">
<el-input-number
:disabled="true"
placeholder=""
:min="1"
:max="threadGroup.duration"
v-model="threadGroup.rampUpTime"
size="mini"/>
</el-form-item>
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
<el-input-number
:disabled="true"
placeholder=""
:min="1"
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
v-model="threadGroup.step"
size="mini"/>
</el-form-item>
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
</el-form>
</el-col>
<el-col :span="14">
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
<ms-chart class="chart-container" :options="threadGroup.orgOptions" :autoresize="true"></ms-chart>
</el-col>
</el-collapse-item>
</el-collapse>
</el-row>
</div>
</template>
<script>
import echarts from "echarts";
import MsChart from "@/business/components/common/chart/MsChart";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
const TARGET_LEVEL = "TargetLevel";
const RAMP_UP = "RampUp";
@ -108,62 +91,91 @@ export default {
data() {
return {
result: {},
threadNumber: 10,
duration: 10,
rampUpTime: 10,
step: 10,
rpsLimit: 10,
threadNumber: 0,
duration: 0,
rampUpTime: 0,
step: 0,
rpsLimit: 0,
rpsLimitEnable: false,
orgOptions: {},
resourcePool: null,
resourcePools: [],
activeNames: ["0"],
threadGroups: [],
}
},
mounted() {
this.getLoadConfig();
// this.getJmxContent();
},
methods: {
calculateLoadConfiguration: function (data) {
data.forEach(d => {
switch (d.key) {
case TARGET_LEVEL:
this.threadNumber = d.value;
break;
case RAMP_UP:
this.rampUpTime = d.value;
break;
case DURATION:
this.duration = d.value;
break;
case STEPS:
this.step = d.value;
break;
case RPS_LIMIT:
this.rpsLimit = d.value;
break;
default:
break;
for (let i = 0; i < data.length; i++) {
let d = data[i];
if (d instanceof Array) {
d.forEach(item => {
switch (item.key) {
case TARGET_LEVEL:
this.threadGroups[i].threadNumber = item.value;
break;
case RAMP_UP:
this.threadGroups[i].rampUpTime = item.value;
break;
case DURATION:
this.threadGroups[i].duration = item.value;
break;
case STEPS:
this.threadGroups[i].step = item.value;
break;
case RPS_LIMIT:
this.threadGroups[i].rpsLimit = item.value;
break;
case RPS_LIMIT_ENABLE:
this.threadGroups[i].rpsLimitEnable = item.value;
break;
default:
break;
}
})
this.calculateChart(this.threadGroups[i]);
} else {
switch (d.key) {
case TARGET_LEVEL:
this.threadGroups[0].threadNumber = d.value;
break;
case RAMP_UP:
this.threadGroups[0].rampUpTime = d.value;
break;
case DURATION:
this.threadGroups[0].duration = d.value;
break;
case STEPS:
this.threadGroups[0].step = d.value;
break;
case RPS_LIMIT:
this.threadGroups[0].rpsLimit = d.value;
break;
case RPS_LIMIT_ENABLE:
this.threadGroups[0].rpsLimitEnable = d.value;
break;
default:
break;
}
this.calculateChart(this.threadGroups[0]);
}
});
this.threadNumber = this.threadNumber || 10;
this.duration = this.duration || 30;
this.rampUpTime = this.rampUpTime || 12;
this.step = this.step || 3;
this.rpsLimit = this.rpsLimit || 10;
this.calculateChart();
}
},
getLoadConfig() {
if (!this.report.id) {
return;
}
this.$get("/performance/report/" + this.report.id, res => {
this.result = this.$get("/performance/report/" + this.report.id, res => {
let data = res.data;
if (data) {
if (data.loadConfiguration) {
let d = JSON.parse(data.loadConfiguration);
this.calculateLoadConfiguration(d);
} else {
this.$get('/performance/get-load-config/' + this.report.testId, (response) => {
this.$get('/performance/get-load-config/' + this.report.id, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
this.calculateLoadConfiguration(data);
@ -175,14 +187,30 @@ export default {
}
});
},
calculateChart() {
if (this.duration < this.rampUpTime) {
this.rampUpTime = this.duration;
getJmxContent() {
console.log(this.report.testId);
if (!this.report.testId) {
return;
}
if (this.rampUpTime < this.step) {
this.step = this.rampUpTime;
this.result = this.$get('/performance/get-jmx-content/' + this.report.testId, (response) => {
if (response.data) {
this.threadGroups = findThreadGroup(response.data);
this.threadGroups.forEach(tg => {
tg.orgOptions = {};
});
this.getLoadConfig();
}
});
},
calculateTotalChart() {
let handler = this;
if (handler.duration < handler.rampUpTime) {
handler.rampUpTime = handler.duration;
}
this.orgOptions = {
if (handler.rampUpTime < handler.step) {
handler.step = handler.rampUpTime;
}
handler.orgOptions = {
xAxis: {
type: 'category',
boundaryGap: false,
@ -235,16 +263,130 @@ export default {
},
}]
};
let timePeriod = Math.floor(this.rampUpTime / this.step);
let threads = [];
for (let i = 0; i < handler.threadGroups.length; i++) {
let tg = handler.threadGroups[i];
threads[i] = [];
let timePeriod = Math.floor(tg.rampUpTime / tg.step);
let timeInc = timePeriod;
let threadPeriod = Math.floor(tg.threadNumber / tg.step);
let threadInc1 = Math.floor(tg.threadNumber / tg.step);
let threadInc2 = Math.ceil(tg.threadNumber / tg.step);
let inc2count = tg.threadNumber - tg.step * threadInc1;
for (let j = 0; j <= tg.duration; j++) {
if (j > timePeriod) {
timePeriod += timeInc;
if (inc2count > 0) {
threadPeriod = threadPeriod + threadInc2;
inc2count--;
} else {
threadPeriod = threadPeriod + threadInc1;
}
if (threadPeriod > tg.threadNumber) {
threadPeriod = tg.threadNumber;
}
}
// x
let xAxis = handler.orgOptions.xAxis.data;
if (xAxis.indexOf(j) < 0) {
xAxis.push(j);
}
threads[i].push(threadPeriod);
}
}
let maxLength = 0;
for (let i = 0; i < threads.length; i++) {
if (maxLength < threads[i].length) {
maxLength = threads[i].length;
}
}
for (let i = 0; i < maxLength; i++) {
let temp = 0;
for (let j = 0; j < threads.length; j++) {
if (threads[j].length <= maxLength) {
temp += threads[j][i] || 0;
}
}
handler.orgOptions.series[0].data.push(temp);
}
},
calculateChart(threadGroup) {
let handler = this;
if (threadGroup) {
handler = threadGroup;
}
if (handler.duration < handler.rampUpTime) {
handler.rampUpTime = handler.duration;
}
if (handler.rampUpTime < handler.step) {
handler.step = handler.rampUpTime;
}
handler.orgOptions = {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
type: 'value'
},
tooltip: {
trigger: 'axis',
formatter: '{a}: {c0}',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
series: [{
name: 'User',
data: [],
type: 'line',
step: 'start',
smooth: false,
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
}]
};
let timePeriod = Math.floor(handler.rampUpTime / handler.step);
let timeInc = timePeriod;
let threadPeriod = Math.floor(this.threadNumber / this.step);
let threadInc1 = Math.floor(this.threadNumber / this.step);
let threadInc2 = Math.ceil(this.threadNumber / this.step);
let inc2count = this.threadNumber - this.step * threadInc1;
for (let i = 0; i <= this.duration; i++) {
let threadPeriod = Math.floor(handler.threadNumber / handler.step);
let threadInc1 = Math.floor(handler.threadNumber / handler.step);
let threadInc2 = Math.ceil(handler.threadNumber / handler.step);
let inc2count = handler.threadNumber - handler.step * threadInc1;
for (let i = 0; i <= handler.duration; i++) {
// x
this.orgOptions.xAxis.data.push(i);
handler.orgOptions.xAxis.data.push(i);
if (i > timePeriod) {
timePeriod += timeInc;
if (inc2count > 0) {
@ -253,25 +395,22 @@ export default {
} else {
threadPeriod = threadPeriod + threadInc1;
}
if (threadPeriod > this.threadNumber) {
threadPeriod = this.threadNumber;
if (threadPeriod > handler.threadNumber) {
threadPeriod = handler.threadNumber;
}
this.orgOptions.series[0].data.push(threadPeriod);
handler.orgOptions.series[0].data.push(threadPeriod);
} else {
this.orgOptions.series[0].data.push(threadPeriod);
handler.orgOptions.series[0].data.push(threadPeriod);
}
}
this.calculateTotalChart();
},
},
watch: {
report: {
handler(val) {
if (!val.testId) {
return;
}
this.getLoadConfig();
'report.testId': {
handler() {
this.getJmxContent();
},
deep: true
}
}
}
@ -295,6 +434,7 @@ export default {
.chart-container {
width: 100%;
height: 300px;
}
.el-col .el-form {

View File

@ -37,7 +37,8 @@
<el-tabs class="testplan-config" v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('load_test.basic_config')">
<performance-basic-config :is-read-only="isReadOnly" :test-plan="testPlan" ref="basicConfig"/>
<performance-basic-config :is-read-only="isReadOnly" :test-plan="testPlan" ref="basicConfig"
@fileChange="fileChange"/>
</el-tab-pane>
<el-tab-pane :label="$t('load_test.pressure_config')">
<performance-pressure-config :is-read-only="isReadOnly" :test-plan="testPlan" :test-id="testId"
@ -304,6 +305,18 @@ export default {
return {
pass: true
}
},
fileChange(threadGroups) {
let handler = this.$refs.pressureConfig;
handler.threadGroups = threadGroups;
threadGroups.forEach(tg => {
tg.threadNumber = tg.threadNumber || 10;
tg.duration = tg.duration || 10;
tg.rampUpTime = tg.rampUpTime || 5;
tg.step = tg.step || 5;
tg.rpsLimit = tg.rpsLimit || 10;
handler.calculateChart(tg);
});
}
}
}

View File

@ -56,6 +56,7 @@
<script>
import {Message} from "element-ui";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
export default {
name: "PerformanceBasicConfig",
@ -90,6 +91,19 @@ export default {
if (this.testPlan.id) {
this.getFileMetadata(this.testPlan)
}
},
uploadList() {
let self = this;
let fileList = self.uploadList.filter(f => f.name.endsWith(".jmx"));
if (fileList.length > 0) {
let file = fileList[0];
let jmxReader = new FileReader();
jmxReader.onload = function (event) {
let threadGroups = findThreadGroup(event.target.result);
self.$emit('fileChange', threadGroups);
};
jmxReader.readAsText(file);
}
}
},
methods: {

View File

@ -1,91 +1,9 @@
<template>
<div v-loading="result.loading" class="pressure-config-container">
<el-row>
<el-col :span="10">
<el-col>
<el-form :inline="true">
<el-form-item>
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="isReadOnly"
:placeholder="$t('load_test.input_thread_num')"
v-model="threadNumber"
@change="calculateChart"
:min="1"
size="mini"/>
</el-form-item>
</el-form>
<el-form :inline="true">
<el-form-item>
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="isReadOnly"
:placeholder="$t('load_test.duration')"
v-model="duration"
:min="1"
@change="calculateChart"
size="mini"/>
</el-form-item>
</el-form>
<el-form :inline="true">
<el-form-item>
<el-form-item>
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
</el-form-item>
<el-form-item>
<el-switch v-model="rpsLimitEnable"/>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="isReadOnly || !rpsLimitEnable"
:placeholder="$t('load_test.input_rps_limit')"
v-model="rpsLimit"
@change="calculateChart"
:min="1"
size="mini"/>
</el-form-item>
</el-form-item>
</el-form>
<el-form :inline="true" class="input-bottom-border">
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="isReadOnly"
placeholder=""
:min="1"
:max="duration"
v-model="rampUpTime"
@change="calculateChart"
size="mini"/>
</el-form-item>
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
</el-form-item>
<el-form-item>
<el-input-number
:disabled="isReadOnly"
placeholder=""
:min="1"
:max="Math.min(threadNumber, rampUpTime)"
v-model="step"
@change="calculateChart"
size="mini"/>
</el-form-item>
<el-form-item>
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
</el-form-item>
</el-form>
<el-form :inline="true" class="input-bottom-border">
<el-form-item>
<div>{{ $t('load_test.select_resource_pool') }}</div>
</el-form-item>
<el-form-item>
<el-form-item :label="$t('load_test.select_resource_pool')">
<el-select v-model="resourcePool" :disabled="isReadOnly" size="mini">
<el-option
v-for="item in resourcePools"
@ -96,18 +14,85 @@
</el-select>
</el-form-item>
</el-form>
</el-col>
<el-col :span="14">
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
<ms-chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></ms-chart>
</el-col>
</el-row>
<el-row>
<el-collapse v-model="activeNames">
<el-collapse-item :title="threadGroup.attributes.testname" :name="index"
v-for="(threadGroup, index) in threadGroups"
:key="index">
<el-col :span="10">
<el-form :inline="true">
<el-form-item :label="$t('load_test.thread_num')">
<el-input-number
:disabled="isReadOnly"
:placeholder="$t('load_test.input_thread_num')"
v-model="threadGroup.threadNumber"
@change="calculateChart(threadGroup)"
:min="1"
size="mini"/>
</el-form-item>
<br>
<el-form-item :label="$t('load_test.duration')">
<el-input-number
:disabled="isReadOnly"
:placeholder="$t('load_test.duration')"
v-model="threadGroup.duration"
:min="1"
@change="calculateChart(threadGroup)"
size="mini"/>
</el-form-item>
<br>
<el-form-item :label="$t('load_test.rps_limit')">
<el-switch v-model="rpsLimitEnable"/>
&nbsp;
<el-input-number
:disabled="isReadOnly || !threadGroup.rpsLimitEnable"
:placeholder="$t('load_test.input_rps_limit')"
v-model="threadGroup.rpsLimit"
@change="calculateChart(threadGroup)"
:min="1"
size="mini"/>
</el-form-item>
<br>
<el-form-item :label="$t('load_test.ramp_up_time_within')">
<el-input-number
:disabled="isReadOnly"
placeholder=""
:min="1"
:max="threadGroup.duration"
v-model="threadGroup.rampUpTime"
@change="calculateChart(threadGroup)"
size="mini"/>
</el-form-item>
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
<el-input-number
:disabled="isReadOnly"
placeholder=""
:min="1"
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
v-model="threadGroup.step"
@change="calculateChart(threadGroup)"
size="mini"/>
</el-form-item>
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
</el-form>
</el-col>
<el-col :span="14">
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
<ms-chart class="chart-container" :options="threadGroup.orgOptions" :autoresize="true"></ms-chart>
</el-col>
</el-collapse-item>
</el-collapse>
</el-row>
</div>
</template>
<script>
import echarts from "echarts";
import MsChart from "@/business/components/common/chart/MsChart";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
const TARGET_LEVEL = "TargetLevel";
const RAMP_UP = "RampUp";
@ -134,22 +119,24 @@ export default {
data() {
return {
result: {},
threadNumber: 10,
duration: 10,
rampUpTime: 10,
step: 10,
rpsLimit: 10,
threadNumber: 0,
duration: 0,
rampUpTime: 0,
step: 0,
rpsLimit: 0,
rpsLimitEnable: false,
orgOptions: {},
resourcePool: null,
resourcePools: [],
activeNames: ["0"],
threadGroups: [],
}
},
mounted() {
if (this.testId) {
this.getLoadConfig();
this.getJmxContent();
} else {
this.calculateChart();
this.calculateTotalChart();
}
this.resourcePool = this.testPlan.testResourcePoolId;
this.getResourcePools();
@ -160,9 +147,9 @@ export default {
},
testId() {
if (this.testId) {
this.getLoadConfig();
this.getJmxContent();
} else {
this.calculateChart();
this.calculateTotalChart();
}
this.getResourcePools();
},
@ -178,56 +165,89 @@ export default {
})
},
getLoadConfig() {
if (this.testId) {
this.$get('/performance/get-load-config/' + this.testId, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
data.forEach(d => {
this.$get('/performance/get-load-config/' + this.testId, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
for (let i = 0; i < data.length; i++) {
let d = data[i];
if (d instanceof Array) {
d.forEach(item => {
switch (item.key) {
case TARGET_LEVEL:
this.threadGroups[i].threadNumber = item.value;
break;
case RAMP_UP:
this.threadGroups[i].rampUpTime = item.value;
break;
case DURATION:
this.threadGroups[i].duration = item.value;
break;
case STEPS:
this.threadGroups[i].step = item.value;
break;
case RPS_LIMIT:
this.threadGroups[i].rpsLimit = item.value;
break;
case RPS_LIMIT_ENABLE:
this.threadGroups[i].rpsLimitEnable = item.value;
break;
default:
break;
}
})
this.calculateChart(this.threadGroups[i]);
} else {
switch (d.key) {
case TARGET_LEVEL:
this.threadNumber = d.value;
this.threadGroups[0].threadNumber = d.value;
break;
case RAMP_UP:
this.rampUpTime = d.value;
this.threadGroups[0].rampUpTime = d.value;
break;
case DURATION:
this.duration = d.value;
this.threadGroups[0].duration = d.value;
break;
case STEPS:
this.step = d.value;
this.threadGroups[0].step = d.value;
break;
case RPS_LIMIT:
this.rpsLimit = d.value;
this.threadGroups[0].rpsLimit = d.value;
break;
case RPS_LIMIT_ENABLE:
this.rpsLimitEnable = d.value;
this.threadGroups[0].rpsLimitEnable = d.value;
break;
default:
break;
}
this.calculateChart(this.threadGroups[0]);
}
}
this.calculateTotalChart();
}
});
},
getJmxContent() {
if (this.testId) {
this.$get('/performance/get-jmx-content/' + this.testId, (response) => {
if (response.data) {
this.threadGroups = findThreadGroup(response.data);
this.threadGroups.forEach(tg => {
tg.orgOptions = {};
});
this.threadNumber = this.threadNumber || 10;
this.duration = this.duration || 30;
this.rampUpTime = this.rampUpTime || 12;
this.step = this.step || 3;
this.rpsLimit = this.rpsLimit || 10;
this.calculateChart();
this.getLoadConfig();
}
});
}
},
calculateChart() {
if (this.duration < this.rampUpTime) {
this.rampUpTime = this.duration;
calculateTotalChart() {
let handler = this;
if (handler.duration < handler.rampUpTime) {
handler.rampUpTime = handler.duration;
}
if (this.rampUpTime < this.step) {
this.step = this.rampUpTime;
if (handler.rampUpTime < handler.step) {
handler.step = handler.rampUpTime;
}
this.orgOptions = {
handler.orgOptions = {
xAxis: {
type: 'category',
boundaryGap: false,
@ -280,16 +300,130 @@ export default {
},
}]
};
let timePeriod = Math.floor(this.rampUpTime / this.step);
let threads = [];
for (let i = 0; i < handler.threadGroups.length; i++) {
let tg = handler.threadGroups[i];
threads[i] = [];
let timePeriod = Math.floor(tg.rampUpTime / tg.step);
let timeInc = timePeriod;
let threadPeriod = Math.floor(tg.threadNumber / tg.step);
let threadInc1 = Math.floor(tg.threadNumber / tg.step);
let threadInc2 = Math.ceil(tg.threadNumber / tg.step);
let inc2count = tg.threadNumber - tg.step * threadInc1;
for (let j = 0; j <= tg.duration; j++) {
if (j > timePeriod) {
timePeriod += timeInc;
if (inc2count > 0) {
threadPeriod = threadPeriod + threadInc2;
inc2count--;
} else {
threadPeriod = threadPeriod + threadInc1;
}
if (threadPeriod > tg.threadNumber) {
threadPeriod = tg.threadNumber;
}
}
// x
let xAxis = handler.orgOptions.xAxis.data;
if (xAxis.indexOf(j) < 0) {
xAxis.push(j);
}
threads[i].push(threadPeriod);
}
}
let maxLength = 0;
for (let i = 0; i < threads.length; i++) {
if (maxLength < threads[i].length) {
maxLength = threads[i].length;
}
}
for (let i = 0; i < maxLength; i++) {
let temp = 0;
for (let j = 0; j < threads.length; j++) {
if (threads[j].length <= maxLength) {
temp += threads[j][i] || 0;
}
}
handler.orgOptions.series[0].data.push(temp);
}
},
calculateChart(threadGroup) {
let handler = this;
if (threadGroup) {
handler = threadGroup;
}
if (handler.duration < handler.rampUpTime) {
handler.rampUpTime = handler.duration;
}
if (handler.rampUpTime < handler.step) {
handler.step = handler.rampUpTime;
}
handler.orgOptions = {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
type: 'value'
},
tooltip: {
trigger: 'axis',
formatter: '{a}: {c0}',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
series: [{
name: 'User',
data: [],
type: 'line',
step: 'start',
smooth: false,
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
}]
};
let timePeriod = Math.floor(handler.rampUpTime / handler.step);
let timeInc = timePeriod;
let threadPeriod = Math.floor(this.threadNumber / this.step);
let threadInc1 = Math.floor(this.threadNumber / this.step);
let threadInc2 = Math.ceil(this.threadNumber / this.step);
let inc2count = this.threadNumber - this.step * threadInc1;
for (let i = 0; i <= this.duration; i++) {
let threadPeriod = Math.floor(handler.threadNumber / handler.step);
let threadInc1 = Math.floor(handler.threadNumber / handler.step);
let threadInc2 = Math.ceil(handler.threadNumber / handler.step);
let inc2count = handler.threadNumber - handler.step * threadInc1;
for (let i = 0; i <= handler.duration; i++) {
// x
this.orgOptions.xAxis.data.push(i);
handler.orgOptions.xAxis.data.push(i);
if (i > timePeriod) {
timePeriod += timeInc;
if (inc2count > 0) {
@ -298,14 +432,15 @@ export default {
} else {
threadPeriod = threadPeriod + threadInc1;
}
if (threadPeriod > this.threadNumber) {
threadPeriod = this.threadNumber;
if (threadPeriod > handler.threadNumber) {
threadPeriod = handler.threadNumber;
}
this.orgOptions.series[0].data.push(threadPeriod);
handler.orgOptions.series[0].data.push(threadPeriod);
} else {
this.orgOptions.series[0].data.push(threadPeriod);
handler.orgOptions.series[0].data.push(threadPeriod);
}
}
this.calculateTotalChart();
},
validConfig() {
if (!this.resourcePool) {
@ -315,24 +450,31 @@ export default {
return false;
}
if (!this.threadNumber || !this.duration || !this.rampUpTime || !this.step || !this.rpsLimit) {
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
this.$emit('changeActive', '1');
return false;
for (let i = 0; i < this.threadGroups.length; i++) {
if (!this.threadGroups[i].threadNumber || !this.threadGroups[i].duration
|| !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step || !this.threadGroups[i].rpsLimit) {
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
this.$emit('changeActive', '1');
return false;
}
}
return true;
},
convertProperty() {
/// todo4jmeter ConcurrencyThreadGroup plugin
return [
{key: TARGET_LEVEL, value: this.threadNumber},
{key: RAMP_UP, value: this.rampUpTime},
{key: STEPS, value: this.step},
{key: DURATION, value: this.duration},
{key: RPS_LIMIT, value: this.rpsLimit},
{key: RPS_LIMIT_ENABLE, value: this.rpsLimitEnable},
];
let result = [];
for (let i = 0; i < this.threadGroups.length; i++) {
result.push([
{key: TARGET_LEVEL, value: this.threadGroups[i].threadNumber},
{key: RAMP_UP, value: this.threadGroups[i].rampUpTime},
{key: STEPS, value: this.threadGroups[i].step},
{key: DURATION, value: this.threadGroups[i].duration},
{key: RPS_LIMIT, value: this.threadGroups[i].rpsLimit},
{key: RPS_LIMIT_ENABLE, value: this.threadGroups[i].rpsLimitEnable},
]);
}
return result;
}
}
}
@ -356,6 +498,7 @@ export default {
.chart-container {
width: 100%;
height: 300px;
}
.el-col .el-form {

View File

@ -0,0 +1,20 @@
import {xml2json} from "xml-js";
let travel = function (elements, threadGroups) {
if (!elements) {
return;
}
for (let element of elements) {
if (element.name === 'ThreadGroup') {
threadGroups.push(element);
}
travel(element.elements, threadGroups)
}
}
export function findThreadGroup(jmxContent) {
let jmxJson = JSON.parse(xml2json(jmxContent));
let threadGroups = [];
travel(jmxJson.elements, threadGroups);
return threadGroups;
}