feat(性能测试): 资源池增加cpu以及是否有任务执行的显示

--story=1013023 --user=宋天阳 【通用功能】选择资源池运行时支持查看资源池的使用状态以及各节点CPU使用率
https://www.tapd.cn/55049933/s/1424391
This commit is contained in:
song-tianyang 2023-10-10 15:39:40 +08:00 committed by 刘瑞斌
parent 09e7e0b247
commit d76e854e14
14 changed files with 461 additions and 37 deletions

View File

@ -1,6 +1,10 @@
import { get } from 'metersphere-frontend/src/plugins/request'; import {get, post} from 'metersphere-frontend/src/plugins/request';
export function getTestResourcePools() { export function getTestResourcePools() {
let url = '/testresourcepool/list/quota/valid'; let url = '/testresourcepool/list/quota/valid';
return get(url); return get(url);
} }
export function getNodeOperationInfo(request) {
return post(`/prometheus/query/node-operation-info`, request)
}

View File

@ -64,6 +64,11 @@
:label="item.name" :label="item.name"
:disabled="!item.api" :disabled="!item.api"
:value="item.id"> :value="item.id">
<template v-slot>
<node-operation-label
:nodeName="item.name"
:node-operation-info="nodeInfo(item.id)"/>
</template>
</el-option> </el-option>
</el-select> </el-select>
</div> </div>
@ -83,19 +88,20 @@
</template> </template>
<script> <script>
import { apiScenarioEnvMap } from '@/api/scenario'; import {apiScenarioEnvMap} from '@/api/scenario';
import MsDialogFooter from 'metersphere-frontend/src/components/MsDialogFooter'; import MsDialogFooter from 'metersphere-frontend/src/components/MsDialogFooter';
import { ENV_TYPE } from 'metersphere-frontend/src/utils/constants'; import {ENV_TYPE} from 'metersphere-frontend/src/utils/constants';
import { strMapToObj } from 'metersphere-frontend/src/utils'; import {strMapToObj} from 'metersphere-frontend/src/utils';
import { getOwnerProjects, getProjectConfig } from '@/api/project'; import {getOwnerProjects, getProjectConfig} from '@/api/project';
import { getTestResourcePools } from '@/api/test-resource-pool'; import {getNodeOperationInfo, getTestResourcePools} from '@/api/test-resource-pool';
import { getCurrentProjectID } from 'metersphere-frontend/src/utils/token'; import {getCurrentProjectID} from 'metersphere-frontend/src/utils/token';
import EnvSelectPopover from '@/business/automation/scenario/EnvSelectPopover'; import EnvSelectPopover from '@/business/automation/scenario/EnvSelectPopover';
import { getApiCaseEnvironments } from '@/api/api-test-case'; import {getApiCaseEnvironments} from '@/api/api-test-case';
import NodeOperationLabel from "metersphere-frontend/src/components/node/NodeOperationLabel";
export default { export default {
name: 'ApiRunMode', name: 'ApiRunMode',
components: { MsDialogFooter, EnvSelectPopover }, components: {MsDialogFooter, EnvSelectPopover, NodeOperationLabel},
data() { data() {
return { return {
loading: false, loading: false,
@ -116,6 +122,7 @@ export default {
projectList: [], projectList: [],
projectIds: new Set(), projectIds: new Set(),
caseIdEnvNameMap: {}, caseIdEnvNameMap: {},
nodeOperationInfo: {},
}; };
}, },
props: { props: {
@ -127,6 +134,26 @@ export default {
}, },
}, },
methods: { methods: {
nodeInfo(nodeId) {
return this.nodeOperationInfo[nodeId];
},
selectNodeOperation() {
let nodeOperationInfoRequest = {nodeIds: []};
this.resourcePools.forEach(item => {
nodeOperationInfoRequest.nodeIds.push(item.id);
});
getNodeOperationInfo(nodeOperationInfoRequest)
.then(response => {
this.parseNodeOperationStatus(response.data);
});
},
parseNodeOperationStatus(nodeOperationData) {
this.nodeOperationInfo = {};
nodeOperationData.forEach(item => {
this.nodeOperationInfo[item.id] = item;
});
},
open() { open() {
this.runModeVisible = true; this.runModeVisible = true;
this.getResourcePools(); this.getResourcePools();
@ -173,6 +200,7 @@ export default {
getResourcePools() { getResourcePools() {
this.result = getTestResourcePools().then((response) => { this.result = getTestResourcePools().then((response) => {
this.resourcePools = response.data; this.resourcePools = response.data;
this.selectNodeOperation();
this.getProjectApplication(); this.getProjectApplication();
}); });
}, },

View File

@ -20,6 +20,10 @@ const message = {
trash: "Trash", trash: "Trash",
yes: "yes", yes: "yes",
no: "no", no: "no",
running: "Running",
idle: "idle",
running_status: "Running Status",
cpu_usage: "CPU Usage",
expand_all: "Expand all", expand_all: "Expand all",
close_all: "Close all", close_all: "Close all",
example: "Demo", example: "Demo",

View File

@ -20,6 +20,10 @@ const message = {
trash: "回收站", trash: "回收站",
yes: "是", yes: "是",
no: "否", no: "否",
running: "运行中",
idle: "空闲",
running_status: "运行状态",
cpu_usage: "CPU 使用率",
expand_all: "一键展开", expand_all: "一键展开",
close_all: "一键收起", close_all: "一键收起",
example: "示例", example: "示例",

View File

@ -20,6 +20,10 @@ const message = {
trash: "回收站", trash: "回收站",
yes: "是", yes: "是",
no: "否", no: "否",
running: "運行中",
idle: "空閑",
running_status: "運行狀態",
cpu_usage: "CPU 使用率",
expand_all: "一鍵展開", expand_all: "一鍵展開",
close_all: "一鍵收起", close_all: "一鍵收起",
example: "示例", example: "示例",

View File

@ -0,0 +1,25 @@
package io.metersphere.controller;
import io.metersphere.dto.ResourcePoolOperationInfo;
import io.metersphere.request.NodeOperationSelectRequest;
import io.metersphere.service.PrometheusService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/prometheus")
public class PrometheusController {
@Resource
private PrometheusService prometheusService;
@PostMapping("/query/node-operation-info")
public List<ResourcePoolOperationInfo> queryMetric(@RequestBody NodeOperationSelectRequest request) {
return prometheusService.queryNodeOperationInfo(request);
}
}

View File

@ -0,0 +1,39 @@
package io.metersphere.dto;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class ResourcePoolOperationInfo {
private String id;
private String cpuUsage;
private int runningTask = 0;
Map<String, NodeOperationInfo> nodeOperationInfos = new HashMap<>();
public void addNodeOperationInfo(String taskResourceId, String ip, String port, String cpuUsage, int runningTask) {
NodeOperationInfo nodeOperationInfo = new NodeOperationInfo();
nodeOperationInfo.setIp(ip);
nodeOperationInfo.setPort(port);
nodeOperationInfo.setCpuUsage(cpuUsage);
nodeOperationInfo.setRunningTask(runningTask);
nodeOperationInfos.put(taskResourceId, nodeOperationInfo);
this.cpuUsage = cpuUsage;
this.runningTask += runningTask;
if (nodeOperationInfos.size() > 1) {
//多节点的情况下暂不处理CPU使用率
this.cpuUsage = null;
}
}
}
@Data
class NodeOperationInfo {
private String ip;
private String port;
private String cpuUsage;
private int runningTask;
}

View File

@ -0,0 +1,10 @@
package io.metersphere.request;
import lombok.Data;
import java.util.List;
@Data
public class NodeOperationSelectRequest {
private List<String> nodeIds;
}

View File

@ -14,13 +14,13 @@ import io.metersphere.log.vo.DetailColumn;
import io.metersphere.log.vo.OperatingLogDetails; import io.metersphere.log.vo.OperatingLogDetails;
import io.metersphere.log.vo.system.SystemReference; import io.metersphere.log.vo.system.SystemReference;
import io.metersphere.request.resourcepool.QueryResourcePoolRequest; import io.metersphere.request.resourcepool.QueryResourcePoolRequest;
import jakarta.annotation.Resource;
import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -37,18 +37,18 @@ public class BaseTestResourcePoolService {
@Resource @Resource
private TestResourceMapper testResourceMapper; private TestResourceMapper testResourceMapper;
public List<TestResourcePoolDTO> listResourcePools(QueryResourcePoolRequest request) {
public List<TestResourcePoolDTO> listResourcePoolById(List<String> testResourcePoolId) {
TestResourcePoolExample example = new TestResourcePoolExample(); TestResourcePoolExample example = new TestResourcePoolExample();
TestResourcePoolExample.Criteria criteria = example.createCriteria(); TestResourcePoolExample.Criteria criteria = example.createCriteria();
if (StringUtils.isNotBlank(request.getName())) { criteria.andIdIn(testResourcePoolId).andTypeEqualTo("NODE");
criteria.andNameLike(StringUtils.wrapIfMissing(request.getName(), "%"));
}
if (StringUtils.isNotBlank(request.getStatus())) {
criteria.andStatusEqualTo(request.getStatus());
}
criteria.andStatusNotEqualTo(DELETE.name()); criteria.andStatusNotEqualTo(DELETE.name());
example.setOrderByClause("update_time desc");
List<TestResourcePool> testResourcePools = testResourcePoolMapper.selectByExample(example); List<TestResourcePool> testResourcePools = testResourcePoolMapper.selectByExample(example);
return this.generateTestResourcePoolDTO(testResourcePools);
}
private List<TestResourcePoolDTO> generateTestResourcePoolDTO(List<TestResourcePool> testResourcePools) {
List<TestResourcePoolDTO> testResourcePoolDTOS = new ArrayList<>(); List<TestResourcePoolDTO> testResourcePoolDTOS = new ArrayList<>();
testResourcePools.forEach(pool -> { testResourcePools.forEach(pool -> {
TestResourceExample example2 = new TestResourceExample(); TestResourceExample example2 = new TestResourceExample();
@ -67,6 +67,21 @@ public class BaseTestResourcePoolService {
return testResourcePoolDTOS; return testResourcePoolDTOS;
} }
public List<TestResourcePoolDTO> listResourcePools(QueryResourcePoolRequest request) {
TestResourcePoolExample example = new TestResourcePoolExample();
TestResourcePoolExample.Criteria criteria = example.createCriteria();
if (StringUtils.isNotBlank(request.getName())) {
criteria.andNameLike(StringUtils.wrapIfMissing(request.getName(), "%"));
}
if (StringUtils.isNotBlank(request.getStatus())) {
criteria.andStatusEqualTo(request.getStatus());
}
criteria.andStatusNotEqualTo(DELETE.name());
example.setOrderByClause("update_time desc");
List<TestResourcePool> testResourcePools = testResourcePoolMapper.selectByExample(example);
return this.generateTestResourcePoolDTO(testResourcePools);
}
public TestResourcePool getResourcePool(String resourcePoolId) { public TestResourcePool getResourcePool(String resourcePoolId) {
return testResourcePoolMapper.selectByPrimaryKey(resourcePoolId); return testResourcePoolMapper.selectByPrimaryKey(resourcePoolId);

View File

@ -0,0 +1,170 @@
package io.metersphere.service;
import io.metersphere.base.domain.TestResource;
import io.metersphere.commons.constants.ParamConstants;
import io.metersphere.commons.utils.CodingUtil;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.ResourcePoolOperationInfo;
import io.metersphere.dto.TestResourcePoolDTO;
import io.metersphere.request.NodeOperationSelectRequest;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
@Transactional(rollbackFor = Exception.class)
public class PrometheusService {
@Resource
private SystemParameterService systemParameterService;
@Resource
private RestTemplate restTemplate;
@Resource
private BaseTestResourcePoolService baseTestResourcePoolService;
public List<ResourcePoolOperationInfo> queryNodeOperationInfo(NodeOperationSelectRequest request) {
if (CollectionUtils.isEmpty(request.getNodeIds())) {
return new ArrayList<>();
}
String host = systemParameterService.getValue(ParamConstants.BASE.PROMETHEUS_HOST.getValue());
String prometheusHost = StringUtils.isNotBlank(host) ? host : "http://ms-prometheus:9090";
List<TestResourcePoolDTO> testResourcePoolDTOS = baseTestResourcePoolService.listResourcePoolById(request.getNodeIds());
return this.queryNodeOperationInfoByPrometheus(prometheusHost, testResourcePoolDTOS);
}
private List<ResourcePoolOperationInfo> queryNodeOperationInfoByPrometheus(String host, List<TestResourcePoolDTO> testResourcePoolDTOS) {
List<ResourcePoolOperationInfo> returnList = new ArrayList<>();
HttpHeaders headers;
try {
headers = new HttpHeaders();
headers.add("Content-Type", "application/x-www-form-urlencoded");
// 如果prometheus开启了认证需要在请求头中添加认证信息
if (host.contains("@")) {
URL url = new URL(host);
// 获取认证信息部分
String userInfo = url.getUserInfo();
headers.add("Authorization", "Basic " + CodingUtil.base64Encoding(userInfo));
host = host.replace(userInfo + "@", "");
}
} catch (Exception e) {
LogUtil.error("Get prometheus header fail.");
LogUtil.error(e.getMessage(), e);
return returnList;
}
DecimalFormat decimalFormat = new DecimalFormat("#0.00");
decimalFormat.setMinimumFractionDigits(2);
decimalFormat.setMaximumFractionDigits(2);
for (TestResourcePoolDTO testResourcePoolDTO : testResourcePoolDTOS) {
ResourcePoolOperationInfo nodeOperationInfo = new ResourcePoolOperationInfo();
nodeOperationInfo.setId(testResourcePoolDTO.getId());
boolean queryCpuUsage = true;
if (testResourcePoolDTO.getResources().size() > 1) {
queryCpuUsage = false;
}
String cpuUsage = null;
int runningTask = 0;
for (TestResource testResource : testResourcePoolDTO.getResources()) {
String config = testResource.getConfiguration();
if (StringUtils.isNotBlank(config)) {
Map<String, Object> configMap = JSON.parseObject(config, Map.class);
String ip = String.valueOf(configMap.get("ip"));
String port = String.valueOf(configMap.get("port"));
String nodeId = ip + ":" + port;
if (queryCpuUsage) {
String cpuUsageQL = this.generatePromQL(new String[]{"system_cpu_usage"}, nodeId);
LogUtil.debug(host + "/api/v1/query?query=" + cpuUsageQL);
String cpuUsageDouble = this.runPromQL(headers, host, cpuUsageQL);
cpuUsage = decimalFormat.format(Double.parseDouble(cpuUsageDouble) * 100) + "%";
}
// 查询任务数
List<String> taskSeriesNames = new ArrayList<>() {{
this.add("running_tasks_load_count");
this.add("running_tasks_api_count");
}};
String taskCountQL = this.generatePromQL(taskSeriesNames.toArray(new String[0]), nodeId);
String result = this.runPromQL(headers, host, taskCountQL);
if (StringUtils.isNotBlank(result)) {
runningTask += Integer.parseInt(result);
}
nodeOperationInfo.addNodeOperationInfo(String.valueOf(configMap.get("id")), ip, port, cpuUsage, runningTask);
}
}
if (MapUtils.isNotEmpty(nodeOperationInfo.getNodeOperationInfos())) {
returnList.add(nodeOperationInfo);
}
}
return returnList;
}
private String generatePromQL(String[] series, String nodeId) {
StringBuilder promQL = new StringBuilder();
for (int i = 0; i < series.length; i++) {
String seriesName = series[i];
if (i > 0) {
promQL.append("+");
}
promQL.append(seriesName);
promQL.append("{");
promQL.append("instance=\"");
promQL.append(nodeId);
promQL.append("\"}");
}
return promQL.toString();
}
private String runPromQL(HttpHeaders headers, String host, String promQL) {
try {
MultiValueMap<String, Object> postParameters = new LinkedMultiValueMap<>();
postParameters.add("query", promQL);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(postParameters, headers);
return this.parseResultValue(restTemplate.postForObject(host + "/api/v1/query", httpEntity, Map.class));
} catch (Exception e) {
LogUtil.error("query prometheus metric fail.");
LogUtil.error(e.getMessage(), e);
}
return null;
}
private String parseResultValue(Map response) {
String value = null;
if (response != null && StringUtils.equals((String) response.get("status"), "success")) {
Map data = (Map) response.get("data");
List result = (List) data.get("result");
if (CollectionUtils.isNotEmpty(result)) {
Map resultObject = (Map) result.get(0);
List valueMetrics = (List) resultObject.get("value");
if (CollectionUtils.isNotEmpty(valueMetrics)) {
value = valueMetrics.get(1).toString();
}
}
}
return value;
}
}

View File

@ -237,3 +237,7 @@ export function exportScenarioJmx(condition) {
export function searchScenarioList(goPage, pageSize, condition) { export function searchScenarioList(goPage, pageSize, condition) {
return post(`/api/automation/list/${goPage}/${pageSize}`, condition) return post(`/api/automation/list/${goPage}/${pageSize}`, condition)
} }
export function getNodeOperationInfo(request) {
return post(`/prometheus/query/node-operation-info`, request)
}

View File

@ -11,6 +11,11 @@
:label="item.name" :label="item.name"
:disabled="!item.performance" :disabled="!item.performance"
:value="item.id"> :value="item.id">
<template v-slot>
<node-operation-label
:nodeName="item.name"
:node-operation-info="nodeInfo(item.id)"/>
</template>
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -219,22 +224,62 @@
:value="index"> :value="index">
</el-option> </el-option>
</el-select> </el-select>
<div style="margin-left: 5px;float: right">
<el-tag size="mini" v-if="nodeTaskCount(resourceNodes[threadGroup.resourceNodeIndex])===0"
style="color:#E5594B;background-color: #FFFFFF;border-color: #E5594B;margin-left: 5px;margin-right: 5px">
{{ $t("commons.idle") }}
</el-tag>
<el-tag size="mini" v-else-if="nodeTaskCount(resourceNodes[threadGroup.resourceNodeIndex])>0"
style="color:#89DB7E;background-color: #FFFFFF;border-color: #89DB7E;margin-left: 5px;margin-right: 5px">
{{ $t("commons.running") }}
</el-tag>
<span v-if="nodeTaskCount(resourceNodes[threadGroup.resourceNodeIndex]) !== -1">
{{
" " + $t("commons.cpu_usage") + " " + nodeCpuUsage(resourceNodes[threadGroup.resourceNodeIndex])
}}
</span>
</div>
</el-form-item> </el-form-item>
</div> </div>
<div v-else> <div v-else>
<el-table class="adjust-table" :data="threadGroup.resourceNodes" :max-height="200"> <el-table class="adjust-table" :data="threadGroup.resourceNodes" :max-height="200">
<el-table-column type="index" width="50"/> <el-table-column type="index" width="50"/>
<el-table-column prop="ip" label="IP"/> <el-table-column prop="ip" label="IP"/>
<el-table-column prop="runStatus" :label="$t('commons.running_status')">
<template v-slot:default="{row}">
<el-tag size="mini" v-if="nodeTaskCount(row)===0"
style="color:#E5594B;background-color: #FFFFFF;border-color: #E5594B;margin-left: 5px;margin-right: 5px">
{{ $t("commons.idle") }}
</el-tag>
<el-tag size="mini" v-else-if="nodeTaskCount(row)>0"
style="color:#89DB7E;background-color: #FFFFFF;border-color: #89DB7E;margin-left: 5px;margin-right: 5px">
{{ $t("commons.running") }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="maxConcurrency" :label="$t('test_resource_pool.max_threads')"/> <el-table-column prop="maxConcurrency" :label="$t('test_resource_pool.max_threads')"/>
<el-table-column prop="ratio" :label="$t('test_track.home.percentage')"> <el-table-column prop="ratio" :label="$t('test_track.home.percentage')" width="120px">
<template v-slot:default="{row}"> <template v-slot:default="{row}">
<el-input-number size="mini" v-model="row.ratio" <el-input-number size="mini" v-model="row.ratio"
v-if="rampUpTimeVisible" v-if="rampUpTimeVisible"
@change="customNodeChange(threadGroup)" @change="customNodeChange(threadGroup)"
style="width: 100px"
:min="0" :step=".1" controls-position="right" :min="0" :step=".1" controls-position="right"
:max="1"></el-input-number> :max="1"></el-input-number>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="cpuUsage" :label="$t('commons.cpu_usage')">
<template v-slot:default="{row}">
{{ nodeCpuUsage(row) }}
</template>
</el-table-column>
<el-table-column prop="refresh" width="50px">
<template v-slot:header>
<el-button icon="el-icon-refresh" size="mini" @click="refreshNodeOperation" circle></el-button>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
</div> </div>
@ -252,8 +297,9 @@
<script> <script>
import MsChart from "metersphere-frontend/src/components/chart/MsChart"; import MsChart from "metersphere-frontend/src/components/chart/MsChart";
import NodeOperationLabel from "metersphere-frontend/src/components/node/NodeOperationLabel";
import {findThreadGroup} from "../../../business/test/model/ThreadGroup"; import {findThreadGroup} from "../../../business/test/model/ThreadGroup";
import {getJmxContent, getLoadConfig, getResourcePools} from "../../../api/performance"; import {getJmxContent, getLoadConfig, getNodeOperationInfo, getResourcePools} from "../../../api/performance";
const HANDLER = "handler"; const HANDLER = "handler";
const THREAD_GROUP_TYPE = "tgType"; const THREAD_GROUP_TYPE = "tgType";
@ -288,7 +334,7 @@ const hexToRgb = function (hex) {
export default { export default {
name: "PerformancePressureConfig", name: "PerformancePressureConfig",
components: {MsChart}, components: {MsChart, NodeOperationLabel},
props: { props: {
test: { test: {
type: Object type: Object
@ -333,6 +379,7 @@ export default {
autoStop: false, autoStop: false,
autoStopDelay: 30, autoStopDelay: 30,
rampUpTimeVisible: true, rampUpTimeVisible: true,
nodeOperationInfo: {},
}; };
}, },
computed: { computed: {
@ -344,7 +391,7 @@ export default {
{value: 'stoptest', label: this.$t('load_test.stoptest')}, {value: 'stoptest', label: this.$t('load_test.stoptest')},
{value: 'stoptestnow', label: this.$t('load_test.stoptestnow')}, {value: 'stoptestnow', label: this.$t('load_test.stoptestnow')},
]; ];
} },
}, },
mounted() { mounted() {
if (this.testId) { if (this.testId) {
@ -395,6 +442,36 @@ export default {
} }
}, },
methods: { methods: {
refreshNodeOperation() {
let nodeOperationInfoRequest = {nodeIds: []};
this.resourcePools.forEach(item => {
nodeOperationInfoRequest.nodeIds.push(item.id);
});
getNodeOperationInfo(nodeOperationInfoRequest)
.then(response => {
this.parseNodeOperationStatus(response.data);
});
},
nodeCpuUsage(row) {
let nodeInfo = this.nodeOperationInfo[this.resourcePool];
if (nodeInfo) {
return this.nodeOperationInfo[this.resourcePool].nodeOperationInfos[row.id].cpuUsage;
} else {
return "";
}
},
nodeTaskCount(row) {
let nodeInfo = this.nodeOperationInfo[this.resourcePool];
if (nodeInfo) {
return this.nodeOperationInfo[this.resourcePool].nodeOperationInfos[row.id].runningTask;
} else {
return -1;
}
},
nodeInfo(nodeId) {
return this.nodeOperationInfo[nodeId];
},
getResourcePools() { getResourcePools() {
getResourcePools(this.isShare) getResourcePools(this.isShare)
.then(response => { .then(response => {
@ -408,9 +485,24 @@ export default {
} }
} }
let nodeOperationInfoRequest = {nodeIds: []};
this.resourcePools.forEach(item => {
nodeOperationInfoRequest.nodeIds.push(item.id);
});
getNodeOperationInfo(nodeOperationInfoRequest)
.then(response => {
this.parseNodeOperationStatus(response.data);
});
this.resourcePoolChange(); this.resourcePoolChange();
}); });
}, },
parseNodeOperationStatus(nodeOperationData) {
this.nodeOperationInfo = {};
nodeOperationData.forEach(item => {
this.nodeOperationInfo[item.id] = item;
});
},
getLoadConfig() { getLoadConfig() {
this.result.loading = true; this.result.loading = true;
getLoadConfig(this.testId, this.reportId, this.isShare) getLoadConfig(this.testId, this.reportId, this.isShare)

View File

@ -1,5 +1,4 @@
import {get, post} from "metersphere-frontend/src/plugins/request" import {get, post} from "metersphere-frontend/src/plugins/request"
import {hasLicense} from "metersphere-frontend/src/utils/permission";
import {getCurrentProjectID} from "metersphere-frontend/src/utils/token"; import {getCurrentProjectID} from "metersphere-frontend/src/utils/token";
export function getProject(id) { export function getProject(id) {
@ -38,6 +37,10 @@ export function getProjectConfig(projectId, type) {
return get(url); return get(url);
} }
export function getNodeOperationInfo(request) {
return post(`/prometheus/query/node-operation-info`, request)
}
export function apiTestReRun(condition) { export function apiTestReRun(condition) {
return post('/api/test/exec/rerun', condition); return post('/api/test/exec/rerun', condition);
} }

View File

@ -73,6 +73,11 @@
:label="item.name" :label="item.name"
:value="item.id" :value="item.id"
> >
<template v-slot>
<node-operation-label
:nodeName="item.name"
:node-operation-info="nodeInfo(item.id)"/>
</template>
</el-option> </el-option>
</el-select> </el-select>
</div> </div>
@ -155,25 +160,19 @@
<script> <script>
import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter"; import MsDialogFooter from "metersphere-frontend/src/components/MsDialogFooter";
import { strMapToObj } from "metersphere-frontend/src/utils"; import {strMapToObj} from "metersphere-frontend/src/utils";
import MsTag from "metersphere-frontend/src/components/MsTag"; import MsTag from "metersphere-frontend/src/components/MsTag";
import { ENV_TYPE } from "metersphere-frontend/src/utils/constants"; import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import { import {getCurrentProjectID, getOwnerProjects,} from "@/business/utils/sdk-utils";
getCurrentProjectID, import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
getOwnerProjects,
} from "@/business/utils/sdk-utils";
import { getQuotaValidResourcePools } from "@/api/remote/resource-pool";
import EnvGroupPopover from "@/business/plan/env/EnvGroupPopover"; import EnvGroupPopover from "@/business/plan/env/EnvGroupPopover";
import { getApiCaseEnv } from "@/api/remote/plan/test-plan-api-case"; import {getApiCaseEnv} from "@/api/remote/plan/test-plan-api-case";
import { import {getApiScenarioEnv, getPlanCaseEnv, getPlanCaseProjectIds,} from "@/api/remote/plan/test-plan";
getApiScenarioEnv,
getPlanCaseEnv,
getPlanCaseProjectIds,
} from "@/api/remote/plan/test-plan";
import EnvGroupWithOption from "../env/EnvGroupWithOption"; import EnvGroupWithOption from "../env/EnvGroupWithOption";
import EnvironmentGroup from "@/business/plan/env/EnvironmentGroupList"; import EnvironmentGroup from "@/business/plan/env/EnvironmentGroupList";
import EnvSelectPopover from "@/business/plan/env/EnvSelectPopover"; import EnvSelectPopover from "@/business/plan/env/EnvSelectPopover";
import { getProjectConfig } from "@/api/project"; import {getNodeOperationInfo, getProjectConfig} from "@/api/project";
import NodeOperationLabel from "metersphere-frontend/src/components/node/NodeOperationLabel";
export default { export default {
name: "MsTestPlanRunModeWithEnv", name: "MsTestPlanRunModeWithEnv",
@ -184,6 +183,7 @@ export default {
EnvGroupWithOption, EnvGroupWithOption,
EnvironmentGroup, EnvironmentGroup,
EnvSelectPopover, EnvSelectPopover,
NodeOperationLabel,
}, },
computed: { computed: {
ENV_TYPE() { ENV_TYPE() {
@ -229,6 +229,7 @@ export default {
}, },
], ],
value: "confirmAndRun", value: "confirmAndRun",
nodeOperationInfo: {},
browsers: [ browsers: [
{ {
label: this.$t("chrome"), label: this.$t("chrome"),
@ -263,6 +264,26 @@ export default {
}, },
}, },
methods: { methods: {
nodeInfo(nodeId) {
return this.nodeOperationInfo[nodeId];
},
selectNodeOperation() {
let nodeOperationInfoRequest = {nodeIds: []};
this.resourcePools.forEach(item => {
nodeOperationInfoRequest.nodeIds.push(item.id);
});
getNodeOperationInfo(nodeOperationInfoRequest)
.then(response => {
this.parseNodeOperationStatus(response.data);
});
},
parseNodeOperationStatus(nodeOperationData) {
this.nodeOperationInfo = {};
nodeOperationData.forEach(item => {
this.nodeOperationInfo[item.id] = item;
});
},
open(testType, runModeConfig) { open(testType, runModeConfig) {
this.defaultEnvMap = {}; this.defaultEnvMap = {};
if (this.type === "plan") { if (this.type === "plan") {
@ -350,6 +371,7 @@ export default {
getResourcePools() { getResourcePools() {
getQuotaValidResourcePools().then((response) => { getQuotaValidResourcePools().then((response) => {
this.resourcePools = response.data; this.resourcePools = response.data;
this.selectNodeOperation();
this.getProjectApplication(); this.getProjectApplication();
}); });
}, },