You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
572 lines
15 KiB
572 lines
15 KiB
<template>
|
|
<div class="home">
|
|
<div class="home-left">
|
|
<Block title="资源使用率">
|
|
<!-- <template #extra>
|
|
<div class="all-btn" @click="sendRouteChange('/admin/vgpu/card/admin')">
|
|
全部<svg-icon icon="more" style="margin-left: 4px" />
|
|
</div>
|
|
</template> -->
|
|
<div class="card-overview">
|
|
<div v-for="item in cardGaugeConfig.slice(0, 5)" :key="item.title">
|
|
<Gauge v-bind="item" />
|
|
</div>
|
|
</div>
|
|
<div class="card-overview">
|
|
<div v-for="item in cardGaugeConfig.slice(-3)" :key="item.title">
|
|
<Gauge v-bind="item" />
|
|
</div>
|
|
</div>
|
|
</Block>
|
|
<Block title="资源总览">
|
|
<!-- <template #extra>
|
|
<div class="all-btn" @click="sendRouteChange('/admin/vgpu/card/admin')">
|
|
全部<svg-icon icon="more" style="margin-left: 4px" />
|
|
</div>
|
|
</template> -->
|
|
<ul class="resourceOverview">
|
|
<li v-for="{ title, count, icon, to, unit } in resourceOverview" :key="title"
|
|
:style="{ cursor: to ? 'pointer' : 'default' }" @click="sendRouteChange(to)">
|
|
<div class="avatar">
|
|
<svg-icon :icon="icon" />
|
|
</div>
|
|
<div class="main">
|
|
<div>
|
|
{{ title }}
|
|
</div>
|
|
<div class="count">
|
|
{{ count }} <span style="font-size: 12px">{{ unit }}</span>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</Block>
|
|
|
|
<Block v-for="{ title, dataSource } in rangeConfig" :title="title" :key="title">
|
|
<template #extra>
|
|
<time-picker v-model="times" type="datetimerange" size="small" />
|
|
</template>
|
|
<echarts-plus :options="getRangeOptions(dataSource)" style="height: 250px" />
|
|
</Block>
|
|
</div>
|
|
|
|
<div class="home-right">
|
|
<Block title="节点总览" style="margin-bottom: 16px">
|
|
<template #extra>
|
|
<div class="all-btn" @click="sendRouteChange('/admin/vgpu/node/admin')">
|
|
全部<svg-icon icon="more" style="margin-left: 4px" />
|
|
</div>
|
|
</template>
|
|
<ul class="node-all">
|
|
<li v-for="{ title, status, count, color } in nodes" :key="title" @click="
|
|
sendRouteChange(`/admin/vgpu/node/admin?isSchedulable=${status}`)
|
|
">
|
|
<div class="title">{{ title }}</div>
|
|
<div class="count" :style="{ color }">
|
|
{{ count }}
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</Block>
|
|
<Block title="显卡类型分布" style="margin-bottom: 16px">
|
|
<template #extra>
|
|
<div class="all-btn" @click="sendRouteChange('/admin/vgpu/card/admin')">
|
|
全部<svg-icon icon="more" style="margin-left: 4px" />
|
|
</div>
|
|
</template>
|
|
<div style="height: 218px">
|
|
<echarts-plus :options="getCardOptions(cardData, chartWidth)" :onClick="handlePieClick" />
|
|
</div>
|
|
</Block>
|
|
|
|
<TabTop v-bind="nodeTotalTop" :onClick="(params) => handleChartClick(params, router)"
|
|
style="margin-bottom: 16px" />
|
|
<TabTop v-bind="nodeUsedTop" :onClick="(params) => handleChartClick(params, router)" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, ref, computed, reactive, watch, watchEffect } from 'vue';
|
|
import {
|
|
getCardOptions,
|
|
handleChartClick,
|
|
getRangeOptions,
|
|
} from './getOptions';
|
|
import Block from './Block.vue';
|
|
import './style.scss';
|
|
import { timeParse, getDaysInRange, getRandom, bytesToGB } from '@/utils';
|
|
import { useRouter } from 'vue-router';
|
|
import UserCard from '@/components/UserCard.vue';
|
|
import nodeApi from '~/vgpu/api/node';
|
|
import pollApi from '~/vgpu/api/poll';
|
|
import taskApi from '~/vgpu/api/task';
|
|
import monitorApi from '~/vgpu/api/monitor';
|
|
import cardApi from '~/vgpu/api/card';
|
|
import useInstantVector from '~/vgpu/hooks/useInstantVector';
|
|
import useParentAction from '~/vgpu/hooks/useParentAction';
|
|
import useFetchList from '@/hooks/useFetchList';
|
|
import { getTopOptions } from '~/vgpu/components/config';
|
|
import EchartsPlus from '@/components/Echarts-plus.vue';
|
|
import TabTop from '~/vgpu/components/TabTop.vue';
|
|
import TimeSelect from '~/vgpu/components/timeSelect.vue';
|
|
import Gauge from '~/vgpu/components/gauge.vue';
|
|
import { rangeConfigInit } from './config';
|
|
|
|
const router = useRouter();
|
|
|
|
const end = new Date();
|
|
const start = new Date();
|
|
start.setTime(start.getTime() - 3600 * 1000);
|
|
|
|
const { sendRouteChange } = useParentAction();
|
|
|
|
const times = ref([start, end]);
|
|
|
|
const handlePieClick = (params) => {
|
|
sendRouteChange(`/admin/vgpu/card/admin?type=${params.data.name}`);
|
|
};
|
|
|
|
|
|
const alarmData = ref([])
|
|
const chartWidth = ref(200);
|
|
|
|
const cardGaugeConfig = useInstantVector([
|
|
{
|
|
title: 'CPU 使用率',
|
|
percent: 0,
|
|
query: `sum(count by(instance) (node_cpu_seconds_total{mode="idle"})*(1 - avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[1m]))))`,
|
|
totalQuery: `sum(count(node_cpu_seconds_total{mode="system"}) by (instance))`,
|
|
percentQuery: ``,
|
|
total: 0,
|
|
used: 0,
|
|
unit: '核',
|
|
},
|
|
{
|
|
title: '内存 使用率',
|
|
percent: 0,
|
|
query: `(sum(node_memory_MemTotal_bytes) - sum(node_memory_MemAvailable_bytes)) / 1024 / 1024 / 1024`,
|
|
totalQuery: `sum(node_memory_MemTotal_bytes) / 1024 / 1024 / 1024`,
|
|
percentQuery: ``,
|
|
total: 0,
|
|
used: 0,
|
|
unit: 'GiB',
|
|
},
|
|
{
|
|
title: '磁盘 使用率',
|
|
percent: 0,
|
|
query: `(sum(node_filesystem_size_bytes{fstype!~"tmpfs|overlay"})-sum(node_filesystem_free_bytes{fstype!~"tmpfs|overlay"})) / 1024 / 1024 / 1024`,
|
|
totalQuery: `sum(node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) / 1024 / 1024 / 1024`,
|
|
percentQuery: ``,
|
|
total: 0,
|
|
used: 0,
|
|
unit: 'GiB',
|
|
},
|
|
{
|
|
title: 'vGPU 分配率',
|
|
percent: 0,
|
|
query: `avg(sum (hami_container_vgpu_allocated) by (instance))`,
|
|
totalQuery: `avg(sum (hami_vgpu_count) by (instance))`,
|
|
percentQuery: `avg(sum (hami_container_vgpu_allocated) by (instance))/avg(sum (hami_vgpu_count) by (instance)) *100`,
|
|
total: 0,
|
|
used: 0,
|
|
unit: '个',
|
|
},
|
|
{
|
|
title: '算力分配率',
|
|
percent: 0,
|
|
query: `avg(sum(hami_container_vcore_allocated) by (instance))`,
|
|
totalQuery: `avg(sum(hami_core_size) by (instance))`,
|
|
percentQuery: `avg(sum(hami_container_vcore_allocated) by (instance))/avg(sum(hami_core_size) by (instance)) *100`,
|
|
total: 0,
|
|
used: 0,
|
|
unit: ' ',
|
|
},
|
|
{
|
|
title: '显存分配率',
|
|
percent: 0,
|
|
query: `avg(sum(hami_container_vmemory_allocated) by (instance)) / 1024`,
|
|
totalQuery: `avg(sum(hami_memory_size) by (instance)) / 1024`,
|
|
percentQuery: `(avg(sum(hami_container_vmemory_allocated) by (instance)) / 1024 )/(avg(sum(hami_memory_size) by (instance)) / 1024) *100 `,
|
|
total: 0,
|
|
used: 0,
|
|
unit: 'GiB',
|
|
},
|
|
{
|
|
title: '算力使用率',
|
|
percent: 0,
|
|
query: `avg(sum(hami_core_util) by (instance))`,
|
|
percentQuery: `avg(sum(hami_core_util_avg) by (instance))`,
|
|
totalQuery: `avg(sum(hami_core_size) by (instance))`,
|
|
total: 100,
|
|
used: 0,
|
|
unit: ' ',
|
|
},
|
|
{
|
|
title: '显存使用率',
|
|
percent: 0,
|
|
query: `avg(sum(hami_memory_used) by (instance)) / 1024`,
|
|
totalQuery: `avg(sum(hami_memory_size) by (instance))/1024`,
|
|
percentQuery: `(avg(sum(hami_memory_used) by (instance)) / 1024)/(avg(sum(hami_memory_size) by (instance))/1024)*100`,
|
|
total: 0,
|
|
used: 0,
|
|
unit: 'GiB',
|
|
},
|
|
]);
|
|
|
|
const resourceOverview = ref([
|
|
{
|
|
title: '节点',
|
|
count: 0,
|
|
icon: 'vgpu-node',
|
|
unit: '个',
|
|
to: '/admin/vgpu/node/admin'
|
|
},
|
|
{
|
|
title: '资源池',
|
|
count: 0,
|
|
icon: 'vgpu-pool-tab',
|
|
unit: '个',
|
|
to: '/admin/vgpu/poll/admin'
|
|
},
|
|
{
|
|
title: 'CPU',
|
|
count: 0,
|
|
icon: 'vgpu-chip',
|
|
unit: '核',
|
|
},
|
|
{
|
|
title: '内存',
|
|
count: 0,
|
|
icon: 'vgpu-memory',
|
|
unit: 'GIB',
|
|
},
|
|
{
|
|
title: '磁盘',
|
|
count: 0,
|
|
icon: 'vgpu-disk',
|
|
unit: 'GIB',
|
|
},
|
|
{
|
|
title: '显卡',
|
|
count: 0,
|
|
icon: 'vgpu-gpu-d',
|
|
unit: '张',
|
|
to: '/admin/vgpu/card/admin'
|
|
},
|
|
{
|
|
title: 'vGPU',
|
|
count: 0,
|
|
icon: 'vgpu-card',
|
|
unit: '个',
|
|
},
|
|
{
|
|
title: '算力',
|
|
count: 12,
|
|
icon: 'vgpu-core',
|
|
unit: ' ',
|
|
},
|
|
{
|
|
title: '显存',
|
|
count: 31,
|
|
icon: 'vgpu-mem',
|
|
unit: 'GiB',
|
|
},
|
|
]);
|
|
|
|
const nodeConfig = reactive({
|
|
instance: [
|
|
{ title: '可调度', key: 'yes', color: '#2563EB', value: 0 },
|
|
{ title: '禁止调度', key: 'no', color: '#DC2626', value: 0 },
|
|
],
|
|
core: [
|
|
{ title: '已分配', key: 'used', color: '#2563EB', value: 0 },
|
|
{ title: '闲置', key: 'free', color: '#B6C2CD', value: 0 },
|
|
{ title: '分配率', key: 'percent', color: '#B6C2CD', value: 0, unit: '%' },
|
|
],
|
|
memory: [
|
|
{
|
|
title: '已分配',
|
|
key: 'used',
|
|
color: '#2563EB',
|
|
value: 0,
|
|
unit: 'GiB',
|
|
},
|
|
{
|
|
title: '闲置',
|
|
key: 'free',
|
|
color: '#B6C2CD',
|
|
value: 0,
|
|
unit: 'GiB',
|
|
},
|
|
{
|
|
title: '分配率',
|
|
key: 'percent',
|
|
color: '#B6C2CD',
|
|
value: 0,
|
|
unit: '%',
|
|
},
|
|
],
|
|
});
|
|
|
|
const nodeData = useFetchList(nodeApi.getNodeListReq({ filters: {} }));
|
|
|
|
const cardData = useFetchList(cardApi.getCardListReq({ filters: {} }));
|
|
|
|
const pollData = useFetchList(pollApi.getPollList(), 'data');
|
|
|
|
const cardDetail = useInstantVector([
|
|
{
|
|
title: 'vGPU',
|
|
count: 0,
|
|
query: 'avg(hami_vgpu_count)',
|
|
unit: '个',
|
|
icon: 'gpu2',
|
|
},
|
|
{
|
|
title: '算力',
|
|
count: 0,
|
|
unit: ' ',
|
|
icon: 'account',
|
|
query: 'avg(hami_core_size)',
|
|
},
|
|
{
|
|
title: '显存',
|
|
count: 0,
|
|
unit: 'GiB',
|
|
icon: 'volume',
|
|
query: 'avg(hami_memory_size) / 1024',
|
|
},
|
|
]);
|
|
|
|
const nodes = computed(() => [
|
|
|
|
{
|
|
title: '可调度',
|
|
count: nodeData.value.filter((item) => !item.isExternal && item.isSchedulable).length,
|
|
isSchedulable: true,
|
|
isExternal: false,
|
|
status: 'true',
|
|
color: '#16A34A',
|
|
},
|
|
{
|
|
title: '禁止调度',
|
|
count: nodeData.value.filter((item) => !item.isExternal && !item.isSchedulable).length,
|
|
isSchedulable: false,
|
|
isExternal: false,
|
|
status: 'false',
|
|
color: '#1D2B3A',
|
|
},
|
|
]);
|
|
|
|
const exceed = useInstantVector([
|
|
{ title: 'vGPU 超配', count: 0, type: 'vgpu', query: 'avg(hami_vgpu_count)' },
|
|
{
|
|
title: '算力超配',
|
|
count: 0,
|
|
type: 'core',
|
|
query: 'avg(hami_vcore_scaling)',
|
|
},
|
|
{
|
|
title: '显存超配',
|
|
count: 0,
|
|
type: 'memory',
|
|
query: 'avg(hami_vmemory_scaling)',
|
|
},
|
|
]);
|
|
|
|
const nodeUsedTop = {
|
|
title: '节点资源使用率 Top5',
|
|
key: 'used',
|
|
config: [
|
|
// {
|
|
// tab: 'CPU',
|
|
// key: 'cpu',
|
|
// nameKey: 'instance',
|
|
// data: [],
|
|
// query: 'topk(5, avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])))',
|
|
// },
|
|
// {
|
|
// tab: '内存',
|
|
// key: 'internal',
|
|
// nameKey: 'instance',
|
|
// data: [],
|
|
// query: 'topk(5, ((node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes))',
|
|
// },
|
|
{
|
|
tab: '算力',
|
|
key: 'core',
|
|
nameKey: 'node',
|
|
data: [],
|
|
query: 'topk(5, avg(hami_core_util_avg) by (node))',
|
|
},
|
|
{
|
|
tab: '显存',
|
|
key: 'memory',
|
|
data: [],
|
|
nameKey: 'node',
|
|
query:
|
|
'topk(5, avg(hami_memory_used) by (node) / avg(hami_memory_size) by (node) * 100)',
|
|
},
|
|
],
|
|
};
|
|
|
|
const nodeTotalTop = {
|
|
title: '节点资源分配率 Top5',
|
|
key: 'used',
|
|
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',
|
|
nameKey: 'node',
|
|
data: [],
|
|
query: `topk(5, avg(hami_container_vgpu_allocated{}) by (node) / avg(hami_vgpu_count{}) by (node) * 100)`,
|
|
},
|
|
{
|
|
tab: '算力',
|
|
key: 'core',
|
|
nameKey: 'node',
|
|
data: [],
|
|
query:
|
|
'topk(5, avg(hami_container_vcore_allocated{}) by (node) / avg(hami_core_size{}) by (node) * 100)',
|
|
},
|
|
{
|
|
tab: '显存',
|
|
key: 'memory',
|
|
data: [],
|
|
nameKey: 'node',
|
|
query:
|
|
'topk(5, avg(hami_container_vmemory_allocated{}) by (node) / avg(hami_memory_size{}) by (node) * 100)',
|
|
},
|
|
],
|
|
};
|
|
|
|
const rangeConfig = ref(rangeConfigInit);
|
|
|
|
const fetchRangeData = () => {
|
|
|
|
const params = {
|
|
range: {
|
|
start: timeParse(times.value[0]),
|
|
end: timeParse(times.value[1]),
|
|
step: '1m',
|
|
},
|
|
};
|
|
|
|
for (const item of rangeConfig.value) {
|
|
for (const v of item.dataSource) {
|
|
cardApi
|
|
.getRangeVector({
|
|
...params,
|
|
query: v.query,
|
|
})
|
|
.then((res) => {
|
|
v.data = res.data[0].values;
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
// cardApi
|
|
// .getRangeVector({
|
|
// ...params,
|
|
// query: `sum({__name__=~"alert:.*:count"})`,
|
|
// })
|
|
// .then((res) => {
|
|
// alarmData.value = res.data[0].values;
|
|
// });
|
|
|
|
};
|
|
|
|
watchEffect(() => {
|
|
resourceOverview.value[0].count = nodeData.value.length;
|
|
resourceOverview.value[1].count = pollData.value.length;
|
|
resourceOverview.value[2].count = cardGaugeConfig.value[0].total;
|
|
resourceOverview.value[3].count = Math.round(cardGaugeConfig.value[1].total);
|
|
resourceOverview.value[4].count = Math.round(cardGaugeConfig.value[2].total);
|
|
resourceOverview.value[5].count = cardData.value.length;
|
|
resourceOverview.value[6].count = cardGaugeConfig.value[3].total;
|
|
resourceOverview.value[7].count = cardGaugeConfig.value[4].total;
|
|
resourceOverview.value[8].count = cardGaugeConfig.value[5].total.toFixed(0);
|
|
});
|
|
|
|
onMounted(async () => {
|
|
const summary = await monitorApi.summary({
|
|
filters: {},
|
|
});
|
|
|
|
const nodeDataRes = {
|
|
yes: nodeData.value.filter((item) => item.isSchedulable).length,
|
|
no: nodeData.value.filter((item) => !item.isSchedulable).length,
|
|
};
|
|
|
|
nodeConfig.instance = nodeConfig.instance.map((item) => {
|
|
return { ...item, value: nodeDataRes[item.key] };
|
|
});
|
|
|
|
nodeConfig.core = nodeConfig.core.map((item) => {
|
|
const core_total = cardDetail.value[1].percent;
|
|
const coreData = {
|
|
percent: ((summary.coreUsed / core_total).toFixed(2) * 100).toFixed(0),
|
|
used: summary.coreUsed,
|
|
free: summary.coreTotal - summary.coreUsed,
|
|
};
|
|
|
|
return { ...item, value: coreData[item.key] };
|
|
});
|
|
|
|
nodeConfig.memory = nodeConfig.memory.map((item) => {
|
|
const memory_total = cardDetail.value[2].percent;
|
|
const coreData = {
|
|
percent: (
|
|
(summary.memoryUsed / 1024 / memory_total).toFixed(2) * 100
|
|
).toFixed(0),
|
|
used: summary.memoryUsed,
|
|
free: summary.memoryTotal - summary.memoryUsed,
|
|
};
|
|
|
|
return { ...item, value: coreData[item.key] };
|
|
});
|
|
});
|
|
|
|
watch(
|
|
times,
|
|
() => {
|
|
// fetchLineData();
|
|
fetchRangeData();
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
</script>
|
|
|
|
<style>
|
|
.el-progress-bar__outer {
|
|
background-color: #b6c2cd;
|
|
}
|
|
|
|
.card-overview {
|
|
padding-bottom: 10px;
|
|
height: 190px;
|
|
display: grid;
|
|
grid-template-columns: repeat(5, 1fr);
|
|
|
|
.gauge-info {
|
|
margin-top: 10px;
|
|
}
|
|
}
|
|
</style>
|