* [PATCH bpf-next v2] selftests/bpf: Add bpf_proactive_reclaim test
@ 2026-09-28 6:37 Hui Zhu
2026-09-28 6:50 ` Barry Song
2026-09-28 7:31 ` bot+bpf-ci
0 siblings, 2 replies; 3+ messages in thread
From: Hui Zhu @ 2026-09-28 6:37 UTC (permalink / raw)
To: Roman Gushchin, JP Kobryn, Shakeel Butt, Andrew Morton,
Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, David Hildenbrand, Barry Song,
Geliang Tang, David S. Miller, Jakub Kicinski,
Jesper Dangaard Brouer, John Fastabend, Stanislav Fomichev,
linux-kernel, bpf, linux-mm, linux-kselftest, netdev
Cc: Hui Zhu
From: Hui Zhu <zhuhui@kylinos.cn>
bpf_proactive_reclaim() performs one bounded reclaim pass per call and,
unlike a write to memory.reclaim, does not retry until the goal is
reached.
Charge 32 MiB of page cache to a cgroup, ask for all of it in one call,
and check that the result is positive but well below the request: one
pass is capped at MEMCG_CHARGE_BATCH pages, far short of 32 MiB on both
4K and 64K page kernels.
The test deliberately covers only the kfunc itself. A full example of
the intended asynchronous use, where a BPF program watches one cgroup's
workingset refaults and reclaims another one from bpf_wq callbacks, is
maintained out of tree at [1], released under GPLv2.
Add CONFIG_MEMCG to the config fragment, without which mm/bpf_memcontrol.c
is not built at all.
[1] https://github.com/teawater/memcg-async-reclaim
Signed-off-by: Hui Zhu <zhuhui@kylinos.cn>
---
Changelog:
v2:
According to the comment of Alexei, remove the samples/bpf sample patch.
Ship it out-of-tree as a standalone project.
tools/testing/selftests/bpf/config | 1 +
.../bpf/prog_tests/memcg_proactive_reclaim.c | 124 ++++++++++++++++++
.../bpf/progs/memcg_proactive_reclaim.c | 34 +++++
3 files changed, 159 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/memcg_proactive_reclaim.c
create mode 100644 tools/testing/selftests/bpf/progs/memcg_proactive_reclaim.c
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index d292cb60a5a4..21bb0d4a37cb 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -57,6 +57,7 @@ CONFIG_LIRC=y
CONFIG_LIVEPATCH=y
CONFIG_LWTUNNEL=y
CONFIG_LWTUNNEL_BPF=y
+CONFIG_MEMCG=y
CONFIG_MODULE_SIG=y
CONFIG_MODULE_SRCVERSION_ALL=y
CONFIG_MODULE_UNLOAD=y
diff --git a/tools/testing/selftests/bpf/prog_tests/memcg_proactive_reclaim.c b/tools/testing/selftests/bpf/prog_tests/memcg_proactive_reclaim.c
new file mode 100644
index 000000000000..d642658a2198
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/memcg_proactive_reclaim.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <limits.h>
+#include <linux/magic.h>
+#include <sys/statfs.h>
+#include <unistd.h>
+
+#include "cgroup_helpers.h"
+#include "memcg_proactive_reclaim.skel.h"
+
+#define CG_PATH "/memcg_proactive_reclaim"
+
+/*
+ * Large enough that a single reclaim pass cannot come close to it, so that
+ * "reclaimed less than was asked for" is not page-size noise: one pass is
+ * capped at MEMCG_CHARGE_BATCH pages, which is 256 KiB on 4K pages but 4 MiB
+ * on 64K pages.
+ */
+#define FILE_SIZE (32 * 1024 * 1024UL)
+#define BUF_SIZE (64 * 1024)
+
+struct reclaim_args {
+ __u64 cgroup_id;
+ __u64 size;
+};
+
+/*
+ * The data file has to sit on a regular filesystem: tmpfs pages are charged
+ * as shmem, so whether they can be reclaimed at all depends on swap being
+ * available. /tmp is tmpfs on many systems, and test_progs is routinely run
+ * from a tmpfs working directory, so both candidates need the check.
+ */
+static const char *workload_dir(void)
+{
+ static const char * const dirs[] = { "/tmp", "." };
+ struct statfs st;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dirs); i++)
+ if (!statfs(dirs[i], &st) && st.f_type != TMPFS_MAGIC &&
+ st.f_type != RAMFS_MAGIC)
+ return dirs[i];
+
+ return NULL;
+}
+
+void test_memcg_proactive_reclaim(void)
+{
+ struct memcg_proactive_reclaim *skel = NULL;
+ struct reclaim_args args = {};
+
+ LIBBPF_OPTS(bpf_test_run_opts, opts,
+ .ctx_in = &args,
+ .ctx_size_in = sizeof(args));
+
+ char data_file[PATH_MAX];
+ static char buf[BUF_SIZE];
+ __u64 cgroup_id;
+ const char *dir;
+ off_t off;
+ int cg_fd = -1, data_fd = -1, err;
+
+ dir = workload_dir();
+ if (!ASSERT_OK_PTR(dir, "workload dir on a regular filesystem"))
+ return;
+
+ snprintf(data_file, sizeof(data_file),
+ "%s/memcg_proactive_reclaim_XXXXXX", dir);
+ data_fd = mkstemp(data_file);
+ if (!ASSERT_GE(data_fd, 0, "mkstemp"))
+ return;
+
+ cg_fd = cgroup_setup_and_join(CG_PATH);
+ if (!ASSERT_OK_FD(cg_fd, "cgroup_setup_and_join"))
+ goto out;
+
+ cgroup_id = get_cgroup_id(CG_PATH);
+ if (!ASSERT_GT(cgroup_id, 0, "get_cgroup_id"))
+ goto out;
+
+ skel = memcg_proactive_reclaim__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ goto out;
+
+ /*
+ * Charge FILE_SIZE of page cache to the cgroup. Reading rather than
+ * writing keeps the pages clean, so reclaim does not have to start
+ * writeback before it can evict them.
+ */
+ if (!ASSERT_OK(ftruncate(data_fd, FILE_SIZE), "ftruncate"))
+ goto out;
+ for (off = 0; off < (off_t)FILE_SIZE; off += sizeof(buf))
+ if (!ASSERT_GT(read(data_fd, buf, sizeof(buf)), 0, "read"))
+ goto out;
+
+ args.cgroup_id = cgroup_id;
+ args.size = FILE_SIZE;
+ skel->bss->reclaimed = 0;
+ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.memcg_proactive_reclaim),
+ &opts);
+ if (!ASSERT_OK(err, "test_run"))
+ goto out;
+ if (!ASSERT_EQ(opts.retval, 0, "retval"))
+ goto out;
+
+ /*
+ * A single call is a single bounded pass: it reclaims something, but
+ * stops well short of the requested size instead of retrying until the
+ * goal is reached the way a write to memory.reclaim does.
+ */
+ ASSERT_GT(skel->bss->reclaimed, 0, "reclaimed");
+ ASSERT_LT(skel->bss->reclaimed, (__s64)FILE_SIZE, "single pass");
+
+out:
+ if (skel)
+ memcg_proactive_reclaim__destroy(skel);
+ if (cg_fd >= 0)
+ close(cg_fd);
+ if (data_fd >= 0) {
+ close(data_fd);
+ unlink(data_file);
+ }
+ cleanup_cgroup_environment();
+}
diff --git a/tools/testing/selftests/bpf/progs/memcg_proactive_reclaim.c b/tools/testing/selftests/bpf/progs/memcg_proactive_reclaim.c
new file mode 100644
index 000000000000..b551192526fc
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/memcg_proactive_reclaim.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+struct reclaim_args {
+ __u64 cgroup_id;
+ __u64 size;
+};
+
+/* Signed, because bpf_test_run_opts.retval is a __u32. */
+__s64 reclaimed;
+
+SEC("syscall")
+int memcg_proactive_reclaim(struct reclaim_args *ctx)
+{
+ struct mem_cgroup *memcg;
+ struct cgroup *cgrp;
+
+ cgrp = bpf_cgroup_from_id(ctx->cgroup_id);
+ if (!cgrp)
+ return 0;
+
+ memcg = bpf_get_mem_cgroup(&cgrp->self);
+ if (memcg) {
+ reclaimed = bpf_proactive_reclaim(memcg, ctx->size, -1);
+ bpf_put_mem_cgroup(memcg);
+ }
+ bpf_cgroup_release(cgrp);
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH bpf-next v2] selftests/bpf: Add bpf_proactive_reclaim test
2026-09-28 6:37 [PATCH bpf-next v2] selftests/bpf: Add bpf_proactive_reclaim test Hui Zhu
@ 2026-09-28 6:50 ` Barry Song
2026-09-28 7:31 ` bot+bpf-ci
1 sibling, 0 replies; 3+ messages in thread
From: Barry Song @ 2026-09-28 6:50 UTC (permalink / raw)
To: Hui Zhu
Cc: Roman Gushchin, JP Kobryn, Shakeel Butt, Andrew Morton,
Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, David Hildenbrand, Geliang Tang,
David S. Miller, Jakub Kicinski, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf, linux-mm,
linux-kselftest, netdev, Hui Zhu
On Mon, Sep 28, 2026 at 2:37 PM Hui Zhu <hui.zhu@linux.dev> wrote:
>
> From: Hui Zhu <zhuhui@kylinos.cn>
>
> bpf_proactive_reclaim() performs one bounded reclaim pass per call and,
> unlike a write to memory.reclaim, does not retry until the goal is
> reached.
>
> Charge 32 MiB of page cache to a cgroup, ask for all of it in one call,
> and check that the result is positive but well below the request: one
> pass is capped at MEMCG_CHARGE_BATCH pages, far short of 32 MiB on both
> 4K and 64K page kernels.
>
> The test deliberately covers only the kfunc itself. A full example of
> the intended asynchronous use, where a BPF program watches one cgroup's
> workingset refaults and reclaims another one from bpf_wq callbacks, is
> maintained out of tree at [1], released under GPLv2.
>
> Add CONFIG_MEMCG to the config fragment, without which mm/bpf_memcontrol.c
> is not built at all.
>
> [1] https://github.com/teawater/memcg-async-reclaim
>
> Signed-off-by: Hui Zhu <zhuhui@kylinos.cn>
> ---
[...]
> +
> + /*
> + * Charge FILE_SIZE of page cache to the cgroup. Reading rather than
> + * writing keeps the pages clean, so reclaim does not have to start
> + * writeback before it can evict them.
> + */
> + if (!ASSERT_OK(ftruncate(data_fd, FILE_SIZE), "ftruncate"))
> + goto out;
> + for (off = 0; off < (off_t)FILE_SIZE; off += sizeof(buf))
> + if (!ASSERT_GT(read(data_fd, buf, sizeof(buf)), 0, "read"))
> + goto out;
> +
In case we might map file holes to the zero PFN someday (not sure when),
this wouldn't necessarily give us page cache. So maybe write + fsync is
more future-proof? In that case, you could also drop the two lines of
comments.
Best Regards
Barry
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH bpf-next v2] selftests/bpf: Add bpf_proactive_reclaim test
2026-09-28 6:37 [PATCH bpf-next v2] selftests/bpf: Add bpf_proactive_reclaim test Hui Zhu
2026-09-28 6:50 ` Barry Song
@ 2026-09-28 7:31 ` bot+bpf-ci
1 sibling, 0 replies; 3+ messages in thread
From: bot+bpf-ci @ 2026-09-28 7:31 UTC (permalink / raw)
To: hui.zhu, roman.gushchin, inwardvessel, shakeel.butt, akpm,
andrii, eddyz87, ihor.solodrai, ast, daniel, memxor, martin.lau,
song, yonghong.song, jolsa, emil, shuah, david, baohua, geliang,
davem, kuba, hawk, john.fastabend, sdf, linux-kernel, bpf,
linux-mm, linux-kselftest, netdev
Cc: zhuhui, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song,
mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2636 bytes --]
> diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
> index 2b883b388f90..5621ef94ad7e 100644
> --- a/tools/testing/selftests/bpf/config
> +++ b/tools/testing/selftests/bpf/config
> @@ -57,6 +57,7 @@ CONFIG_LIRC=y
> CONFIG_LIVEPATCH=y
> CONFIG_LWTUNNEL=y
> CONFIG_LWTUNNEL_BPF=y
> +CONFIG_MEMCG=y
> CONFIG_MODULE_SIG=y
> CONFIG_MODULE_SRCVERSION_ALL=y
> CONFIG_MODULE_UNLOAD=y
[ ... ]
> diff --git a/tools/testing/selftests/bpf/prog_tests/memcg_proactive_reclaim.c b/tools/testing/selftests/bpf/prog_tests/memcg_proactive_reclaim.c
> new file mode 100644
> index 000000000000..d642658a2198
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/memcg_proactive_reclaim.c
[ ... ]
> + /*
> + * Charge FILE_SIZE of page cache to the cgroup. Reading rather than
> + * writing keeps the pages clean, so reclaim does not have to start
> + * writeback before it can evict them.
> + */
> + if (!ASSERT_OK(ftruncate(data_fd, FILE_SIZE), "ftruncate"))
> + goto out;
> + for (off = 0; off < (off_t)FILE_SIZE; off += sizeof(buf))
> + if (!ASSERT_GT(read(data_fd, buf, sizeof(buf)), 0, "read"))
> + goto out;
Could this allocation method cause issues if file holes are mapped to the
zero PFN in the future? The read() approach assumes that accessing a
ftruncate()-created sparse file will allocate page cache, but if the kernel
someday maps file holes to a shared zero page instead of allocating backing
pages, this wouldn't necessarily give us page cache to reclaim.
Barry Song suggested using write() followed by fsync() as a more future-proof
way to ensure actual page cache allocation, which would also allow removing
the two lines of explanatory comments above.
> +
> + args.cgroup_id = cgroup_id;
> + args.size = FILE_SIZE;
> + skel->bss->reclaimed = 0;
> + err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.memcg_proactive_reclaim),
> + &opts);
> + if (!ASSERT_OK(err, "test_run"))
> + goto out;
> + if (!ASSERT_EQ(opts.retval, 0, "retval"))
> + goto out;
> +
> + /*
> + * A single call is a single bounded pass: it reclaims something, but
> + * stops well short of the requested size instead of retrying until the
> + * goal is reached the way a write to memory.reclaim does.
> + */
> + ASSERT_GT(skel->bss->reclaimed, 0, "reclaimed");
> + ASSERT_LT(skel->bss->reclaimed, (__s64)FILE_SIZE, "single pass");
[ ... ]
---
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/36389535910
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-28 7:31 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-28 6:37 [PATCH bpf-next v2] selftests/bpf: Add bpf_proactive_reclaim test Hui Zhu
2026-09-28 6:50 ` Barry Song
2026-09-28 7:31 ` bot+bpf-ci
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®