mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 17:59:28 +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"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`)
|
||||||
|
|
||||||
type dockerManager struct {
|
type dockerManager struct {
|
||||||
client *http.Client // Client to query Docker API
|
client *http.Client // Client to query Docker API
|
||||||
wg sync.WaitGroup // WaitGroup to wait for all goroutines to finish
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if container has valid data, otherwise may be in restart loop (#103)
|
var usedMemory uint64
|
||||||
if res.MemoryStats.Usage == 0 {
|
var cpuPct float64
|
||||||
return fmt.Errorf("%s - no memory stats - see https://github.com/henrygd/beszel/issues/144", name)
|
|
||||||
|
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 {
|
if cpuPct > 100 {
|
||||||
return fmt.Errorf("%s cpu pct greater than 100: %+v", name, cpuPct)
|
return fmt.Errorf("%s cpu pct greater than 100: %+v", name, cpuPct)
|
||||||
}
|
}
|
||||||
@@ -324,3 +337,31 @@ func getDockerHost() string {
|
|||||||
}
|
}
|
||||||
return scheme + socks[0]
|
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
|
// Docker container resources from /containers/{id}/stats
|
||||||
type ApiStats struct {
|
type ApiStats struct {
|
||||||
// Common stats
|
// Common stats
|
||||||
// Read time.Time `json:"read"`
|
Read time.Time `json:"read"`
|
||||||
// PreRead time.Time `json:"preread"`
|
// PreRead time.Time `json:"preread"`
|
||||||
|
|
||||||
// Linux specific stats, not populated on Windows.
|
// Linux specific stats, not populated on Windows.
|
||||||
@@ -36,7 +36,7 @@ type ApiStats struct {
|
|||||||
// BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
// BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
||||||
|
|
||||||
// Windows specific stats, not populated on Linux.
|
// Windows specific stats, not populated on Linux.
|
||||||
// NumProcs uint32 `json:"num_procs"`
|
NumProcs uint32 `json:"num_procs"`
|
||||||
// StorageStats StorageStats `json:"storage_stats,omitempty"`
|
// StorageStats StorageStats `json:"storage_stats,omitempty"`
|
||||||
// Networks request version >=1.21
|
// Networks request version >=1.21
|
||||||
Networks map[string]NetworkStats
|
Networks map[string]NetworkStats
|
||||||
@@ -101,7 +101,7 @@ type MemoryStats struct {
|
|||||||
// // peak committed bytes
|
// // peak committed bytes
|
||||||
// CommitPeak uint64 `json:"commitpeakbytes,omitempty"`
|
// CommitPeak uint64 `json:"commitpeakbytes,omitempty"`
|
||||||
// // private working set
|
// // private working set
|
||||||
// PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
|
PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryStatsStats struct {
|
type MemoryStatsStats struct {
|
||||||
@@ -131,4 +131,5 @@ type Stats struct {
|
|||||||
NetworkRecv float64 `json:"nr"`
|
NetworkRecv float64 `json:"nr"`
|
||||||
PrevCpu [2]uint64 `json:"-"`
|
PrevCpu [2]uint64 `json:"-"`
|
||||||
PrevNet prevNetStats `json:"-"`
|
PrevNet prevNetStats `json:"-"`
|
||||||
|
PrevRead time.Time `json:"-"`
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user