diff --git a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportDetail.java b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportDetail.java new file mode 100644 index 0000000000..80425ac525 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportDetail.java @@ -0,0 +1,27 @@ +package io.metersphere.base.domain; + +import java.io.Serializable; + +public class LoadTestReportDetail implements Serializable { + private String reportId; + + private String content; + + private static final long serialVersionUID = 1L; + + public String getReportId() { + return reportId; + } + + public void setReportId(String reportId) { + this.reportId = reportId == null ? null : reportId.trim(); + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content == null ? null : content.trim(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/domain/LoadTestReportDetailExample.java b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportDetailExample.java new file mode 100644 index 0000000000..af84db6d79 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/domain/LoadTestReportDetailExample.java @@ -0,0 +1,270 @@ +package io.metersphere.base.domain; + +import java.util.ArrayList; +import java.util.List; + +public class LoadTestReportDetailExample { + protected String orderByClause; + + protected boolean distinct; + + protected List oredCriteria; + + public LoadTestReportDetailExample() { + oredCriteria = new ArrayList(); + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isDistinct() { + return distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andReportIdIsNull() { + addCriterion("report_id is null"); + return (Criteria) this; + } + + public Criteria andReportIdIsNotNull() { + addCriterion("report_id is not null"); + return (Criteria) this; + } + + public Criteria andReportIdEqualTo(String value) { + addCriterion("report_id =", value, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdNotEqualTo(String value) { + addCriterion("report_id <>", value, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdGreaterThan(String value) { + addCriterion("report_id >", value, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdGreaterThanOrEqualTo(String value) { + addCriterion("report_id >=", value, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdLessThan(String value) { + addCriterion("report_id <", value, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdLessThanOrEqualTo(String value) { + addCriterion("report_id <=", value, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdLike(String value) { + addCriterion("report_id like", value, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdNotLike(String value) { + addCriterion("report_id not like", value, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdIn(List values) { + addCriterion("report_id in", values, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdNotIn(List values) { + addCriterion("report_id not in", values, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdBetween(String value1, String value2) { + addCriterion("report_id between", value1, value2, "reportId"); + return (Criteria) this; + } + + public Criteria andReportIdNotBetween(String value1, String value2) { + addCriterion("report_id not between", value1, value2, "reportId"); + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + + protected Criteria() { + super(); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportDetailMapper.java b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportDetailMapper.java new file mode 100644 index 0000000000..d19c9961ed --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportDetailMapper.java @@ -0,0 +1,35 @@ +package io.metersphere.base.mapper; + +import io.metersphere.base.domain.LoadTestReportDetail; +import io.metersphere.base.domain.LoadTestReportDetailExample; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface LoadTestReportDetailMapper { + long countByExample(LoadTestReportDetailExample example); + + int deleteByExample(LoadTestReportDetailExample example); + + int deleteByPrimaryKey(String reportId); + + int insert(LoadTestReportDetail record); + + int insertSelective(LoadTestReportDetail record); + + List selectByExampleWithBLOBs(LoadTestReportDetailExample example); + + List selectByExample(LoadTestReportDetailExample example); + + LoadTestReportDetail selectByPrimaryKey(String reportId); + + int updateByExampleSelective(@Param("record") LoadTestReportDetail record, @Param("example") LoadTestReportDetailExample example); + + int updateByExampleWithBLOBs(@Param("record") LoadTestReportDetail record, @Param("example") LoadTestReportDetailExample example); + + int updateByExample(@Param("record") LoadTestReportDetail record, @Param("example") LoadTestReportDetailExample example); + + int updateByPrimaryKeySelective(LoadTestReportDetail record); + + int updateByPrimaryKeyWithBLOBs(LoadTestReportDetail record); +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportDetailMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportDetailMapper.xml new file mode 100644 index 0000000000..8a19ff8977 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/LoadTestReportDetailMapper.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + report_id + + + content + + + + + + delete from load_test_report_detail + where report_id = #{reportId,jdbcType=VARCHAR} + + + delete from load_test_report_detail + + + + + + insert into load_test_report_detail (report_id, content) + values (#{reportId,jdbcType=VARCHAR}, #{content,jdbcType=LONGVARCHAR}) + + + insert into load_test_report_detail + + + report_id, + + + content, + + + + + #{reportId,jdbcType=VARCHAR}, + + + #{content,jdbcType=LONGVARCHAR}, + + + + + + update load_test_report_detail + + + report_id = #{record.reportId,jdbcType=VARCHAR}, + + + content = #{record.content,jdbcType=LONGVARCHAR}, + + + + + + + + update load_test_report_detail + set report_id = #{record.reportId,jdbcType=VARCHAR}, + content = #{record.content,jdbcType=LONGVARCHAR} + + + + + + update load_test_report_detail + set report_id = #{record.reportId,jdbcType=VARCHAR} + + + + + + update load_test_report_detail + + + content = #{content,jdbcType=LONGVARCHAR}, + + + where report_id = #{reportId,jdbcType=VARCHAR} + + + update load_test_report_detail + set content = #{content,jdbcType=LONGVARCHAR} + where report_id = #{reportId,jdbcType=VARCHAR} + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportDetailMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportDetailMapper.java new file mode 100644 index 0000000000..ceb2c64339 --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportDetailMapper.java @@ -0,0 +1,7 @@ +package io.metersphere.base.mapper.ext; + +import org.apache.ibatis.annotations.Param; + +public interface ExtLoadTestReportDetailMapper { + int appendLine(@Param("reportId") String id, @Param("line") String line); +} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportDetailMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportDetailMapper.xml new file mode 100644 index 0000000000..ca33e6e79d --- /dev/null +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtLoadTestReportDetailMapper.xml @@ -0,0 +1,9 @@ + + + + + UPDATE load_test_report_detail + SET content = concat(content, #{line}) + WHERE report_id = #{reportId} + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/commons/constants/PerformanceTestStatus.java b/backend/src/main/java/io/metersphere/commons/constants/PerformanceTestStatus.java new file mode 100644 index 0000000000..242883eb3f --- /dev/null +++ b/backend/src/main/java/io/metersphere/commons/constants/PerformanceTestStatus.java @@ -0,0 +1,5 @@ +package io.metersphere.commons.constants; + +public enum PerformanceTestStatus { + Saved, Starting, Running, Completed, Error +} diff --git a/backend/src/main/java/io/metersphere/commons/constants/TestStatus.java b/backend/src/main/java/io/metersphere/commons/constants/TestStatus.java deleted file mode 100644 index ccb4f1de29..0000000000 --- a/backend/src/main/java/io/metersphere/commons/constants/TestStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.metersphere.commons.constants; - -public enum TestStatus { - Starting, Running, Completed, Error -} diff --git a/backend/src/main/java/io/metersphere/controller/PerformanceTestController.java b/backend/src/main/java/io/metersphere/controller/PerformanceTestController.java index 0e50956747..a046035ed0 100644 --- a/backend/src/main/java/io/metersphere/controller/PerformanceTestController.java +++ b/backend/src/main/java/io/metersphere/controller/PerformanceTestController.java @@ -4,7 +4,6 @@ import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import io.metersphere.base.domain.FileMetadata; import io.metersphere.commons.constants.RoleConstants; -import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.Pager; import io.metersphere.controller.request.testplan.*; @@ -86,10 +85,7 @@ public class PerformanceTestController { @PostMapping("/run") public void run(@RequestBody RunTestPlanRequest request) { - boolean started = performanceTestService.run(request); - if (!started) { - MSException.throwException("Start engine error, please check log."); - } + performanceTestService.run(request); } @GetMapping("/file/metadata/{testId}") diff --git a/backend/src/main/java/io/metersphere/engine/AbstractEngine.java b/backend/src/main/java/io/metersphere/engine/AbstractEngine.java index cb90d08c1e..c7e07b5d90 100644 --- a/backend/src/main/java/io/metersphere/engine/AbstractEngine.java +++ b/backend/src/main/java/io/metersphere/engine/AbstractEngine.java @@ -6,8 +6,8 @@ import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.LoadTestWithBLOBs; import io.metersphere.base.domain.TestResource; import io.metersphere.base.domain.TestResourcePool; +import io.metersphere.commons.constants.PerformanceTestStatus; import io.metersphere.commons.constants.ResourcePoolTypeEnum; -import io.metersphere.commons.constants.TestStatus; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.config.JmeterProperties; @@ -70,7 +70,7 @@ public abstract class AbstractEngine implements Engine { List loadTests = performanceTestService.selectByTestResourcePoolId(loadTest.getTestResourcePoolId()); // 使用当前资源池正在运行的测试占用的并发数 return loadTests.stream() - .filter(t -> TestStatus.Running.name().equals(t.getStatus())) + .filter(t -> PerformanceTestStatus.Running.name().equals(t.getStatus())) .map(this::getThreadNum) .reduce(Integer::sum) .orElse(0); diff --git a/backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java b/backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java index 06c7892170..1c208ffc49 100644 --- a/backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java +++ b/backend/src/main/java/io/metersphere/engine/docker/DockerTestEngine.java @@ -12,6 +12,7 @@ import io.metersphere.engine.EngineContext; import io.metersphere.engine.EngineFactory; import io.metersphere.engine.docker.request.BaseRequest; import io.metersphere.engine.docker.request.TestRequest; +import io.metersphere.i18n.Translator; import org.springframework.web.client.RestTemplate; import java.util.List; @@ -41,7 +42,7 @@ public class DockerTestEngine extends AbstractEngine { .reduce(Integer::sum) .orElse(0); if (threadNum > totalThreadNum - runningSumThreadNum) { - MSException.throwException("Insufficient resources"); + MSException.throwException(Translator.get("max_thread_insufficient")); } List resourceRatio = resourceList.stream() .filter(r -> ResourceStatusEnum.VALID.name().equals(r.getStatus())) diff --git a/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java b/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java index dedb5e7a8a..e1e8a3f68d 100644 --- a/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java +++ b/backend/src/main/java/io/metersphere/engine/kubernetes/KubernetesTestEngine.java @@ -15,6 +15,7 @@ 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.i18n.Translator; import org.apache.commons.collections.MapUtils; import java.util.HashMap; @@ -43,7 +44,7 @@ public class KubernetesTestEngine extends AbstractEngine { Integer maxConcurrency = clientCredential.getMaxConcurrency(); // 当前测试需要的并发数大于剩余的并发数报错 if (threadNum > maxConcurrency - sumThreadNum) { - MSException.throwException("Insufficient resources"); + MSException.throwException(Translator.get("max_thread_insufficient")); } try { EngineContext context = EngineFactory.createContext(loadTest, threadNum, this.getStartTime(), this.getReportId()); diff --git a/backend/src/main/java/io/metersphere/report/JtlResolver.java b/backend/src/main/java/io/metersphere/report/JtlResolver.java index 2801927e00..f518c37b38 100644 --- a/backend/src/main/java/io/metersphere/report/JtlResolver.java +++ b/backend/src/main/java/io/metersphere/report/JtlResolver.java @@ -24,6 +24,8 @@ import java.util.stream.Collectors; public class JtlResolver { private static final Integer ERRORS_TOP_SIZE = 5; + private static final String DATE_TIME_PATTERN = "yyyy/MM/dd HH:mm:ss"; + private static final String TIME_PATTERN = "HH:mm:ss"; private static List resolver(String jtlString) { HeaderColumnNameMappingStrategy ms = new HeaderColumnNameMappingStrategy<>(); @@ -102,7 +104,7 @@ public class JtlResolver { String average = decimalFormat.format((float) oneLineElapsedTime / jtlSamplesSize); requestStatistics.setAverage(average); - /** + /* * TP90的计算 * 1,把一段时间内全部的请求的响应时间,从小到大排序,获得序列A * 2,总的请求数量,乘以90%,获得90%对应的请求个数C @@ -121,7 +123,7 @@ public class JtlResolver { requestStatistics.setMax(elapsedList.get(jtlSamplesSize - 1) + ""); requestStatistics.setErrors(decimalFormat.format(failSize * 100.0 / jtlSamplesSize) + "%"); requestStatistics.setKo(failSize); - /** + /* * 所有的相同请求的bytes总和 / 1024 / 请求持续运行的时间=sum(bytes)/1024/total time * total time = 最大时间戳 - 最小时间戳 + 最后请求的响应时间 */ @@ -266,20 +268,34 @@ public class JtlResolver { DecimalFormat decimalFormat = new DecimalFormat("0.00"); List totalLineList = JtlResolver.resolver(jtlString); + // todo + List totalLineList2 = JtlResolver.resolver(jtlString); + // 时间戳转时间 + for (Metric metric : totalLineList2) { + metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp())); + } + + Map> collect2 = Objects.requireNonNull(totalLineList2).stream().collect(Collectors.groupingBy(Metric::getTimestamp)); + List>> entries = new ArrayList<>(collect2.entrySet()); + int maxUsers = 0; + for (Map.Entry> entry : entries) { + List metrics = entry.getValue(); + Map> metricsMap = metrics.stream().collect(Collectors.groupingBy(Metric::getThreadName)); + if (metricsMap.size() > maxUsers) { + maxUsers = metricsMap.size(); + } + } + Map> collect = totalLineList.stream().collect(Collectors.groupingBy(Metric::getTimestamp)); Iterator>> iterator = collect.entrySet().iterator(); - int maxUsers = 0, totalElapsed = 0; + int totalElapsed = 0; float totalBytes = 0f; while (iterator.hasNext()) { Map.Entry> entry = iterator.next(); List metricList = entry.getValue(); - if (metricList.size() > maxUsers) { - maxUsers = metricList.size(); - } - for (Metric metric : metricList) { String elapsed = metric.getElapsed(); totalElapsed += Integer.parseInt(elapsed); @@ -323,7 +339,7 @@ public class JtlResolver { if (totalMetricList != null) { for (Metric metric : totalMetricList) { - metric.setTimestamp(stampToDate(metric.getTimestamp())); + metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp())); } } Map> collect = Objects.requireNonNull(totalMetricList).stream().collect(Collectors.groupingBy(Metric::getTimestamp)); @@ -376,7 +392,7 @@ public class JtlResolver { List totalMetricList = JtlResolver.resolver(jtlString); totalMetricList.forEach(metric -> { - metric.setTimestamp(stampToDate(metric.getTimestamp())); + metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp())); }); Map> metricMap = totalMetricList.stream().collect(Collectors.groupingBy(Metric::getTimestamp)); @@ -426,6 +442,11 @@ public class JtlResolver { reportTimeInfo.setStartTime(startTime); reportTimeInfo.setEndTime(endTime); + Date startDate = new Date(Long.parseLong(startTimeStamp)); + Date endDate = new Date(Long.parseLong(endTimeStamp)); + long timestamp = endDate.getTime() - startDate.getTime(); + reportTimeInfo.setDuration(String.valueOf(timestamp*1.0 / 1000 / 60)); + // todo 时间问题 long seconds = Duration.between(Instant.ofEpochMilli(Long.parseLong(startTimeStamp)), Instant.ofEpochMilli(Long.parseLong(endTimeStamp))).getSeconds(); reportTimeInfo.setDuration(String.valueOf(seconds)); @@ -433,11 +454,10 @@ public class JtlResolver { return reportTimeInfo; } - private static String stampToDate(String timeStamp) { - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - long lt = Long.parseLong(timeStamp); - Date date = new Date(lt); - return simpleDateFormat.format(date); + private static String stampToDate(String pattern, String timeStamp) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern); + LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(timeStamp)), ZoneId.systemDefault()); + return localDateTime.format(dateTimeFormatter); } /** @@ -445,8 +465,8 @@ public class JtlResolver { * @return "HH:mm:ss" */ private static String formatDate(String dateString) throws ParseException { - SimpleDateFormat before = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - SimpleDateFormat after = new SimpleDateFormat("HH:mm:ss"); + SimpleDateFormat before = new SimpleDateFormat(DATE_TIME_PATTERN); + SimpleDateFormat after = new SimpleDateFormat(TIME_PATTERN); return after.format(before.parse(dateString)); } diff --git a/backend/src/main/java/io/metersphere/service/PerformanceTestService.java b/backend/src/main/java/io/metersphere/service/PerformanceTestService.java index e72024a14a..37b22f71be 100644 --- a/backend/src/main/java/io/metersphere/service/PerformanceTestService.java +++ b/backend/src/main/java/io/metersphere/service/PerformanceTestService.java @@ -3,9 +3,10 @@ package io.metersphere.service; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtLoadTestMapper; +import io.metersphere.base.mapper.ext.ExtLoadTestReportDetailMapper; import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper; import io.metersphere.commons.constants.FileType; -import io.metersphere.commons.constants.TestStatus; +import io.metersphere.commons.constants.PerformanceTestStatus; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.LogUtil; import io.metersphere.controller.request.testplan.*; @@ -48,6 +49,10 @@ public class PerformanceTestService { private LoadTestReportMapper loadTestReportMapper; @Resource private ExtLoadTestReportMapper extLoadTestReportMapper; + @Resource + private LoadTestReportDetailMapper loadTestReportDetailMapper; + @Resource + private ExtLoadTestReportDetailMapper extLoadTestReportDetailMapper; public List list(QueryTestPlanRequest request) { return extLoadTestMapper.list(request); @@ -93,6 +98,7 @@ public class PerformanceTestService { loadTest.setTestResourcePoolId(request.getTestResourcePoolId()); loadTest.setLoadConfiguration(request.getLoadConfiguration()); loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration()); + loadTest.setStatus(PerformanceTestStatus.Saved.name()); loadTestMapper.insert(loadTest); return loadTest; } @@ -158,19 +164,22 @@ public class PerformanceTestService { loadTest.setLoadConfiguration(request.getLoadConfiguration()); loadTest.setAdvancedConfiguration(request.getAdvancedConfiguration()); loadTest.setTestResourcePoolId(request.getTestResourcePoolId()); + // todo 修改 load_test 的时候排除状态,这里存在修改了 Running 的测试状态的风险 +// loadTest.setStatus(PerformanceTestStatus.Saved.name()); loadTestMapper.updateByPrimaryKeySelective(loadTest); } return request.getId(); } - public boolean run(RunTestPlanRequest request) { + @Transactional(noRollbackFor = MSException.class)// 保存失败的信息 + public void run(RunTestPlanRequest request) { final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId()); if (loadTest == null) { MSException.throwException(Translator.get("run_load_test_not_found") + request.getId()); } - if (StringUtils.equalsAny(loadTest.getStatus(), TestStatus.Running.name(), TestStatus.Starting.name())) { + if (StringUtils.equalsAny(loadTest.getStatus(), PerformanceTestStatus.Running.name(), PerformanceTestStatus.Starting.name())) { MSException.throwException(Translator.get("load_test_is_running")); } @@ -181,12 +190,12 @@ public class PerformanceTestService { MSException.throwException(String.format("Test cannot be run,test ID:%s", request.getId())); } - return startEngine(loadTest, engine); + startEngine(loadTest, engine); // todo:通过调用stop方法能够停止正在运行的engine,但是如果部署了多个backend实例,页面发送的停止请求如何定位到具体的engine } - private boolean startEngine(LoadTestWithBLOBs loadTest, Engine engine) { + private void startEngine(LoadTestWithBLOBs loadTest, Engine engine) { LoadTestReportWithBLOBs testReport = new LoadTestReportWithBLOBs(); testReport.setId(engine.getReportId()); testReport.setCreateTime(engine.getStartTime()); @@ -194,31 +203,32 @@ public class PerformanceTestService { testReport.setTestId(loadTest.getId()); testReport.setName(loadTest.getName()); // 启动测试 - boolean started = true; + try { engine.start(); - // 标记running状态 - loadTest.setStatus(TestStatus.Starting.name()); + // 启动正常修改状态 starting + loadTest.setStatus(PerformanceTestStatus.Starting.name()); loadTestMapper.updateByPrimaryKeySelective(loadTest); - + // 启动正常插入 report testReport.setContent(HEADERS); - testReport.setStatus(TestStatus.Starting.name()); + testReport.setStatus(PerformanceTestStatus.Starting.name()); loadTestReportMapper.insertSelective(testReport); + + LoadTestReportDetail reportDetail = new LoadTestReportDetail(); + reportDetail.setContent(HEADERS); + reportDetail.setReportId(testReport.getId()); + loadTestReportDetailMapper.insertSelective(reportDetail); // append \n extLoadTestReportMapper.appendLine(testReport.getId(), "\n"); - - } catch (Exception e) { + // append \n + extLoadTestReportDetailMapper.appendLine(testReport.getId(), "\n"); + } catch (MSException e) { LogUtil.error(e); - started = false; - - loadTest.setStatus(TestStatus.Error.name()); + loadTest.setStatus(PerformanceTestStatus.Error.name()); + loadTest.setDescription(e.getMessage()); loadTestMapper.updateByPrimaryKeySelective(loadTest); - // - testReport.setStatus(TestStatus.Error.name()); - testReport.setDescription(e.getMessage()); - loadTestReportMapper.insertSelective(testReport); + throw e; } - return started; } public List recentTestPlans(QueryTestPlanRequest request) { diff --git a/backend/src/main/java/io/metersphere/service/ReportService.java b/backend/src/main/java/io/metersphere/service/ReportService.java index 890bfc133c..17b2f047fa 100644 --- a/backend/src/main/java/io/metersphere/service/ReportService.java +++ b/backend/src/main/java/io/metersphere/service/ReportService.java @@ -5,7 +5,7 @@ import io.metersphere.base.domain.LoadTestReportExample; import io.metersphere.base.domain.LoadTestReportWithBLOBs; import io.metersphere.base.mapper.LoadTestReportMapper; import io.metersphere.base.mapper.ext.ExtLoadTestReportMapper; -import io.metersphere.commons.constants.TestStatus; +import io.metersphere.commons.constants.PerformanceTestStatus; import io.metersphere.commons.exception.MSException; import io.metersphere.controller.request.ReportRequest; import io.metersphere.dto.ReportDTO; @@ -112,9 +112,9 @@ public class ReportService { public void checkReportStatus(String reportId) { LoadTestReportWithBLOBs loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId); String reportStatus = loadTestReport.getStatus(); - if (StringUtils.equals(TestStatus.Running.name(), reportStatus)) { + if (StringUtils.equals(PerformanceTestStatus.Running.name(), reportStatus)) { MSException.throwException("Reporting in progress..."); - } else if (StringUtils.equals(TestStatus.Error.name(), reportStatus)) { + } else if (StringUtils.equals(PerformanceTestStatus.Error.name(), reportStatus)) { MSException.throwException("Report generation error!"); } } diff --git a/backend/src/main/resources/db/migration/V2__metersphere_ddl.sql b/backend/src/main/resources/db/migration/V2__metersphere_ddl.sql index 2225e75a3a..74b8ff4364 100644 --- a/backend/src/main/resources/db/migration/V2__metersphere_ddl.sql +++ b/backend/src/main/resources/db/migration/V2__metersphere_ddl.sql @@ -62,6 +62,15 @@ CREATE TABLE IF NOT EXISTS `load_test_report` ( DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin; +CREATE TABLE IF NOT EXISTS `load_test_report_detail` ( + `report_id` varchar(50) NOT NULL, + `content` longtext, + PRIMARY KEY (`report_id`) +) + ENGINE=InnoDB + DEFAULT CHARSET=utf8mb4 + COLLATE=utf8mb4_bin; + CREATE TABLE IF NOT EXISTS `organization` ( `id` varchar(50) NOT NULL COMMENT 'Organization ID', `name` varchar(64) NOT NULL COMMENT 'Organization name', diff --git a/backend/src/main/resources/i18n/en-US.json b/backend/src/main/resources/i18n/en-US.json index b88c742725..c5329f867f 100644 --- a/backend/src/main/resources/i18n/en-US.json +++ b/backend/src/main/resources/i18n/en-US.json @@ -18,5 +18,6 @@ "no_nodes_message": "No node message", "duplicate_node_ip": "Duplicate IPs", "only_one_k8s": "Only one K8s can be added", - "organization_id_is_null": "Organization ID cannot be null" + "organization_id_is_null": "Organization ID cannot be null", + "max_thread_insufficient": "The number of concurrent users exceeds" } \ No newline at end of file diff --git a/backend/src/main/resources/i18n/zh-CN.json b/backend/src/main/resources/i18n/zh-CN.json index 56352f221a..0884b07c0f 100644 --- a/backend/src/main/resources/i18n/zh-CN.json +++ b/backend/src/main/resources/i18n/zh-CN.json @@ -18,5 +18,6 @@ "no_nodes_message": "没有节点信息", "duplicate_node_ip": "节点 IP 重复", "only_one_k8s": "只能添加一个 K8s", - "organization_id_is_null": "组织 ID 不能为空" + "organization_id_is_null": "组织 ID 不能为空", + "max_thread_insufficient": "并发用户数超额" } \ No newline at end of file diff --git a/backend/src/test/java/io/metersphere/ReportContentTests.java b/backend/src/test/java/io/metersphere/ReportContentTests.java new file mode 100644 index 0000000000..bac9afcd74 --- /dev/null +++ b/backend/src/test/java/io/metersphere/ReportContentTests.java @@ -0,0 +1,61 @@ +package io.metersphere; + +import com.opencsv.bean.CsvToBean; +import com.opencsv.bean.CsvToBeanBuilder; +import com.opencsv.bean.HeaderColumnNameMappingStrategy; +import io.metersphere.base.domain.LoadTestReportDetail; +import io.metersphere.base.domain.LoadTestReportWithBLOBs; +import io.metersphere.base.mapper.LoadTestReportDetailMapper; +import io.metersphere.base.mapper.LoadTestReportMapper; +import io.metersphere.report.base.Metric; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; +import java.io.Reader; +import java.io.StringReader; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class ReportContentTests { + @Resource + private LoadTestReportDetailMapper loadTestReportDetailMapper; + @Resource + private LoadTestReportMapper loadTestReportMapper; + + @Test + public void test1() { + String reportId = "ba972086-7d74-4f58-99b0-9c014114fd99"; + LoadTestReportDetail loadTestReportDetail = loadTestReportDetailMapper.selectByPrimaryKey(reportId); + LoadTestReportWithBLOBs loadTestReportWithBLOBs = loadTestReportMapper.selectByPrimaryKey(reportId); + + HeaderColumnNameMappingStrategy ms = new HeaderColumnNameMappingStrategy<>(); + ms.setType(Metric.class); + try (Reader reader = new StringReader(loadTestReportDetail.getContent())) { + CsvToBean cb = new CsvToBeanBuilder(reader) + .withType(Metric.class) + .withSkipLines(0) + .withMappingStrategy(ms) + .withIgnoreLeadingWhiteSpace(true) + .build(); + System.out.println(cb.parse().size()); + + } catch (Exception ex) { + ex.printStackTrace(); + } + try (Reader reader = new StringReader(loadTestReportWithBLOBs.getContent())) { + CsvToBean cb = new CsvToBeanBuilder(reader) + .withType(Metric.class) + .withSkipLines(0) + .withMappingStrategy(ms) + .withIgnoreLeadingWhiteSpace(true) + .build(); + System.out.println(cb.parse().size()); + + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} diff --git a/frontend/src/business/components/api/test/ApiTestList.vue b/frontend/src/business/components/api/test/ApiTestList.vue index ca3f38bc8e..b60c6aed03 100644 --- a/frontend/src/business/components/api/test/ApiTestList.vue +++ b/frontend/src/business/components/api/test/ApiTestList.vue @@ -7,11 +7,11 @@ {{$t('commons.test')}} - - + + @@ -56,34 +56,21 @@ -
- - -
- - -
-
-
-
+ + + diff --git a/frontend/src/business/components/performance/report/PerformanceReportView.vue b/frontend/src/business/components/performance/report/PerformanceReportView.vue index 3122b407c8..58a63cc689 100644 --- a/frontend/src/business/components/performance/report/PerformanceReportView.vue +++ b/frontend/src/business/components/performance/report/PerformanceReportView.vue @@ -147,7 +147,9 @@ if(data){ this.startTime = data.startTime; this.endTime = data.endTime; - this.duration = data.duration; + let duration = data.duration; + this.minutes = Math.floor(duration / 60); + this.seconds = duration % 60; } }) window.location.reload(); diff --git a/frontend/src/business/components/performance/report/PerformanceTestReport.vue b/frontend/src/business/components/performance/report/PerformanceTestReport.vue index 38760aafe1..4a3c2b3cde 100644 --- a/frontend/src/business/components/performance/report/PerformanceTestReport.vue +++ b/frontend/src/business/components/performance/report/PerformanceTestReport.vue @@ -53,6 +53,9 @@ {{ row.status }} + + {{ row.status }} + -
- - -
- - -
-
-
-
+ @@ -99,8 +87,11 @@ diff --git a/frontend/src/business/components/settings/organization/OrganizationMember.vue b/frontend/src/business/components/settings/organization/OrganizationMember.vue index de0e9aaa91..a1f9a7fd8f 100644 --- a/frontend/src/business/components/settings/organization/OrganizationMember.vue +++ b/frontend/src/business/components/settings/organization/OrganizationMember.vue @@ -2,7 +2,7 @@