251 lines
6.4 KiB
Go
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)
|
|
}
|