feat(性能测试): 支持组合场景场景

This commit is contained in:
Captain.B 2021-03-02 17:20:22 +08:00
parent 739d194854
commit 4a9ccc2ea5
19 changed files with 681 additions and 232 deletions

View File

@ -2,6 +2,7 @@ package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.LoadTest; import io.metersphere.base.domain.LoadTest;
import io.metersphere.dto.LoadTestDTO; import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.LoadTestFileDTO;
import io.metersphere.track.request.testplan.QueryTestPlanRequest; import io.metersphere.track.request.testplan.QueryTestPlanRequest;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@ -16,4 +17,7 @@ public interface ExtLoadTestMapper {
int checkLoadTestOwner(@Param("testId") String testId, @Param("workspaceIds") Set<String> workspaceIds); int checkLoadTestOwner(@Param("testId") String testId, @Param("workspaceIds") Set<String> workspaceIds);
LoadTest getNextNum(@Param("projectId") String projectId); LoadTest getNextNum(@Param("projectId") String projectId);
List<LoadTestFileDTO> getProjectFiles(@Param("projectId") String projectId, @Param("loadTypes") List<String> loadType);
} }

View File

@ -131,4 +131,16 @@
<select id="getNextNum" resultType="io.metersphere.base.domain.LoadTest"> <select id="getNextNum" resultType="io.metersphere.base.domain.LoadTest">
select * from load_test lt where lt.project_id = #{projectId} ORDER BY num DESC LIMIT 1; select * from load_test lt where lt.project_id = #{projectId} ORDER BY num DESC LIMIT 1;
</select> </select>
<select id="getProjectFiles" resultType="io.metersphere.dto.LoadTestFileDTO">
SELECT file_metadata.*, load_test.id as testId, load_test.name as testName
FROM load_test
JOIN load_test_file ON load_test.id = load_test_file.test_id
JOIN file_metadata ON load_test_file.file_id = file_metadata.id
WHERE file_metadata.type IN
<foreach collection="loadTypes" item="id" separator="," open="(" close=")">
#{id}
</foreach>
AND project_id = #{projectId,jdbcType=VARCHAR}
</select>
</mapper> </mapper>

View File

@ -1,20 +1,24 @@
package io.metersphere.commons.utils; package io.metersphere.commons.utils;
import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
public class UrlTestUtils { public class UrlTestUtils {
public static boolean testUrlWithTimeOut(String urlString, int timeOutMillSeconds) { public static boolean testUrlWithTimeOut(String address, int timeOutMillSeconds) {
try { try {
URL url = new URL(urlString); URL urlObj = new URL(address);
URLConnection co = url.openConnection(); HttpURLConnection oc = (HttpURLConnection) urlObj.openConnection();
co.setConnectTimeout(timeOutMillSeconds); oc.setUseCaches(false);
co.connect(); oc.setConnectTimeout(timeOutMillSeconds); // 设置超时时间
return true; int status = oc.getResponseCode();// 请求状态
if (200 == status) {
return true;
}
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(e); e.printStackTrace();
return false; return false;
} }
return false;
} }
} }

View File

@ -0,0 +1,12 @@
package io.metersphere.dto;
import io.metersphere.base.domain.FileMetadata;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class LoadTestFileDTO extends FileMetadata {
private String testId;
private String testName;
}

View File

@ -12,6 +12,7 @@ import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.QueryScheduleRequest; import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.DashboardTestDTO; import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.LoadTestDTO; import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.LoadTestFileDTO;
import io.metersphere.dto.ScheduleDao; import io.metersphere.dto.ScheduleDao;
import io.metersphere.performance.service.PerformanceTestService; import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.service.CheckPermissionService; import io.metersphere.service.CheckPermissionService;
@ -112,6 +113,14 @@ public class PerformanceTestController {
return performanceTestService.getJmxContent(testId); return performanceTestService.getJmxContent(testId);
} }
@GetMapping("/project/{loadType}/{projectId}/{goPage}/{pageSize}")
public Pager<List<LoadTestFileDTO>> getProjectFiles(@PathVariable String projectId, @PathVariable String loadType,
@PathVariable int goPage, @PathVariable int pageSize) {
checkPermissionService.checkProjectOwner(projectId);
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, performanceTestService.getProjectFiles(projectId, loadType));
}
@PostMapping("/delete") @PostMapping("/delete")
public void delete(@RequestBody DeleteTestPlanRequest request) { public void delete(@RequestBody DeleteTestPlanRequest request) {
checkPermissionService.checkPerformanceTestOwner(request.getId()); checkPermissionService.checkPerformanceTestOwner(request.getId());

View File

@ -9,6 +9,7 @@ import io.metersphere.config.KafkaProperties;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
import io.metersphere.performance.engine.EngineContext; import io.metersphere.performance.engine.EngineContext;
import io.metersphere.performance.parse.xml.reader.DocumentParser; import io.metersphere.performance.parse.xml.reader.DocumentParser;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -639,6 +640,25 @@ public class JmeterDocumentParser implements DocumentParser {
((List<?>) holds).remove(0); ((List<?>) holds).remove(0);
hold = o.toString(); hold = o.toString();
} }
Object deleteds = context.getProperty("deleted");
String deleted = "false";
if (deleteds instanceof List) {
Object o = ((List<?>) deleteds).get(0);
((List<?>) deleteds).remove(0);
deleted = o.toString();
}
Object enableds = context.getProperty("enabled");
String enabled = "true";
if (enableds instanceof List) {
Object o = ((List<?>) enableds).get(0);
((List<?>) enableds).remove(0);
enabled = o.toString();
}
threadGroup.setAttribute("enabled", enabled);
if (BooleanUtils.toBoolean(deleted)) {
threadGroup.setAttribute("enabled", "false");
}
Element elementProp = document.createElement("elementProp"); Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "ThreadGroup.main_controller"); elementProp.setAttribute("name", "ThreadGroup.main_controller");
elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController"); elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController");

View File

@ -16,6 +16,7 @@ import io.metersphere.controller.request.OrderRequest;
import io.metersphere.controller.request.QueryScheduleRequest; import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.DashboardTestDTO; import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.LoadTestDTO; import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.LoadTestFileDTO;
import io.metersphere.dto.ScheduleDao; import io.metersphere.dto.ScheduleDao;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
import io.metersphere.job.sechedule.PerformanceTestJob; import io.metersphere.job.sechedule.PerformanceTestJob;
@ -38,6 +39,7 @@ import javax.annotation.Resource;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.*; import java.util.*;
@ -126,21 +128,54 @@ public class PerformanceTestService {
} }
public String save(SaveTestPlanRequest request, List<MultipartFile> files) { public String save(SaveTestPlanRequest request, List<MultipartFile> files) {
if (files == null) {
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
}
checkQuota(request, true); checkQuota(request, true);
final LoadTestWithBLOBs loadTest = saveLoadTest(request); LoadTestWithBLOBs loadTest = saveLoadTest(request);
files.forEach(file -> {
final FileMetadata fileMetadata = fileService.saveFile(file); // 新选择了一个文件删除原来的文件
List<FileMetadata> importFiles = request.getUpdatedFileList();
List<String> importFileIds = importFiles.stream().map(FileMetadata::getId).collect(Collectors.toList());
// 导入项目里其他的文件
this.importFiles(importFileIds, loadTest.getId());
// 保存上传的问题件
this.saveUploadFiles(files, loadTest.getId());
// 直接上传了jmx用于API导入的场景
String jmx = request.getJmx();
if (StringUtils.isNotBlank(jmx)) {
byte[] bytes = jmx.getBytes(StandardCharsets.UTF_8);
FileMetadata fileMetadata = fileService.saveFile(bytes, request.getName() + ".jmx", (long) bytes.length);
LoadTestFile loadTestFile = new LoadTestFile(); LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(loadTest.getId()); loadTestFile.setTestId(loadTest.getId());
loadTestFile.setFileId(fileMetadata.getId()); loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile); loadTestFileMapper.insert(loadTestFile);
}); }
return loadTest.getId(); return loadTest.getId();
} }
private void saveUploadFiles(List<MultipartFile> files, String testId) {
if (files != null) {
files.forEach(file -> {
final FileMetadata fileMetadata = fileService.saveFile(file);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(testId);
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
});
}
}
private void importFiles(List<String> importFileIds, String testId) {
importFileIds.forEach(fileId -> {
if (StringUtils.isBlank(fileId)) {
return;
}
FileMetadata fileMetadata = fileService.copyFile(fileId);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(testId);
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
});
}
private LoadTestWithBLOBs saveLoadTest(SaveTestPlanRequest request) { private LoadTestWithBLOBs saveLoadTest(SaveTestPlanRequest request) {
LoadTestExample example = new LoadTestExample(); LoadTestExample example = new LoadTestExample();
@ -183,15 +218,19 @@ public class PerformanceTestService {
// 相减 // 相减
List<String> deleteFileIds = ListUtils.subtract(originFileIds, updatedFileIds); List<String> deleteFileIds = ListUtils.subtract(originFileIds, updatedFileIds);
fileService.deleteFileByIds(deleteFileIds); fileService.deleteFileByIds(deleteFileIds);
// 导入项目里其他的文件
if (files != null) { List<String> addFileIds = ListUtils.subtract(updatedFileIds, originFileIds);
files.forEach(file -> { this.importFiles(addFileIds, request.getId());
final FileMetadata fileMetadata = fileService.saveFile(file); this.saveUploadFiles(files, request.getId());
LoadTestFile loadTestFile = new LoadTestFile(); // 直接上传了jmx用于API导入的场景
loadTestFile.setTestId(request.getId()); String jmx = request.getJmx();
loadTestFile.setFileId(fileMetadata.getId()); if (StringUtils.isNotBlank(jmx)) {
loadTestFileMapper.insert(loadTestFile); byte[] bytes = jmx.getBytes(StandardCharsets.UTF_8);
}); FileMetadata fileMetadata = fileService.saveFile(bytes, request.getName() + ".jmx", (long) bytes.length);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(request.getId());
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
} }
loadTest.setName(request.getName()); loadTest.setName(request.getName());
@ -508,4 +547,14 @@ public class PerformanceTestService {
return Optional.of(loadTest.getNum() + 1).orElse(100001); return Optional.of(loadTest.getNum() + 1).orElse(100001);
} }
} }
public List<LoadTestFileDTO> getProjectFiles(String projectId, String loadType) {
List<String> loadTypes = new ArrayList<>();
loadTypes.add(StringUtils.upperCase(loadType));
if (StringUtils.equalsIgnoreCase(loadType, "resource")) {
loadTypes.add(FileType.CSV.name());
loadTypes.add(FileType.JAR.name());
}
return extLoadTestMapper.getProjectFiles(projectId, loadTypes);
}
} }

View File

@ -169,4 +169,10 @@ public class FileService {
public FileMetadata getFileMetadataById(String fileId) { public FileMetadata getFileMetadataById(String fileId) {
return fileMetadataMapper.selectByPrimaryKey(fileId); return fileMetadataMapper.selectByPrimaryKey(fileId);
} }
public List<FileMetadata> getProjectJMXs(String projectId) {
FileMetadataExample example = new FileMetadataExample();
fileMetadataMapper.selectByExample(example);
return null;
}
} }

View File

@ -1,4 +1,14 @@
package io.metersphere.track.request.testplan; package io.metersphere.track.request.testplan;
import io.metersphere.base.domain.FileMetadata;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class SaveTestPlanRequest extends TestPlanRequest { public class SaveTestPlanRequest extends TestPlanRequest {
private List<FileMetadata> updatedFileList;
} }

View File

@ -16,7 +16,7 @@ public class TestPlanRequest {
private String description; private String description;
private String scenarioDefinition; private String jmx;
private Long createTime; private Long createTime;

View File

@ -8,17 +8,17 @@
<el-row> <el-row>
<el-collapse v-model="activeNames"> <el-collapse v-model="activeNames">
<el-collapse-item :title="threadGroup.attributes.testname" :name="index" <el-collapse-item :title="threadGroup.attributes.testname" :name="index"
v-for="(threadGroup, index) in threadGroups" v-for="(threadGroup, index) in threadGroups.filter(th=>th.enabled === 'true' && th.deleted=='false')"
:key="index"> :key="index">
<el-col :span="10"> <el-col :span="10">
<el-form :inline="true"> <el-form :inline="true">
<el-form-item :label="$t('load_test.thread_num')"> <el-form-item :label="$t('load_test.thread_num')">
<el-input-number <el-input-number
:disabled="true" :disabled="true"
:placeholder="$t('load_test.input_thread_num')" :placeholder="$t('load_test.input_thread_num')"
v-model="threadGroup.threadNumber" v-model="threadGroup.threadNumber"
:min="1" :min="1"
size="mini"/> size="mini"/>
</el-form-item> </el-form-item>
<br> <br>
<el-form-item> <el-form-item>
@ -31,72 +31,72 @@
<div v-if="threadGroup.threadType === 'DURATION'"> <div v-if="threadGroup.threadType === 'DURATION'">
<el-form-item :label="$t('load_test.duration')"> <el-form-item :label="$t('load_test.duration')">
<el-input-number <el-input-number
:disabled="true" :disabled="true"
v-model="threadGroup.duration" v-model="threadGroup.duration"
:min="1" :min="1"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
size="mini"/> size="mini"/>
</el-form-item> </el-form-item>
<br> <br>
<el-form-item :label="$t('load_test.rps_limit')"> <el-form-item :label="$t('load_test.rps_limit')">
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/> <el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
&nbsp; &nbsp;
<el-input-number <el-input-number
:disabled="true " :disabled="true "
v-model="threadGroup.rpsLimit" v-model="threadGroup.rpsLimit"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
:min="1" :min="1"
size="mini"/> size="mini"/>
</el-form-item> </el-form-item>
<br> <br>
<el-form-item :label="$t('load_test.ramp_up_time_within')"> <el-form-item :label="$t('load_test.ramp_up_time_within')">
<el-input-number <el-input-number
:disabled="true" :disabled="true"
:min="1" :min="1"
:max="threadGroup.duration" :max="threadGroup.duration"
v-model="threadGroup.rampUpTime" v-model="threadGroup.rampUpTime"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
size="mini"/> size="mini"/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('load_test.ramp_up_time_minutes')"> <el-form-item :label="$t('load_test.ramp_up_time_minutes')">
<el-input-number <el-input-number
:disabled="true" :disabled="true"
:min="1" :min="1"
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)" :max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
v-model="threadGroup.step" v-model="threadGroup.step"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
size="mini"/> size="mini"/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('load_test.ramp_up_time_times')"/> <el-form-item :label="$t('load_test.ramp_up_time_times')"/>
</div> </div>
<div v-if="threadGroup.threadType === 'ITERATION'"> <div v-if="threadGroup.threadType === 'ITERATION'">
<el-form-item :label="$t('load_test.iterate_num')"> <el-form-item :label="$t('load_test.iterate_num')">
<el-input-number <el-input-number
:disabled="true" :disabled="true"
v-model="threadGroup.iterateNum" v-model="threadGroup.iterateNum"
:min="1" :min="1"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
size="mini"/> size="mini"/>
</el-form-item> </el-form-item>
<br> <br>
<el-form-item :label="$t('load_test.rps_limit')"> <el-form-item :label="$t('load_test.rps_limit')">
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/> <el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
&nbsp; &nbsp;
<el-input-number <el-input-number
:disabled="true || !threadGroup.rpsLimitEnable" :disabled="true || !threadGroup.rpsLimitEnable"
v-model="threadGroup.rpsLimit" v-model="threadGroup.rpsLimit"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
:min="1" :min="1"
size="mini"/> size="mini"/>
</el-form-item> </el-form-item>
<br> <br>
<el-form-item :label="$t('load_test.ramp_up_time_within')"> <el-form-item :label="$t('load_test.ramp_up_time_within')">
<el-input-number <el-input-number
:disabled="true" :disabled="true"
:min="1" :min="1"
v-model="threadGroup.iterateRampUp" v-model="threadGroup.iterateRampUp"
@change="calculateChart(threadGroup)" @change="calculateChart(threadGroup)"
size="mini"/> size="mini"/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('load_test.ramp_up_time_seconds')"/> <el-form-item :label="$t('load_test.ramp_up_time_seconds')"/>
</div> </div>
@ -126,14 +126,16 @@ const RPS_LIMIT_ENABLE = "rpsLimitEnable";
const THREAD_TYPE = "threadType"; const THREAD_TYPE = "threadType";
const ITERATE_NUM = "iterateNum"; const ITERATE_NUM = "iterateNum";
const ITERATE_RAMP_UP = "iterateRampUpTime"; const ITERATE_RAMP_UP = "iterateRampUpTime";
const ENABLED = "enabled";
const DELETED = "deleted";
const hexToRgba = function (hex, opacity) { const hexToRgba = function (hex, opacity) {
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ',' return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
+ parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')'; + parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
} }
const hexToRgb = function (hex) { const hexToRgb = function (hex) {
return 'rgb(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) return 'rgb(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5))
+ ',' + parseInt('0x' + hex.slice(5, 7)) + ')'; + ',' + parseInt('0x' + hex.slice(5, 7)) + ')';
} }
export default { export default {
@ -163,83 +165,51 @@ export default {
calculateLoadConfiguration: function (data) { calculateLoadConfiguration: function (data) {
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
let d = data[i]; let d = data[i];
if (d instanceof Array) { d.forEach(item => {
d.forEach(item => { switch (item.key) {
switch (item.key) {
case TARGET_LEVEL:
this.threadGroups[i].threadNumber = item.value;
break;
case RAMP_UP:
this.threadGroups[i].rampUpTime = item.value;
break;
case ITERATE_RAMP_UP:
this.threadGroups[i].iterateRampUp = item.value;
break;
case DURATION:
if (item.unit) {
this.threadGroups[i].duration = item.value;
} else {
this.threadGroups[i].duration = item.value * 60;
}
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;
case THREAD_TYPE:
this.threadGroups[i].threadType = item.value;
break;
case ITERATE_NUM:
this.threadGroups[i].iterateNum = item.value;
break;
default:
break;
}
})
this.calculateChart(this.threadGroups[i]);
} else {
switch (d.key) {
case TARGET_LEVEL: case TARGET_LEVEL:
this.threadGroups[0].threadNumber = d.value; this.threadGroups[i].threadNumber = item.value;
break; break;
case RAMP_UP: case RAMP_UP:
this.threadGroups[0].rampUpTime = d.value; this.threadGroups[i].rampUpTime = item.value;
break; break;
case ITERATE_RAMP_UP: case ITERATE_RAMP_UP:
this.threadGroups[0].iterateRampUp = d.value; this.threadGroups[i].iterateRampUp = item.value;
break; break;
case DURATION: case DURATION:
if (d.unit) { if (item.unit) {
this.threadGroups[0].duration = d.value; this.threadGroups[i].duration = item.value;
} else { } else {
this.threadGroups[0].duration = d.value * 60; this.threadGroups[i].duration = item.value * 60;
} }
break; break;
case STEPS: case STEPS:
this.threadGroups[0].step = d.value; this.threadGroups[i].step = item.value;
break; break;
case RPS_LIMIT: case RPS_LIMIT:
this.threadGroups[0].rpsLimit = d.value; this.threadGroups[i].rpsLimit = item.value;
break; break;
case RPS_LIMIT_ENABLE: case RPS_LIMIT_ENABLE:
this.threadGroups[0].rpsLimitEnable = d.value; this.threadGroups[i].rpsLimitEnable = item.value;
break; break;
case THREAD_TYPE: case THREAD_TYPE:
this.threadGroups[0].threadType = d.value; this.threadGroups[i].threadType = item.value;
break; break;
case ITERATE_NUM: case ITERATE_NUM:
this.threadGroups[0].iterateNum = d.value; this.threadGroups[i].iterateNum = item.value;
break;
case ENABLED:
this.threadGroups[i].enabled = item.value;
break;
case DELETED:
this.threadGroups[i].deleted = item.value;
break; break;
default: default:
break; break;
} }
this.calculateChart(this.threadGroups[0]); })
} this.calculateChart(this.threadGroups[i]);
} }
}, },
getLoadConfig() { getLoadConfig() {
@ -307,6 +277,9 @@ export default {
for (let i = 0; i < handler.threadGroups.length; i++) { for (let i = 0; i < handler.threadGroups.length; i++) {
if (handler.threadGroups[i].enabled === 'false' || handler.threadGroups[i].deleted === 'true') {
continue;
}
let seriesData = { let seriesData = {
name: handler.threadGroups[i].attributes.testname, name: handler.threadGroups[i].attributes.testname,
data: [], data: [],

View File

@ -14,7 +14,7 @@
<el-card shadow="always" class="ms-card-index-2"> <el-card shadow="always" class="ms-card-index-2">
<span class="ms-card-data"> <span class="ms-card-data">
<span class="ms-card-data-digital">{{ avgTransactions }}</span> <span class="ms-card-data-digital">{{ avgTransactions }}</span>
<span class="ms-card-data-unit"> Transactions/s</span> <span class="ms-card-data-unit"> TPS</span>
</span> </span>
<span class="ms-card-desc">Avg.Transactions</span> <span class="ms-card-desc">Avg.Transactions</span>
</el-card> </el-card>

View File

@ -8,7 +8,7 @@
class="input-with-select" class="input-with-select"
maxlength="30" show-word-limit maxlength="30" show-word-limit
> >
<template slot="prepend">测试名称</template> <template slot="prepend">{{ $t('load_test.name') }}</template>
</el-input> </el-input>
</el-col> </el-col>
<el-col :span="12" :offset="2"> <el-col :span="12" :offset="2">
@ -32,6 +32,7 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('load_test.pressure_config')"> <el-tab-pane :label="$t('load_test.pressure_config')">
<performance-pressure-config :is-read-only="isReadOnly" :test="test" :test-id="testId" <performance-pressure-config :is-read-only="isReadOnly" :test="test" :test-id="testId"
@fileChange="fileChange"
ref="pressureConfig" @changeActive="changeTabActive"/> ref="pressureConfig" @changeActive="changeTabActive"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config"> <el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
@ -305,6 +306,9 @@ export default {
this.$set(handler, "threadGroups", threadGroups); this.$set(handler, "threadGroups", threadGroups);
this.$refs.basicConfig.threadGroups = threadGroups;
this.$refs.pressureConfig.threadGroups = threadGroups;
threadGroups.forEach(tg => { threadGroups.forEach(tg => {
handler.calculateChart(tg); handler.calculateChart(tg);
}) })
@ -317,7 +321,6 @@ export default {
.testplan-config { .testplan-config {
margin-top: 15px; margin-top: 15px;
text-align: center;
} }
.el-select { .el-select {

View File

@ -1,25 +1,89 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-upload <el-row type="flex" justify="space-between" align="middle">
accept=".jmx,.csv,.jar" <h4>{{ $t('load_test.scenario_list') }}</h4>
drag </el-row>
action="" <el-row type="flex" justify="start" align="middle">
:limit="fileNumLimit" <el-upload
multiple style="padding-right: 10px;"
:show-file-list="false" accept=".jmx"
:before-upload="beforeUpload" action=""
:http-request="handleUpload" :limit="fileNumLimit"
:on-exceed="handleExceed" :show-file-list="false"
:disabled="isReadOnly" :before-upload="beforeUploadJmx"
:file-list="fileList"> :http-request="handleUpload"
<i class="el-icon-upload"/> :on-exceed="handleExceed"
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div> :disabled="isReadOnly"
<template v-slot:tip> :file-list="fileList">
<div class="el-upload__tip">{{ $t('load_test.upload_type') }}</div> <ms-table-button :is-tester-permission="true" icon="el-icon-upload2"
</template> :content="$t('load_test.upload_jmx')"/>
</el-upload> </el-upload>
<ms-table-button :is-tester-permission="true" icon="el-icon-circle-plus-outline"
:content="$t('load_test.load_exist_jmx')" @click="loadJMX()"/>
<ms-table-button :is-tester-permission="true" icon="el-icon-share"
@click="loadApiAutomation()"
:content="$t('load_test.load_api_automation_jmx')"/>
</el-row>
<el-table class="basic-config" :data="threadGroups.filter(tg=>tg.deleted=='false')">
<el-table-column
:label="$t('load_test.scenario_name')">
<template v-slot:default="{row}">
{{ row.attributes.testname }}
</template>
</el-table-column>
<el-table-column
label="Enable/Disable">
<template v-slot:default="{row}">
<el-switch v-model="row.enabled"
inactive-color="#DCDFE6"
active-value="true"
inactive-value="false"
:disabled="threadGroupDisable(row)"
/>
</template>
</el-table-column>
<el-table-column
label="ThreadGroup">
<template v-slot:default="{row}">
{{ row.name.substring(row.name.lastIndexOf(".") + 1) }}
</template>
</el-table-column>
<el-table-column
:label="$t('commons.operating')">
<template v-slot:default="{row}">
<el-button :disabled="isReadOnly || threadGroupDisable(row)"
@click="handleDeleteThreadGroup(row)"
type="danger"
icon="el-icon-delete" size="mini"
circle/>
</template>
</el-table-column>
</el-table>
<el-table class="basic-config" :data="tableData"> <el-row type="flex" justify="space-between" align="middle">
<h4>{{ $t('load_test.other_resource') }}</h4>
</el-row>
<el-row type="flex" justify="start" align="middle">
<el-upload
style="padding-right: 10px;"
accept=".jar,.csv"
action=""
:limit="fileNumLimit"
multiple
:show-file-list="false"
:before-upload="beforeUploadFile"
:http-request="handleUpload"
:on-exceed="handleExceed"
:disabled="isReadOnly"
:file-list="fileList">
<ms-table-button :is-tester-permission="true" icon="el-icon-upload2"
:content="$t('load_test.upload_file')"/>
</el-upload>
<ms-table-button :is-tester-permission="true" icon="el-icon-circle-plus-outline"
:content="$t('load_test.load_exist_file')" @click="loadFile()"/>
</el-row>
<el-table class="basic-config" :data="tableData.filter(f => !f.name.toUpperCase().endsWith('.JMX'))">
<el-table-column <el-table-column
prop="name" prop="name"
:label="$t('load_test.file_name')"> :label="$t('load_test.file_name')">
@ -51,15 +115,80 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-dialog :title="$t('load_test.exist_jmx')" width="70%" :visible.sync="loadFileVisible">
<el-table class="basic-config" :data="existFiles" v-loading="projectLoadingResult.loading">
<el-table-column
prop="testName"
:label="$t('load_test.test')">
</el-table-column>
<el-table-column
prop="name"
:label="$t('load_test.file_name')">
</el-table-column>
<el-table-column
prop="type"
:label="$t('load_test.file_type')">
</el-table-column>
<el-table-column
:label="$t('load_test.last_modify_time')">
<template v-slot:default="scope">
<i class="el-icon-time"/>
<span class="last-modified">{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
:label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator-button :is-tester-permission="true"
:tip="$t('api_test.api_import.label')"
icon="el-icon-upload"
@exec="handleImport(scope.row)"/>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="getProjectFiles" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-dialog>
<el-dialog :title="$t('load_test.scenario_list')" width="60%" :visible.sync="loadApiAutomationVisible">
<el-table class="basic-config" :data="apiScenarios" v-loading="projectLoadingResult.loading">
<el-table-column
prop="num"
label="ID">
</el-table-column>
<el-table-column
prop="name"
:label="$t('load_test.scenario_name')">
</el-table-column>
<el-table-column
:label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator-button :is-tester-permission="true"
:tip="$t('api_test.api_import.label')"
icon="el-icon-upload"
@exec="handleImportApi(scope.row)"/>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="getProjectFiles" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import {Message} from "element-ui"; import {Message} from "element-ui";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup"; import {findTestPlan, findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
import MsTableButton from "@/business/components/common/components/MsTableButton";
import {getCurrentProjectID} from "@/common/js/utils";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
export default { export default {
name: "PerformanceBasicConfig", name: "PerformanceBasicConfig",
components: {MsTableOperatorButton, MsTablePagination, MsTableButton},
props: { props: {
test: { test: {
type: Object type: Object
@ -72,6 +201,7 @@ export default {
data() { data() {
return { return {
result: {}, result: {},
projectLoadingResult: {},
getFileMetadataPath: "/performance/file/metadata", getFileMetadataPath: "/performance/file/metadata",
jmxDownloadPath: '/performance/file/download', jmxDownloadPath: '/performance/file/download',
jmxDeletePath: '/performance/file/delete', jmxDeletePath: '/performance/file/delete',
@ -79,6 +209,15 @@ export default {
tableData: [], tableData: [],
uploadList: [], uploadList: [],
fileNumLimit: 10, fileNumLimit: 10,
threadGroups: [],
loadFileVisible: false,
currentPage: 1,
pageSize: 5,
total: 0,
existFiles: [],
loadType: 'jmx',
apiScenarios: [],
loadApiAutomationVisible: false,
}; };
}, },
created() { created() {
@ -98,9 +237,9 @@ export default {
if (fileList.length > 0) { if (fileList.length > 0) {
let file = fileList[0]; let file = fileList[0];
let jmxReader = new FileReader(); let jmxReader = new FileReader();
jmxReader.onload = function (event) { jmxReader.onload = (event) => {
let threadGroups = findThreadGroup(event.target.result); self.threadGroups = findThreadGroup(event.target.result);
self.$emit('fileChange', threadGroups); self.$emit('fileChange', self.threadGroups);
}; };
jmxReader.readAsText(file); jmxReader.readAsText(file);
} }
@ -113,11 +252,11 @@ export default {
this.uploadList = []; this.uploadList = [];
this.result = this.$get(this.getFileMetadataPath + "/" + test.id, response => { this.result = this.$get(this.getFileMetadataPath + "/" + test.id, response => {
let files = response.data; let files = response.data;
if (!files) { if (!files) {
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true}); Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
return; return;
} }
console.log(files);
// deep copy // deep copy
this.fileList = JSON.parse(JSON.stringify(files)); this.fileList = JSON.parse(JSON.stringify(files));
this.tableData = JSON.parse(JSON.stringify(files)); this.tableData = JSON.parse(JSON.stringify(files));
@ -126,12 +265,40 @@ export default {
}); });
}) })
}, },
beforeUpload(file) { deleteExistJmx: function () {
// jmx
let jmxs = this.tableData.filter(f => {
let type = f.name.substring(f.name.lastIndexOf(".") + 1);
return type.toUpperCase() === 'JMX';
});
for (let i = 0; i < jmxs.length; i++) {
let index = this.tableData.indexOf(jmxs[i]);
if (index > -1) {
this.tableData.splice(index, 1);
}
let index2 = this.uploadList.indexOf(jmxs[i]);
if (index2 > -1) {
this.uploadList.splice(index2, 1);
}
}
jmxs = this.fileList.filter(f => {
let type = f.name.substring(f.name.lastIndexOf(".") + 1);
return type.toUpperCase() === 'JMX';
});
for (let i = 0; i < jmxs.length; i++) {
let index3 = this.fileList.indexOf(jmxs[i]);
if (index3 > -1) {
this.fileList.splice(index3, 1);
}
}
},
beforeUploadJmx(file) {
if (!this.fileValidator(file)) { if (!this.fileValidator(file)) {
/// todo: /// todo:
return false; return false;
} }
this.deleteExistJmx();
if (this.tableData.filter(f => f.name === file.name).length > 0) { if (this.tableData.filter(f => f.name === file.name).length > 0) {
this.$error(this.$t('load_test.delete_file')); this.$error(this.$t('load_test.delete_file'));
return false; return false;
@ -141,7 +308,28 @@ export default {
this.tableData.push({ this.tableData.push({
name: file.name, name: file.name,
size: file.size + ' Bytes', /// todo: ByteKBMB size: (file.size / 1024).toFixed(2) + ' KB',
type: type.toUpperCase(),
updateTime: file.lastModified,
});
return true;
},
beforeUploadFile(file) {
if (!this.fileValidator(file)) {
/// todo:
return false;
}
if (this.tableData.filter(f => f.name === file.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
return false;
}
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
this.tableData.push({
name: file.name,
size: (file.size / 1024).toFixed(2) + ' KB',
type: type.toUpperCase(), type: type.toUpperCase(),
updateTime: file.lastModified, updateTime: file.lastModified,
}); });
@ -181,25 +369,116 @@ export default {
Message.error({message: e.message, showClose: true}); Message.error({message: e.message, showClose: true});
}); });
}, },
handleDelete(file, index) { handleImport(row) {
if (this.tableData.filter(f => f.name === row.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
return;
}
if (this.loadType === 'resource') {
this.fileList.push(row);
this.tableData.push(row);
this.$success(this.$t('test_track.case.import.success'));
this.loadFileVisible = false;
return;
}
this.result = this.$get('/performance/get-jmx-content/' + row.testId, (response) => {
if (response.data) {
let testPlan = findTestPlan(response.data);
testPlan.elements.forEach(e => {
if (e.attributes.name === 'TestPlan.serialize_threadgroups') {
this.serializeThreadgroups = Boolean(e.elements[0].text);
}
});
this.threadGroups = findThreadGroup(response.data);
this.threadGroups.forEach(tg => {
tg.options = {};
});
this.$emit('fileChange', this.threadGroups);
}
this.deleteExistJmx();
this.fileList.push(row);
this.tableData.push(row);
this.$success(this.$t('test_track.case.import.success'));
this.loadFileVisible = false;
});
},
countStrToBit(str) {
let count = 0
const arr = str.split('')
arr.forEach(item => {
count += Math.ceil(item.charCodeAt().toString(2).length / 8)
})
return count
},
handleImportApi(row) {
let condition = {
projectId: getCurrentProjectID(),
ids: [row.id]
};
this.projectLoadingResult = this.$post('api/automation/export/jmx', condition, response => {
let data = response.data[0];
this.threadGroups = findThreadGroup(data.jmx);
this.threadGroups.forEach(tg => {
tg.options = {};
});
this.$emit('fileChange', this.threadGroups);
this.deleteExistJmx();
let bytes = this.countStrToBit(data.jmx);
this.fileList.push({
name: this.test.name + ".jmx",
size: bytes,
type: "JMX",
updateTime: new Date().getTime(),
});
this.tableData.push({
name: this.test.name + ".jmx",
size: (bytes / 1024).toFixed(2) + ' KB',
type: "JMX",
updateTime: new Date().getTime(),
});
this.test.jmx = data.jmx;
this.$success(this.$t('test_track.case.import.success'));
this.loadApiAutomationVisible = false;
})
},
handleDelete(file) {
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "", '', { this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "", '', {
confirmButtonText: this.$t('commons.confirm'), confirmButtonText: this.$t('commons.confirm'),
callback: (action) => { callback: (action) => {
if (action === 'confirm') { if (action === 'confirm') {
this._handleDelete(file, index); this._handleDelete(file);
} }
} }
}); });
}, },
_handleDelete(file, index) { _handleDelete(file) {
this.fileList.splice(index, 1); let index = this.fileList.findIndex(f => f.name === file.name);
this.tableData.splice(index, 1); if (index > -1) {
this.fileList.splice(index, 1);
}
index = this.tableData.findIndex(f => f.name === file.name);
if (index > -1) {
this.tableData.splice(index, 1);
}
// //
let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name); let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name);
if (i > -1) { if (i > -1) {
this.uploadList.splice(i, 1); this.uploadList.splice(i, 1);
} }
}, },
handleDeleteThreadGroup(tg) {
this.$alert(this.$t('load_test.delete_threadgroup_confirm') + tg.attributes.testname + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
tg.deleted = 'true';
}
}
});
},
threadGroupDisable(row) {
return this.threadGroups.filter(tg => tg.enabled == 'true').length === 1 && row.enabled == 'true';
},
handleExceed() { handleExceed() {
this.$error(this.$t('load_test.file_size_limit')); this.$error(this.$t('load_test.file_size_limit'));
}, },
@ -210,6 +489,39 @@ export default {
updatedFileList() { updatedFileList() {
return this.fileList;// return this.fileList;//
}, },
loadJMX() {
this.loadFileVisible = true;
this.loadType = "jmx";
this.getProjectFiles();
},
loadFile() {
this.loadFileVisible = true;
this.loadType = "resource";
this.getProjectFiles();
},
loadApiAutomation() {
this.loadApiAutomationVisible = true;
this.getProjectScenarios();
},
getProjectFiles() {
this.projectLoadingResult = this.$get('/performance/project/' + this.loadType + '/' + getCurrentProjectID() + "/" + this.currentPage + "/" + this.pageSize, res => {
let data = res.data;
this.total = data.itemCount;
this.existFiles = data.listObject;
})
},
getProjectScenarios() {
let condition = {
projectId: getCurrentProjectID(),
filters: {status: ["Prepare", "Underway", "Completed"]}
}
this.projectLoadingResult = this.$post('/api/automation/list/' + this.currentPage + "/" + this.pageSize, condition, res => {
let data = res.data;
this.total = data.itemCount;
this.apiScenarios = data.listObject;
})
},
validConfig() { validConfig() {
let newJmxNum = 0, oldJmxNum = 0, newCsvNum = 0, oldCsvNum = 0, newJarNum = 0, oldJarNum = 0; let newJmxNum = 0, oldJmxNum = 0, newCsvNum = 0, oldCsvNum = 0, newJarNum = 0, oldJarNum = 0;
if (this.uploadList.length > 0) { if (this.uploadList.length > 0) {
@ -246,6 +558,10 @@ export default {
this.$error(this.$t('load_test.jmx_is_null')); this.$error(this.$t('load_test.jmx_is_null'));
return false; return false;
} }
if (this.threadGroups.filter(tg => tg.attributes.enabled == 'true').length === 0) {
this.$error(this.$t('load_test.threadgroup_at_least_one'));
return false;
}
return true; return true;
} }
}, },
@ -260,4 +576,8 @@ export default {
.last-modified { .last-modified {
margin-left: 5px; margin-left: 5px;
} }
.el-dialog >>> .el-dialog__body {
padding: 10px 20px;
}
</style> </style>

View File

@ -20,7 +20,7 @@
<el-row> <el-row>
<el-collapse v-model="activeNames"> <el-collapse v-model="activeNames">
<el-collapse-item :title="threadGroup.attributes.testname" :name="index" <el-collapse-item :title="threadGroup.attributes.testname" :name="index"
v-for="(threadGroup, index) in threadGroups" v-for="(threadGroup, index) in threadGroups.filter(th=>th.enabled === 'true' && th.deleted=='false')"
:key="index"> :key="index">
<el-col :span="10"> <el-col :span="10">
<el-form :inline="true"> <el-form :inline="true">
@ -140,6 +140,8 @@ const RPS_LIMIT_ENABLE = "rpsLimitEnable";
const HOLD = "Hold"; const HOLD = "Hold";
const THREAD_TYPE = "threadType"; const THREAD_TYPE = "threadType";
const ITERATE_NUM = "iterateNum"; const ITERATE_NUM = "iterateNum";
const ENABLED = "enabled";
const DELETED = "deleted";
const hexToRgba = function (hex, opacity) { const hexToRgba = function (hex, opacity) {
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ',' return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
@ -221,90 +223,57 @@ export default {
let data = JSON.parse(response.data); let data = JSON.parse(response.data);
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
let d = data[i]; let d = data[i];
if (d instanceof Array) { d.forEach(item => {
d.forEach(item => { switch (item.key) {
switch (item.key) {
case TARGET_LEVEL:
this.threadGroups[i].threadNumber = item.value;
break;
case RAMP_UP:
this.threadGroups[i].rampUpTime = item.value;
break;
case ITERATE_RAMP_UP:
this.threadGroups[i].iterateRampUp = item.value;
break;
case DURATION:
if (item.unit) {
this.threadGroups[i].duration = item.value;
} else {
this.threadGroups[i].duration = item.value * 60;
}
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;
case THREAD_TYPE:
this.threadGroups[i].threadType = item.value;
break;
case ITERATE_NUM:
this.threadGroups[i].iterateNum = item.value;
break;
default:
break;
}
//
this.$set(this.threadGroups[i], "threadType", this.threadGroups[i].threadType || 'DURATION');
this.$set(this.threadGroups[i], "iterateNum", this.threadGroups[i].iterateNum || 1);
this.$set(this.threadGroups[i], "iterateRampUp", this.threadGroups[i].iterateRampUp || 10);
})
this.calculateChart(this.threadGroups[i]);
} else {
switch (d.key) {
case TARGET_LEVEL: case TARGET_LEVEL:
this.threadGroups[0].threadNumber = d.value; this.threadGroups[i].threadNumber = item.value;
break; break;
case RAMP_UP: case RAMP_UP:
this.threadGroups[0].rampUpTime = d.value; this.threadGroups[i].rampUpTime = item.value;
break; break;
case ITERATE_RAMP_UP: case ITERATE_RAMP_UP:
this.threadGroups[0].iterateRampUp = d.value; this.threadGroups[i].iterateRampUp = item.value;
break; break;
case DURATION: case DURATION:
if (d.unit) { if (item.unit) {
this.threadGroups[0].duration = d.value; this.threadGroups[i].duration = item.value;
} else { } else {
this.threadGroups[0].duration = d.value * 60; this.threadGroups[i].duration = item.value * 60;
} }
break; break;
case STEPS: case STEPS:
this.threadGroups[0].step = d.value; this.threadGroups[i].step = item.value;
break; break;
case RPS_LIMIT: case RPS_LIMIT:
this.threadGroups[0].rpsLimit = d.value; this.threadGroups[i].rpsLimit = item.value;
break; break;
case RPS_LIMIT_ENABLE: case RPS_LIMIT_ENABLE:
this.threadGroups[0].rpsLimitEnable = d.value; this.threadGroups[i].rpsLimitEnable = item.value;
break; break;
case THREAD_TYPE: case THREAD_TYPE:
this.threadGroups[0].threadType = d.value; this.threadGroups[i].threadType = item.value;
break; break;
case ITERATE_NUM: case ITERATE_NUM:
this.threadGroups[0].iterateNum = d.value; this.threadGroups[i].iterateNum = item.value;
break;
case ENABLED:
this.threadGroups[i].enabled = item.value;
break;
case DELETED:
this.threadGroups[i].deleted = item.value;
break; break;
default: default:
break; break;
} }
this.$set(this.threadGroups[0], "threadType", this.threadGroups[0].threadType || 'DURATION'); //
this.$set(this.threadGroups[0], "iterateNum", this.threadGroups[0].iterateNum || 1); this.$set(this.threadGroups[i], "threadType", this.threadGroups[i].threadType || 'DURATION');
this.$set(this.threadGroups[0], "iterateRampUp", this.threadGroups[0].iterateRampUp || 10); this.$set(this.threadGroups[i], "iterateNum", this.threadGroups[i].iterateNum || 1);
this.calculateChart(this.threadGroups[0]); this.$set(this.threadGroups[i], "iterateRampUp", this.threadGroups[i].iterateRampUp || 10);
} this.$set(this.threadGroups[i], "enabled", this.threadGroups[i].enabled || 'true');
this.$set(this.threadGroups[i], "deleted", this.threadGroups[i].deleted || 'false');
})
this.calculateChart(this.threadGroups[i]);
} }
this.calculateTotalChart(); this.calculateTotalChart();
} }
@ -324,6 +293,7 @@ export default {
this.threadGroups.forEach(tg => { this.threadGroups.forEach(tg => {
tg.options = {}; tg.options = {};
}); });
this.$emit('fileChange', this.threadGroups);
this.getLoadConfig(); this.getLoadConfig();
} }
}); });
@ -355,6 +325,9 @@ export default {
}; };
for (let i = 0; i < handler.threadGroups.length; i++) { for (let i = 0; i < handler.threadGroups.length; i++) {
if (handler.threadGroups[i].enabled === 'false' || handler.threadGroups[i].deleted === 'true') {
continue;
}
let seriesData = { let seriesData = {
name: handler.threadGroups[i].attributes.testname, name: handler.threadGroups[i].attributes.testname,
data: [], data: [],
@ -560,6 +533,8 @@ export default {
{key: THREAD_TYPE, value: this.threadGroups[i].threadType}, {key: THREAD_TYPE, value: this.threadGroups[i].threadType},
{key: ITERATE_NUM, value: this.threadGroups[i].iterateNum}, {key: ITERATE_NUM, value: this.threadGroups[i].iterateNum},
{key: ITERATE_RAMP_UP, value: this.threadGroups[i].iterateRampUp}, {key: ITERATE_RAMP_UP, value: this.threadGroups[i].iterateRampUp},
{key: ENABLED, value: this.threadGroups[i].enabled},
{key: DELETED, value: this.threadGroups[i].deleted},
]); ]);
} }
return result; return result;

View File

@ -5,8 +5,17 @@ let travel = function (elements, threadGroups) {
return; return;
} }
for (let element of elements) { for (let element of elements) {
if (element.name === 'ThreadGroup') { switch (element.name) {
threadGroups.push(element); case "ThreadGroup":
case "kg.apc.jmeter.threads.UltimateThreadGroup":
case "com.blazemeter.jmeter.threads.concurrency.ConcurrencyThreadGroup":
case "com.blazemeter.jmeter.threads.arrivals.FreeFormArrivalsThreadGroup":
case "com.blazemeter.jmeter.threads.arrivals.ArrivalsThreadGroup":
case "com.octoperf.jmeter.OctoPerfThreadGroup":
threadGroups.push(element);
break;
default:
break;
} }
travel(element.elements, threadGroups) travel(element.elements, threadGroups)
} }
@ -16,6 +25,10 @@ export function findThreadGroup(jmxContent) {
let jmxJson = JSON.parse(xml2json(jmxContent)); let jmxJson = JSON.parse(xml2json(jmxContent));
let threadGroups = []; let threadGroups = [];
travel(jmxJson.elements, threadGroups); travel(jmxJson.elements, threadGroups);
threadGroups.forEach(tg => {
tg.deleted = 'false';
tg.enabled = tg.attributes.enabled;
})
return threadGroups; return threadGroups;
} }

View File

@ -420,6 +420,8 @@ export default {
delete_batch_confirm: 'Confirm batch delete report', delete_batch_confirm: 'Confirm batch delete report',
}, },
load_test: { load_test: {
test: 'Test',
name: 'Test Name',
same_project_test: 'Only tests within the same project can be run', same_project_test: 'Only tests within the same project can be run',
run: 'One click operation', run: 'One click operation',
operating: 'Operating', operating: 'Operating',
@ -488,7 +490,18 @@ export default {
user_name: 'Creator', user_name: 'Creator',
special_characters_are_not_supported: 'Test name does not support special characters', special_characters_are_not_supported: 'Test name does not support special characters',
pressure_config_params_is_empty: 'Pressure configuration parameters cannot be empty!', pressure_config_params_is_empty: 'Pressure configuration parameters cannot be empty!',
schedule_tip: 'The interval must not be less than the pressure measuring time' schedule_tip: 'The interval must not be less than the pressure measuring time',
delete_threadgroup_confirm: 'Confirm delete scenario: ',
scenario_list: 'Scenario List',
scenario_name: 'Scenario Name',
upload_jmx: 'Upload JMX',
exist_jmx: 'Existed Files',
other_resource: 'Other Files',
upload_file: 'Upload Files',
load_exist_file: 'Load Project Files',
load_exist_jmx: 'Load Project JMX',
threadgroup_at_least_one: 'At least one ThreadGroup is enabled',
load_api_automation_jmx: 'Import API automation scenario',
}, },
api_test: { api_test: {
creator: "Creator", creator: "Creator",
@ -605,7 +618,7 @@ export default {
edit_time_Reverse_order: "From back to front by update time", edit_time_Reverse_order: "From back to front by update time",
request_method: "Request method", request_method: "Request method",
request_interface: "Request interface", request_interface: "Request interface",
search_by_api_name : "Search by api name", search_by_api_name: "Search by api name",
request_info: "Request info", request_info: "Request info",
request_head: "Request head", request_head: "Request head",
request_param: "Param", request_param: "Param",
@ -615,7 +628,7 @@ export default {
response_head: "Response head", response_head: "Response head",
response_body: "Response body", response_body: "Response body",
response_code: "Response code", response_code: "Response code",
table_coloum:{ table_coloum: {
name: "name", name: "name",
value: "value", value: "value",
is_required: "Is it required", is_required: "Is it required",

View File

@ -418,6 +418,8 @@ export default {
delete_batch_confirm: '确认批量删除报告', delete_batch_confirm: '确认批量删除报告',
}, },
load_test: { load_test: {
test: '测试',
name: '测试名称',
same_project_test: '只能运行同一项目内的测试', same_project_test: '只能运行同一项目内的测试',
already_exists: '测试名称不能重复', already_exists: '测试名称不能重复',
operating: '操作', operating: '操作',
@ -487,7 +489,18 @@ export default {
user_name: '创建人', user_name: '创建人',
special_characters_are_not_supported: '测试名称不支持特殊字符', special_characters_are_not_supported: '测试名称不支持特殊字符',
pressure_config_params_is_empty: '压力配置参数不能为空!', pressure_config_params_is_empty: '压力配置参数不能为空!',
schedule_tip: '间隔时间不能小于压测时长' schedule_tip: '间隔时间不能小于压测时长',
delete_threadgroup_confirm: '确认删除场景',
scenario_list: '场景列表',
scenario_name: "场景名称",
upload_jmx: '上传 JMX 文件',
exist_jmx: '已存在的文件',
other_resource: '其他资源',
upload_file: '上传新文件',
load_exist_file: '加载已有文件',
load_exist_jmx: '加载已有 JMX 文件',
threadgroup_at_least_one: '至少启用一个线程组',
load_api_automation_jmx: '引用接口自动化场景',
}, },
api_test: { api_test: {
creator: "创建人", creator: "创建人",
@ -606,7 +619,7 @@ export default {
edit_time_Reverse_order: "按更新时间从后到前", edit_time_Reverse_order: "按更新时间从后到前",
request_method: "请求方式", request_method: "请求方式",
request_interface: "请求接口", request_interface: "请求接口",
search_by_api_name : "名称搜索", search_by_api_name: "名称搜索",
request_info: "请求信息", request_info: "请求信息",
request_head: "请求头", request_head: "请求头",
request_param: "参数", request_param: "参数",
@ -616,7 +629,7 @@ export default {
response_head: "响应头", response_head: "响应头",
response_body: "响应体", response_body: "响应体",
response_code: "响应码", response_code: "响应码",
table_coloum:{ table_coloum: {
name: "名称", name: "名称",
value: "值", value: "值",
is_required: "是否必填", is_required: "是否必填",

View File

@ -418,6 +418,8 @@ export default {
delete_batch_confirm: '確認批量刪除報告', delete_batch_confirm: '確認批量刪除報告',
}, },
load_test: { load_test: {
test: '測試',
name: '測試名稱',
same_project_test: '只能運行同壹項目內的測試', same_project_test: '只能運行同壹項目內的測試',
already_exists: '測試名稱不能重復', already_exists: '測試名稱不能重復',
operating: '操作', operating: '操作',
@ -487,7 +489,18 @@ export default {
user_name: '創建人', user_name: '創建人',
special_characters_are_not_supported: '測試名稱不支持特殊字符', special_characters_are_not_supported: '測試名稱不支持特殊字符',
pressure_config_params_is_empty: '壓力配置參數不能為空!', pressure_config_params_is_empty: '壓力配置參數不能為空!',
schedule_tip: '間隔時間不能小於壓測時長' schedule_tip: '間隔時間不能小於壓測時長',
delete_threadgroup_confirm: '確認刪除場景: ',
scenario_list: '場景列表',
scenario_name: '場景名稱',
upload_jmx: '上傳 JMX文件',
exist_jmx: '已存在的文件',
other_resource: '其他資源',
upload_file: '上傳新文件',
load_exist_file: '加載已有文件',
load_exist_jmx: '加載已有 JMX 文件',
threadgroup_at_least_one: '至少啟用一個線程組',
load_api_automation_jmx: '引用接口自動化場景',
}, },
api_test: { api_test: {
creator: "創建人", creator: "創建人",
@ -605,7 +618,7 @@ export default {
edit_time_Reverse_order: "按更新時間從後到前", edit_time_Reverse_order: "按更新時間從後到前",
request_method: "請求方式", request_method: "請求方式",
request_interface: "請求接口e", request_interface: "請求接口e",
search_by_api_name : "API名稱搜索", search_by_api_name: "API名稱搜索",
request_info: "請求信息", request_info: "請求信息",
request_head: "請求頭", request_head: "請求頭",
request_param: "參數", request_param: "參數",
@ -615,7 +628,7 @@ export default {
response_head: "響應頭", response_head: "響應頭",
response_body: "響應體", response_body: "響應體",
response_code: "響應碼", response_code: "響應碼",
table_coloum:{ table_coloum: {
name: "名稱", name: "名稱",
value: "值", value: "值",
is_required: "是否必填", is_required: "是否必填",