节点详情

main
陈博文 6 days ago
parent aa0cdc70ba
commit 5bc98d2e83

@ -2,7 +2,6 @@ import request from '@/utils/request';
const apiPrefix = '/api/vgpu';
class nodeApi {
getNodeList(data) {
return {
@ -46,6 +45,13 @@ class nodeApi {
data,
});
}
stop(data) {
return request({
url: apiPrefix + '/v1/node/status/update',
method: 'POST',
data,
});
}
}
export default new nodeApi();

@ -1,32 +1,19 @@
<template>
<ul class="preview">
<li class="preview-item" style="width: 20%; flex: none" v-if="!hidePie">
<block-box
:title="`${type === 'node' ? '节点显卡厂商分布' : '显卡类型分布'}`"
class="nodeCard"
>
<block-box :title="`${type === 'node' ? '节点显卡厂商分布' : '显卡类型分布'}`" class="nodeCard">
<div class="pie">
<echarts-plus
:options="getPreviewBarPie(pieData, props)"
:onClick="handlePieClick"
ref="echartsRef"
/>
<echarts-plus :options="getPreviewBarPie(pieData, props)" :onClick="handlePieClick" ref="echartsRef" />
</div>
<ul class="nodeCard-legend">
<li
v-for="{ name, value, color } in pieData"
:style="{
<li v-for="{ name, value, color } in pieData" :style="{
fontWeight: currentName === name ? 'bold' : 'normal',
}"
>
}">
<div class="left">
<span
class="color-box"
:style="{
<span class="color-box" :style="{
'background-color': color,
}"
></span>
}"></span>
<span> {{ name }}</span>
</div>
@ -70,6 +57,20 @@ const echartsRef = ref();
const totalTop = {
title: `${props.title}资源分配率 Top5`,
config: [
{
tab: 'CPU',
key: 'cpu',
nameKey: 'instance',
data: [],
query: `topk(5, sum(hami_container_vcore_allocated) by (instance) / sum(hami_core_size) by (instance) * 100)`,
},
{
tab: '内存',
key: 'internal',
nameKey: 'instance',
data: [],
query: `topk(5, sum(hami_container_vmemory_allocated) by (instance) / sum(hami_memory_size) by (instance) * 100)`,
},
{
tab: 'vGPU',
key: 'vgpu',
@ -97,6 +98,21 @@ const totalTop = {
const usedTop = {
title: `${props.title}资源使用率 Top5`,
config: [
{
tab: 'CPU',
key: 'cpu',
nameKey: 'instance',
data: [],
query: `topk(5, (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)) * 100)
`,
},
{
tab: '内存',
key: 'internal',
nameKey: 'instance',
data: [],
query: `topk(5, ((sum(node_memory_MemTotal_bytes) by (instance)) - sum(node_memory_MemAvailable_bytes) by (instance)) / sum(node_memory_MemTotal_bytes) by (instance) * 100)`,
},
{
tab: '算力',
key: 'core',
@ -162,11 +178,13 @@ ul {
padding: 0;
list-style: none;
}
.preview {
width: 100%;
display: flex;
gap: 20px;
margin-bottom: 20px;
.preview-item {
flex: 1;
}
@ -204,11 +222,13 @@ ul {
justify-content: space-between;
font-size: 12px;
align-items: center;
.left {
display: flex;
align-items: center;
gap: 5px;
}
.color-box {
width: 10px;
height: 10px;
@ -223,10 +243,12 @@ ul {
flex-direction: column;
min-height: 350px;
height: 100%;
& > :nth-child(2) {
&> :nth-child(2) {
flex: 1;
max-height: 280px;
}
.node-top-echarts {
//flex: 1;
}

@ -2,22 +2,22 @@
<div>
<back-header>
节点管理 > {{ detail.name }}
<!-- <template #extra>-->
<!-- <el-form-item-->
<!-- label="节点调度"-->
<!-- style="margin-bottom: 0; margin-right: 20px"-->
<!-- >-->
<!-- <el-radio-group-->
<!-- :disabled="detail.isExternal"-->
<!-- v-model="tempSchedulable"-->
<!-- size="small"-->
<!-- @change="onChangeSchedulable"-->
<!-- >-->
<!-- <el-radio-button label="启用" :value="true" />-->
<!-- <el-radio-button label="禁用" :value="false" />-->
<!-- </el-radio-group>-->
<!-- </el-form-item>-->
<!-- </template>-->
<!-- <template #extra>-->
<!-- <el-form-item-->
<!-- label="节点调度"-->
<!-- style="margin-bottom: 0; margin-right: 20px"-->
<!-- >-->
<!-- <el-radio-group-->
<!-- :disabled="detail.isExternal"-->
<!-- v-model="tempSchedulable"-->
<!-- size="small"-->
<!-- @change="onChangeSchedulable"-->
<!-- >-->
<!-- <el-radio-button label="启用" :value="true" />-->
<!-- <el-radio-button label="禁用" :value="false" />-->
<!-- </el-radio-group>-->
<!-- </el-form-item>-->
<!-- </template>-->
</back-header>
<block-box class="node-block">
@ -39,11 +39,21 @@
<block-box>
<ul class="card-gauges">
<li v-for="(item, index) in gaugeConfig" :key="index">
<template v-if="!detail.isExternal || index >= 2">
<li v-for="(item, index) in gaugeConfig.slice(0, 4)" :key="index">
<template v-if="!detail.isExternal || item.title.includes('使用率')">
<Gauge v-bind="item" />
</template>
<template v-else-if="detail.isExternal && index < 2">
<template v-else-if="detail.isExternal && item.title.includes('')">
<el-empty description="暂无资源分配数据" :image-size="90" />
</template>
</li>
</ul>
<ul class="card-gauges" style="margin-top: 20px;">
<li v-for="(item, index) in gaugeConfig.slice(4, 7)" :key="index">
<template v-if="!detail.isExternal || item.title.includes('使用率')">
<Gauge v-bind="item" />
</template>
<template v-else-if="detail.isExternal && item.title.includes('')">
<el-empty description="暂无资源分配数据" :image-size="90" />
</template>
</li>
@ -56,14 +66,13 @@
<time-picker v-model="times" type="datetimerange" size="small" />
</template>
<div style="height: 200px">
<echarts-plus
:options="
getRangeOptions({
core: gaugeConfig[0].data,
memory: gaugeConfig[1].data,
<echarts-plus :options="getRangeOptions({
cpu: gaugeConfig[7].data,
internal: gaugeConfig[8].data,
core: gaugeConfig[4].data,
memory: gaugeConfig[5].data,
})
"
/>
" />
</div>
</block-box>
<block-box title="资源使用趋势(%">
@ -71,14 +80,13 @@
<time-picker v-model="times" type="datetimerange" size="small" />
</template>
<div style="height: 200px">
<echarts-plus
:options="
getRangeOptions({
core: gaugeConfig[2].data,
<echarts-plus :options="getRangeOptions({
cpu: gaugeConfig[0].data,
internal: gaugeConfig[1].data,
core: gaugeConfig[6].data,
memory: gaugeConfig[3].data,
})
"
/>
" />
</div>
</block-box>
</div>
@ -103,7 +111,7 @@
import BackHeader from '@/components/BackHeader.vue';
import { useRoute, useRouter } from 'vue-router';
import BlockBox from '@/components/BlockBox.vue';
import {computed, onMounted, ref, watch} from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { Tools } from '@element-plus/icons-vue';
import CardList from '~/vgpu/views/card/admin/index.vue';
import TaskList from '~/vgpu/views/task/admin/index.vue';
@ -116,10 +124,12 @@ import { getLineOptions } from '~/vgpu/views/monitor/overview/getOptions';
import { ElMessage, ElMessageBox } from 'element-plus';
import api from '~/vgpu/api/task';
import { getRangeOptions } from './getOptions';
import {getDaysInRange} from "@/utils";
import { bytesToGB } from "@/utils";
import useParentAction from '~/vgpu/hooks/useParentAction';
const { sendRouteChange } = useParentAction();
const route = useRoute();
const router = useRouter();
const detail = ref({});
@ -155,6 +165,46 @@ const cp = useInstantVector(
const gaugeConfig = useInstantVector(
[
{
title: 'CPU 使用率',
percent: 0,
query: `count(node_cpu_seconds_total{mode="idle", instance=~"$node"}) by (instance)*(1 - avg(rate(node_cpu_seconds_total{mode="idle", instance=~"$node"}[5m])) by (instance))`,
totalQuery: `count(node_cpu_seconds_total{mode="idle", instance=~"$node"}) by (instance)`,
percentQuery: `100 * (1 - avg by(instance)(irate(node_cpu_seconds_total{mode="idle", instance=~"$node"}[1m])))`,
total: 0,
used: 0,
unit: '核',
},
{
title: '内存 使用率',
percent: 0,
query: `avg(node_memory_MemTotal_bytes{instance=~"$node"} - node_memory_MemAvailable_bytes{instance=~"$node"}) by (instance) / 1024 / 1024 / 1024`,
totalQuery: `avg(node_memory_MemTotal_bytes{instance=~"$node"}) by (instance) / 1024 / 1024 / 1024`,
percentQuery: `100 * (1 - node_memory_MemAvailable_bytes{instance=~"$node"} / node_memory_MemTotal_bytes{instance=~"$node"})`,
total: 0,
used: 0,
unit: 'GiB',
},
{
title: '磁盘 使用率',
percent: 0,
query: `sum(node_filesystem_size_bytes{instance=~"$node", fstype=~"ext4|xfs", mountpoint!~"/var/lib/kubelet/pods.*"} - node_filesystem_free_bytes{instance=~"$node", fstype=~"ext4|xfs", mountpoint!~"/var/lib/kubelet/pods.*"}) by (instance) / 1024 / 1024 / 1024`,
totalQuery: `sum(node_filesystem_size_bytes{instance=~"$node", fstype=~"ext4|xfs", mountpoint!~"/var/lib/kubelet/pods.*"}) by (instance) / 1024 / 1024 / 1024`,
percentQuery: ``,
total: 0,
used: 0,
unit: 'GiB',
},
{
title: '显存使用率',
percent: 0,
query: `avg(sum(hami_memory_used{node=~"$node"}) by (instance)) / 1024`,
totalQuery: `avg(sum(hami_memory_size{node=~"$node"}) by (instance))/1024`,
percentQuery: `(avg(sum(hami_memory_used{node=~"$node"}) by (instance)) / 1024)/(avg(sum(hami_memory_size{node=~"$node"}) by (instance))/1024)*100`,
total: 0,
used: 0,
unit: 'GiB',
},
{
title: '算力分配率',
percent: 0,
@ -186,11 +236,21 @@ const gaugeConfig = useInstantVector(
unit: ' ',
},
{
title: '显存使用率',
title: 'CPU 分配率',
percent: 0,
query: `avg(sum(hami_memory_used{node=~"$node"}) by (instance)) / 1024`,
totalQuery: `avg(sum(hami_memory_size{node=~"$node"}) by (instance))/1024`,
percentQuery: `(avg(sum(hami_memory_used{node=~"$node"}) by (instance)) / 1024)/(avg(sum(hami_memory_size{node=~"$node"}) by (instance))/1024)*100`,
query: ``,
totalQuery: ``,
percentQuery: `avg(sum(hami_container_vcore_allocated{node=~"$node"}) by (instance) / sum(hami_core_size{node=~"$node"}) by (instance) * 100)`,
total: 0,
used: 0,
unit: '核',
},
{
title: '内存 分配率',
percent: 0,
query: ``,
totalQuery: ``,
percentQuery: `avg(sum(hami_container_memory_allocated{node=~"$node"}) by (instance) / sum(hami_memory_size{node=~"$node"}) by (instance) * 100)`,
total: 0,
used: 0,
unit: 'GiB',
@ -231,7 +291,7 @@ const detailColumns = [
value: 'operatingSystem',
render: ({ operatingSystem }) => (
<span>
{operatingSystem==='' ? '--' : operatingSystem}
{operatingSystem === '' ? '--' : operatingSystem}
</span>
),
},
@ -240,7 +300,7 @@ const detailColumns = [
value: 'architecture',
render: ({ architecture }) => (
<span>
{architecture==='' ? '--' : architecture}
{architecture === '' ? '--' : architecture}
</span>
),
},
@ -249,7 +309,7 @@ const detailColumns = [
value: 'kubeletVersion',
render: ({ kubeletVersion }) => (
<span>
{kubeletVersion==='' ? '--' : kubeletVersion}
{kubeletVersion === '' ? '--' : kubeletVersion}
</span>
),
},
@ -258,7 +318,7 @@ const detailColumns = [
value: 'osImage',
render: ({ osImage }) => (
<span>
{osImage==='' ? '--' : osImage}
{osImage === '' ? '--' : osImage}
</span>
),
},
@ -267,7 +327,7 @@ const detailColumns = [
value: 'kernelVersion',
render: ({ kernelVersion }) => (
<span>
{kernelVersion==='' ? '--' : kernelVersion}
{kernelVersion === '' ? '--' : kernelVersion}
</span>
),
},
@ -276,7 +336,7 @@ const detailColumns = [
value: 'kubeProxyVersion',
render: ({ kubeProxyVersion }) => (
<span>
{kubeProxyVersion==='' ? '--' : kubeProxyVersion}
{kubeProxyVersion === '' ? '--' : kubeProxyVersion}
</span>
),
},
@ -285,7 +345,7 @@ const detailColumns = [
value: 'containerRuntimeVersion',
render: ({ containerRuntimeVersion }) => (
<span>
{containerRuntimeVersion==='' ? '--' : containerRuntimeVersion}
{containerRuntimeVersion === '' ? '--' : containerRuntimeVersion}
</span>
),
},
@ -294,7 +354,7 @@ const detailColumns = [
value: 'cardCnt',
render: ({ cardCnt }) => (
<span>
{cardCnt==='' ? '--' : cardCnt}
{cardCnt === '' ? '--' : cardCnt}
</span>
),
},
@ -303,7 +363,7 @@ const detailColumns = [
value: 'creationTimestamp',
render: ({ creationTimestamp }) => (
<span>
{creationTimestamp==='' ? '--' : creationTimestamp}
{creationTimestamp === '' ? '--' : creationTimestamp}
</span>
),
},
@ -373,6 +433,7 @@ onMounted(async () => {
//line-height: 20px;
margin-bottom: 20px;
}
//.node-detail-left {
// min-width: 800px;
//}
@ -405,6 +466,7 @@ onMounted(async () => {
.gauges {
flex: 1;
display: flex;
li {
flex: 1;
}
@ -417,8 +479,9 @@ onMounted(async () => {
list-style: none;
display: flex;
height: 200px;
li {
flex: 1;
flex: 0.25;
}
}
@ -431,6 +494,7 @@ onMounted(async () => {
.node-block {
display: flex;
flex-direction: column;
.home-block-content {
flex: 1;
}

@ -1,6 +1,11 @@
import { timeParse } from '@/utils';
export const getRangeOptions = ({ core = [], memory = [] }) => {
export const getRangeOptions = ({
core = [],
memory = [],
cpu = [],
internal = [],
}) => {
return {
legend: {
// data: [],
@ -115,6 +120,72 @@ export const getRangeOptions = ({ core = [], memory = [] }) => {
color: 'rgb(145, 204, 117)', // 设置线条颜色为橙色
},
},
{
name: 'CPU',
data: cpu,
type: 'line',
areaStyle: {
normal: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(255, 99, 71, 0.16)',
},
{
offset: 1,
color: 'rgba(255, 99, 71, 0.00)',
},
],
global: false,
},
},
},
itemStyle: {
color: 'rgb(255, 99, 71)',
},
lineStyle: {
color: 'rgb(255, 99, 71)',
},
},
{
name: '内存',
data: internal,
type: 'line',
areaStyle: {
normal: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(138, 43, 226, 0.16)',
},
{
offset: 1,
color: 'rgba(138, 43, 226, 0.00)',
},
],
global: false,
},
},
},
itemStyle: {
color: 'rgb(138, 43, 226)',
},
lineStyle: {
color: 'rgb(138, 43, 226)',
},
},
],
};
};

@ -5,7 +5,7 @@
</template>
</list-header>
<preview-bar :handle-click=handleClick />
<preview-bar :handle-click=handleClick :key="componentKey" />
<table-plus :api="nodeApi.getNodeList()" :columns="columns" :rowAction="rowAction" :searchSchema="searchSchema"
:hasPagination="false" style="height: auto" hideTag ref="table" staticPage>
@ -36,16 +36,19 @@
<script setup lang="jsx">
import nodeApi from '~/vgpu/api/node';
import searchSchema from '~/vgpu/views/node/admin/searchSchema';
import { useRouter } from 'vue-router';
import PreviewBar from '~/vgpu/components/previewBar.vue';
import { roundToDecimal } from '@/utils';
import { bytesToGB, roundToDecimal } from '@/utils';
import { ElMessage, ElMessageBox } from 'element-plus';
import { ref } from 'vue';
import useParentAction from '~/vgpu/hooks/useParentAction';
const router = useRouter();
const { sendRouteChange } = useParentAction();
const table = ref();
const componentKey = ref(0);
//
const dialogVisible = ref(false)
const nodeList = ref([])
@ -61,7 +64,7 @@ const handleClick = async (params) => {
const node = list.find(node => node.name === name);
if (node) {
const uuid = node.uid;
router.push(`/admin/vgpu/node/admin/${uuid}?nodeName=${name}`);
sendRouteChange(`/admin/vgpu/node/admin/${uuid}?nodeName=${name}`);
} else {
ElMessage.error('节点未找到');
}
@ -83,7 +86,8 @@ const handleOk = async () => {
node_names
})
if (res?.code === 200) {
getList();
table.value.fetchData();
componentKey.value += 1;
dialogVisible.value = false;
}
} finally {
@ -152,6 +156,26 @@ const columns = [
// return r.map((item) => ({ text: item, value: item }));
// },
},
{
title: 'CPU',
dataIndex: 'coreTotal',
render: ({ coreTotal }) => `${coreTotal}`,
},
{
title: '内存',
dataIndex: 'memoryTotal',
render: ({ memoryTotal }) => `${bytesToGB(memoryTotal)}GiB`,
},
{
title: '磁盘',
dataIndex: 'diskSize',
render: ({ diskSize }) => `${bytesToGB(diskSize)}GiB`,
},
{
title: '所属资源池',
dataIndex: 'resourcePools',
render: ({ resourcePools }) => `${resourcePools.join('、')}`,
},
{
title: '显卡数量',
dataIndex: 'cardCnt',
@ -167,6 +191,7 @@ const columns = [
},
{
title: '算力(已分配/总量)',
width: 120,
dataIndex: 'used',
render: ({ coreTotal, coreUsed, isExternal }) => (
<span>
@ -177,6 +202,7 @@ const columns = [
{
title: '显存(已分配/总量)',
dataIndex: 'w',
width: 120,
render: ({ memoryTotal, memoryUsed, isExternal }) => (
<span>
{isExternal ? '--' : roundToDecimal(memoryUsed / 1024, 1)}/
@ -190,72 +216,72 @@ const rowAction = [
{
title: '查看详情',
onClick: (row) => {
router.push(`/admin/vgpu/node/admin/${row.uid}?nodeName=${row.name}`);
sendRouteChange(`/admin/vgpu/node/admin/${row.uid}?nodeName=${row.name}`);
},
},
{
title: '禁用',
hidden: (row) => !row.isSchedulable,
onClick: async (row) => {
ElMessageBox.confirm(`确认对该节点进行禁用操作?`, '操作确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
await nodeApi.stop(
{
nodeName: row.name,
status: 'DISABLED'
}
).then(
() => {
setTimeout(() => {
ElMessage.success('节点禁用成功');
table.value.fetchData();
}, 500);
}
)
} catch (error) {
ElMessage.error(error.message);
}
})
.catch(() => { });
},
},
{
title: '开启',
hidden: (row) => row.isSchedulable,
disabled: (row) => row.isExternal,
onClick: async (row) => {
ElMessageBox.confirm(`确认对该节点进行开启调度操作?`, '操作确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
await nodeApi.stop(
{
nodeName: row.name,
status: 'ENABLE'
}
).then(
() => {
setTimeout(() => {
ElMessage.success('节点开启调度成功');
table.value.fetchData();
}, 500);
}
)
} catch (error) {
ElMessage.error(error.message);
}
})
.catch(() => { });
},
},
// {
// title: '',
// hidden: (row) => !row.isSchedulable,
// onClick: async (row) => {
// ElMessageBox.confirm(``, '', {
// confirmButtonText: '',
// cancelButtonText: '',
// type: 'warning',
// })
// .then(async () => {
// try {
// await nodeApi.stop(
// {
// nodeName: row.name,
// switch: 'on'
// }
// ).then(
// () => {
// setTimeout(() => {
// ElMessage.success('');
// table.value.fetchData();
// }, 500);
// }
// )
// } catch (error) {
// ElMessage.error(error.message);
// }
// })
// .catch(() => {});
// },
// },
// {
// title: '',
// hidden: (row) => row.isSchedulable,
// disabled: (row) => row.isExternal,
// onClick: async (row) => {
// ElMessageBox.confirm(``, '', {
// confirmButtonText: '',
// cancelButtonText: '',
// type: 'warning',
// })
// .then(async () => {
// try {
// await nodeApi.stop(
// {
// nodeName: row.name,
// switch: 'off'
// }
// ).then(
// () => {
// setTimeout(() => {
// ElMessage.success('');
// table.value.fetchData();
// }, 500);
// }
// )
// } catch (error) {
// ElMessage.error(error.message);
// }
// })
// .catch(() => {});
// },
// },
];
</script>

Loading…
Cancel
Save