feat: 容器增加资源使用、端口显示 (#786)

This commit is contained in:
ssongliu 2023-04-25 22:06:17 +08:00 committed by GitHub
parent 05e7506f61
commit 2d31c5b005
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 38 deletions

View file

@ -22,6 +22,10 @@ type ContainerInfo struct {
State string `json:"state"`
RunTime string `json:"runTime"`
CPUPercent float64 `json:"cpuPercent"`
MemoryPercent float64 `json:"memoryPercent"`
Ports []string `json:"ports"`
IsFromApp bool `json:"isFromApp"`
IsFromCompose bool `json:"isFromCompose"`
}

View file

@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@ -97,27 +98,45 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
records = list[start:end]
}
var wg sync.WaitGroup
wg.Add(len(records))
for _, container := range records {
IsFromCompose := false
if _, ok := container.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := container.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
backDatas = append(backDatas, dto.ContainerInfo{
ContainerID: container.ID,
CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
Name: container.Names[0][1:],
ImageId: strings.Split(container.ImageID, ":")[1],
ImageName: container.Image,
State: container.State,
RunTime: container.Status,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
})
go func(item types.Container) {
IsFromCompose := false
if _, ok := item.Labels[composeProjectLabel]; ok {
IsFromCompose = true
}
IsFromApp := false
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" {
IsFromApp = true
}
var ports []string
for _, port := range item.Ports {
if port.IP == "::" || port.PublicPort == 0 {
continue
}
ports = append(ports, fmt.Sprintf("%v:%v/%s", port.PublicPort, port.PrivatePort, port.Type))
}
cpu, mem := loadCpuAndMem(client, item.ID)
backDatas = append(backDatas, dto.ContainerInfo{
ContainerID: item.ID,
CreateTime: time.Unix(item.Created, 0).Format("2006-01-02 15:04:05"),
Name: item.Names[0][1:],
ImageId: strings.Split(item.ImageID, ":")[1],
ImageName: item.Image,
State: item.State,
RunTime: item.Status,
CPUPercent: cpu,
MemoryPercent: mem,
Ports: ports,
IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose,
})
wg.Done()
}(container)
}
wg.Wait()
return int64(total), backDatas, nil
}
@ -270,16 +289,16 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
body, err := io.ReadAll(res.Body)
if err != nil {
res.Body.Close()
return nil, err
}
res.Body.Close()
var stats *types.StatsJSON
if err := json.Unmarshal(body, &stats); err != nil {
return nil, err
}
var data dto.ContainterStats
previousCPU := stats.PreCPUStats.CPUUsage.TotalUsage
previousSystem := stats.PreCPUStats.SystemUsage
data.CPUPercent = calculateCPUPercentUnix(previousCPU, previousSystem, stats)
data.CPUPercent = calculateCPUPercentUnix(stats)
data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats)
data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
@ -301,18 +320,26 @@ func stringsToMap(list []string) map[string]string {
}
return lableMap
}
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
var (
cpuPercent = 0.0
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
)
func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
cpuPercent := 0.0
cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage)
systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage)
if systemDelta > 0.0 && cpuDelta > 0.0 {
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
cpuPercent = (cpuDelta / systemDelta) * float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) * 100.0
}
return cpuPercent
}
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
memPercent := 0.0
memUsage := float64(memStats.Usage - memStats.Stats["cache"])
memLimit := float64(memStats.Limit)
if memUsage > 0.0 && memLimit > 0.0 {
memPercent = (memUsage / memLimit) * 100.0
}
return memPercent
}
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) {
for _, bioEntry := range blkio.IoServiceBytesRecursive {
switch strings.ToLower(bioEntry.Op) {
@ -363,3 +390,25 @@ func pullImages(ctx context.Context, client *client.Client, image string) error
}
return nil
}
func loadCpuAndMem(client *client.Client, container string) (float64, float64) {
res, err := client.ContainerStats(context.Background(), container, false)
if err != nil {
return 0, 0
}
body, err := io.ReadAll(res.Body)
if err != nil {
res.Body.Close()
return 0, 0
}
res.Body.Close()
var stats *types.StatsJSON
if err := json.Unmarshal(body, &stats); err != nil {
return 0, 0
}
CPUPercent := calculateCPUPercentUnix(stats)
MemPercent := calculateMemPercentUnix(stats.MemoryStats)
return CPUPercent, MemPercent
}

View file

@ -42,7 +42,9 @@ export namespace Container {
createTime: string;
state: string;
runTime: string;
cpuPercent: number;
memoryPercent: number;
ports: Array<string>;
isFromApp: boolean;
isFromCompose: boolean;
}

View file

@ -3,7 +3,7 @@ import { ResPage, SearchWithPage } from '../interface';
import { Container } from '../interface/container';
export const searchContainer = (params: Container.ContainerSearch) => {
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params, 400000);
};
export const createContainer = (params: Container.ContainerCreate) => {
return http.post(`/containers`, params, 3000000);

View file

@ -446,6 +446,7 @@ const message = {
lastHour: 'Last Hour',
last10Min: 'Last 10 Minutes',
newName: 'New name',
source: 'Resource rate',
user: 'User',
command: 'Command',

View file

@ -463,6 +463,7 @@ const message = {
lastHour: '最近 1 小时',
last10Min: '最近 10 分钟',
newName: '新名称',
source: '资源使用率',
user: '用户',
command: '命令',

View file

@ -71,20 +71,42 @@
min-width="80"
prop="imageName"
/>
<el-table-column :label="$t('commons.table.status')" min-width="50" prop="state" fix>
<el-table-column :label="$t('commons.table.status')" min-width="60" prop="state" fix>
<template #default="{ row }">
<Status :key="row.state" :status="row.state"></Status>
</template>
</el-table-column>
<el-table-column :label="$t('container.upTime')" min-width="80" prop="runTime" fix />
<el-table-column :label="$t('container.source')" show-overflow-tooltip min-width="125" fix>
<template #default="{ row }">
cpu: {{ row.cpuPercent.toFixed(2) }}% {{ $t('monitor.memory') }}:
{{ row.memoryPercent.toFixed(2) }}%
</template>
</el-table-column>
<el-table-column :label="$t('container.port')" min-width="80" prop="ports" fix>
<template #default="{ row }">
<div v-if="row.ports">
<div v-for="(item, index) in row.ports" :key="index">
<div v-if="row.expand || (!row.expand && index < 3)">
<el-tag class="tagMargin">{{ item }}</el-tag>
</div>
</div>
<div v-if="!row.expand && row.ports.length > 3">
<el-button type="primary" link @click="row.expand = true">
{{ $t('commons.button.expand') }}...
</el-button>
</div>
</div>
</template>
</el-table-column>
<el-table-column
prop="createTime"
:label="$t('commons.table.date')"
:formatter="dateFormat"
:label="$t('container.upTime')"
min-width="70"
show-overflow-tooltip
prop="runTime"
fix
/>
<fu-table-operations
width="370px"
width="300px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
@ -117,7 +139,6 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
import Status from '@/components/status/index.vue';
import { reactive, onMounted, ref } from 'vue';
import { dateFormat } from '@/utils/util';
import { ContainerOperator, inspect, loadDockerStatus, searchContainer } from '@/api/modules/container';
import { Container } from '@/api/interface/container';
import { ElMessageBox } from 'element-plus';
@ -324,3 +345,9 @@ onMounted(() => {
loadStatus();
});
</script>
<style scoped lang="scss">
.tagMargin {
margin-top: 2px;
}
</style>