ArinDash/apis/docker/docker.go

156 lines
3.4 KiB
Go

package docker
import (
"ArinDash/util"
"context"
"io"
"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 func(cli *client.Client) {
err := cli.Close()
if err != nil {
return
}
}(cli)
_, 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 func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
}
}(stats.Body)
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
if stats := s.MemoryStats.Stats; stats != nil {
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
}