octoplex/container/stats.go
2025-02-05 20:32:36 +01:00

100 lines
2.6 KiB
Go

package container
import (
"cmp"
"context"
"encoding/json"
"errors"
"io"
"log/slog"
"github.com/docker/docker/api/types/container"
)
func handleStats(
ctx context.Context,
containerID string,
apiClient DockerClient,
networkCountConfig NetworkCountConfig,
logger *slog.Logger,
ch chan<- stats,
) {
networkNameRx := cmp.Or(networkCountConfig.Rx, "eth0")
networkNameTx := cmp.Or(networkCountConfig.Tx, "eth0")
statsReader, err := apiClient.ContainerStats(ctx, containerID, true)
if err != nil {
// TODO: error handling?
logger.Error("Error getting container stats", "err", err, "id", shortID(containerID))
return
}
defer statsReader.Body.Close()
var (
processedAny bool
lastNetworkRx uint64
lastNetworkTx uint64
)
getAvgRxRate := rolling(10)
getAvgTxRate := rolling(10)
buf := make([]byte, 4_096)
for {
n, err := statsReader.Body.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) {
break
}
logger.Error("Error reading stats", "err", err, "id", shortID(containerID))
break
}
var statsResp container.StatsResponse
if err = json.Unmarshal(buf[:n], &statsResp); err != nil {
logger.Error("Error unmarshalling stats", "err", err, "id", shortID(containerID))
break
}
// https://stackoverflow.com/a/30292327/62871
cpuDelta := float64(statsResp.CPUStats.CPUUsage.TotalUsage - statsResp.PreCPUStats.CPUUsage.TotalUsage)
systemDelta := float64(statsResp.CPUStats.SystemUsage - statsResp.PreCPUStats.SystemUsage)
var avgRxRate, avgTxRate float64
if processedAny {
secondsSinceLastReceived := statsResp.Read.Sub(statsResp.PreRead).Seconds()
diffRxBytes := (statsResp.Networks[networkNameRx].RxBytes - lastNetworkRx)
diffTxBytes := (statsResp.Networks[networkNameTx].TxBytes - lastNetworkTx)
rxRate := float64(diffRxBytes) / secondsSinceLastReceived / 1000.0 * 8
txRate := float64(diffTxBytes) / secondsSinceLastReceived / 1000.0 * 8
avgRxRate = getAvgRxRate(rxRate)
avgTxRate = getAvgTxRate(txRate)
}
lastNetworkRx = statsResp.Networks[networkNameRx].RxBytes
lastNetworkTx = statsResp.Networks[networkNameTx].TxBytes
processedAny = true
ch <- stats{
cpuPercent: (cpuDelta / systemDelta) * float64(statsResp.CPUStats.OnlineCPUs) * 100,
memoryUsageBytes: statsResp.MemoryStats.Usage,
rxRate: int(avgRxRate),
txRate: int(avgTxRate),
}
}
}
// https://stackoverflow.com/a/12539781/62871
func rolling(n int) func(float64) float64 {
bins := make([]float64, n)
var avg float64
var i int
return func(x float64) float64 {
avg += (x - bins[i]) / float64(n)
bins[i] = x
i = (i + 1) % n
return avg
}
}