Skip to contentSkip to conversation

Fix test_cgroup_iter_memcg issues found during back-porting

PATCH Superseded Hui Zhu 3 participants 7 messages Feb 13 – Feb 16

Subsystems (5)

v2 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

Hui Zhu

author
#0 lore raw

v2 cover Fix test_cgroup_iter_memcg issues found during back-porting

From: Hui Zhu <[email protected]>

While back-porting "mm: bpf kfuncs to access memcg data", I encountered issues with test_cgroup_iter_memcg, specifically in test_kmem.

These patches are my fixes for the problems I encountered.

Changelog:
v2:
According to the comments of JP Kobryn, added bpf_core_enum_value() usage in the BPF program to handle cross-kernel enum value differences at load-time instead of compile-time.
Dropped the mm/memcontrol.c patch.
Modified test_kmem handling: instead of skipping when nokmem is set, verify that kmem value is zero as expected.
According to the comments of bot, fixed assertion message: changed "bpf_mem_cgroup_page_state" to "bpf_mem_cgroup_vm_events" for PGFAULT check.

Hui Zhu (3):
bpf: Use bpf_core_enum_value for stats in cgroup_iter_memcg selftests/bpf: Check bpf_mem_cgroup_page_state return value
bpf: selftests: Skip kmem test when cgroup.memory=nokmem is set

 .../bpf/prog_tests/cgroup_iter_memcg.c        | 42 +++++++++++++++++++
 .../selftests/bpf/progs/cgroup_iter_memcg.c   | 41 ++++++++++++++----
 2 files changed, 76 insertions(+), 7 deletions(-)
Signature · 1 line
2.43.0
author
#1 lore raw

PATCH v2 1/3 bpf: Use bpf_core_enum_value for stats in cgroup_iter_memcg

From: Hui Zhu <[email protected]>

Replace hardcoded enum values with bpf_core_enum_value() calls in cgroup_iter_memcg test to improve portability across different kernel versions.

The change adds runtime enum value resolution for:
- node_stat_item: NR_ANON_MAPPED, NR_SHMEM, NR_FILE_PAGES, NR_FILE_MAPPED
- memcg_stat_item: MEMCG_KMEM
- vm_event_item: PGFAULT

This ensures the BPF program can adapt to enum value changes between kernel versions, returning early if any enum value is unavailable (returns 0).

---
 .../selftests/bpf/progs/cgroup_iter_memcg.c   | 41 +++++++++++++++----
 1 file changed, 34 insertions(+), 7 deletions(-)
tools/testing/selftests/bpf/progs/cgroup_iter_memcg.c +34 -7
index 59fb70a3cc50..b020951dd7e6 100644
@@ -15,6 +15,8 @@ int cgroup_memcg_query(struct bpf_iter__cgroup *ctx)
 	struct cgroup *cgrp = ctx->cgroup;
 	struct cgroup_subsys_state *css;
 	struct mem_cgroup *memcg;
+	int ret = 1;
+	int idx;
 
 	if (!cgrp)
 		return 1;
@@ -26,14 +28,39 @@ int cgroup_memcg_query(struct bpf_iter__cgroup *ctx)
 
 	bpf_mem_cgroup_flush_stats(memcg);
 
-	memcg_query.nr_anon_mapped = bpf_mem_cgroup_page_state(memcg, NR_ANON_MAPPED);
-	memcg_query.nr_shmem = bpf_mem_cgroup_page_state(memcg, NR_SHMEM);
-	memcg_query.nr_file_pages = bpf_mem_cgroup_page_state(memcg, NR_FILE_PAGES);
-	memcg_query.nr_file_mapped = bpf_mem_cgroup_page_state(memcg, NR_FILE_MAPPED);
-	memcg_query.memcg_kmem = bpf_mem_cgroup_page_state(memcg, MEMCG_KMEM);
-	memcg_query.pgfault = bpf_mem_cgroup_vm_events(memcg, PGFAULT);
+	idx = bpf_core_enum_value(enum node_stat_item, NR_ANON_MAPPED);
+	if (idx == 0)
+		goto out;
+	memcg_query.nr_anon_mapped = bpf_mem_cgroup_page_state(memcg, idx);
 
+	idx = bpf_core_enum_value(enum node_stat_item, NR_SHMEM);
+	if (idx == 0)
+		goto out;
+	memcg_query.nr_shmem = bpf_mem_cgroup_page_state(memcg, idx);
+
+	idx = bpf_core_enum_value(enum node_stat_item, NR_FILE_PAGES);
+	if (idx == 0)
+		goto out;
+	memcg_query.nr_file_pages = bpf_mem_cgroup_page_state(memcg, idx);
+
+	idx = bpf_core_enum_value(enum node_stat_item, NR_FILE_MAPPED);
+	if (idx == 0)
+		goto out;
+	memcg_query.nr_file_mapped = bpf_mem_cgroup_page_state(memcg, idx);
+
+	idx = bpf_core_enum_value(enum memcg_stat_item, MEMCG_KMEM);
+	if (idx == 0)
+		goto out;
+	memcg_query.memcg_kmem = bpf_mem_cgroup_page_state(memcg, idx);
+
+	idx = bpf_core_enum_value(enum vm_event_item, PGFAULT);
+	if (idx == 0)
+		goto out;
+	memcg_query.pgfault = bpf_mem_cgroup_vm_events(memcg, idx);
+
+	ret = 0;
+out:
 	bpf_put_mem_cgroup(memcg);
 
-	return 0;
+	return ret;
 }
Signature · 1 line
2.43.0
#2 lore raw

Re: [PATCH bpf-next v2 1/3] bpf: Use bpf_core_enum_value for stats in cgroup_iter_memcg

Quoted 62 lines · Hui Zhu

From: Hui Zhu <[email protected]>

Replace hardcoded enum values with bpf_core_enum_value() calls in cgroup_iter_memcg test to improve portability across different kernel versions.

The change adds runtime enum value resolution for:
- node_stat_item: NR_ANON_MAPPED, NR_SHMEM, NR_FILE_PAGES, NR_FILE_MAPPED
- memcg_stat_item: MEMCG_KMEM
- vm_event_item: PGFAULT

This ensures the BPF program can adapt to enum value changes between kernel versions, returning early if any enum value is unavailable (returns 0).

---
  .../selftests/bpf/progs/cgroup_iter_memcg.c   | 41 +++++++++++++++----
  1 file changed, 34 insertions(+), 7 deletions(-)
tools/testing/selftests/bpf/progs/cgroup_iter_memcg.c +19 -6
index 59fb70a3cc50..b020951dd7e6 100644
@@ -15,6 +15,8 @@ int cgroup_memcg_query(struct bpf_iter__cgroup *ctx)
  	struct cgroup *cgrp = ctx->cgroup;
  	struct cgroup_subsys_state *css;
  	struct mem_cgroup *memcg;
+	int ret = 1;
+	int idx;
 
  	if (!cgrp)
  		return 1;
@@ -26,14 +28,39 @@ int cgroup_memcg_query(struct bpf_iter__cgroup *ctx)
 
  	bpf_mem_cgroup_flush_stats(memcg);
 
-	memcg_query.nr_anon_mapped = bpf_mem_cgroup_page_state(memcg, NR_ANON_MAPPED);
-	memcg_query.nr_shmem = bpf_mem_cgroup_page_state(memcg, NR_SHMEM);
-	memcg_query.nr_file_pages = bpf_mem_cgroup_page_state(memcg, NR_FILE_PAGES);
-	memcg_query.nr_file_mapped = bpf_mem_cgroup_page_state(memcg, NR_FILE_MAPPED);
-	memcg_query.memcg_kmem = bpf_mem_cgroup_page_state(memcg, MEMCG_KMEM);
-	memcg_query.pgfault = bpf_mem_cgroup_vm_events(memcg, PGFAULT);
+	idx = bpf_core_enum_value(enum node_stat_item, NR_ANON_MAPPED);
+	if (idx == 0)
+		goto out;
+	memcg_query.nr_anon_mapped = bpf_mem_cgroup_page_state(memcg, idx);
 
+	idx = bpf_core_enum_value(enum node_stat_item, NR_SHMEM);
+	if (idx == 0)
+		goto out;
+	memcg_query.nr_shmem = bpf_mem_cgroup_page_state(memcg, idx);
+
+	idx = bpf_core_enum_value(enum node_stat_item, NR_FILE_PAGES);
+	if (idx == 0)
+		goto out;
+	memcg_query.nr_file_pages = bpf_mem_cgroup_page_state(memcg, idx);
+
+	idx = bpf_core_enum_value(enum node_stat_item, NR_FILE_MAPPED);
+	if (idx == 0)
+		goto out;

tools/testing/selftests/bpf/progs/cgroup_iter_memcg.c int cgroup_memcg_query(struct bpf_iter__cgroup *ctx) · line 58

Quoted 10 lines
tools/testing/selftests/bpf/progs/cgroup_iter_memcg.c +10 -0
+	memcg_query.nr_file_mapped = bpf_mem_cgroup_page_state(memcg, idx);
+
+	idx = bpf_core_enum_value(enum memcg_stat_item, MEMCG_KMEM);
+	if (idx == 0)
+		goto out;
+	memcg_query.memcg_kmem = bpf_mem_cgroup_page_state(memcg, idx);
+
+	idx = bpf_core_enum_value(enum vm_event_item, PGFAULT);
+	if (idx == 0)
+		goto out;

This is getting messy. Most of these values should be on any system regardless of boot-params. I was expecting you would make the call inline.

Let's simplify this whole series. This selftest only needs to exercise bpf_mem_cgroup_page_state() and bpf_mem_cgroup_vm_events(). I think we can remove the kmem subtest altogether and still have the coverage we need. You could then call bpf_core_enum_value() inline as an argument to the kfuncs above and not need to check if they exist (just let them give you the host-specific co-re value).

author
#3 lore raw

PATCH v2 2/3 selftests/bpf: Check bpf_mem_cgroup_page_state return value

In reply to Hui Zhu

From: Hui Zhu <[email protected]>

When back-porting test_progs to different kernel versions, I encountered an issue where the test_cgroup_iter_memcg test would falsely pass even when bpf_mem_cgroup_page_state() failed.

This patch adds explicit checks to ensure bpf_mem_cgroup_page_state() doesn't return -1 before validating the actual statistics values.

---
 .../selftests/bpf/prog_tests/cgroup_iter_memcg.c     | 12 ++++++++++++
 1 file changed, 12 insertions(+)
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +12 -0
index a5afd16705f0..897b17b58df3 100644
@@ -53,6 +53,8 @@ static void test_anon(struct bpf_link *link, struct memcg_query *memcg_query)
 	if (!ASSERT_OK(read_stats(link), "read stats"))
 		goto cleanup;
 
+	ASSERT_NEQ(memcg_query->nr_anon_mapped, (unsigned long)-1,
+		  "bpf_mem_cgroup_page_state NR_ANON_MAPPED");
 	ASSERT_GT(memcg_query->nr_anon_mapped, 0, "final anon mapped val");
 
 cleanup:
@@ -88,6 +90,10 @@ static void test_file(struct bpf_link *link, struct memcg_query *memcg_query)
 	if (!ASSERT_OK(read_stats(link), "read stats"))
 		goto cleanup_map;
 
+	ASSERT_NEQ(memcg_query->nr_file_pages, (unsigned long)-1,
+		  "bpf_mem_cgroup_page_state NR_FILE_PAGES");
+	ASSERT_NEQ(memcg_query->nr_file_mapped, (unsigned long)-1,
+		  "bpf_mem_cgroup_page_state NR_FILE_MAPPED");
 	ASSERT_GT(memcg_query->nr_file_pages, 0, "final file value");
 	ASSERT_GT(memcg_query->nr_file_mapped, 0, "final file mapped value");
 
@@ -119,6 +125,8 @@ static void test_shmem(struct bpf_link *link, struct memcg_query *memcg_query)
 	if (!ASSERT_OK(read_stats(link), "read stats"))
 		goto cleanup;
 
+	ASSERT_NEQ(memcg_query->nr_shmem, (unsigned long)-1,
+		  "bpf_mem_cgroup_page_state NR_SHMEM");
 	ASSERT_GT(memcg_query->nr_shmem, 0, "final shmem value");
 
 cleanup:
@@ -143,6 +151,8 @@ static void test_kmem(struct bpf_link *link, struct memcg_query *memcg_query)
 	if (!ASSERT_OK(read_stats(link), "read stats"))
 		goto cleanup;
 
+	ASSERT_NEQ(memcg_query->memcg_kmem, (unsigned long)-1,
+		  "bpf_mem_cgroup_vm_events MEMCG_KMEM");
 	ASSERT_GT(memcg_query->memcg_kmem, 0, "kmem value");
 
 cleanup:
@@ -170,6 +180,8 @@ static void test_pgfault(struct bpf_link *link, struct memcg_query *memcg_query)
 	if (!ASSERT_OK(read_stats(link), "read stats"))
 		goto cleanup;
 
+	ASSERT_NEQ(memcg_query->pgfault, (unsigned long)-1,
+		  "bpf_mem_cgroup_page_state PGFAULT");
 	ASSERT_GT(memcg_query->pgfault, 0, "final pgfault val");
 
 cleanup:
Signature · 1 line
2.43.0
#4 lore raw

Re: [PATCH bpf-next v2 2/3] selftests/bpf: Check bpf_mem_cgroup_page_state return value

Should this assertion string say "bpf_mem_cgroup_page_state MEMCG_KMEM" instead? The BPF program in progs/cgroup_iter_memcg.c populates memcg_kmem using bpf_mem_cgroup_page_state(), not…
Quoted 3 lines
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +0 -0

[ ... ]

tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c static void test_kmem(struct bpf_link *link, struct memcg_query *memcg_query) · line 155

Quoted 6 lines
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +2 -0
@@ -143,6 +151,8 @@ static void test_kmem(struct bpf_link *link, struct memcg_query *memcg_query)
 	if (!ASSERT_OK(read_stats(link), "read stats"))
 		goto cleanup;
 
+	ASSERT_NEQ(memcg_query->memcg_kmem, (unsigned long)-1,
+		  "bpf_mem_cgroup_vm_events MEMCG_KMEM");

Should this assertion string say "bpf_mem_cgroup_page_state MEMCG_KMEM" instead? The BPF program in progs/cgroup_iter_memcg.c populates memcg_kmem using bpf_mem_cgroup_page_state(), not bpf_mem_cgroup_vm_events():

    idx = bpf_core_enum_value(enum memcg_stat_item, MEMCG_KMEM);
    ...
    memcg_query.memcg_kmem = bpf_mem_cgroup_page_state(memcg, idx);

tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c static void test_pgfault(struct bpf_link *link, struct memcg_query *memcg_query) · line 184

Quoted 6 lines
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +2 -0
@@ -170,6 +180,8 @@ static void test_pgfault(struct bpf_link *link, struct memcg_query *memcg_query)
 	if (!ASSERT_OK(read_stats(link), "read stats"))
 		goto cleanup;
 
+	ASSERT_NEQ(memcg_query->pgfault, (unsigned long)-1,
+		  "bpf_mem_cgroup_page_state PGFAULT");

Similarly, should this say "bpf_mem_cgroup_vm_events PGFAULT"? The BPF program retrieves pgfault using bpf_mem_cgroup_vm_events():

    idx = bpf_core_enum_value(enum vm_event_item, PGFAULT);
    ...
    memcg_query.pgfault = bpf_mem_cgroup_vm_events(memcg, idx);

It looks like the two assertion description strings for test_kmem and test_pgfault were swapped.

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/21978734425

AI-authorship-score: low
AI-authorship-explanation: The commit describes a specific back-porting scenario, and the copy-paste error in swapped assertion strings is characteristic of human authorship.
issues-found: 2
issue-severity-score: low
issue-severity-explanation: Two assertion description strings are swapped between test_kmem and test_pgfault, producing misleading diagnostics on test failure but not affecting test correctness or system stability.

author
#5 lore raw

PATCH v2 3/3 bpf: selftests: Skip kmem test when cgroup.memory=nokmem is set

In reply to Hui Zhu

From: Hui Zhu <[email protected]>

When cgroup.memory=nokmem is set in the kernel command line, kmem accounting is disabled. This causes the test_kmem subtest in cgroup_iter_memcg to fail because it expects non-zero kmem values.

Fix this by checking /proc/cmdline for the nokmem parameter. If found, verify that kmem value is zero and return early, skipping the pipe creation test that would otherwise fail.

---
 .../bpf/prog_tests/cgroup_iter_memcg.c        | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +30 -0
index 897b17b58df3..2b9c148cebf0 100644
@@ -134,11 +134,41 @@ static void test_shmem(struct bpf_link *link, struct memcg_query *memcg_query)
 	shm_unlink("/tmp_shmem");
 }
 
+static bool cmdline_has(const char *arg)
+{
+	char cmdline[4096];
+	int fd;
+	ssize_t len;
+	bool ret = false;
+
+	fd = open("/proc/cmdline", O_RDONLY);
+	if (fd < 0)
+		return false;
+
+	len = read(fd, cmdline, sizeof(cmdline) - 1);
+	close(fd);
+	if (len < 0)
+		return false;
+
+	cmdline[len] = '\0';
+	if (strstr(cmdline, arg))
+		ret = true;
+
+	return ret;
+}
+
 #define NR_PIPES 64
 static void test_kmem(struct bpf_link *link, struct memcg_query *memcg_query)
 {
 	int fds[NR_PIPES][2], i;
 
+	if (cmdline_has("cgroup.memory=nokmem")) {
+		if (!ASSERT_OK(read_stats(link), "read stats"))
+			return;
+		ASSERT_EQ(memcg_query->memcg_kmem, 0, "kmem value");
+		return;
+	}
+
 	/*
 	 * Increase kmem value by creating pipes which will allocate some
 	 * kernel buffers.
Signature · 1 line
2.43.0
#6 lore raw

Re: [PATCH bpf-next v2 3/3] bpf: selftests: Skip kmem test when cgroup.memory=nokmem is set

The kernel's cgroup.memory= parameter accepts a comma-separated list of options. The parser in mm/memcontrol.c cgroup_memory() splits the value on commas with strsep():
Quoted 3 lines
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +0 -0

[ ... ]

Quoted 2 lines
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +2 -0
+static bool cmdline_has(const char *arg)
+{

[ ... ]

Quoted 6 lines
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +6 -0
+	cmdline[len] = '\0';
+	if (strstr(cmdline, arg))
+		ret = true;
+
+	return ret;
+}

[ ... ]

tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c static void test_shmem(struct bpf_link *link, struct memcg_query *memcg_query) · line 165

Quoted 1 line
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +1 -0
+	if (cmdline_has("cgroup.memory=nokmem")) {

The kernel's cgroup.memory= parameter accepts a comma-separated list of options. The parser in mm/memcontrol.c cgroup_memory() splits the value on commas with strsep():

mm/memcontrol.c:cgroup_memory() {
    while ((token = strsep(&s, ",")) != NULL) {
        ...
        if (!strcmp(token, "nokmem"))
            cgroup_memory_nokmem = true;
        ...
    }
}

This means valid command lines include configurations like "cgroup.memory=nosocket,nokmem" or "cgroup.memory=nobpf,nokmem".

Since cmdline_has() uses strstr() to match the full literal string "cgroup.memory=nokmem", it only matches when nokmem is the first (or only) option. For "cgroup.memory=nosocket,nokmem", strstr() won't find "cgroup.memory=nokmem" as a substring, so the check returns false even though nokmem is active.

Would it be more robust to search for just "nokmem" within the cgroup.memory= value, or to split on commas similar to how the kernel parses it?

tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c static void test_shmem(struct bpf_link *link, struct memcg_query *memcg_query) · line 170

Quoted 5 lines
tools/testing/selftests/bpf/prog_tests/cgroup_iter_memcg.c +5 -0
+		if (!ASSERT_OK(read_stats(link), "read stats"))
+			return;
+		ASSERT_EQ(memcg_query->memcg_kmem, 0, "kmem value");
+		return;
+	}

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/21978734425

AI-authorship-score: low
AI-authorship-explanation: The patch addresses a specific real-world back-porting issue, shows iterative development across versions in response to human reviewer feedback, and uses standard kernel C patterns with no hallmarks of AI generation.
issues-found: 1
issue-severity-score: low
issue-severity-explanation: The strstr-based cmdline check misses nokmem when it is not the first option in a comma-separated cgroup.memory= value, causing the test to fail on those configurations instead of being skipped.