package docker import ( "ArinDash/util" "context" "strings" containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" ) type ContainerMetrics struct { ID string Name string Image string Status string CPUPercent float64 MemUsage uint64 MemLimit uint64 MemPercent float64 NetRx uint64 NetTx uint64 BlockRead uint64 BlockWrite uint64 PidsCurrent uint64 } func newDockerClient() (*client.Client, error) { return client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) } func FetchDockerMetrics(ctx context.Context) ([]ContainerMetrics, error) { cli, err := newDockerClient() if err != nil { return nil, err } defer cli.Close() _, err = cli.Ping(ctx) if err != nil { return nil, err } containers, err := cli.ContainerList(ctx, containertypes.ListOptions{All: true}) if err != nil { return nil, err } out := make([]ContainerMetrics, 0, len(containers)) for _, c := range containers { m := ContainerMetrics{ ID: c.ID, Image: c.Image, Name: containerName(c.Names), Status: c.Status, } stats, err := cli.ContainerStats(ctx, c.ID, false) if err != nil { continue } func() { defer stats.Body.Close() var sj containertypes.StatsResponse if err := util.DecodeJSON(stats.Body, &sj); err != nil { return } m.CPUPercent = cpuPercentFromStats(sj) m.MemUsage, m.MemLimit, m.MemPercent = memoryFromStats(sj) m.NetRx, m.NetTx = networkTotals(sj) m.BlockRead, m.BlockWrite = blockIOTotals(sj) m.PidsCurrent = sj.PidsStats.Current }() out = append(out, m) } return out, nil } func containerName(names []string) string { for _, n := range names { n = strings.TrimSpace(n) n = strings.TrimPrefix(n, "/") if n != "" { return n } } return "" } func cpuPercentFromStats(s containertypes.StatsResponse) float64 { // Protect against zero or missing data. if s.PreCPUStats.SystemUsage == 0 || s.CPUStats.SystemUsage == 0 { return 0 } cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage - s.PreCPUStats.CPUUsage.TotalUsage) systemDelta := float64(s.CPUStats.SystemUsage - s.PreCPUStats.SystemUsage) if systemDelta <= 0 || cpuDelta < 0 { return 0 } // Number of CPUs available to the container. numCPU := float64(len(s.CPUStats.CPUUsage.PercpuUsage)) if numCPU == 0 { numCPU = 1 } return (cpuDelta / systemDelta) * numCPU * 100.0 } func memoryFromStats(s containertypes.StatsResponse) (usage, limit uint64, percent float64) { usage = s.MemoryStats.Usage limit = s.MemoryStats.Limit // Optionally discount cached memory if stats present. if stats := s.MemoryStats.Stats; stats != nil { // The common Linux approach: usage - cache if cache, ok := stats["cache"]; ok && cache <= usage { usage -= cache } } if limit > 0 { percent = (float64(usage) / float64(limit)) * 100 } return } func networkTotals(s containertypes.StatsResponse) (rx, tx uint64) { if s.Networks == nil { return 0, 0 } for _, v := range s.Networks { rx += v.RxBytes tx += v.TxBytes } return rx, tx } func blockIOTotals(s containertypes.StatsResponse) (read, write uint64) { for _, e := range s.BlkioStats.IoServiceBytesRecursive { switch strings.ToLower(e.Op) { case "read": read += e.Value case "write": write += e.Value } } return read, write }