package docker import ( "context" "encoding/json" "fmt" "time" "github.com/docker/docker/api/types/container" ) // Stats — snapshot metrik kontainer (kumulatif untuk net/disk; app menghitung // laju dari selisih antar snapshot). type Stats struct { CPUPercent float64 MemUsed uint64 // usage − cache (seperti `docker stats`) MemLimit uint64 MemPercent float64 NetRx uint64 // kumulatif NetTx uint64 // kumulatif BlkRead uint64 // kumulatif BlkWrite uint64 // kumulatif PIDs int At time.Time } // StatsOnce mengambil satu snapshot statistik (one-shot, tanpa stream). func (c *Client) StatsOnce(ctx context.Context, id string) (Stats, error) { resp, err := c.cli.ContainerStatsOneShot(ctx, id) if err != nil { return Stats{}, fmt.Errorf("docker: stats: %w", err) } defer resp.Body.Close() var raw container.StatsResponse if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil { return Stats{}, fmt.Errorf("docker: decode stats: %w", err) } return computeStats(&raw), nil } // computeStats — rumus yang sama dengan `docker stats` (CPU dari delta // pre/cpu-stats, memori usage − cache), dipisah agar bisa diuji tanpa daemon. func computeStats(s *container.StatsResponse) Stats { // CPU% = (Δcpu / Δsystem) × onlineCPU × 100 cpuDelta := float64(s.CPUStats.CPUUsage.TotalUsage) - float64(s.PreCPUStats.CPUUsage.TotalUsage) sysDelta := float64(s.CPUStats.SystemUsage) - float64(s.PreCPUStats.SystemUsage) online := s.CPUStats.OnlineCPUs if online == 0 { online = uint32(len(s.CPUStats.CPUUsage.PercpuUsage)) } cpuPct := 0.0 if sysDelta > 0 && cpuDelta > 0 { cpuPct = cpuDelta / sysDelta * float64(online) * 100 } memUsed := s.MemoryStats.Usage if cache, ok := s.MemoryStats.Stats["cache"]; ok && cache < memUsed { memUsed -= cache } memPct := 0.0 if s.MemoryStats.Limit > 0 { memPct = float64(memUsed) / float64(s.MemoryStats.Limit) * 100 } // Net: jumlahkan semua interface (kumulatif). var rx, tx uint64 for _, n := range s.Networks { rx += n.RxBytes tx += n.TxBytes } // Blok I/O: jumlahkan per operasi (kumulatif). var rd, wr uint64 for _, b := range s.BlkioStats.IoServiceBytesRecursive { switch b.Op { case "Read", "read": rd += b.Value case "Write", "write": wr += b.Value } } return Stats{ CPUPercent: cpuPct, MemUsed: memUsed, MemLimit: s.MemoryStats.Limit, MemPercent: memPct, NetRx: rx, NetTx: tx, BlkRead: rd, BlkWrite: wr, PIDs: int(s.PidsStats.Current), At: time.Now(), } }