Skip to content

Commit 380d703

Browse files
authored
Merge pull request containerd#3369 from cezar-r/stats-bug
fix: nerdctl stats on containers without a memory limit returns host memory limit
2 parents 5ef3856 + ad64f42 commit 380d703

File tree

2 files changed

+64
-4
lines changed

2 files changed

+64
-4
lines changed

cmd/nerdctl/container/container_stats_linux_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,31 @@ func TestStats(t *testing.T) {
4545
base.Cmd("container", "stats", "--no-stream").AssertOutContains(testContainerName)
4646
base.Cmd("container", "stats", "--no-stream", testContainerName).AssertOK()
4747
}
48+
49+
func TestStatsMemoryLimitNotSet(t *testing.T) {
50+
if rootlessutil.IsRootless() && infoutil.CgroupsVersion() == "1" {
51+
t.Skip("test skipped for rootless containers on cgroup v1")
52+
}
53+
testContainerName := testutil.Identifier(t)[:12]
54+
55+
base := testutil.NewBase(t)
56+
defer base.Cmd("rm", "-f", testContainerName).Run()
57+
58+
base.Cmd("run", "-d", "--name", testContainerName, testutil.AlpineImage, "sleep", "10").AssertOK()
59+
base.Cmd("stats", "--no-stream").AssertOutNotContains("16EiB")
60+
base.Cmd("stats", "--no-stream", testContainerName).AssertOK()
61+
}
62+
63+
func TestStatsMemoryLimitSet(t *testing.T) {
64+
if rootlessutil.IsRootless() && infoutil.CgroupsVersion() == "1" {
65+
t.Skip("test skipped for rootless containers on cgroup v1")
66+
}
67+
testContainerName := testutil.Identifier(t)[:12]
68+
69+
base := testutil.NewBase(t)
70+
defer base.Cmd("rm", "-f", testContainerName).Run()
71+
72+
base.Cmd("run", "-d", "--name", testContainerName, "--memory", "1g", testutil.AlpineImage, "sleep", "10").AssertOK()
73+
base.Cmd("stats", "--no-stream").AssertOutContains("1GiB")
74+
base.Cmd("stats", "--no-stream", testContainerName).AssertOK()
75+
}

pkg/statsutil/stats_linux.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
package statsutil
1818

1919
import (
20+
"bufio"
21+
"os"
22+
"strconv"
23+
"strings"
2024
"time"
2125

2226
"github.com/vishvananda/netlink"
@@ -35,11 +39,10 @@ func calculateMemPercent(limit float64, usedNo float64) float64 {
3539
}
3640

3741
func SetCgroupStatsFields(previousStats *ContainerStats, data *v1.Metrics, links []netlink.Link) (StatsEntry, error) {
38-
3942
cpuPercent := calculateCgroupCPUPercent(previousStats, data)
4043
blkRead, blkWrite := calculateCgroupBlockIO(data)
4144
mem := calculateCgroupMemUsage(data)
42-
memLimit := float64(data.Memory.Usage.Limit)
45+
memLimit := getCgroupMemLimit(float64(data.Memory.Usage.Limit))
4346
memPercent := calculateMemPercent(memLimit, mem)
4447
pidsStatsCurrent := data.Pids.Current
4548
netRx, netTx := calculateCgroupNetwork(links)
@@ -59,11 +62,10 @@ func SetCgroupStatsFields(previousStats *ContainerStats, data *v1.Metrics, links
5962
}
6063

6164
func SetCgroup2StatsFields(previousStats *ContainerStats, metrics *v2.Metrics, links []netlink.Link) (StatsEntry, error) {
62-
6365
cpuPercent := calculateCgroup2CPUPercent(previousStats, metrics)
6466
blkRead, blkWrite := calculateCgroup2IO(metrics)
6567
mem := calculateCgroup2MemUsage(metrics)
66-
memLimit := float64(metrics.Memory.UsageLimit)
68+
memLimit := getCgroupMemLimit(float64(metrics.Memory.UsageLimit))
6769
memPercent := calculateMemPercent(memLimit, mem)
6870
pidsStatsCurrent := metrics.Pids.Current
6971
netRx, netTx := calculateCgroupNetwork(links)
@@ -82,6 +84,36 @@ func SetCgroup2StatsFields(previousStats *ContainerStats, metrics *v2.Metrics, l
8284

8385
}
8486

87+
func getCgroupMemLimit(memLimit float64) float64 {
88+
if memLimit == float64(^uint64(0)) {
89+
return getHostMemLimit()
90+
}
91+
return memLimit
92+
}
93+
94+
func getHostMemLimit() float64 {
95+
file, err := os.Open("/proc/meminfo")
96+
if err != nil {
97+
return float64(^uint64(0))
98+
}
99+
defer file.Close()
100+
101+
scanner := bufio.NewScanner(file)
102+
for scanner.Scan() {
103+
if strings.HasPrefix(scanner.Text(), "MemTotal:") {
104+
fields := strings.Fields(scanner.Text())
105+
if len(fields) >= 2 {
106+
memKb, err := strconv.ParseUint(fields[1], 10, 64)
107+
if err == nil {
108+
return float64(memKb * 1024) // kB to bytes
109+
}
110+
}
111+
break
112+
}
113+
}
114+
return float64(^uint64(0))
115+
}
116+
85117
func calculateCgroupCPUPercent(previousStats *ContainerStats, metrics *v1.Metrics) float64 {
86118
var (
87119
cpuPercent = 0.0

0 commit comments

Comments
 (0)