mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
compute cpu and memory stats for docker on windows (#653)
The Docker daemon's API returns different values on Windows and non-Windows systems. This impacts the calculation of `cpuPct` and `usedMemory` for each container. The systems are disciminate on the `Server` header send by the server. Uses the unix stats calculation in case the header is not set. `docker stats` implementation for reference: https://github.com/docker/cli/blob/master/cli/command/container/stats_helpers.go#L100 Co-authored-by: Benoit VIRY <benoit.viry@cgx-group.com>
This commit is contained in:
@@ -14,9 +14,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"regexp"
|
||||
|
||||
"github.com/blang/semver"
|
||||
)
|
||||
|
||||
var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`)
|
||||
|
||||
type dockerManager struct {
|
||||
client *http.Client // Client to query Docker API
|
||||
wg sync.WaitGroup // WaitGroup to wait for all goroutines to finish
|
||||
@@ -167,22 +171,31 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// check if container has valid data, otherwise may be in restart loop (#103)
|
||||
if res.MemoryStats.Usage == 0 {
|
||||
return fmt.Errorf("%s - no memory stats - see https://github.com/henrygd/beszel/issues/144", name)
|
||||
var usedMemory uint64
|
||||
var cpuPct float64
|
||||
|
||||
if getDockerOS(resp.Header.Get("Server")) == "windows" {
|
||||
|
||||
usedMemory = res.MemoryStats.PrivateWorkingSet
|
||||
cpuPct = calculateCPUPercentWindows(res, stats.PrevCpu[0], stats.PrevRead)
|
||||
stats.PrevRead = res.Read
|
||||
|
||||
} else {
|
||||
// check if container has valid data, otherwise may be in restart loop (#103)
|
||||
if res.MemoryStats.Usage == 0 {
|
||||
return fmt.Errorf("%s - no memory stats - see https://github.com/henrygd/beszel/issues/144", name)
|
||||
}
|
||||
memCache := res.MemoryStats.Stats.InactiveFile
|
||||
if memCache == 0 {
|
||||
memCache = res.MemoryStats.Stats.Cache
|
||||
}
|
||||
usedMemory = res.MemoryStats.Usage - memCache
|
||||
|
||||
cpuDelta := res.CPUStats.CPUUsage.TotalUsage - stats.PrevCpu[0]
|
||||
systemDelta := res.CPUStats.SystemUsage - stats.PrevCpu[1]
|
||||
cpuPct = float64(cpuDelta) / float64(systemDelta) * 100
|
||||
}
|
||||
|
||||
// memory (https://docs.docker.com/reference/cli/docker/container/stats/)
|
||||
memCache := res.MemoryStats.Stats.InactiveFile
|
||||
if memCache == 0 {
|
||||
memCache = res.MemoryStats.Stats.Cache
|
||||
}
|
||||
usedMemory := res.MemoryStats.Usage - memCache
|
||||
|
||||
// cpu
|
||||
cpuDelta := res.CPUStats.CPUUsage.TotalUsage - stats.PrevCpu[0]
|
||||
systemDelta := res.CPUStats.SystemUsage - stats.PrevCpu[1]
|
||||
cpuPct := float64(cpuDelta) / float64(systemDelta) * 100
|
||||
if cpuPct > 100 {
|
||||
return fmt.Errorf("%s cpu pct greater than 100: %+v", name, cpuPct)
|
||||
}
|
||||
@@ -324,3 +337,31 @@ func getDockerHost() string {
|
||||
}
|
||||
return scheme + socks[0]
|
||||
}
|
||||
|
||||
// getDockerOS returns the operating system based on the server header from the daemon.
|
||||
// from: https://github.com/docker/cli/blob/master/vendor/github.com/docker/docker/client/utils.go#L34
|
||||
func getDockerOS(serverHeader string) string {
|
||||
var osType string
|
||||
matches := headerRegexp.FindStringSubmatch(serverHeader)
|
||||
if len(matches) > 0 {
|
||||
osType = matches[1]
|
||||
}
|
||||
return osType
|
||||
}
|
||||
|
||||
// from: https://github.com/docker/cli/blob/master/cli/command/container/stats_helpers.go#L185
|
||||
func calculateCPUPercentWindows(v container.ApiStats, prevCPUSsage uint64, prevRead time.Time) float64 {
|
||||
// Max number of 100ns intervals between the previous time read and now
|
||||
possIntervals := uint64(v.Read.Sub(prevRead).Nanoseconds())
|
||||
possIntervals /= 100 // Convert to number of 100ns intervals
|
||||
possIntervals *= uint64(v.NumProcs) // Multiple by the number of processors
|
||||
|
||||
// Intervals used
|
||||
intervalsUsed := v.CPUStats.CPUUsage.TotalUsage - prevCPUSsage
|
||||
|
||||
// Percentage avoiding divide-by-zero
|
||||
if possIntervals > 0 {
|
||||
return float64(intervalsUsed) / float64(possIntervals) * 100.0
|
||||
}
|
||||
return 0.00
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ type ApiInfo struct {
|
||||
// Docker container resources from /containers/{id}/stats
|
||||
type ApiStats struct {
|
||||
// Common stats
|
||||
// Read time.Time `json:"read"`
|
||||
Read time.Time `json:"read"`
|
||||
// PreRead time.Time `json:"preread"`
|
||||
|
||||
// Linux specific stats, not populated on Windows.
|
||||
@@ -36,7 +36,7 @@ type ApiStats struct {
|
||||
// BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
||||
|
||||
// Windows specific stats, not populated on Linux.
|
||||
// NumProcs uint32 `json:"num_procs"`
|
||||
NumProcs uint32 `json:"num_procs"`
|
||||
// StorageStats StorageStats `json:"storage_stats,omitempty"`
|
||||
// Networks request version >=1.21
|
||||
Networks map[string]NetworkStats
|
||||
@@ -101,7 +101,7 @@ type MemoryStats struct {
|
||||
// // peak committed bytes
|
||||
// CommitPeak uint64 `json:"commitpeakbytes,omitempty"`
|
||||
// // private working set
|
||||
// PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
|
||||
PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
|
||||
}
|
||||
|
||||
type MemoryStatsStats struct {
|
||||
@@ -131,4 +131,5 @@ type Stats struct {
|
||||
NetworkRecv float64 `json:"nr"`
|
||||
PrevCpu [2]uint64 `json:"-"`
|
||||
PrevNet prevNetStats `json:"-"`
|
||||
PrevRead time.Time `json:"-"`
|
||||
}
|
||||
|
Reference in New Issue
Block a user