From ad64f4217d55835d22d0511587e7005eb88e32bb Mon Sep 17 00:00:00 2001 From: Cezar Rata Date: Mon, 26 Aug 2024 16:05:47 -0700 Subject: [PATCH] fix: nerdctl stats on a container without a memory limit returns host memory limit Signed-off-by: Cezar Rata Signed-off-by: Cezar Rata Signed-off-by: Cezar Rata Signed-off-by: Cezar Rata --- .../container/container_stats_linux_test.go | 28 +++++++++++++ pkg/statsutil/stats_linux.go | 40 +++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/cmd/nerdctl/container/container_stats_linux_test.go b/cmd/nerdctl/container/container_stats_linux_test.go index 5ea2017d949..42b2861aec4 100644 --- a/cmd/nerdctl/container/container_stats_linux_test.go +++ b/cmd/nerdctl/container/container_stats_linux_test.go @@ -45,3 +45,31 @@ func TestStats(t *testing.T) { base.Cmd("container", "stats", "--no-stream").AssertOutContains(testContainerName) base.Cmd("container", "stats", "--no-stream", testContainerName).AssertOK() } + +func TestStatsMemoryLimitNotSet(t *testing.T) { + if rootlessutil.IsRootless() && infoutil.CgroupsVersion() == "1" { + t.Skip("test skipped for rootless containers on cgroup v1") + } + testContainerName := testutil.Identifier(t)[:12] + + base := testutil.NewBase(t) + defer base.Cmd("rm", "-f", testContainerName).Run() + + base.Cmd("run", "-d", "--name", testContainerName, testutil.AlpineImage, "sleep", "10").AssertOK() + base.Cmd("stats", "--no-stream").AssertOutNotContains("16EiB") + base.Cmd("stats", "--no-stream", testContainerName).AssertOK() +} + +func TestStatsMemoryLimitSet(t *testing.T) { + if rootlessutil.IsRootless() && infoutil.CgroupsVersion() == "1" { + t.Skip("test skipped for rootless containers on cgroup v1") + } + testContainerName := testutil.Identifier(t)[:12] + + base := testutil.NewBase(t) + defer base.Cmd("rm", "-f", testContainerName).Run() + + base.Cmd("run", "-d", "--name", testContainerName, "--memory", "1g", testutil.AlpineImage, "sleep", "10").AssertOK() + base.Cmd("stats", "--no-stream").AssertOutContains("1GiB") + base.Cmd("stats", "--no-stream", testContainerName).AssertOK() +} diff --git a/pkg/statsutil/stats_linux.go b/pkg/statsutil/stats_linux.go index 61afba099bb..4f1f53bc828 100644 --- a/pkg/statsutil/stats_linux.go +++ b/pkg/statsutil/stats_linux.go @@ -17,6 +17,10 @@ package statsutil import ( + "bufio" + "os" + "strconv" + "strings" "time" "github.com/vishvananda/netlink" @@ -35,11 +39,10 @@ func calculateMemPercent(limit float64, usedNo float64) float64 { } func SetCgroupStatsFields(previousStats *ContainerStats, data *v1.Metrics, links []netlink.Link) (StatsEntry, error) { - cpuPercent := calculateCgroupCPUPercent(previousStats, data) blkRead, blkWrite := calculateCgroupBlockIO(data) mem := calculateCgroupMemUsage(data) - memLimit := float64(data.Memory.Usage.Limit) + memLimit := getCgroupMemLimit(float64(data.Memory.Usage.Limit)) memPercent := calculateMemPercent(memLimit, mem) pidsStatsCurrent := data.Pids.Current netRx, netTx := calculateCgroupNetwork(links) @@ -59,11 +62,10 @@ func SetCgroupStatsFields(previousStats *ContainerStats, data *v1.Metrics, links } func SetCgroup2StatsFields(previousStats *ContainerStats, metrics *v2.Metrics, links []netlink.Link) (StatsEntry, error) { - cpuPercent := calculateCgroup2CPUPercent(previousStats, metrics) blkRead, blkWrite := calculateCgroup2IO(metrics) mem := calculateCgroup2MemUsage(metrics) - memLimit := float64(metrics.Memory.UsageLimit) + memLimit := getCgroupMemLimit(float64(metrics.Memory.UsageLimit)) memPercent := calculateMemPercent(memLimit, mem) pidsStatsCurrent := metrics.Pids.Current netRx, netTx := calculateCgroupNetwork(links) @@ -82,6 +84,36 @@ func SetCgroup2StatsFields(previousStats *ContainerStats, metrics *v2.Metrics, l } +func getCgroupMemLimit(memLimit float64) float64 { + if memLimit == float64(^uint64(0)) { + return getHostMemLimit() + } + return memLimit +} + +func getHostMemLimit() float64 { + file, err := os.Open("/proc/meminfo") + if err != nil { + return float64(^uint64(0)) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + if strings.HasPrefix(scanner.Text(), "MemTotal:") { + fields := strings.Fields(scanner.Text()) + if len(fields) >= 2 { + memKb, err := strconv.ParseUint(fields[1], 10, 64) + if err == nil { + return float64(memKb * 1024) // kB to bytes + } + } + break + } + } + return float64(^uint64(0)) +} + func calculateCgroupCPUPercent(previousStats *ContainerStats, metrics *v1.Metrics) float64 { var ( cpuPercent = 0.0