147 lines
3.3 KiB
Go
147 lines
3.3 KiB
Go
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
|
|
}
|