This commit is contained in:
chenjianxing 2020-03-26 16:54:50 +08:00
commit 40473911c2
24 changed files with 725 additions and 193 deletions

View File

@ -1,38 +1,10 @@
package io.metersphere.commons.constants;
/**
* Author: chunxing
* Date: 2018/6/26 下午3:44
* Description:
*/
public interface ParamConstants {
String getValue();
enum KeyCloak implements ParamConstants {
USERNAME("keycloak.username"),
PASSWORD("keycloak.password"),
REALM("keycloak.realm"),
AUTH_SERVER_URL("keycloak.auth-server-url"),
ADDRESS("keycloak-server-address");
private String value;
KeyCloak(String value) {
this.value = value;
}
@Override
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
enum Type implements ParamConstants {
PASSWORD("password"),
@ -57,9 +29,6 @@ public interface ParamConstants {
enum Classify implements ParamConstants {
KEYCLOAK("keycloak"),
MAIL("smtp"),
UI("ui"),
REGISTRY("registry");
private String value;
@ -78,77 +47,6 @@ public interface ParamConstants {
}
}
enum UI implements ParamConstants {
LOGO("ui.logo"),
SYSTEM_NAME("ui.system.name"),
THEME_PRIMARY("ui.theme.primary"),
THEME_ACCENT("ui.theme.accent"),
FAVICON("ui.favicon"),
LOGIN_TITLE("ui.login.title"),
LOGIN_IMG("ui.login.img"),
SUPPORT_NAME("ui.support.name"),
SUPPORT_URL("ui.support.url"),
TITLE("ui.title");
private String value;
UI(String value) {
this.value = value;
}
@Override
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
enum MAIL {
SERVER("smtp.server", 1),
PORT("smtp.port", 2),
ACCOUNT("smtp.account", 3),
PASSWORD("smtp.password", 4),
SSL("smtp.ssl", 5),
TLS("smtp.tls", 6);
private String key;
private Integer value;
MAIL(String key, Integer value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public Integer getValue() {
return value;
}
}
enum Log implements ParamConstants {
KEEP_MONTHS("log.keep.months");
private String value;
Log(String value) {
this.value = value;
}
@Override
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
enum Registry implements ParamConstants {
URL("registry.url"),

View File

@ -8,7 +8,9 @@ import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.ReportRequest;
import io.metersphere.dto.ReportDTO;
import io.metersphere.report.base.Errors;
import io.metersphere.report.base.RequestStatistics;
import io.metersphere.report.base.RequestStatisticsDTO;
import io.metersphere.service.ReportService;
import io.metersphere.user.SessionUtils;
import org.apache.shiro.authz.annotation.Logical;
@ -54,9 +56,14 @@ public class ReportController {
}
@GetMapping("/content/{reportId}")
public List<RequestStatistics> getReportContent(@PathVariable String reportId) {
public RequestStatisticsDTO getReportContent(@PathVariable String reportId) {
return reportService.getReport(reportId);
}
@GetMapping("/content/errors/{reportId}")
public List<Errors> getReportErrors(@PathVariable String reportId) {
return reportService.getReportErrors(reportId);
}
}

View File

@ -41,11 +41,9 @@ public class TestResourcePoolController {
return PageUtils.setPageInfo(page, testResourcePoolService.listResourcePools(request));
}
@GetMapping("list/all")
public List<TestResourcePoolDTO> listResourcePools() {
PageHelper.startPage(1, 10000, true);
QueryResourcePoolRequest request = new QueryResourcePoolRequest();
return testResourcePoolService.listResourcePools(request);
@GetMapping("list/all/valid")
public List<TestResourcePool> listValidResourcePools() {
return testResourcePoolService.listValidResourcePools();
}

View File

@ -1,10 +1,15 @@
package io.metersphere.controller.request;
import java.util.HashMap;
import java.util.Map;
public class TestRequest {
int size;
String fileString;
String testId;
private int size;
private String fileString;
private String testId;
private String image;
private Map<String, String> testData = new HashMap<>();
public int getSize() {
return size;
@ -29,4 +34,20 @@ public class TestRequest {
public void setTestId(String testId) {
this.testId = testId;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Map<String, String> getTestData() {
return testData;
}
public void setTestData(Map<String, String> testData) {
this.testData = testData;
}
}

View File

@ -19,6 +19,8 @@ import org.apache.commons.lang3.StringUtils;
import java.util.List;
public abstract class AbstractEngine implements Engine {
public static final String JMETER_IMAGE = "jmeter-master:0.0.2";
protected LoadTestWithBLOBs loadTest;
protected LoadTestService loadTestService;
protected Integer threadNum;
@ -32,8 +34,7 @@ public abstract class AbstractEngine implements Engine {
testResourceService = CommonBeanFactory.getBean(TestResourceService.class);
}
@Override
public boolean init(LoadTestWithBLOBs loadTest) {
protected void init(LoadTestWithBLOBs loadTest) {
if (loadTest == null) {
MSException.throwException("LoadTest is null.");
}
@ -50,14 +51,13 @@ public abstract class AbstractEngine implements Engine {
if (resourcePool == null) {
MSException.throwException("Resource Pool is empty");
}
if (!ResourcePoolTypeEnum.K8S.name().equals(resourcePool.getType())) {
if (!ResourcePoolTypeEnum.K8S.name().equals(resourcePool.getType()) && !ResourcePoolTypeEnum.NODE.name().equals(resourcePool.getType())) {
MSException.throwException("Invalid Resource Pool type.");
}
this.resourceList = testResourceService.getResourcesByPoolId(resourcePool.getId());
if (CollectionUtils.isEmpty(this.resourceList)) {
MSException.throwException("Test Resource is empty");
}
return true;
}
protected Integer getRunningThreadNum() {

View File

@ -1,9 +1,6 @@
package io.metersphere.engine;
import io.metersphere.base.domain.LoadTestWithBLOBs;
public interface Engine {
boolean init(LoadTestWithBLOBs loadTest);
void start();

View File

@ -47,9 +47,9 @@ public class EngineFactory {
switch (type) {
case NODE:
return new DockerTestEngine();
return new DockerTestEngine(loadTest);
case K8S:
return new KubernetesTestEngine();
return new KubernetesTestEngine(loadTest);
}
return null;
}

View File

@ -2,6 +2,7 @@ package io.metersphere.engine.docker;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.LoadTestWithBLOBs;
import io.metersphere.base.domain.TestResource;
import io.metersphere.commons.constants.ResourceStatusEnum;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
@ -10,6 +11,7 @@ import io.metersphere.dto.NodeDTO;
import io.metersphere.engine.AbstractEngine;
import io.metersphere.engine.EngineContext;
import io.metersphere.engine.EngineFactory;
import io.metersphere.engine.kubernetes.registry.RegistryService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.client.RestTemplate;
@ -18,22 +20,27 @@ import java.util.List;
import java.util.stream.Collectors;
public class DockerTestEngine extends AbstractEngine {
private static final String BASE_URL = "http://%s:%d";
private RestTemplate restTemplate;
private RegistryService registryService;
public DockerTestEngine(LoadTestWithBLOBs loadTest) {
this.init(loadTest);
}
@Override
public boolean init(LoadTestWithBLOBs loadTest) {
protected void init(LoadTestWithBLOBs loadTest) {
super.init(loadTest);
this.restTemplate = CommonBeanFactory.getBean(RestTemplate.class);
this.registryService = CommonBeanFactory.getBean(RegistryService.class);
// todo 初始化操作
return true;
}
@Override
public void start() {
Integer runningSumThreadNum = getRunningThreadNum();
Integer totalThreadNum = resourceList.stream()
int totalThreadNum = resourceList.stream()
.filter(r -> ResourceStatusEnum.VALID.name().equals(r.getStatus()))
.map(r -> JSON.parseObject(r.getConfiguration(), NodeDTO.class).getMaxConcurrency())
.reduce(Integer::sum)
@ -46,34 +53,41 @@ public class DockerTestEngine extends AbstractEngine {
.map(r -> JSON.parseObject(r.getConfiguration(), NodeDTO.class).getMaxConcurrency())
.collect(Collectors.toList());
resourceRatio.forEach(ratio -> {
for (int i = 0, size = resourceList.size(); i < size; i++) {
int ratio = resourceRatio.get(i);
double realThreadNum = ((double) ratio / totalThreadNum) * threadNum;
runTest(Math.round(realThreadNum));
});
runTest(resourceList.get(i), Math.round(realThreadNum));
}
}
private void runTest(long realThreadNum) {
private void runTest(TestResource resource, long realThreadNum) {
// todo 运行测试
EngineContext context = null;
try {
context = EngineFactory.createContext(loadTest, realThreadNum);
} catch (Exception e) {
e.printStackTrace();
MSException.throwException(e);
}
String configuration = resource.getConfiguration();
NodeDTO node = JSON.parseObject(configuration, NodeDTO.class);
String nodeIp = node.getIp();
Integer port = node.getPort();
String testId = context.getTestId();
String content = context.getContent();
String uri = "http://localhost:8082/jmeter/container/start";
String uri = String.format(BASE_URL + "/jmeter/container/start", nodeIp, port);
TestRequest testRequest = new TestRequest();
testRequest.setSize(1);
testRequest.setTestId(testId);
testRequest.setFileString(content);
testRequest.setImage(registryService.getRegistry() + JMETER_IMAGE);
testRequest.setTestData(context.getTestData());
// todo 判断测试状态
String taskStatusUri = "http://localhost:8082/jmeter/task/status/" + testId;
String taskStatusUri = String.format(BASE_URL + "/jmeter/task/status/" + testId, nodeIp, port);
List containerList = restTemplate.getForObject(taskStatusUri, List.class);
for (int i = 0; i < containerList.size(); i++) {
HashMap h = (HashMap) containerList.get(i);
@ -88,12 +102,16 @@ public class DockerTestEngine extends AbstractEngine {
@Override
public void stop() {
// TODO 停止运行测试
// RestTemplate restTemplate = new RestTemplate();
String testId = loadTest.getId();
this.resourceList.forEach(r -> {
NodeDTO node = JSON.parseObject(r.getConfiguration(), NodeDTO.class);
String ip = node.getIp();
Integer port = node.getPort();
String uri = "http://localhost:8082/jmeter/container/stop/" + testId;
String uri = String.format(BASE_URL + "/jmeter/container/stop/" + testId, ip, port);
restTemplate.postForObject(uri, "", String.class);
});
}
}

View File

@ -6,6 +6,7 @@ import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.metersphere.base.domain.LoadTestWithBLOBs;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.engine.AbstractEngine;
import io.metersphere.engine.EngineContext;
@ -14,16 +15,23 @@ import io.metersphere.engine.kubernetes.crds.jmeter.Jmeter;
import io.metersphere.engine.kubernetes.crds.jmeter.JmeterSpec;
import io.metersphere.engine.kubernetes.provider.ClientCredential;
import io.metersphere.engine.kubernetes.provider.KubernetesProvider;
import io.metersphere.engine.kubernetes.registry.RegistryService;
import org.apache.commons.collections.MapUtils;
import java.util.HashMap;
public class KubernetesTestEngine extends AbstractEngine {
private RegistryService registryService;
public KubernetesTestEngine(LoadTestWithBLOBs loadTest) {
this.init(loadTest);
}
@Override
public boolean init(LoadTestWithBLOBs loadTest) {
public void init(LoadTestWithBLOBs loadTest) {
super.init(loadTest);
return true;
this.registryService = CommonBeanFactory.getBean(RegistryService.class);
}
@ -42,17 +50,20 @@ public class KubernetesTestEngine extends AbstractEngine {
}
try {
EngineContext context = EngineFactory.createContext(loadTest, threadNum);
runTest(context, clientCredential, 1);
runTest(context, clientCredential);
} catch (Exception e) {
LogUtil.error(e);
MSException.throwException(e);
}
});
}
private void runTest(EngineContext context, ClientCredential credential, int replicas) {
private void runTest(EngineContext context, ClientCredential credential) {
KubernetesProvider kubernetesProvider = new KubernetesProvider(JSON.toJSONString(credential));
// create namespace
kubernetesProvider.confirmNamespace(context.getNamespace());
// docker registry
registryService.dockerRegistry(kubernetesProvider, context.getNamespace());
// create cm
try (KubernetesClient client = kubernetesProvider.getKubernetesClient()) {
String configMapName = context.getTestId() + "-files";
@ -80,18 +91,40 @@ public class KubernetesTestEngine extends AbstractEngine {
setName(context.getTestId());
}});
jmeter.setSpec(new JmeterSpec() {{
setReplicas(replicas);
setImage("registry.fit2cloud.com/metersphere/jmeter-master:0.0.2");
setReplicas(1);
setImage(registryService.getRegistry() + JMETER_IMAGE);
}});
LogUtil.info("Load test started. " + context.getTestId());
kubernetesProvider.applyCustomResource(jmeter);
} catch (Exception e) {
LogUtil.error(e);
MSException.throwException(e);
}
}
@Override
public void stop() {
resourceList.forEach(r -> {
try {
EngineContext context = EngineFactory.createContext(loadTest, threadNum);
String configuration = r.getConfiguration();
ClientCredential clientCredential = JSON.parseObject(configuration, ClientCredential.class);
KubernetesProvider provider = new KubernetesProvider(JSON.toJSONString(clientCredential));
provider.confirmNamespace(context.getNamespace());
Jmeter jmeter = new Jmeter();
jmeter.setMetadata(new ObjectMeta() {{
setName(context.getTestId());
setNamespace(context.getNamespace());
}});
jmeter.setSpec(new JmeterSpec() {{
setReplicas(1);
setImage(registryService.getRegistry() + JMETER_IMAGE);
}});
provider.deleteCustomResource(jmeter);
} catch (Exception e) {
MSException.throwException(e);
}
});
}
}

View File

@ -0,0 +1,94 @@
package io.metersphere.engine.kubernetes.registry;
import io.metersphere.base.domain.SystemParameter;
import io.metersphere.base.domain.SystemParameterExample;
import io.metersphere.base.mapper.SystemParameterMapper;
import io.metersphere.commons.constants.ParamConstants;
import io.metersphere.commons.utils.EncryptUtils;
import io.metersphere.engine.kubernetes.provider.AbstractClientProvider;
import io.metersphere.engine.kubernetes.provider.DockerRegistry;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class RegistryService {
@Resource
private SystemParameterMapper parameterMapper;
/**
* 获取镜像仓库地址
* 地址+项目
*
* @return eg: registry.demo.com/demo/
*/
public String getRegistry() {
StringBuilder sb = new StringBuilder();
Map<String, String> paramMap = getRegistryInfo();
String url = paramMap.getOrDefault(ParamConstants.Registry.URL.getValue(), "");
String project = paramMap.getOrDefault(ParamConstants.Registry.REPO.getValue(), "");
if (url.startsWith("http://") || url.startsWith("https://")) {
url = url.replace("http://", "");
url = url.replace("https://", "");
}
sb.append(url);
if (!url.endsWith("/")) {
sb.append("/");
}
if (StringUtils.isNotEmpty(project)) {
sb.append(project);
sb.append("/");
}
return sb.toString();
}
public String getRegistryUrl() {
Map<String, String> paramMap = getRegistryInfo();
String url = paramMap.get(ParamConstants.Registry.URL.getValue());
if (url.startsWith("http://") || url.startsWith("https://")) {
url = url.replace("http://", "");
url = url.replace("https://", "");
}
return url;
}
public String getRegistryUsername() {
Map<String, String> paramMap = getRegistryInfo();
return paramMap.get(ParamConstants.Registry.USERNAME.getValue());
}
public String getRegistryPassword() {
Map<String, String> paramMap = getRegistryInfo();
return paramMap.get(ParamConstants.Registry.PASSWORD.getValue());
}
public void dockerRegistry(AbstractClientProvider clientProvider, String namespace) {
DockerRegistry registry = new DockerRegistry();
registry.setUrl(this.getRegistryUrl());
registry.setUsername(this.getRegistryUsername());
registry.setPassword(this.getRegistryPassword());
registry.setNamespace(namespace);
clientProvider.dockerRegistry(registry);
}
private Map<String, String> getRegistryInfo() {
Map<String, String> map = new HashMap<>();
SystemParameterExample example = new SystemParameterExample();
example.createCriteria().andParamKeyLike(ParamConstants.Classify.REGISTRY.getValue() + "%");
List<SystemParameter> parameters = parameterMapper.selectByExample(example);
for (SystemParameter parameter : parameters) {
if (StringUtils.equalsIgnoreCase(ParamConstants.Type.PASSWORD.getValue(), parameter.getType())) {
parameter.setParamValue(EncryptUtils.aesDecrypt(parameter.getParamValue()).toString());
}
parameters.forEach(param -> map.put(param.getParamKey(), param.getParamValue()));
}
return map;
}
}

View File

@ -1,11 +1,13 @@
package io.metersphere.report;
import com.alibaba.fastjson.JSONObject;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import io.metersphere.report.base.Errors;
import io.metersphere.report.base.Metric;
import io.metersphere.report.base.RequestStatistics;
import io.metersphere.report.base.RequestStatisticsDTO;
import org.apache.commons.lang3.StringUtils;
import java.io.Reader;
import java.io.StringReader;
@ -33,9 +35,12 @@ public class JtlResolver {
return null;
}
private static List<RequestStatistics> getOneRpsResult(Map<String, List<Metric>> map){
private static RequestStatisticsDTO getOneRpsResult(Map<String, List<Metric>> map){
RequestStatisticsDTO statisticsDTO = new RequestStatisticsDTO();
List<RequestStatistics> requestStatisticsList = new ArrayList<>();
Iterator<Map.Entry<String, List<Metric>>> iterator = map.entrySet().iterator();
List<Integer> allelapse = new ArrayList<>();
Integer totalAverage = 0;
while (iterator.hasNext()) {
Map.Entry<String, List<Metric>> entry = iterator.next();
String label = entry.getKey();
@ -45,7 +50,7 @@ public class JtlResolver {
//总的响应时间
int sumElapsed=0;
Integer failSize = 0;
Integer totalBytes = 0;
Float totalBytes = 0f;
List<Integer> elapsedList = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
@ -54,7 +59,9 @@ public class JtlResolver {
//响应时间
String elapsed = row.getElapsed();
sumElapsed += Integer.valueOf(elapsed);
totalAverage += Integer.valueOf(elapsed);
elapsedList.add(Integer.valueOf(elapsed));
allelapse.add(Integer.valueOf(elapsed));
//成功与否
String success = row.getSuccess();
if (!"true".equals(success)){
@ -62,25 +69,19 @@ public class JtlResolver {
}
//字节
String bytes = row.getBytes();
totalBytes += Integer.valueOf(bytes);
totalBytes += Float.valueOf(bytes);
index++;
}catch (Exception e){
System.out.println("exception i:"+i);
}
}
Collections.sort(elapsedList, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
Collections.sort(elapsedList);
Integer tp90 = elapsedList.size()*9/10;
Integer tp90 = elapsedList.size()*90/100;
Integer tp95 = elapsedList.size()*95/100;
Integer tp99 = elapsedList.size()*99/100;
Long l = Long.valueOf(timestampList.get(index-1)) - Long.valueOf(timestampList.get(0));
Long l = Long.valueOf(timestampList.get(timestampList.size()-1)) - Long.valueOf(timestampList.get(0));
RequestStatistics requestStatistics = new RequestStatistics();
requestStatistics.setRequestLabel(label);
@ -101,16 +102,80 @@ public class JtlResolver {
requestStatistics.setMin(elapsedList.get(0)+"");
requestStatistics.setMax(elapsedList.get(index-1)+"");
requestStatistics.setErrors(String.format("%.2f",failSize*100.0/index)+"%");
requestStatistics.setKo(failSize);
/**
* 所有的相同请求的bytes总和 / 1024 / 请求持续运行的时间=sum(bytes)/1024/total time
*/
// todo Avg Bandwidth(KBytes/s) 请求之间时间戳间隔l 可能为0
requestStatistics.setKbPerSec(String.format("%.2f",totalBytes*1.0/1024/(l*1.0/1000)));
requestStatisticsList.add(requestStatistics);
}
return requestStatisticsList;
Collections.sort(allelapse);
Integer totalTP90 = allelapse.size()*90/100;
Integer totalTP95 = allelapse.size()*95/100;
Integer totalTP99 = allelapse.size()*99/100;
Integer min = allelapse.get(0);
Integer max = allelapse.get(allelapse.size() - 1);
Integer allSamples = requestStatisticsList.stream().mapToInt(RequestStatistics::getSamples).sum();
Integer failSize = requestStatisticsList.stream().mapToInt(RequestStatistics::getKo).sum();
DecimalFormat df = new DecimalFormat("0.00");
double errors = (double)failSize / allSamples * 100;
String totalerrors = df.format(errors);
double average = (double)totalAverage / allSamples;
String totalaverage = df.format(average);
statisticsDTO.setRequestStatisticsList(requestStatisticsList);
statisticsDTO.setTotalLabel("Total");
statisticsDTO.setTotalSamples(String.valueOf(allSamples));
statisticsDTO.setTotalErrors(totalerrors + "%");
statisticsDTO.setTotalAverage(totalaverage);
statisticsDTO.setTotalMin(String.valueOf(min));
statisticsDTO.setTotalMax(String.valueOf(max));
statisticsDTO.setTotalTP90(String.valueOf(allelapse.get(totalTP90)));
statisticsDTO.setTotalTP95(String.valueOf(allelapse.get(totalTP95)));
statisticsDTO.setTotalTP99(String.valueOf(allelapse.get(totalTP99)));
// todo
// statisticsDTO.setTotalAvgBandwidth();
return statisticsDTO;
}
public static List<RequestStatistics> getRequestStatistics(String jtlString) {
// Aggregate Report
public static RequestStatisticsDTO getRequestStatistics(String jtlString) {
List<Metric> totalLines = resolver(jtlString);
Map<String, List<Metric>> map = totalLines.stream().collect(Collectors.groupingBy(Metric::getLabel));
return getOneRpsResult(map);
}
// Errors
public static List<Errors> getErrorsList(String jtlString) {
List<Metric> totalLines = resolver(jtlString);
List<Metric> falseList = totalLines.stream().filter(metric -> StringUtils.equals("false", metric.getSuccess())).collect(Collectors.toList());
List<Errors> errorsList = new ArrayList<>();
Map<String, List<Metric>> collect = falseList.stream().collect(Collectors.groupingBy(JtlResolver::getResponseCodeAndFailureMessage));
Iterator<Map.Entry<String, List<Metric>>> iterator = collect.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, List<Metric>> next = iterator.next();
String key = next.getKey();
List<Metric> value = next.getValue();
Errors errors = new Errors();
errors.setErrorType(key);
errors.setErrorNumber(String.valueOf(value.size()));
Integer errorSize = value.size();
Integer errorAllSize = falseList.size();
Integer allSamples = totalLines.size();
DecimalFormat df = new DecimalFormat("0.00");
errors.setPrecentOfErrors(df.format((double)errorSize / errorAllSize * 100) + "%");
errors.setPrecentOfAllSamples(df.format((double)errorSize / allSamples * 100) + "%");
errorsList.add(errors);
}
return errorsList;
}
private static String getResponseCodeAndFailureMessage(Metric metric) {
return metric.getResponseCode() + "/" + metric.getResponseMessage();
}
}

View File

@ -0,0 +1,41 @@
package io.metersphere.report.base;
public class Errors {
private String errorType;
private String errorNumber;
private String precentOfErrors;
private String precentOfAllSamples;
public String getErrorType() {
return errorType;
}
public void setErrorType(String errorType) {
this.errorType = errorType;
}
public String getErrorNumber() {
return errorNumber;
}
public void setErrorNumber(String errorNumber) {
this.errorNumber = errorNumber;
}
public String getPrecentOfErrors() {
return precentOfErrors;
}
public void setPrecentOfErrors(String precentOfErrors) {
this.precentOfErrors = precentOfErrors;
}
public String getPrecentOfAllSamples() {
return precentOfAllSamples;
}
public void setPrecentOfAllSamples(String precentOfAllSamples) {
this.precentOfAllSamples = precentOfAllSamples;
}
}

View File

@ -35,6 +35,9 @@ public class RequestStatistics {
/**错误率 Error Percentage */
private String errors;
/**错误个数*/
private Integer ko;
public String getRequestLabel() {
return requestLabel;
}
@ -122,4 +125,12 @@ public class RequestStatistics {
public void setErrors(String errors) {
this.errors = errors;
}
public Integer getKo() {
return ko;
}
public void setKo(Integer ko) {
this.ko = ko;
}
}

View File

@ -0,0 +1,116 @@
package io.metersphere.report.base;
import java.util.List;
public class RequestStatisticsDTO extends RequestStatistics {
private List<RequestStatistics> requestStatisticsList;
private String totalLabel;
private String totalSamples;
private String totalErrors;
private String totalAverage;
private String totalMin;
private String totalMax;
private String totalTP90;
private String totalTP95;
private String totalTP99;
private String totalAvgBandwidth;
public List<RequestStatistics> getRequestStatisticsList() {
return requestStatisticsList;
}
public void setRequestStatisticsList(List<RequestStatistics> requestStatisticsList) {
this.requestStatisticsList = requestStatisticsList;
}
public String getTotalLabel() {
return totalLabel;
}
public void setTotalLabel(String totalLabel) {
this.totalLabel = totalLabel;
}
public String getTotalSamples() {
return totalSamples;
}
public void setTotalSamples(String totalSamples) {
this.totalSamples = totalSamples;
}
public String getTotalErrors() {
return totalErrors;
}
public void setTotalErrors(String totalErrors) {
this.totalErrors = totalErrors;
}
public String getTotalAverage() {
return totalAverage;
}
public void setTotalAverage(String totalAverage) {
this.totalAverage = totalAverage;
}
public String getTotalMin() {
return totalMin;
}
public void setTotalMin(String totalMin) {
this.totalMin = totalMin;
}
public String getTotalMax() {
return totalMax;
}
public void setTotalMax(String totalMax) {
this.totalMax = totalMax;
}
public String getTotalTP90() {
return totalTP90;
}
public void setTotalTP90(String totalTP90) {
this.totalTP90 = totalTP90;
}
public String getTotalTP95() {
return totalTP95;
}
public void setTotalTP95(String totalTP95) {
this.totalTP95 = totalTP95;
}
public String getTotalTP99() {
return totalTP99;
}
public void setTotalTP99(String totalTP99) {
this.totalTP99 = totalTP99;
}
public String getTotalAvgBandwidth() {
return totalAvgBandwidth;
}
public void setTotalAvgBandwidth(String totalAvgBandwidth) {
this.totalAvgBandwidth = totalAvgBandwidth;
}
}

View File

@ -174,16 +174,7 @@ public class LoadTestService {
MSException.throwException(String.format("Test cannot be runtest ID%s", request.getId()));
}
boolean init = true;
try {
init = engine.init(loadTest);
} catch (Exception e) {
MSException.throwException(e);
}
if (!init) {
MSException.throwException(Translator.get("run_load_test_file_init_error") + request.getId());
}
// 启动测试
engine.start();
// 标记running状态
loadTest.setStatus(TestStatus.Running.name());

View File

@ -0,0 +1,47 @@
package io.metersphere.service;
import io.metersphere.base.domain.SystemParameter;
import io.metersphere.base.domain.SystemParameterExample;
import io.metersphere.base.mapper.SystemParameterMapper;
import io.metersphere.commons.constants.ParamConstants;
import io.metersphere.commons.utils.EncryptUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
@Service
@Transactional(rollbackFor = Exception.class)
public class RegistryParamService {
@Resource
private SystemParameterMapper parameterMapper;
public List<SystemParameter> getRegistry(String type) {
List<SystemParameter> paramList = this.getParamList(type);
for (SystemParameter parameter : paramList) {
if (StringUtils.equalsIgnoreCase(ParamConstants.Type.PASSWORD.getValue(), parameter.getType())) {
parameter.setParamValue(EncryptUtils.aesDecrypt(parameter.getParamValue()).toString());
}
}
paramList.sort(Comparator.comparingInt(SystemParameter::getSort));
return paramList;
}
public void updateRegistry(List<SystemParameter> parameters) {
for (SystemParameter parameter : parameters) {
if (StringUtils.equalsIgnoreCase(ParamConstants.Type.PASSWORD.getValue(), parameter.getType())) {
parameter.setParamValue(EncryptUtils.aesEncrypt(parameter.getParamValue()).toString());
}
parameterMapper.updateByPrimaryKey(parameter);
}
}
public List<SystemParameter> getParamList(String type) {
SystemParameterExample example = new SystemParameterExample();
example.createCriteria().andParamKeyLike(type + "%");
return parameterMapper.selectByExample(example);
}
}

View File

@ -7,7 +7,9 @@ import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper;
import io.metersphere.controller.request.ReportRequest;
import io.metersphere.dto.ReportDTO;
import io.metersphere.report.JtlResolver;
import io.metersphere.report.base.Errors;
import io.metersphere.report.base.RequestStatistics;
import io.metersphere.report.base.RequestStatisticsDTO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -44,10 +46,17 @@ public class ReportService {
return extLoadTestReportMapper.getReportTestAndProInfo(reportId);
}
public List<RequestStatistics> getReport(String id) {
public RequestStatisticsDTO getReport(String id) {
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(id);
String content = loadTestReport.getContent();
List<RequestStatistics> requestStatistics = JtlResolver.getRequestStatistics(content);
RequestStatisticsDTO requestStatistics = JtlResolver.getRequestStatistics(content);
return requestStatistics;
}
public List<Errors> getReportErrors(String id) {
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(id);
String content = loadTestReport.getContent();
List<Errors> errors = JtlResolver.getErrorsList(content);
return errors;
}
}

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.TestResource;
import io.metersphere.base.domain.TestResourceExample;
import io.metersphere.base.domain.TestResourcePool;
import io.metersphere.base.domain.TestResourcePoolExample;
import io.metersphere.base.mapper.TestResourceMapper;
import io.metersphere.base.mapper.TestResourcePoolMapper;
import io.metersphere.base.mapper.ext.ExtTestReourcePoolMapper;
@ -25,6 +26,8 @@ import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
import static io.metersphere.commons.constants.ResourceStatusEnum.VALID;
/**
* @author dongbin
*/
@ -45,7 +48,7 @@ public class TestResourcePoolService {
testResourcePool.setId(UUID.randomUUID().toString());
testResourcePool.setCreateTime(System.currentTimeMillis());
testResourcePool.setUpdateTime(System.currentTimeMillis());
testResourcePool.setStatus(ResourceStatusEnum.VALID.name());
testResourcePool.setStatus(VALID.name());
validateTestResourcePool(testResourcePool);
testResourcePoolMapper.insertSelective(testResourcePool);
return testResourcePool;
@ -87,7 +90,7 @@ public class TestResourcePoolService {
testResourcePool.setStatus(ResourceStatusEnum.INVALID.name());
resource.setStatus(ResourceStatusEnum.INVALID.name());
} else {
resource.setStatus(ResourceStatusEnum.VALID.name());
resource.setStatus(VALID.name());
}
resource.setTestResourcePoolId(testResourcePool.getId());
updateTestResource(resource);
@ -116,7 +119,7 @@ public class TestResourcePoolService {
try {
KubernetesProvider provider = new KubernetesProvider(testResource.getConfiguration());
provider.validateCredential();
testResource.setStatus(ResourceStatusEnum.VALID.name());
testResource.setStatus(VALID.name());
} catch (Exception e) {
testResource.setStatus(ResourceStatusEnum.INVALID.name());
testResourcePool.setStatus(ResourceStatusEnum.INVALID.name());
@ -142,4 +145,10 @@ public class TestResourcePoolService {
public TestResourcePool getResourcePool(String resourcePoolId) {
return testResourcePoolMapper.selectByPrimaryKey(resourcePoolId);
}
public List<TestResourcePool> listValidResourcePools() {
TestResourcePoolExample example = new TestResourcePoolExample();
example.createCriteria().andStatusEqualTo(ResourceStatusEnum.VALID.name());
return testResourcePoolMapper.selectByExample(example);
}
}

View File

@ -9,3 +9,8 @@ INSERT INTO role (id, name, description, type, create_time, update_time) VALUES
INSERT INTO role (id, name, description, type, create_time, update_time) VALUES ('test_manager', '测试经理', null, null, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time) VALUES ('test_user', '测试人员', null, null, 1581576575948, 1581576575948);
INSERT INTO role (id, name, description, type, create_time, update_time) VALUES ('test_viewer', 'Viewer', null, null, 1581576575948, 1581576575948);
INSERT INTO `system_parameter`(`param_key`, `param_value`, `type`, `sort`) VALUES ('registry.password', '', 'password', 4);
INSERT INTO `system_parameter`(`param_key`, `param_value`, `type`, `sort`) VALUES ('registry.repo', '', 'text', 2);
INSERT INTO `system_parameter`(`param_key`, `param_value`, `type`, `sort`) VALUES ('registry.url', '', 'text', 1);
INSERT INTO `system_parameter`(`param_key`, `param_value`, `type`, `sort`) VALUES ('registry.username', '', 'text', 3);

View File

@ -1,9 +1,12 @@
package io.metersphere;
import io.metersphere.base.domain.User;
import io.metersphere.base.domain.SystemParameter;
import io.metersphere.base.mapper.UserMapper;
import io.metersphere.commons.constants.ParamConstants;
import io.metersphere.commons.utils.CompressUtils;
import io.metersphere.engine.kubernetes.registry.RegistryService;
import io.metersphere.service.RegistryParamService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
@ -12,11 +15,15 @@ import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.List;
//@RunWith(SpringRunner.class)
//@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApplicationTests {
// @Resource
// UserMapper userMapper;
@Resource
UserMapper userMapper;
@Resource
private RegistryParamService registryParamService;
@Resource
private RegistryService registryService;
@Test
public void test1() {
@ -24,4 +31,33 @@ public class ApplicationTests {
final Object unzip = CompressUtils.unzip(test);
System.out.println(new String((byte[]) unzip));
}
@Test
public void test2() {
List<SystemParameter> registry = registryParamService.getRegistry(ParamConstants.Classify.REGISTRY.getValue());
System.out.println(registry);
for (SystemParameter sp : registry) {
if ("registry.password".equals(sp.getParamKey())) {
sp.setParamValue("Calong@2015");
}
if ("registry.url".equals(sp.getParamKey())) {
sp.setParamValue("registry.fit2cloud.com");
}
if ("registry.repo".equals(sp.getParamKey())) {
sp.setParamValue("metersphere");
}
if ("registry.username".equals(sp.getParamKey())) {
sp.setParamValue("developer");
}
}
registryParamService.updateRegistry(registry);
}
@Test
public void test3() {
String registry = registryService.getRegistry();
System.out.println(registry);
}
}

View File

@ -148,7 +148,7 @@
},
methods: {
getResourcePools() {
this.$get('/testresourcepool/list/all', response => {
this.$get('/testresourcepool/list/all/valid', response => {
this.resourcePools = response.data;
})
},

View File

@ -41,7 +41,7 @@
<ms-report-request-statistics :id="reportId"/>
</el-tab-pane>
<el-tab-pane :label="$t('report.test_error_log')">
<ms-report-error-log />
<ms-report-error-log :id="reportId"/>
</el-tab-pane>
<el-tab-pane :label="$t('report.test_log_details')">
<ms-report-log-details />

View File

@ -1,32 +1,123 @@
<template>
<div>
<span class="table-title">Errors</span>
<el-table
:data="tableData"
border
stripe
style="width: 100%"
:default-sort = "{prop: 'elementLabel'}"
>
<el-table-column
prop="typeOfError"
prop="errorType"
label="Type of Error"
sortable>
</el-table-column>
<el-table-column
prop="numberOfErrors"
prop="errorNumber"
label="Number of errors"
sortable>
</el-table-column>
<el-table-column
prop="error"
prop="precentOfErrors"
label="% in errors"
sortable>
</el-table-column>
<el-table-column
prop="allSamples"
prop="precentOfAllSamples"
label="% in all samples"
sortable>
</el-table-column>
</el-table>
<div style="margin-top: 40px;"></div>
<span class="table-title">Top 5 Errors by sampler </span>
<el-table
:data="tableData"
border
stripe
style="width: 100%"
>
<el-table-column
prop="errorType"
label="Sample"
width="400"
>
</el-table-column>
<el-table-column
prop="errorNumber"
label="#Samples"
width="120"
>
</el-table-column>
<el-table-column
prop="#Errors"
label="#Errors"
width="100"
>
</el-table-column>
<el-table-column
prop="Error"
label="Error"
width="400"
>
</el-table-column>
<el-table-column
prop="#Errors"
label="#Errors"
width="100"
>
</el-table-column>
<el-table-column
prop="Error"
label="Error"
width="400"
>
</el-table-column>
<el-table-column
prop="#Errors"
label="#Errors"
width="100"
>
</el-table-column>
<el-table-column
prop="Error"
label="Error"
width="400"
>
</el-table-column>
<el-table-column
prop="#Errors"
label="#Errors"
width="100"
>
</el-table-column>
<el-table-column
prop="Error"
label="Error"
width="400"
>
</el-table-column>
<el-table-column
prop="#Errors"
label="#Errors"
width="100"
>
</el-table-column>
<el-table-column
prop="Error"
label="Error"
width="400"
>
</el-table-column>
<el-table-column
prop="#Errors"
label="#Errors"
width="100"
>
</el-table-column>
</el-table>
</div>
</template>
@ -39,11 +130,36 @@
}
},
methods: {
initTableData() {
this.$get("/report/content/errors/" + this.id, res => {
this.tableData = res.data;
})
}
},
created() {
this.initTableData();
},
props: ['id'],
watch: {
'$route'(to) {
if (to.name === "perReportView") {
let reportId = to.path.split('/')[4];
if(reportId){
this.$get("/report/content/errors/" + this.id, res => {
this.tableData = res.data;
})
}
}
}
}
}
</script>
<style scoped>
.table-title {
font-size: 20px;
color: #8492a6;
display: block;
margin-bottom: 8px;
}
</style>

View File

@ -6,6 +6,7 @@
border
style="width: 100%"
show-summary
:summary-method="getSummaries"
:default-sort = "{prop: 'samples', order: 'descending'}"
>
<el-table-column label="Requests" fixed width="450" align="center">
@ -27,7 +28,7 @@
prop="errors"
label="Error%"
align="center"
fixed="right"/>
/>
</el-table-column>
<el-table-column label="Response Times(ms)" align="center">
@ -79,30 +80,49 @@
data() {
return {
tableData: [{},{},{},{},{}],
totalInfo: {}
}
},
methods: {
initTableData() {
this.$get("/report/content/" + this.id, res => {
this.tableData = res.data;
this.tableData = res.data.requestStatisticsList;
this.totalInfo = res.data;
})
},
getSummaries () {
const sums = []
sums[0] = this.totalInfo.totalLabel;
sums[1] = this.totalInfo.totalSamples;
sums[2] = this.totalInfo.totalErrors;
sums[3] = this.totalInfo.totalAverage;
sums[4] = this.totalInfo.totalMin;
sums[5] = this.totalInfo.totalMax;
sums[6] = this.totalInfo.totalTP90;
sums[7] = this.totalInfo.totalTP95;
sums[8] = this.totalInfo.totalTP99;
return sums;
}
},
created() {
this.initTableData()
this.getSummaries()
},
props: ['id'],
watch: {
'$route'(to) {
if (to.name === "perReportView") {
let reportId = to.path.split('/')[4];
if(reportId){
this.$get("/report/content/" + reportId, res => {
this.tableData = res.data;
this.tableData = res.data.requestStatisticsList;
this.totalInfo = res.data;
})
}
}
}
}
}
</script>
<style scoped>