|
|
<template>
|
|
|
<div>
|
|
|
<back-header> {{ title }}管理 > {{ props.name }} </back-header>
|
|
|
<block-box class="node-block">
|
|
|
<div class="node-detail">
|
|
|
<div class="node-detail-left">
|
|
|
<div class="title">详细信息</div>
|
|
|
<ul class="node-detail-info">
|
|
|
<li v-for="{ label, value, render } in detailColumns">
|
|
|
<span class="label">{{ label }}</span>
|
|
|
<component v-if="render" :is="render(detail)" />
|
|
|
<span v-else class="value">{{ detail[value] }}</span>
|
|
|
</li>
|
|
|
|
|
|
<li class="cp" v-if="!hideCp">
|
|
|
<span v-for="{ label, count } in cp">
|
|
|
<span class="label">{{ label }}</span>
|
|
|
|
|
|
<span class="value">{{ count }} 倍</span>
|
|
|
</span>
|
|
|
</li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
|
|
|
<ul class="gauges">
|
|
|
<li v-for="item in gaugeConfig">
|
|
|
<Gauge v-bind="item" />
|
|
|
</li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
</block-box>
|
|
|
|
|
|
<div class="line-box">
|
|
|
<block-box
|
|
|
:title="title.replace('率', '趋势(%)')"
|
|
|
v-for="{ title, data } in gaugeConfig"
|
|
|
>
|
|
|
<template #extra>
|
|
|
<TimeSelect v-model="time" />
|
|
|
</template>
|
|
|
<div style="height: 200px">
|
|
|
<echarts-plus :options="getLineOptions({ data })" />
|
|
|
</div>
|
|
|
</block-box>
|
|
|
</div>
|
|
|
|
|
|
<block-box title="显卡列表" v-if="type !== 'deviceuuid'">
|
|
|
<CardList :hideTitle="true" :filters="filters" />
|
|
|
</block-box>
|
|
|
|
|
|
<block-box title="任务列表">
|
|
|
<TaskList :hideTitle="true" :filters="filters" />
|
|
|
</block-box>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="jsx">
|
|
|
import BackHeader from '@/components/BackHeader.vue';
|
|
|
import { useRoute } from 'vue-router';
|
|
|
import BlockBox from '@/components/BlockBox.vue';
|
|
|
import { onMounted, ref, watch } from 'vue';
|
|
|
import CardList from '~/vgpu/views/card/admin/index.vue';
|
|
|
import TaskList from '~/vgpu/views/task/admin/index.vue';
|
|
|
import Gauge from '~/vgpu/components/gauge.vue';
|
|
|
import useInstantVector from '~/vgpu/hooks/useInstantVector';
|
|
|
import EchartsPlus from '@/components/Echarts-plus.vue';
|
|
|
import cardApi from '~/vgpu/api/card';
|
|
|
import { timeParse } from '@/utils';
|
|
|
import { getLineOptions } from '~/vgpu/components/config';
|
|
|
import TimeSelect from '~/vgpu/components/timeSelect.vue';
|
|
|
|
|
|
const props = defineProps([
|
|
|
'title',
|
|
|
'detailColumns',
|
|
|
'type',
|
|
|
'detail',
|
|
|
'name',
|
|
|
'filters',
|
|
|
'hideCp',
|
|
|
]);
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
const time = ref(1 / 24);
|
|
|
|
|
|
const cp = useInstantVector(
|
|
|
[
|
|
|
{
|
|
|
label: 'vGPU 超配',
|
|
|
count: '0',
|
|
|
query: `avg(hami_vgpu_count{node=~"$node"})`,
|
|
|
},
|
|
|
{
|
|
|
label: '算力超配',
|
|
|
count: '0',
|
|
|
query: `avg(hami_vcore_scaling{node=~"$node"})`,
|
|
|
},
|
|
|
{
|
|
|
label: '显存超配',
|
|
|
count: '1.5',
|
|
|
query: `avg(hami_vmemory_scaling{node=~"$node"})`,
|
|
|
},
|
|
|
],
|
|
|
(query) => query.replaceAll('$node', props.detail.name),
|
|
|
);
|
|
|
|
|
|
const gaugeConfig = useInstantVector(
|
|
|
[
|
|
|
{
|
|
|
title: '算力分配率',
|
|
|
percent: 0,
|
|
|
query: `sum(hami_container_vcore_allocated{${props.type}=~"$${props.type}"})`,
|
|
|
totalQuery: `sum(hami_core_size{${props.type}=~"$${props.type}"})`,
|
|
|
percentQuery: `sum(hami_container_vcore_allocated{${props.type}=~"$${props.type}"}) / sum(hami_core_size{${props.type}=~"$${props.type}"}) *100`,
|
|
|
total: 0,
|
|
|
used: 0,
|
|
|
unit: ' ',
|
|
|
},
|
|
|
{
|
|
|
title: '显存分配率',
|
|
|
percent: 0,
|
|
|
query: `sum(hami_container_vmemory_allocated{${props.type}=~"$${props.type}"}) / 1024`,
|
|
|
totalQuery: `sum(hami_memory_size{${props.type}=~"$${props.type}"}) / 1024`,
|
|
|
percentQuery: `(sum(hami_container_vmemory_allocated{${props.type}=~"$${props.type}"}) / 1024) /(sum(hami_memory_size{${props.type}=~"$${props.type}"}) / 1024) *100`,
|
|
|
total: 0,
|
|
|
used: 0,
|
|
|
unit: 'GiB',
|
|
|
},
|
|
|
{
|
|
|
title: '算力使用率',
|
|
|
percent: 0,
|
|
|
query: `avg(hami_core_util{${props.type}=~"$${props.type}"})`,
|
|
|
percentQuery: `avg(hami_core_util_avg{${props.type}=~"$${props.type}"})`,
|
|
|
totalQuery: `sum(hami_core_size{${props.type}=~"$${props.type}"})`,
|
|
|
total: 100,
|
|
|
used: 0,
|
|
|
unit: ' ',
|
|
|
},
|
|
|
{
|
|
|
title: '显存使用率',
|
|
|
percent: 0,
|
|
|
query: `sum(hami_memory_used{${props.type}=~"$${props.type}"}) / 1024`,
|
|
|
totalQuery: `sum(hami_memory_size{${props.type}=~"$${props.type}"})/1024`,
|
|
|
percentQuery: `(sum(hami_memory_used{${props.type}=~"$${props.type}"}) / 1024)/(sum(hami_memory_size{${props.type}=~"$${props.type}"})/1024)*100`,
|
|
|
total: 0,
|
|
|
used: 0,
|
|
|
unit: 'GiB',
|
|
|
},
|
|
|
],
|
|
|
(query) => query.replaceAll(`$${props.type}`, props.name),
|
|
|
time,
|
|
|
);
|
|
|
|
|
|
// const fetchLineData = async () => {
|
|
|
// const start = new Date();
|
|
|
// start.setTime(start.getTime() - 3600 * 1000 * 24 * time.value);
|
|
|
//
|
|
|
// const lineReqs = gaugeConfig.value.map((item) =>
|
|
|
// cardApi.getRangeVector({
|
|
|
// range: {
|
|
|
// start: timeParse(start),
|
|
|
// end: timeParse(new Date()),
|
|
|
// step: '1m',
|
|
|
// },
|
|
|
// query: item.percentQuery.replaceAll(`$${props.type}`, props.name),
|
|
|
// }),
|
|
|
// );
|
|
|
//
|
|
|
// const res = await Promise.all(lineReqs);
|
|
|
//
|
|
|
// gaugeConfig.value = gaugeConfig.value.map((item, index) => ({
|
|
|
// ...item,
|
|
|
// data: res[index].data[0]?.values || [],
|
|
|
// }));
|
|
|
// };
|
|
|
|
|
|
// watch(
|
|
|
// () => props.detail,
|
|
|
// async () => {
|
|
|
// fetchLineData();
|
|
|
// },
|
|
|
// );
|
|
|
|
|
|
// watch(time, () => {
|
|
|
// fetchLineData();
|
|
|
// });
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss">
|
|
|
.node-detail {
|
|
|
display: flex;
|
|
|
height: 100%;
|
|
|
gap: 50px;
|
|
|
|
|
|
ul {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
list-style: none;
|
|
|
}
|
|
|
|
|
|
.title {
|
|
|
color: #1d2b3a;
|
|
|
font-family: 'PingFang SC';
|
|
|
font-size: 14px;
|
|
|
font-style: normal;
|
|
|
font-weight: 500;
|
|
|
//line-height: 20px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
.node-detail-left {
|
|
|
min-width: 500px;
|
|
|
}
|
|
|
.node-detail-info {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 15px;
|
|
|
font-size: 12px;
|
|
|
|
|
|
.label {
|
|
|
display: inline-block;
|
|
|
width: 80px;
|
|
|
color: #939ea9;
|
|
|
}
|
|
|
|
|
|
.cp {
|
|
|
display: flex;
|
|
|
gap: 25px;
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.gauges {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
li {
|
|
|
flex: 1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.line-box {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
column-gap: 20px;
|
|
|
}
|
|
|
|
|
|
.node-block {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
.home-block-content {
|
|
|
flex: 1;
|
|
|
}
|
|
|
}
|
|
|
</style>
|