feat(性能测试): 支持自定义配置NODE节点线程数
This commit is contained in:
parent
bf902538e0
commit
133e9ea772
|
@ -111,6 +111,12 @@ public class EngineFactory {
|
|||
if (org.springframework.util.CollectionUtils.isEmpty(fileMetadataList)) {
|
||||
MSException.throwException(Translator.get("run_load_test_file_not_found") + loadTestReport.getTestId());
|
||||
}
|
||||
// 报告页面点击下载执行zip
|
||||
boolean isLocal = false;
|
||||
if (ratios.length == 1 && ratios[0] < 0) {
|
||||
ratios[0] = 1;
|
||||
isLocal = true;
|
||||
}
|
||||
|
||||
List<FileMetadata> jmxFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JMX.name())).collect(Collectors.toList());
|
||||
List<FileMetadata> resourceFiles = ListUtils.subtract(fileMetadataList, jmxFiles);
|
||||
|
@ -132,6 +138,22 @@ public class EngineFactory {
|
|||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
if (jsonArray.get(i) instanceof List) {
|
||||
JSONArray o = jsonArray.getJSONArray(i);
|
||||
String strategy = "auto";
|
||||
int resourceNodeIndex = 0;
|
||||
JSONArray tgRatios = null;
|
||||
for (int j = 0; j < o.size(); j++) {
|
||||
JSONObject b = o.getJSONObject(j);
|
||||
String key = b.getString("key");
|
||||
if ("strategy".equals(key) && !isLocal) {
|
||||
strategy = b.getString("value");
|
||||
}
|
||||
if ("resourceNodeIndex".equals(key)) {
|
||||
resourceNodeIndex = b.getIntValue("value");
|
||||
}
|
||||
if ("ratios".equals(key)) {
|
||||
tgRatios = b.getJSONArray("value");
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < o.size(); j++) {
|
||||
JSONObject b = o.getJSONObject(j);
|
||||
String key = b.getString("key");
|
||||
|
@ -142,17 +164,45 @@ public class EngineFactory {
|
|||
if (values instanceof List) {
|
||||
Object value = b.get("value");
|
||||
if ("TargetLevel".equals(key)) {
|
||||
Integer targetLevel = ((Integer) b.get("value"));
|
||||
if (resourceIndex + 1 == ratios.length) {
|
||||
double beforeLast = 0; // 前几个线程数
|
||||
for (int k = 0; k < ratios.length - 1; k++) {
|
||||
beforeLast += Math.round(targetLevel * ratios[k]);
|
||||
}
|
||||
value = Math.round(targetLevel - beforeLast);
|
||||
} else {
|
||||
value = Math.round(targetLevel * ratios[resourceIndex]);
|
||||
switch (strategy) {
|
||||
default:
|
||||
case "auto":
|
||||
Integer targetLevel = ((Integer) b.get("value"));
|
||||
if (resourceIndex + 1 == ratios.length) {
|
||||
double beforeLast = 0; // 前几个线程数
|
||||
for (int k = 0; k < ratios.length - 1; k++) {
|
||||
beforeLast += Math.round(targetLevel * ratios[k]);
|
||||
}
|
||||
value = Math.round(targetLevel - beforeLast);
|
||||
} else {
|
||||
value = Math.round(targetLevel * ratios[resourceIndex]);
|
||||
}
|
||||
break;
|
||||
case "specify":
|
||||
Integer threadNum = ((Integer) b.get("value"));
|
||||
if (resourceNodeIndex == resourceIndex) {
|
||||
value = Math.round(threadNum);
|
||||
} else {
|
||||
value = Math.round(0);
|
||||
}
|
||||
break;
|
||||
case "custom":
|
||||
Integer threadNum2 = ((Integer) b.get("value"));
|
||||
if (CollectionUtils.isNotEmpty(tgRatios)) {
|
||||
if (resourceIndex + 1 == tgRatios.size()) {
|
||||
double beforeLast = 0; // 前几个线程数
|
||||
for (int k = 0; k < tgRatios.size() - 1; k++) {
|
||||
beforeLast += Math.round(threadNum2 * tgRatios.getDoubleValue(k));
|
||||
}
|
||||
value = Math.round(threadNum2 - beforeLast);
|
||||
} else {
|
||||
value = Math.round(threadNum2 * tgRatios.getDoubleValue(resourceIndex));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
((List<Object>) values).add(value);
|
||||
engineContext.addProperty(key, values);
|
||||
}
|
||||
|
|
|
@ -382,7 +382,7 @@ export default {
|
|||
let testId = this.report.testId;
|
||||
let reportId = this.report.id;
|
||||
let resourceIndex = 0;
|
||||
let ratio = "1.0";
|
||||
let ratio = "-1";
|
||||
let config = {
|
||||
url: `/jmeter/download?testId=${testId}&ratio=${ratio}&reportId=${reportId}&resourceIndex=${resourceIndex}`,
|
||||
method: 'get',
|
||||
|
|
|
@ -210,6 +210,38 @@
|
|||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_seconds')"/>
|
||||
</div>
|
||||
<!-- 资源池自己配置各个节点的并发 -->
|
||||
<div v-if="resourcePoolType === 'NODE'">
|
||||
<el-radio-group v-model="threadGroup.strategy" :disabled="isReadOnly" style="padding-bottom: 10px;">
|
||||
<el-radio label="auto">自动分配</el-radio>
|
||||
<el-radio label="specify">固定节点</el-radio>
|
||||
<el-radio label="custom">自定义</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="threadGroup.strategy === 'auto'"></div>
|
||||
<div v-else-if="threadGroup.strategy === 'specify'">
|
||||
<el-select v-model="threadGroup.resourceNodeIndex" :disabled="isReadOnly" size="mini">
|
||||
<el-option
|
||||
v-for="(node, index) in resourceNodes"
|
||||
:key="node.ip"
|
||||
:label="node.ip"
|
||||
:value="index">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-table :data="threadGroup.resourceNodes" :max-height="200">
|
||||
<el-table-column type="index" width="50"/>
|
||||
<el-table-column prop="ip" label="IP"/>
|
||||
<el-table-column prop="maxConcurrency" :label="$t('test_resource_pool.max_threads')"/>
|
||||
<el-table-column prop="ratio" label="占比">
|
||||
<template v-slot:default="{row}">
|
||||
<el-input-number size="small" v-model="row.ratio" :min="0" :step=".1"
|
||||
:max="1"></el-input-number>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
@ -249,11 +281,10 @@ const THREAD_TYPE = "threadType";
|
|||
const ITERATE_NUM = "iterateNum";
|
||||
const ENABLED = "enabled";
|
||||
const DELETED = "deleted";
|
||||
const STRATEGY = "strategy";
|
||||
const RESOURCE_NODE_INDEX = "resourceNodeIndex";
|
||||
const RATIOS = "ratios";
|
||||
|
||||
const hexToRgba = function (hex, opacity) {
|
||||
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
|
||||
+ parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
|
||||
};
|
||||
const hexToRgb = function (hex) {
|
||||
return 'rgb(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5))
|
||||
+ ',' + parseInt('0x' + hex.slice(5, 7)) + ')';
|
||||
|
@ -282,6 +313,8 @@ export default {
|
|||
options: {},
|
||||
resourcePool: null,
|
||||
resourcePools: [],
|
||||
resourceNodes: [],
|
||||
resourcePoolType: null,
|
||||
activeNames: ["0"],
|
||||
threadGroups: [],
|
||||
maxThreadNumbers: 5000,
|
||||
|
@ -402,6 +435,15 @@ export default {
|
|||
case ON_SAMPLE_ERROR:
|
||||
this.threadGroups[i].onSampleError = item.value;
|
||||
break;
|
||||
case STRATEGY:
|
||||
this.threadGroups[i].strategy = item.value;
|
||||
break;
|
||||
case RESOURCE_NODE_INDEX:
|
||||
this.threadGroups[i].resourceNodeIndex = item.value;
|
||||
break;
|
||||
case RATIOS:
|
||||
this.threadGroups[i].ratios = item.value;
|
||||
break;
|
||||
case SERIALIZE_THREAD_GROUPS:
|
||||
this.serializeThreadGroups = item.value;// 所有的线程组值一样
|
||||
break;
|
||||
|
@ -430,6 +472,7 @@ export default {
|
|||
tg.durationMinutes = Math.floor((tg.duration / 60 % 60));
|
||||
tg.durationSeconds = Math.floor((tg.duration % 60));
|
||||
}
|
||||
this.resourcePoolChange();
|
||||
this.calculateTotalChart();
|
||||
}
|
||||
});
|
||||
|
@ -454,15 +497,45 @@ export default {
|
|||
let result = this.resourcePools.filter(p => p.id === this.resourcePool);
|
||||
if (result.length === 1) {
|
||||
let threadNumber = 0;
|
||||
this.resourceNodes = [];
|
||||
this.resourcePoolType = result[0].type;
|
||||
result[0].resources.forEach(resource => {
|
||||
threadNumber += JSON.parse(resource.configuration).maxConcurrency;
|
||||
let config = JSON.parse(resource.configuration);
|
||||
threadNumber += config.maxConcurrency;
|
||||
this.resourceNodes.push(config);
|
||||
});
|
||||
this.$set(this, 'maxThreadNumbers', threadNumber);
|
||||
this.threadGroups.forEach(tg => {
|
||||
if (tg.threadNumber > threadNumber) {
|
||||
this.$set(tg, "threadNumber", threadNumber);
|
||||
}
|
||||
let tgRatios = tg.ratios;
|
||||
let resourceNodes = JSON.parse(JSON.stringify(this.resourceNodes));
|
||||
let ratios = resourceNodes.map(n => n.maxConcurrency).reduce((total, curr) => {
|
||||
total += curr;
|
||||
return total;
|
||||
}, 0);
|
||||
let preSum = 0;
|
||||
for (let i = 0; i < resourceNodes.length; i++) {
|
||||
let n = resourceNodes[i];
|
||||
if (resourceNodes.length === tgRatios.length) {
|
||||
n.ratio = tgRatios[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i === resourceNodes.length - 1) {
|
||||
n.ratio = (1 - preSum).toFixed(2);
|
||||
} else {
|
||||
n.ratio = (n.maxConcurrency / ratios).toFixed(2);
|
||||
preSum += Number.parseFloat(n.ratio);
|
||||
}
|
||||
}
|
||||
this.$set(tg, "resourceNodes", resourceNodes);
|
||||
if (tg.resourceNodeIndex > resourceNodes.length - 1) {
|
||||
this.$set(tg, "resourceNodeIndex", 0);
|
||||
}
|
||||
});
|
||||
|
||||
this.calculateTotalChart();
|
||||
}
|
||||
},
|
||||
|
@ -497,8 +570,8 @@ export default {
|
|||
let tg = handler.threadGroups[i];
|
||||
|
||||
if (tg.enabled === 'false' ||
|
||||
tg.deleted === 'true' ||
|
||||
tg.threadType === 'ITERATION') {
|
||||
tg.deleted === 'true' ||
|
||||
tg.threadType === 'ITERATION') {
|
||||
continue;
|
||||
}
|
||||
if (this.getDuration(tg) < tg.rampUpTime) {
|
||||
|
@ -600,8 +673,20 @@ export default {
|
|||
if (tg.enabled === 'false') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tg.strategy === "custom") {
|
||||
let sum = tg.resourceNodes.map(n => n.ratio).reduce((total, curr) => {
|
||||
total += curr;
|
||||
return total;
|
||||
}, 0);
|
||||
if (sum !== 1) {
|
||||
this.$warning(this.$t('load_test.pressure_config_custom_error'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tg.threadNumber || !tg.duration
|
||||
|| !tg.rampUpTime || !tg.step || !tg.iterateNum) {
|
||||
|| !tg.rampUpTime || !tg.step || !tg.iterateNum) {
|
||||
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
|
||||
this.$emit('changeActive', '1');
|
||||
return false;
|
||||
|
@ -623,6 +708,9 @@ export default {
|
|||
tg.duration = tg.durationHours * 60 * 60 + tg.durationMinutes * 60 + tg.durationSeconds;
|
||||
return tg.duration;
|
||||
},
|
||||
getRatios(tg) {
|
||||
return tg.resourceNodes.map(node => node.ratio);
|
||||
},
|
||||
convertProperty() {
|
||||
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
|
||||
let result = [];
|
||||
|
@ -648,6 +736,9 @@ export default {
|
|||
{key: ENABLED, value: this.threadGroups[i].enabled},
|
||||
{key: DELETED, value: this.threadGroups[i].deleted},
|
||||
{key: ON_SAMPLE_ERROR, value: this.threadGroups[i].onSampleError},
|
||||
{key: STRATEGY, value: this.threadGroups[i].strategy},
|
||||
{key: RESOURCE_NODE_INDEX, value: this.threadGroups[i].resourceNodeIndex},
|
||||
{key: RATIOS, value: this.getRatios(this.threadGroups[i])},
|
||||
{key: THREAD_GROUP_TYPE, value: this.threadGroups[i].tgType},
|
||||
{key: SERIALIZE_THREAD_GROUPS, value: this.serializeThreadGroups},
|
||||
{key: AUTO_STOP, value: this.autoStop},
|
||||
|
|
|
@ -62,6 +62,9 @@ export function findThreadGroup(jmxContent, handler) {
|
|||
tg.enabled = tg.attributes.enabled;
|
||||
tg.tgType = tg.name;
|
||||
tg.csvFiles = csvFiles;
|
||||
tg.strategy = 'auto';
|
||||
tg.resourceNodeIndex = 0;
|
||||
tg.ratios = '';
|
||||
if (tg.name === 'SetupThreadGroup' || tg.name === 'PostThreadGroup') {
|
||||
tg.threadType = 'ITERATION';
|
||||
tg.threadNumber = 1;
|
||||
|
|
|
@ -210,6 +210,37 @@
|
|||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_seconds')"/>
|
||||
</div>
|
||||
<div v-if="resourcePoolType === 'NODE'">
|
||||
<el-radio-group v-model="threadGroup.strategy" :disabled="isReadOnly" style="padding-bottom: 10px;">
|
||||
<el-radio label="auto">自动分配</el-radio>
|
||||
<el-radio label="specify">固定节点</el-radio>
|
||||
<el-radio label="custom">自定义</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="threadGroup.strategy === 'auto'"></div>
|
||||
<div v-else-if="threadGroup.strategy === 'specify'">
|
||||
<el-select v-model="threadGroup.resourceNodeIndex" :disabled="isReadOnly" size="mini">
|
||||
<el-option
|
||||
v-for="(node, index) in resourceNodes"
|
||||
:key="node.ip"
|
||||
:label="node.ip"
|
||||
:value="index">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-table :data="threadGroup.resourceNodes" :max-height="200">
|
||||
<el-table-column type="index" width="50"/>
|
||||
<el-table-column prop="ip" label="IP"/>
|
||||
<el-table-column prop="maxConcurrency" :label="$t('test_resource_pool.max_threads')"/>
|
||||
<el-table-column prop="ratio" label="占比">
|
||||
<template v-slot:default="{row}">
|
||||
<el-input-number size="small" v-model="row.ratio" :min="0" :step=".1"
|
||||
:max="1"></el-input-number>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
@ -249,11 +280,10 @@ const THREAD_TYPE = "threadType";
|
|||
const ITERATE_NUM = "iterateNum";
|
||||
const ENABLED = "enabled";
|
||||
const DELETED = "deleted";
|
||||
const STRATEGY = "strategy";
|
||||
const RESOURCE_NODE_INDEX = "resourceNodeIndex";
|
||||
const RATIOS = "ratios";
|
||||
|
||||
const hexToRgba = function (hex, opacity) {
|
||||
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
|
||||
+ parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
|
||||
};
|
||||
const hexToRgb = function (hex) {
|
||||
return 'rgb(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5))
|
||||
+ ',' + parseInt('0x' + hex.slice(5, 7)) + ')';
|
||||
|
@ -287,6 +317,8 @@ export default {
|
|||
rpsLimitEnable: false,
|
||||
options: {},
|
||||
resourcePools: [],
|
||||
resourceNodes: [],
|
||||
resourcePoolType: null,
|
||||
activeNames: ["0"],
|
||||
threadGroups: [],
|
||||
maxThreadNumbers: 5000,
|
||||
|
@ -402,6 +434,15 @@ export default {
|
|||
case ON_SAMPLE_ERROR:
|
||||
this.threadGroups[i].onSampleError = item.value;
|
||||
break;
|
||||
case STRATEGY:
|
||||
this.threadGroups[i].strategy = item.value;
|
||||
break;
|
||||
case RESOURCE_NODE_INDEX:
|
||||
this.threadGroups[i].resourceNodeIndex = item.value;
|
||||
break;
|
||||
case RATIOS:
|
||||
this.threadGroups[i].ratios = item.value;
|
||||
break;
|
||||
case SERIALIZE_THREAD_GROUPS:
|
||||
this.serializeThreadGroups = item.value;// 所有的线程组值一样
|
||||
break;
|
||||
|
@ -430,6 +471,7 @@ export default {
|
|||
tg.durationMinutes = Math.floor((tg.duration / 60 % 60));
|
||||
tg.durationSeconds = Math.floor((tg.duration % 60));
|
||||
}
|
||||
this.resourcePoolChange();
|
||||
this.calculateTotalChart();
|
||||
}
|
||||
});
|
||||
|
@ -454,15 +496,45 @@ export default {
|
|||
let result = this.resourcePools.filter(p => p.id === this.resourcePool);
|
||||
if (result.length === 1) {
|
||||
let threadNumber = 0;
|
||||
this.resourceNodes = [];
|
||||
this.resourcePoolType = result[0].type;
|
||||
result[0].resources.forEach(resource => {
|
||||
threadNumber += JSON.parse(resource.configuration).maxConcurrency;
|
||||
let config = JSON.parse(resource.configuration);
|
||||
threadNumber += config.maxConcurrency;
|
||||
this.resourceNodes.push(config);
|
||||
});
|
||||
this.$set(this, 'maxThreadNumbers', threadNumber);
|
||||
this.threadGroups.forEach(tg => {
|
||||
if (tg.threadNumber > threadNumber) {
|
||||
this.$set(tg, "threadNumber", threadNumber);
|
||||
}
|
||||
let tgRatios = tg.ratios;
|
||||
let resourceNodes = JSON.parse(JSON.stringify(this.resourceNodes));
|
||||
let ratios = resourceNodes.map(n => n.maxConcurrency).reduce((total, curr) => {
|
||||
total += curr;
|
||||
return total;
|
||||
}, 0);
|
||||
let preSum = 0;
|
||||
for (let i = 0; i < resourceNodes.length; i++) {
|
||||
let n = resourceNodes[i];
|
||||
if (resourceNodes.length === tgRatios.length) {
|
||||
n.ratio = tgRatios[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i === resourceNodes.length - 1) {
|
||||
n.ratio = (1 - preSum).toFixed(2);
|
||||
} else {
|
||||
n.ratio = (n.maxConcurrency / ratios).toFixed(2);
|
||||
preSum += Number.parseFloat(n.ratio);
|
||||
}
|
||||
}
|
||||
this.$set(tg, "resourceNodes", resourceNodes);
|
||||
if (tg.resourceNodeIndex > resourceNodes.length - 1) {
|
||||
this.$set(tg, "resourceNodeIndex", 0);
|
||||
}
|
||||
});
|
||||
|
||||
this.calculateTotalChart();
|
||||
}
|
||||
},
|
||||
|
@ -601,6 +673,18 @@ export default {
|
|||
if (tg.enabled === 'false' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tg.strategy === "custom") {
|
||||
let sum = tg.resourceNodes.map(n => n.ratio).reduce((total, curr) => {
|
||||
total += curr;
|
||||
return total;
|
||||
}, 0);
|
||||
if (sum !== 1) {
|
||||
this.$warning(this.$t('load_test.pressure_config_custom_error'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tg.threadNumber || !tg.duration
|
||||
|| !tg.rampUpTime || !tg.step || !tg.iterateNum) {
|
||||
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
|
||||
|
@ -624,6 +708,9 @@ export default {
|
|||
tg.duration = tg.durationHours * 60 * 60 + tg.durationMinutes * 60 + tg.durationSeconds;
|
||||
return tg.duration;
|
||||
},
|
||||
getRatios(tg) {
|
||||
return tg.resourceNodes.map(node => node.ratio);
|
||||
},
|
||||
convertProperty() {
|
||||
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
|
||||
let result = [];
|
||||
|
@ -649,6 +736,9 @@ export default {
|
|||
{key: ENABLED, value: this.threadGroups[i].enabled},
|
||||
{key: DELETED, value: this.threadGroups[i].deleted},
|
||||
{key: ON_SAMPLE_ERROR, value: this.threadGroups[i].onSampleError},
|
||||
{key: STRATEGY, value: this.threadGroups[i].strategy},
|
||||
{key: RESOURCE_NODE_INDEX, value: this.threadGroups[i].resourceNodeIndex},
|
||||
{key: RATIOS, value: this.getRatios(this.threadGroups[i])},
|
||||
{key: THREAD_GROUP_TYPE, value: this.threadGroups[i].tgType},
|
||||
{key: SERIALIZE_THREAD_GROUPS, value: this.serializeThreadGroups},
|
||||
{key: AUTO_STOP, value: this.autoStop},
|
||||
|
|
|
@ -829,6 +829,7 @@ export default {
|
|||
user_name: 'Creator',
|
||||
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_custom_error: 'The sum of custom strategies must be 1',
|
||||
schedule_tip: 'The interval must not be less than the pressure measuring time',
|
||||
delete_threadgroup_confirm: 'Confirm delete scenario: ',
|
||||
scenario_list: 'Scenario List',
|
||||
|
|
|
@ -835,6 +835,7 @@ export default {
|
|||
user_name: '创建人',
|
||||
special_characters_are_not_supported: '测试名称不支持特殊字符',
|
||||
pressure_config_params_is_empty: '压力配置参数不能为空!',
|
||||
pressure_config_custom_error: '自定义策略之和必须为1',
|
||||
schedule_tip: '间隔时间不能小于压测时长',
|
||||
delete_threadgroup_confirm: '确认删除场景',
|
||||
scenario_list: '场景列表',
|
||||
|
|
|
@ -834,6 +834,7 @@ export default {
|
|||
user_name: '創建人',
|
||||
special_characters_are_not_supported: '測試名稱不支持特殊字符',
|
||||
pressure_config_params_is_empty: '壓力配置參數不能為空!',
|
||||
pressure_config_custom_error: '自定義策略之和必須為1',
|
||||
schedule_tip: '間隔時間不能小於壓測時長',
|
||||
delete_threadgroup_confirm: '確認刪除場景',
|
||||
scenario_list: '場景列表',
|
||||
|
|
Loading…
Reference in New Issue