feat: 容器列表资源使用率增加详情显示 (#2371)

Refs #2223 

![image](https://github.com/1Panel-dev/1Panel/assets/73214554/e7f8bd8f-dd9a-4783-91eb-56759bad843e)
This commit is contained in:
ssongliu 2023-09-21 17:34:22 +08:00 committed by GitHub
parent 04eb8191ed
commit 975ff06d69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 173 additions and 16 deletions

View file

@ -63,8 +63,16 @@ type ContainerUpgrade struct {
} }
type ContainerListStats struct { type ContainerListStats struct {
ContainerID string `json:"containerID"` ContainerID string `json:"containerID"`
CPUTotalUsage uint64 `json:"cpuTotalUsage"`
SystemUsage uint64 `json:"systemUsage"`
CPUPercent float64 `json:"cpuPercent"` CPUPercent float64 `json:"cpuPercent"`
PercpuUsage int `json:"percpuUsage"`
MemroyCache uint64 `json:"memoryCache"`
MemoryUsage uint64 `json:"memoryUsage"`
MemoryLimit uint64 `json:"memoryLimit"`
MemoryPercent float64 `json:"memoryPercent"` MemoryPercent float64 `json:"memoryPercent"`
} }

View file

@ -208,8 +208,7 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
wg.Add(len(list)) wg.Add(len(list))
for i := 0; i < len(list); i++ { for i := 0; i < len(list); i++ {
go func(item types.Container) { go func(item types.Container) {
cpu, mem := loadCpuAndMem(client, item.ID) datas = append(datas, loadCpuAndMem(client, item.ID))
datas = append(datas, dto.ContainerListStats{CPUPercent: cpu, MemoryPercent: mem, ContainerID: item.ID})
wg.Done() wg.Done()
}(list[i]) }(list[i])
} }
@ -766,26 +765,36 @@ func pullImages(ctx context.Context, client *client.Client, image string) error
return nil return nil
} }
func loadCpuAndMem(client *client.Client, container string) (float64, float64) { func loadCpuAndMem(client *client.Client, container string) dto.ContainerListStats {
data := dto.ContainerListStats{
ContainerID: container,
}
res, err := client.ContainerStats(context.Background(), container, false) res, err := client.ContainerStats(context.Background(), container, false)
if err != nil { if err != nil {
return 0, 0 return data
} }
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
res.Body.Close() res.Body.Close()
return 0, 0 return data
} }
res.Body.Close() res.Body.Close()
var stats *types.StatsJSON var stats *types.StatsJSON
if err := json.Unmarshal(body, &stats); err != nil { if err := json.Unmarshal(body, &stats); err != nil {
return 0, 0 return data
} }
CPUPercent := calculateCPUPercentUnix(stats) data.CPUTotalUsage = stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage
MemPercent := calculateMemPercentUnix(stats.MemoryStats) data.SystemUsage = stats.CPUStats.SystemUsage - stats.PreCPUStats.SystemUsage
return CPUPercent, MemPercent data.CPUPercent = calculateCPUPercentUnix(stats)
data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage)
data.MemroyCache = stats.MemoryStats.Stats["cache"]
data.MemoryUsage = stats.MemoryStats.Usage
data.MemoryLimit = stats.MemoryStats.Limit
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
return data
} }
func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) { func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) {

View file

@ -70,7 +70,13 @@ export namespace Container {
} }
export interface ContainerListStats { export interface ContainerListStats {
containerID: string; containerID: string;
cpuTotalUsage: number;
systemUsage: number;
cpuPercent: number; cpuPercent: number;
percpuUsage: number;
memoryCache: number;
memoryUsage: number;
memoryLimit: number;
memoryPercent: number; memoryPercent: number;
} }
export interface ContainerStats { export interface ContainerStats {

View file

@ -1,9 +1,9 @@
@font-face { @font-face {
font-family: "panel"; /* Project id 3575356 */ font-family: "panel"; /* Project id 3575356 */
src: url('iconfont.woff2?t=1687338712846') format('woff2'), src: url('iconfont.woff2?t=1695287081776') format('woff2'),
url('iconfont.woff?t=1687338712846') format('woff'), url('iconfont.woff?t=1695287081776') format('woff'),
url('iconfont.ttf?t=1687338712846') format('truetype'), url('iconfont.ttf?t=1695287081776') format('truetype'),
url('iconfont.svg?t=1687338712846#panel') format('svg'); url('iconfont.svg?t=1695287081776#panel') format('svg');
} }
.panel { .panel {
@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.p-xiangqing:before {
content: "\e677";
}
.p-onedrive:before { .p-onedrive:before {
content: "\e601"; content: "\e601";
} }

File diff suppressed because one or more lines are too long

View file

@ -5,6 +5,13 @@
"css_prefix_text": "p-", "css_prefix_text": "p-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "10293150",
"name": "详情",
"font_class": "xiangqing",
"unicode": "e677",
"unicode_decimal": 58999
},
{ {
"icon_id": "13015332", "icon_id": "13015332",
"name": "onedrive", "name": "onedrive",

View file

@ -14,6 +14,8 @@
/> />
<missing-glyph /> <missing-glyph />
<glyph glyph-name="xiangqing" unicode="&#58999;" d="M512 654.27010918m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0ZM512 384m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0ZM512 113.72989082000004m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0Z" horiz-adv-x="1024" />
<glyph glyph-name="onedrive" unicode="&#58881;" d="M597.333333 320s188.885333 129.152 192.96 128.874667A280.021333 280.021333 0 0 1 285.141333 533.333333h2.858667zM405.674667 499.968A222.869333 222.869333 0 0 1 288 533.333333h-2.858667a224 224 0 0 1-180.885333-352L405.333333 213.333333l188.437334 173.973334zM790.293333 448.874667a180.373333 180.373333 0 0 1-12.288 0.448 181.461333 181.461333 0 0 1-72.149333-14.933334l-112.085333-47.146666L725.333333 234.666667l212.906667-53.696a182.016 182.016 0 0 1-147.946667 267.904zM779.285333 276.181333l-46.464 27.733334-106.538666 63.808-32.512 19.477333-85.738667-36.096-164.266667-69.077333-73.642666-30.933334-165.866667-69.76A223.658667 223.658667 0 0 1 288 85.333333h490.005333a181.994667 181.994667 0 0 1 160.234667 95.637334z" horiz-adv-x="1024" /> <glyph glyph-name="onedrive" unicode="&#58881;" d="M597.333333 320s188.885333 129.152 192.96 128.874667A280.021333 280.021333 0 0 1 285.141333 533.333333h2.858667zM405.674667 499.968A222.869333 222.869333 0 0 1 288 533.333333h-2.858667a224 224 0 0 1-180.885333-352L405.333333 213.333333l188.437334 173.973334zM790.293333 448.874667a180.373333 180.373333 0 0 1-12.288 0.448 181.461333 181.461333 0 0 1-72.149333-14.933334l-112.085333-47.146666L725.333333 234.666667l212.906667-53.696a182.016 182.016 0 0 1-147.946667 267.904zM779.285333 276.181333l-46.464 27.733334-106.538666 63.808-32.512 19.477333-85.738667-36.096-164.266667-69.077333-73.642666-30.933334-165.866667-69.76A223.658667 223.658667 0 0 1 288 85.333333h490.005333a181.994667 181.994667 0 0 1 160.234667 95.637334z" horiz-adv-x="1024" />
<glyph glyph-name="caidan" unicode="&#58909;" d="M896 663.272727h-744.727273a34.909091 34.909091 0 0 0 0 69.818182h744.727273a34.909091 34.909091 0 0 0 0-69.818182zM896 11.636364h-744.727273a34.909091 34.909091 0 0 0 0 69.818181h744.727273a34.909091 34.909091 0 0 0 0-69.818181zM709.818182 337.454545h-558.545455a34.909091 34.909091 0 0 0 0 69.818182h558.545455a34.909091 34.909091 0 0 0 0-69.818182z" horiz-adv-x="1024" /> <glyph glyph-name="caidan" unicode="&#58909;" d="M896 663.272727h-744.727273a34.909091 34.909091 0 0 0 0 69.818182h744.727273a34.909091 34.909091 0 0 0 0-69.818182zM896 11.636364h-744.727273a34.909091 34.909091 0 0 0 0 69.818181h744.727273a34.909091 34.909091 0 0 0 0-69.818181zM709.818182 337.454545h-558.545455a34.909091 34.909091 0 0 0 0 69.818182h558.545455a34.909091 34.909091 0 0 0 0-69.818182z" horiz-adv-x="1024" />

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -525,6 +525,12 @@ const message = {
'Clearing logs requires restarting the container, and this operation cannot be rolled back. Do you want to continue?', 'Clearing logs requires restarting the container, and this operation cannot be rolled back. Do you want to continue?',
newName: 'New name', newName: 'New name',
source: 'Resource rate', source: 'Resource rate',
cpuUsage: 'CPU Usage',
cpuTotal: 'CPU Total',
core: 'Core',
memUsage: 'Memory Usage',
memTotal: 'Memory Limit',
memCache: 'Memory Cache',
ip: 'IP address', ip: 'IP address',
cpuShare: 'CPU Share', cpuShare: 'CPU Share',
cpuShareHelper: cpuShareHelper:

View file

@ -511,6 +511,12 @@ const message = {
cleanLogHelper: '清空日誌需要重啟容器該操作無法回滾是否繼續', cleanLogHelper: '清空日誌需要重啟容器該操作無法回滾是否繼續',
newName: '新名稱', newName: '新名稱',
source: '資源使用率', source: '資源使用率',
cpuUsage: 'CPU 使用',
cpuTotal: 'CPU 總計',
core: '核心數',
memUsage: '內存使用',
memTotal: '內存限額',
memCache: '緩存使用',
ip: 'IP 地址', ip: 'IP 地址',
cpuShare: 'CPU 權重', cpuShare: 'CPU 權重',
cpuShareHelper: '容器默認份額為 1024 CPU增大可使當前容器獲得更多的 CPU 時間', cpuShareHelper: '容器默認份額為 1024 CPU增大可使當前容器獲得更多的 CPU 時間',

View file

@ -511,6 +511,12 @@ const message = {
cleanLogHelper: '清空日志需要重启容器该操作无法回滚是否继续', cleanLogHelper: '清空日志需要重启容器该操作无法回滚是否继续',
newName: '新名称', newName: '新名称',
source: '资源使用率', source: '资源使用率',
cpuUsage: 'CPU 使用',
cpuTotal: 'CPU 总计',
core: '核心数',
memUsage: '内存使用',
memTotal: '内存限额',
memCache: '缓存使用',
ip: 'IP 地址', ip: 'IP 地址',
cpuShare: 'CPU 权重', cpuShare: 'CPU 权重',
cpuShareHelper: '容器默认份额为 1024 CPU增大可使当前容器获得更多的 CPU 时间', cpuShareHelper: '容器默认份额为 1024 CPU增大可使当前容器获得更多的 CPU 时间',

View file

@ -92,9 +92,69 @@
<template #default="{ row }"> <template #default="{ row }">
<div v-if="row.hasLoad"> <div v-if="row.hasLoad">
<div class="source-font">CPU: {{ row.cpuPercent.toFixed(2) }}%</div> <div class="source-font">CPU: {{ row.cpuPercent.toFixed(2) }}%</div>
<div class="source-font"> <div class="float-left source-font">
{{ $t('monitor.memory') }}: {{ row.memoryPercent.toFixed(2) }}% {{ $t('monitor.memory') }}: {{ row.memoryPercent.toFixed(2) }}%
</div> </div>
<el-popover placement="right" width="500px" class="float-right">
<template #reference>
<svg-icon iconName="p-xiangqing" class="svg-icon"></svg-icon>
</template>
<template #default>
<el-row>
<el-col :span="8">
<el-statistic
:title="$t('container.cpuUsage')"
:value="loadCPUValue(row.cpuTotalUsage)"
:precision="2"
>
<template #suffix>{{ loadCPUUnit(row.cpuTotalUsage) }}</template>
</el-statistic>
</el-col>
<el-col :span="8">
<el-statistic
:title="$t('container.cpuTotal')"
:value="loadCPUValue(row.systemUsage)"
:precision="2"
>
<template #suffix>{{ loadCPUUnit(row.systemUsage) }}</template>
</el-statistic>
</el-col>
<el-col :span="8">
<el-statistic :title="$t('container.core')" :value="row.percpuUsage" />
</el-col>
</el-row>
<el-row class="mt-4">
<el-col :span="8">
<el-statistic
:title="$t('container.memUsage')"
:value="loadMemValue(row.memoryUsage)"
:precision="2"
>
<template #suffix>{{ loadMemUnit(row.memoryUsage) }}</template>
</el-statistic>
</el-col>
<el-col :span="8">
<el-statistic
:title="$t('container.memCache')"
:value="loadMemValue(row.memoryCache)"
:precision="2"
>
<template #suffix>{{ loadMemUnit(row.memoryCache) }}</template>
</el-statistic>
</el-col>
<el-col :span="8">
<el-statistic
:title="$t('container.memTotal')"
:value="loadMemValue(row.memoryLimit)"
:precision="2"
>
<template #suffix>{{ loadMemUnit(row.memoryLimit) }}</template>
</el-statistic>
</el-col>
</el-row>
</template>
</el-popover>
</div> </div>
<div v-if="!row.hasLoad"> <div v-if="!row.hasLoad">
<el-button link loading></el-button> <el-button link loading></el-button>
@ -314,7 +374,13 @@ const loadStats = async () => {
for (const item of stats) { for (const item of stats) {
if (container.containerID === item.containerID) { if (container.containerID === item.containerID) {
container.hasLoad = true; container.hasLoad = true;
container.cpuTotalUsage = item.cpuTotalUsage;
container.systemUsage = item.systemUsage;
container.cpuPercent = item.cpuPercent; container.cpuPercent = item.cpuPercent;
container.percpuUsage = item.percpuUsage;
container.memoryCache = item.memoryCache;
container.memoryUsage = item.memoryUsage;
container.memoryLimit = item.memoryLimit;
container.memoryPercent = item.memoryPercent; container.memoryPercent = item.memoryPercent;
break; break;
} }
@ -322,6 +388,38 @@ const loadStats = async () => {
} }
}; };
const loadCPUUnit = (t: number) => {
const num = 1000;
if (t < num) return ' ns';
if (t < Math.pow(num, 2)) return ' μs';
if (t < Math.pow(num, 3)) return ' ms';
return ' s';
};
function loadCPUValue(t: number) {
const num = 1000;
if (t < num) return t;
if (t < Math.pow(num, 2)) return Number((t / num).toFixed(2));
if (t < Math.pow(num, 3)) return Number((t / Math.pow(num, 2)).toFixed(2));
return Number((t / Math.pow(num, 3)).toFixed(2));
}
const loadMemUnit = (t: number) => {
if (t == 0) {
return '';
}
const num = 1024;
if (t < num) return ' B';
if (t < Math.pow(num, 2)) return ' KiB';
if (t < Math.pow(num, 3)) return ' MiB';
return ' GiB';
};
function loadMemValue(t: number) {
const num = 1024;
if (t < num) return t;
if (t < Math.pow(num, 2)) return Number((t / num).toFixed(2));
if (t < Math.pow(num, 3)) return Number((t / Math.pow(num, 2)).toFixed(2));
return Number((t / Math.pow(num, 3)).toFixed(2));
}
const dialogOperateRef = ref(); const dialogOperateRef = ref();
const onEdit = async (container: string) => { const onEdit = async (container: string) => {
const res = await loadContainerInfo(container); const res = await loadContainerInfo(container);
@ -549,4 +647,9 @@ onMounted(() => {
.source-font { .source-font {
font-size: 12px; font-size: 12px;
} }
.svg-icon {
margin-top: -3px;
font-size: 6px;
cursor: pointer;
}
</style> </style>