2025-04-02 14:29:57 +08:00

251 lines
6.4 KiB
Go

package docker
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/docker/docker/api/types"
)
// ContainerStats 容器统计数据结构
type ContainerStats struct {
CPUPercentage float64 `json:"cpu_percent"` // CPU 使用百分比
MemoryUsage int64 `json:"memory_usage"` // 内存使用量(字节)
MemoryLimit int64 `json:"memory_limit"` // 内存限制(字节)
MemoryPercentage float64 `json:"memory_percent"` // 内存使用百分比
NetworkRx int64 `json:"network_rx"` // 网络接收(字节)
NetworkTx int64 `json:"network_tx"` // 网络发送(字节)
BlockRead int64 `json:"block_read"` // 块设备读取(字节)
BlockWrite int64 `json:"block_write"` // 块设备写入(字节)
PID int `json:"pid"` // 进程ID
Status string `json:"status"` // 容器状态
Uptime string `json:"uptime"` // 运行时间
}
// GetStats 获取容器的实时统计数据
func GetStats(ctx context.Context, containerID string) (*ContainerStats, error) {
if containerID == "" {
return nil, fmt.Errorf("容器ID不能为空")
}
cli := GetClient()
if cli == nil {
return nil, fmt.Errorf("Docker客户端未初始化")
}
// 获取容器详细信息
inspect, err := cli.ContainerInspect(ctx, containerID)
if err != nil {
return nil, fmt.Errorf("无法获取容器信息: %w", err)
}
// 获取容器状态
status := "stopped"
if inspect.State.Running {
status = "running"
} else if inspect.State.Paused {
status = "paused"
} else if inspect.State.Restarting {
status = "restarting"
}
// 计算运行时间
uptime := "0s"
if inspect.State.Running && inspect.State.StartedAt != "" {
startTime, err := time.Parse(time.RFC3339, inspect.State.StartedAt)
if err == nil {
duration := time.Since(startTime)
uptime = formatDuration(duration)
}
}
// 如果容器未运行,返回基本信息
if !inspect.State.Running {
return &ContainerStats{
Status: status,
Uptime: uptime,
PID: inspect.State.Pid,
}, nil
}
// 获取容器实时统计数据
stats, err := cli.ContainerStats(ctx, containerID, false) // 不需要持续流式数据
if err != nil {
return nil, fmt.Errorf("获取容器统计数据失败: %w", err)
}
defer stats.Body.Close()
// 解析统计数据JSON
var statsJSON types.StatsJSON
if err := json.NewDecoder(stats.Body).Decode(&statsJSON); err != nil {
return nil, fmt.Errorf("解析容器统计数据失败: %w", err)
}
result := &ContainerStats{
Status: status,
Uptime: uptime,
PID: inspect.State.Pid,
}
// 计算CPU使用百分比
result.CPUPercentage = calculateCPUPercentage(&statsJSON)
// 设置内存使用情况
result.MemoryUsage = getMemoryUsage(&statsJSON)
result.MemoryLimit = getMemoryLimit(&statsJSON)
result.MemoryPercentage = calculateMemoryPercentage(&statsJSON)
// 获取网络数据
networkStats := getNetworkStats(&statsJSON)
result.NetworkRx = networkStats.rx
result.NetworkTx = networkStats.tx
// 获取I/O数据
ioStats := getIOStats(&statsJSON)
result.BlockRead = ioStats.read
result.BlockWrite = ioStats.write
return result, nil
}
// 计算CPU使用百分比
func calculateCPUPercentage(stats *types.StatsJSON) float64 {
if stats == nil {
return 0.0
}
// CPU使用率计算公式 = (CPU使用时间 / CPU总时间) * 核心数 * 100%
cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage)
systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage)
numCPUs := uint32(1) // 默认为1个CPU
if len(stats.CPUStats.CPUUsage.PercpuUsage) > 0 {
numCPUs = uint32(len(stats.CPUStats.CPUUsage.PercpuUsage))
}
if systemDelta > 0 && cpuDelta > 0 {
cpuPercent := (cpuDelta / systemDelta) * float64(numCPUs) * 100.0
return roundToTwo(cpuPercent)
}
return 0.0
}
// 获取内存使用量
func getMemoryUsage(stats *types.StatsJSON) int64 {
if stats == nil || stats.MemoryStats.Usage == 0 {
return 0
}
// 某些环境下,需要减去缓存
if stats.MemoryStats.Stats != nil {
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
return int64(stats.MemoryStats.Usage - cache) // 转换为int64
}
}
return int64(stats.MemoryStats.Usage) // 转换为int64
}
// 获取内存限制
func getMemoryLimit(stats *types.StatsJSON) int64 {
if stats == nil || stats.MemoryStats.Limit == 0 {
return 0
}
return int64(stats.MemoryStats.Limit) // 转换为int64
}
// 计算内存使用百分比
func calculateMemoryPercentage(stats *types.StatsJSON) float64 {
if stats == nil {
return 0.0
}
usage := getMemoryUsage(stats)
limit := getMemoryLimit(stats)
if limit > 0 && usage > 0 {
memPercent := float64(usage) / float64(limit) * 100.0
return roundToTwo(memPercent)
}
return 0.0
}
// 网络统计结构
type networkStats struct {
rx int64
tx int64
}
// 获取网络统计数据
func getNetworkStats(stats *types.StatsJSON) networkStats {
if stats == nil || len(stats.Networks) == 0 {
return networkStats{}
}
var rx, tx int64
for _, network := range stats.Networks {
rx += int64(network.RxBytes) // 转换为int64
tx += int64(network.TxBytes) // 转换为int64
}
return networkStats{rx: rx, tx: tx}
}
// IO统计结构
type ioStats struct {
read int64
write int64
}
// 获取IO统计数据
func getIOStats(stats *types.StatsJSON) ioStats {
if stats == nil {
return ioStats{}
}
var read, write int64
if len(stats.BlkioStats.IoServiceBytesRecursive) > 0 {
for _, blkio := range stats.BlkioStats.IoServiceBytesRecursive {
switch blkio.Op {
case "Read":
read += int64(blkio.Value) // 转换为int64
case "Write":
write += int64(blkio.Value) // 转换为int64
}
}
}
return ioStats{read: read, write: write}
}
// 四舍五入到两位小数
func roundToTwo(num float64) float64 {
if num < 0 {
return 0.0 // 避免负值
}
return float64(int(num*100)) / 100
}
// 格式化时间持续
func formatDuration(d time.Duration) string {
if d < 0 {
d = 0 // 避免负值
}
days := int(d.Hours() / 24)
hours := int(d.Hours()) % 24
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60
if days > 0 {
return fmt.Sprintf("%dd %dh %dm", days, hours, minutes)
}
if hours > 0 {
return fmt.Sprintf("%dh %dm", hours, minutes)
}
if minutes > 0 {
return fmt.Sprintf("%dm %ds", minutes, seconds)
}
return fmt.Sprintf("%ds", seconds)
}