diff --git a/cli/command/container/stats_helpers.go b/cli/command/container/stats_helpers.go index 586f4908b1b8..c6849c805aeb 100644 --- a/cli/command/container/stats_helpers.go +++ b/cli/command/container/stats_helpers.go @@ -84,7 +84,7 @@ func collect(ctx context.Context, s *formatter.ContainerStats, cli client.APICli v *types.StatsJSON memPercent, cpuPercent float64 blkRead, blkWrite uint64 // Only used on Linux - mem, memLimit, memPerc float64 + mem, memLimit float64 pidsStatsCurrent uint64 ) @@ -101,18 +101,13 @@ func collect(ctx context.Context, s *formatter.ContainerStats, cli client.APICli daemonOSType = response.OSType if daemonOSType != "windows" { - // MemoryStats.Limit will never be 0 unless the container is not running and we haven't - // got any data from cgroup - if v.MemoryStats.Limit != 0 { - memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 - } previousCPU = v.PreCPUStats.CPUUsage.TotalUsage previousSystem = v.PreCPUStats.SystemUsage cpuPercent = calculateCPUPercentUnix(previousCPU, previousSystem, v) blkRead, blkWrite = calculateBlockIO(v.BlkioStats) - mem = float64(v.MemoryStats.Usage) + mem = calculateMemUsageUnixNoCache(v.MemoryStats) memLimit = float64(v.MemoryStats.Limit) - memPerc = memPercent + memPercent = calculateMemPercentUnixNoCache(memLimit, mem) pidsStatsCurrent = v.PidsStats.Current } else { cpuPercent = calculateCPUPercentWindows(v) @@ -126,7 +121,7 @@ func collect(ctx context.Context, s *formatter.ContainerStats, cli client.APICli ID: v.ID, CPUPercentage: cpuPercent, Memory: mem, - MemoryPercentage: memPerc, + MemoryPercentage: memPercent, MemoryLimit: memLimit, NetworkRx: netRx, NetworkTx: netTx, @@ -227,3 +222,18 @@ func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) } return rx, tx } + +// calculateMemUsageUnixNoCache calculate memory usage of the container. +// Page cache is intentionally excluded to avoid misinterpretation of the output. +func calculateMemUsageUnixNoCache(mem types.MemoryStats) float64 { + return float64(mem.Usage - mem.Stats["cache"]) +} + +func calculateMemPercentUnixNoCache(limit float64, usedNoCache float64) float64 { + // MemoryStats.Limit will never be 0 unless the container is not running and we haven't + // got any data from cgroup + if limit != 0 { + return usedNoCache / limit * 100.0 + } + return 0 +} diff --git a/cli/command/container/stats_helpers_test.go b/cli/command/container/stats_helpers_test.go new file mode 100644 index 000000000000..0425a3ba13d3 --- /dev/null +++ b/cli/command/container/stats_helpers_test.go @@ -0,0 +1,36 @@ +package container + +import ( + "testing" + + "github.com/docker/docker/api/types" + "github.com/stretchr/testify/assert" +) + +func TestCalculateMemUsageUnixNoCache(t *testing.T) { + // Given + stats := types.MemoryStats{Usage: 500, Stats: map[string]uint64{"cache": 400}} + + // When + result := calculateMemUsageUnixNoCache(stats) + + // Then + assert.InDelta(t, 100.0, result, 1e-6) +} + +func TestCalculateMemPercentUnixNoCache(t *testing.T) { + // Given + someLimit := float64(100.0) + noLimit := float64(0.0) + used := float64(70.0) + + // When and Then + t.Run("Limit is set", func(t *testing.T) { + result := calculateMemPercentUnixNoCache(someLimit, used) + assert.InDelta(t, 70.0, result, 1e-6) + }) + t.Run("No limit, no cgroup data", func(t *testing.T) { + result := calculateMemPercentUnixNoCache(noLimit, used) + assert.InDelta(t, 0.0, result, 1e-6) + }) +}