fix: Fix the issue of extremely slow container list retrieval (#11142)

Refs #11129
This commit is contained in:
ssongliu 2025-12-01 18:03:37 +08:00 committed by GitHub
parent d4edf2f874
commit f63658db9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 79 additions and 12 deletions

View file

@ -263,6 +263,21 @@ func (b *BaseApi) ContainerListStats(c *gin.Context) {
helper.SuccessWithData(c, data) helper.SuccessWithData(c, data)
} }
// @Summary Load container stats size
// @Success 200 {object} dto.ContainerItemStats
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /containers/item/stats/:id [get]
func (b *BaseApi) ContainerItemStats(c *gin.Context) {
containerID := c.Param("id")
data, err := containerService.ContainerItemStats(containerID)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Container // @Tags Container
// @Summary Create container // @Summary Create container
// @Accept json // @Accept json

View file

@ -31,9 +31,6 @@ type ContainerInfo struct {
Network []string `json:"network"` Network []string `json:"network"`
Ports []string `json:"ports"` Ports []string `json:"ports"`
SizeRw int64 `json:"sizeRw"`
SizeRootFs int64 `json:"sizeRootFs"`
IsFromApp bool `json:"isFromApp"` IsFromApp bool `json:"isFromApp"`
IsFromCompose bool `json:"isFromCompose"` IsFromCompose bool `json:"isFromCompose"`
@ -129,6 +126,10 @@ type ContainerUpgrade struct {
ForcePull bool `json:"forcePull"` ForcePull bool `json:"forcePull"`
} }
type ContainerItemStats struct {
SizeRw int64 `json:"sizeRw"`
SizeRootFs int64 `json:"sizeRootFs"`
}
type ContainerListStats struct { type ContainerListStats struct {
ContainerID string `json:"containerID"` ContainerID string `json:"containerID"`

View file

@ -70,6 +70,7 @@ type IContainerService interface {
ContainerUpgrade(req dto.ContainerUpgrade) error ContainerUpgrade(req dto.ContainerUpgrade) error
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
ContainerListStats() ([]dto.ContainerListStats, error) ContainerListStats() ([]dto.ContainerListStats, error)
ContainerItemStats(containerID string) (dto.ContainerItemStats, error)
LoadResourceLimit() (*dto.ResourceLimit, error) LoadResourceLimit() (*dto.ResourceLimit, error)
ContainerRename(req dto.ContainerRename) error ContainerRename(req dto.ContainerRename) error
ContainerCommit(req dto.ContainerCommit) error ContainerCommit(req dto.ContainerCommit) error
@ -101,10 +102,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
return 0, nil, err return 0, nil, err
} }
defer client.Close() defer client.Close()
options := container.ListOptions{ options := container.ListOptions{All: true}
All: true,
Size: true,
}
if len(req.Filters) != 0 { if len(req.Filters) != 0 {
options.Filters = filters.NewArgs() options.Filters = filters.NewArgs()
options.Filters.Add("label", req.Filters) options.Filters.Add("label", req.Filters)
@ -259,6 +257,21 @@ func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
} }
return data, nil return data, nil
} }
func (u *ContainerService) ContainerItemStats(containerID string) (dto.ContainerItemStats, error) {
var data dto.ContainerItemStats
client, err := docker.NewDockerClient()
if err != nil {
return data, err
}
defer client.Close()
containerInfo, _, err := client.ContainerInspectWithRaw(context.Background(), containerID, true)
if err != nil {
return data, err
}
data.SizeRw = *containerInfo.SizeRw
data.SizeRootFs = *containerInfo.SizeRootFs
return data, nil
}
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) { func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) {
client, err := docker.NewDockerClient() client, err := docker.NewDockerClient()
if err != nil { if err != nil {
@ -1760,8 +1773,6 @@ func searchWithFilter(req dto.PageContainer, containers []container.Summary) []d
ImageName: item.Image, ImageName: item.Image,
State: item.State, State: item.State,
RunTime: item.Status, RunTime: item.Status,
SizeRw: item.SizeRw,
SizeRootFs: item.SizeRootFs,
IsFromApp: IsFromApp, IsFromApp: IsFromApp,
IsFromCompose: IsFromCompose, IsFromCompose: IsFromCompose,
} }

View file

@ -24,6 +24,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
baRouter.POST("/list/byimage", baseApi.ListContainerByImage) baRouter.POST("/list/byimage", baseApi.ListContainerByImage)
baRouter.GET("/status", baseApi.LoadContainerStatus) baRouter.GET("/status", baseApi.LoadContainerStatus)
baRouter.GET("/list/stats", baseApi.ContainerListStats) baRouter.GET("/list/stats", baseApi.ContainerListStats)
baRouter.GET("/item/stats/:id", baseApi.ContainerItemStats)
baRouter.GET("/search/log", baseApi.ContainerStreamLogs) baRouter.GET("/search/log", baseApi.ContainerStreamLogs)
baRouter.POST("/download/log", baseApi.DownloadContainerLogs) baRouter.POST("/download/log", baseApi.DownloadContainerLogs)
baRouter.GET("/limit", baseApi.LoadResourceLimit) baRouter.GET("/limit", baseApi.LoadResourceLimit)

View file

@ -134,6 +134,10 @@ export namespace Container {
name: string; name: string;
state: string; state: string;
} }
export interface ContainerItemStats {
sizeRw: number;
sizeRootFs: number;
}
export interface ContainerListStats { export interface ContainerListStats {
containerID: string; containerID: string;
cpuTotalUsage: number; cpuTotalUsage: number;

View file

@ -43,6 +43,9 @@ export const cleanContainerLog = (containerName: string, operateNode?: string) =
const params = operateNode ? `?operateNode=${operateNode}` : ''; const params = operateNode ? `?operateNode=${operateNode}` : '';
return http.post(`/containers/clean/log${params}`, { name: containerName }); return http.post(`/containers/clean/log${params}`, { name: containerName });
}; };
export const containerItemStats = (containerID: string) => {
return http.get<Container.ContainerItemStats>(`/containers/item/stats/${containerID}`);
};
export const containerListStats = () => { export const containerListStats = () => {
return http.get<Array<Container.ContainerListStats>>(`/containers/list/stats`); return http.get<Array<Container.ContainerListStats>>(`/containers/list/stats`);
}; };

View file

@ -823,6 +823,7 @@ const message = {
memUsage: 'Memory usage', memUsage: 'Memory usage',
memTotal: 'Memory limit', memTotal: 'Memory limit',
memCache: 'Memory cache', memCache: 'Memory cache',
loadSize: 'Get Container Size',
ip: 'IP address', ip: 'IP address',
cpuShare: 'CPU shares', cpuShare: 'CPU shares',
cpuShareHelper: cpuShareHelper:

View file

@ -826,6 +826,7 @@ const message = {
memUsage: 'Uso de memoria', memUsage: 'Uso de memoria',
memTotal: 'Límite de memoria', memTotal: 'Límite de memoria',
memCache: 'Caché de memoria', memCache: 'Caché de memoria',
loadSize: 'Obtener Tamaño del Contenedor',
ip: 'Dirección IP', ip: 'Dirección IP',
cpuShare: 'Proporción de CPU', cpuShare: 'Proporción de CPU',
cpuShareHelper: cpuShareHelper:

View file

@ -803,6 +803,7 @@ const message = {
memUsage: 'メモリの使用', memUsage: 'メモリの使用',
memTotal: 'メモリ制限', memTotal: 'メモリ制限',
memCache: 'メモリキャッシュ', memCache: 'メモリキャッシュ',
loadSize: 'コンテナサイズを取得',
ip: 'IPアドレス', ip: 'IPアドレス',
cpuShare: 'CPU共有', cpuShare: 'CPU共有',
cpuShareHelper: cpuShareHelper:

View file

@ -796,6 +796,7 @@ const message = {
memUsage: '메모리 사용', memUsage: '메모리 사용',
memTotal: '메모리 한도', memTotal: '메모리 한도',
memCache: '메모리 캐시', memCache: '메모리 캐시',
loadSize: '컨테이너 크기 가져오기',
ip: 'IP 주소', ip: 'IP 주소',
cpuShare: 'CPU 공유', cpuShare: 'CPU 공유',
cpuShareHelper: cpuShareHelper:

View file

@ -816,6 +816,7 @@ const message = {
memUsage: 'Penggunaan memori', memUsage: 'Penggunaan memori',
memTotal: 'Had memori', memTotal: 'Had memori',
memCache: 'Cache memori', memCache: 'Cache memori',
loadSize: 'Dapatkan Saiz Bekas',
ip: 'Alamat IP', ip: 'Alamat IP',
cpuShare: 'Bahagian CPU', cpuShare: 'Bahagian CPU',
cpuShareHelper: cpuShareHelper:

View file

@ -814,6 +814,7 @@ const message = {
memUsage: 'Uso de memória', memUsage: 'Uso de memória',
memTotal: 'Limite de memória', memTotal: 'Limite de memória',
memCache: 'Cache de memória', memCache: 'Cache de memória',
loadSize: 'Obter Tamanho do Contêiner',
ip: 'Endereço IP', ip: 'Endereço IP',
cpuShare: 'Atribuição de CPU', cpuShare: 'Atribuição de CPU',
cpuShareHelper: cpuShareHelper:

View file

@ -812,6 +812,7 @@ const message = {
memUsage: 'Использование памяти', memUsage: 'Использование памяти',
memTotal: 'Лимит памяти', memTotal: 'Лимит памяти',
memCache: 'Кэш памяти', memCache: 'Кэш памяти',
loadSize: 'Получить Размер Контейнера',
ip: 'IP-адрес', ip: 'IP-адрес',
cpuShare: 'Доли CPU', cpuShare: 'Доли CPU',
cpuShareHelper: cpuShareHelper:

View file

@ -832,6 +832,7 @@ const message = {
memUsage: 'Bellek kullanımı', memUsage: 'Bellek kullanımı',
memTotal: 'Bellek sınırı', memTotal: 'Bellek sınırı',
memCache: 'Bellek önbelleği', memCache: 'Bellek önbelleği',
loadSize: 'Konteyner Boyutunu Al',
ip: 'IP adresi', ip: 'IP adresi',
cpuShare: 'CPU paylaşımları', cpuShare: 'CPU paylaşımları',
cpuShareHelper: cpuShareHelper:

View file

@ -788,6 +788,7 @@ const message = {
memUsage: '記憶體使用', memUsage: '記憶體使用',
memTotal: '記憶體限額', memTotal: '記憶體限額',
memCache: '快取使用', memCache: '快取使用',
loadSize: '取得容器大小',
ip: 'IP 位址', ip: 'IP 位址',
cpuShare: 'CPU 權重', cpuShare: 'CPU 權重',
cpuShareHelper: '容器預設份額為 1024 CPU增大可使目前容器獲得更多的 CPU 時間', cpuShareHelper: '容器預設份額為 1024 CPU增大可使目前容器獲得更多的 CPU 時間',

View file

@ -789,6 +789,7 @@ const message = {
memUsage: '内存使用', memUsage: '内存使用',
memTotal: '内存限额', memTotal: '内存限额',
memCache: '缓存使用', memCache: '缓存使用',
loadSize: '获取容器大小',
ip: 'IP 地址', ip: 'IP 地址',
cpuShare: 'CPU 权重', cpuShare: 'CPU 权重',
cpuShareHelper: '容器默认份额为 1024 CPU增大可使当前容器获得更多的 CPU 时间', cpuShareHelper: '容器默认份额为 1024 CPU增大可使当前容器获得更多的 CPU 时间',

View file

@ -209,8 +209,7 @@
<el-descriptions-item :label="$t('container.memTotal')"> <el-descriptions-item :label="$t('container.memTotal')">
{{ computeSizeForDocker(row.memoryLimit) }} {{ computeSizeForDocker(row.memoryLimit) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item v-if="row.hasLoadSize">
<el-descriptions-item>
<template #label> <template #label>
{{ $t('container.sizeRw') }} {{ $t('container.sizeRw') }}
<el-tooltip :content="$t('container.sizeRwHelper')"> <el-tooltip :content="$t('container.sizeRwHelper')">
@ -219,7 +218,10 @@
</template> </template>
{{ computeSize2(row.sizeRw) }} {{ computeSize2(row.sizeRw) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('container.sizeRootFs')"> <el-descriptions-item
:label="$t('container.sizeRootFs')"
v-if="row.hasLoadSize"
>
<template #label> <template #label>
{{ $t('container.sizeRootFs') }} {{ $t('container.sizeRootFs') }}
<el-tooltip :content="$t('container.sizeRootFsHelper')"> <el-tooltip :content="$t('container.sizeRootFsHelper')">
@ -229,6 +231,17 @@
{{ computeSize2(row.sizeRootFs) }} {{ computeSize2(row.sizeRootFs) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-button
class="mt-2"
v-if="!row.hasLoadSize"
size="small"
link
type="primary"
@click="loadSize(row)"
>
{{ $t('container.loadSize') }}
</el-button>
</template> </template>
</el-popover> </el-popover>
</div> </div>
@ -384,6 +397,7 @@ import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
import Status from '@/components/status/index.vue'; import Status from '@/components/status/index.vue';
import { reactive, onMounted, ref, computed } from 'vue'; import { reactive, onMounted, ref, computed } from 'vue';
import { import {
containerItemStats,
containerListStats, containerListStats,
containerOperator, containerOperator,
inspect, inspect,
@ -588,6 +602,14 @@ const refresh = async () => {
} }
}; };
const loadSize = async (row: any) => {
containerItemStats(row.containerID).then((res) => {
row.sizeRw = res.data.sizeRw || 0;
row.sizeRootFs = res.data.sizeRootFs || 0;
row.hasLoadSize = true;
});
};
const loadStats = async () => { const loadStats = async () => {
const res = await containerListStats(); const res = await containerListStats();
let stats = res.data || []; let stats = res.data || [];