mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 17:59:28 +08:00
Small refactoring of docker manager
- Add isWindows flag to dockerManager - `CalculateCpuPercentWindows` and `CalculateCpuPercentLinux` methods added to container.ApiStats - Remove prevNetStats.Time in favor of Stats.PrevRead - Replace regex Windows check with strings.Contains, and check the `/containers/json` response
This commit is contained in:
@@ -14,13 +14,9 @@ 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
|
||||||
@@ -30,6 +26,7 @@ type dockerManager struct {
|
|||||||
containerStatsMap map[string]*container.Stats // Keeps track of container stats
|
containerStatsMap map[string]*container.Stats // Keeps track of container stats
|
||||||
validIds map[string]struct{} // Map of valid container ids, used to prune invalid containers from containerStatsMap
|
validIds map[string]struct{} // Map of valid container ids, used to prune invalid containers from containerStatsMap
|
||||||
goodDockerVersion bool // Whether docker version is at least 25.0.0 (one-shot works correctly)
|
goodDockerVersion bool // Whether docker version is at least 25.0.0 (one-shot works correctly)
|
||||||
|
isWindows bool // Whether the Docker Engine API is running on Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
// userAgentRoundTripper is a custom http.RoundTripper that adds a User-Agent header to all requests
|
// userAgentRoundTripper is a custom http.RoundTripper that adds a User-Agent header to all requests
|
||||||
@@ -73,6 +70,8 @@ func (dm *dockerManager) getDockerStats() ([]*container.Stats, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dm.isWindows = strings.Contains(resp.Header.Get("Server"), "windows")
|
||||||
|
|
||||||
containersLength := len(dm.apiContainerList)
|
containersLength := len(dm.apiContainerList)
|
||||||
|
|
||||||
// store valid ids to clean up old container ids from map
|
// store valid ids to clean up old container ids from map
|
||||||
@@ -84,8 +83,7 @@ func (dm *dockerManager) getDockerStats() ([]*container.Stats, error) {
|
|||||||
|
|
||||||
var failedContainers []*container.ApiInfo
|
var failedContainers []*container.ApiInfo
|
||||||
|
|
||||||
for i := range dm.apiContainerList {
|
for _, ctr := range dm.apiContainerList {
|
||||||
ctr := dm.apiContainerList[i]
|
|
||||||
ctr.IdShort = ctr.Id[:12]
|
ctr.IdShort = ctr.Id[:12]
|
||||||
dm.validIds[ctr.IdShort] = struct{}{}
|
dm.validIds[ctr.IdShort] = struct{}{}
|
||||||
// check if container is less than 1 minute old (possible restart)
|
// check if container is less than 1 minute old (possible restart)
|
||||||
@@ -171,15 +169,13 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculate cpu and memory stats
|
||||||
var usedMemory uint64
|
var usedMemory uint64
|
||||||
var cpuPct float64
|
var cpuPct float64
|
||||||
|
|
||||||
if getDockerOS(resp.Header.Get("Server")) == "windows" {
|
if dm.isWindows {
|
||||||
|
|
||||||
usedMemory = res.MemoryStats.PrivateWorkingSet
|
usedMemory = res.MemoryStats.PrivateWorkingSet
|
||||||
cpuPct = calculateCPUPercentWindows(res, stats.PrevCpu[0], stats.PrevRead)
|
cpuPct = res.CalculateCpuPercentWindows(stats.PrevCpu[0], stats.PrevRead)
|
||||||
stats.PrevRead = res.Read
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// check if container has valid data, otherwise may be in restart loop (#103)
|
// check if container has valid data, otherwise may be in restart loop (#103)
|
||||||
if res.MemoryStats.Usage == 0 {
|
if res.MemoryStats.Usage == 0 {
|
||||||
@@ -191,9 +187,7 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo) error {
|
|||||||
}
|
}
|
||||||
usedMemory = res.MemoryStats.Usage - memCache
|
usedMemory = res.MemoryStats.Usage - memCache
|
||||||
|
|
||||||
cpuDelta := res.CPUStats.CPUUsage.TotalUsage - stats.PrevCpu[0]
|
cpuPct = res.CalculateCpuPercentLinux(stats.PrevCpu)
|
||||||
systemDelta := res.CPUStats.SystemUsage - stats.PrevCpu[1]
|
|
||||||
cpuPct = float64(cpuDelta) / float64(systemDelta) * 100
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cpuPct > 100 {
|
if cpuPct > 100 {
|
||||||
@@ -210,18 +204,18 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo) error {
|
|||||||
var sent_delta, recv_delta float64
|
var sent_delta, recv_delta float64
|
||||||
// prevent first run from sending all prev sent/recv bytes
|
// prevent first run from sending all prev sent/recv bytes
|
||||||
if initialized {
|
if initialized {
|
||||||
secondsElapsed := time.Since(stats.PrevNet.Time).Seconds()
|
secondsElapsed := time.Since(stats.PrevRead).Seconds()
|
||||||
sent_delta = float64(total_sent-stats.PrevNet.Sent) / secondsElapsed
|
sent_delta = float64(total_sent-stats.PrevNet.Sent) / secondsElapsed
|
||||||
recv_delta = float64(total_recv-stats.PrevNet.Recv) / secondsElapsed
|
recv_delta = float64(total_recv-stats.PrevNet.Recv) / secondsElapsed
|
||||||
}
|
}
|
||||||
stats.PrevNet.Sent = total_sent
|
stats.PrevNet.Sent = total_sent
|
||||||
stats.PrevNet.Recv = total_recv
|
stats.PrevNet.Recv = total_recv
|
||||||
stats.PrevNet.Time = time.Now()
|
|
||||||
|
|
||||||
stats.Cpu = twoDecimals(cpuPct)
|
stats.Cpu = twoDecimals(cpuPct)
|
||||||
stats.Mem = bytesToMegabytes(float64(usedMemory))
|
stats.Mem = bytesToMegabytes(float64(usedMemory))
|
||||||
stats.NetworkSent = bytesToMegabytes(sent_delta)
|
stats.NetworkSent = bytesToMegabytes(sent_delta)
|
||||||
stats.NetworkRecv = bytesToMegabytes(recv_delta)
|
stats.NetworkRecv = bytesToMegabytes(recv_delta)
|
||||||
|
stats.PrevRead = res.Read
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -337,31 +331,3 @@ 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
|
|
||||||
}
|
|
||||||
|
@@ -27,38 +27,41 @@ 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
|
Read time.Time `json:"read"` // Time of stats generation
|
||||||
Read time.Time `json:"read"`
|
NumProcs uint32 `json:"num_procs,omitzero"` // Windows specific, not populated on Linux.
|
||||||
// PreRead time.Time `json:"preread"`
|
Networks map[string]NetworkStats
|
||||||
|
CPUStats CPUStats `json:"cpu_stats"`
|
||||||
|
MemoryStats MemoryStats `json:"memory_stats"`
|
||||||
|
}
|
||||||
|
|
||||||
// Linux specific stats, not populated on Windows.
|
func (s *ApiStats) CalculateCpuPercentLinux(prevCpuUsage [2]uint64) float64 {
|
||||||
// PidsStats PidsStats `json:"pids_stats,omitempty"`
|
cpuDelta := s.CPUStats.CPUUsage.TotalUsage - prevCpuUsage[0]
|
||||||
// BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
systemDelta := s.CPUStats.SystemUsage - prevCpuUsage[1]
|
||||||
|
return float64(cpuDelta) / float64(systemDelta) * 100
|
||||||
|
}
|
||||||
|
|
||||||
// Windows specific stats, not populated on Linux.
|
// from: https://github.com/docker/cli/blob/master/cli/command/container/stats_helpers.go#L185
|
||||||
NumProcs uint32 `json:"num_procs"`
|
func (s *ApiStats) CalculateCpuPercentWindows(prevCpuUsage uint64, prevRead time.Time) float64 {
|
||||||
// StorageStats StorageStats `json:"storage_stats,omitempty"`
|
// Max number of 100ns intervals between the previous time read and now
|
||||||
// Networks request version >=1.21
|
possIntervals := uint64(s.Read.Sub(prevRead).Nanoseconds())
|
||||||
Networks map[string]NetworkStats
|
possIntervals /= 100 // Convert to number of 100ns intervals
|
||||||
|
possIntervals *= uint64(s.NumProcs) // Multiple by the number of processors
|
||||||
|
|
||||||
// Shared stats
|
// Intervals used
|
||||||
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
intervalsUsed := s.CPUStats.CPUUsage.TotalUsage - prevCpuUsage
|
||||||
// PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
|
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
// Percentage avoiding divide-by-zero
|
||||||
|
if possIntervals > 0 {
|
||||||
|
return float64(intervalsUsed) / float64(possIntervals) * 100.0
|
||||||
|
}
|
||||||
|
return 0.00
|
||||||
}
|
}
|
||||||
|
|
||||||
type CPUStats struct {
|
type CPUStats struct {
|
||||||
// CPU Usage. Linux and Windows.
|
// CPU Usage. Linux and Windows.
|
||||||
CPUUsage CPUUsage `json:"cpu_usage"`
|
CPUUsage CPUUsage `json:"cpu_usage"`
|
||||||
|
|
||||||
// System Usage. Linux only.
|
// System Usage. Linux only.
|
||||||
SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
|
SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
|
||||||
|
|
||||||
// Online CPUs. Linux only.
|
|
||||||
// OnlineCPUs uint32 `json:"online_cpus,omitempty"`
|
|
||||||
|
|
||||||
// Throttling Data. Linux only.
|
|
||||||
// ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CPUUsage struct {
|
type CPUUsage struct {
|
||||||
@@ -66,41 +69,14 @@ type CPUUsage struct {
|
|||||||
// Units: nanoseconds (Linux)
|
// Units: nanoseconds (Linux)
|
||||||
// Units: 100's of nanoseconds (Windows)
|
// Units: 100's of nanoseconds (Windows)
|
||||||
TotalUsage uint64 `json:"total_usage"`
|
TotalUsage uint64 `json:"total_usage"`
|
||||||
|
|
||||||
// Total CPU time consumed per core (Linux). Not used on Windows.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
// PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
|
|
||||||
|
|
||||||
// Time spent by tasks of the cgroup in kernel mode (Linux).
|
|
||||||
// Time spent by all container processes in kernel mode (Windows).
|
|
||||||
// Units: nanoseconds (Linux).
|
|
||||||
// Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers.
|
|
||||||
// UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
|
|
||||||
|
|
||||||
// Time spent by tasks of the cgroup in user mode (Linux).
|
|
||||||
// Time spent by all container processes in user mode (Windows).
|
|
||||||
// Units: nanoseconds (Linux).
|
|
||||||
// Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers
|
|
||||||
// UsageInUsermode uint64 `json:"usage_in_usermode"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryStats struct {
|
type MemoryStats struct {
|
||||||
// current res_counter usage for memory
|
// current res_counter usage for memory
|
||||||
Usage uint64 `json:"usage,omitempty"`
|
Usage uint64 `json:"usage,omitempty"`
|
||||||
// all the stats exported via memory.stat.
|
// all the stats exported via memory.stat.
|
||||||
Stats MemoryStatsStats `json:"stats,omitempty"`
|
Stats MemoryStatsStats `json:"stats"`
|
||||||
// maximum usage ever recorded.
|
// private working set (Windows only)
|
||||||
// MaxUsage uint64 `json:"max_usage,omitempty"`
|
|
||||||
// TODO(vishh): Export these as stronger types.
|
|
||||||
// number of times memory usage hits limits.
|
|
||||||
// Failcnt uint64 `json:"failcnt,omitempty"`
|
|
||||||
// Limit uint64 `json:"limit,omitempty"`
|
|
||||||
|
|
||||||
// // committed bytes
|
|
||||||
// Commit uint64 `json:"commitbytes,omitempty"`
|
|
||||||
// // peak committed bytes
|
|
||||||
// CommitPeak uint64 `json:"commitpeakbytes,omitempty"`
|
|
||||||
// // private working set
|
|
||||||
PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
|
PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +95,6 @@ type NetworkStats struct {
|
|||||||
type prevNetStats struct {
|
type prevNetStats struct {
|
||||||
Sent uint64
|
Sent uint64
|
||||||
Recv uint64
|
Recv uint64
|
||||||
Time time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Docker container stats
|
// Docker container stats
|
||||||
|
Reference in New Issue
Block a user