From: Zhen Ni <zhen.ni@easystack.cn>
To: ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org,
eddyz87@gmail.com, memxor@gmail.com, martin.lau@linux.dev,
song@kernel.org, yonghong.song@linux.dev, jolsa@kernel.org,
emil@etsalapatis.com, ihor.solodrai@linux.dev,
akpm@linux-foundation.org, vbabka@kernel.org, surenb@google.com,
mhocko@suse.com, brendan.jackman@linux.dev, hannes@cmpxchg.org,
ziy@nvidia.com, shuah@kernel.org
Cc: bpf@vger.kernel.org, linux-mm@kvack.org,
linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
Zhen Ni <zhen.ni@easystack.cn>
Subject: [PATCH v2 5/6] mm/page_owner: add bpf_page_owner_get_memcg_info() kfunc
Date: Tue, 13 Oct 2026 20:21:24 +0800 [thread overview]
Message-ID: <20261013122125.843451-6-zhen.ni@easystack.cn> (raw)
In-Reply-To: <20261013122125.843451-1-zhen.ni@easystack.cn>
Expose per-page memcg information to BPF programs, so that a bpf_iter
over page_owner can aggregate and filter allocations by memcg.
Attributing an allocation to a specific cgroup needs its full cgroup
path, not just the memcg name.
Split memcg info collection out of print_page_owner_memcg() into
get_page_memcg_info(), which fills struct memcg_info and, when given
a caller-provided buffer, writes the full cgroup path via
cgroup_path(). print_page_owner_memcg() now consumes the struct;
this introduces no functional change to the debugfs output.
The helper takes a single READ_ONCE snapshot within one RCU read-side
critical section, so the flags and cgroup path reflect a consistent
view of the page. cgroup_path() returns the path relative to the
cgroup hierarchy root, so memcg attribution works on both cgroup v1
and v2.
The kfunc gets the page via pfn_to_online_page(), so an offline or
removed pfn yields -ENODATA instead of a stale deref.
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
Changes in v2:
- bpf_page_owner_get_memcg_info() now resolves the page with
pfn_to_online_page() for an offline or removed pfn.
- Buffer/info args renamed to info__uninit/buf__uninit/buf__sz.
v1: https://lore.kernel.org/linux-mm/20261009112308.240769-6-zhen.ni@easystack.cn/
---
kernel/bpf/helpers.c | 1 +
mm/page_owner.c | 85 ++++++++++++++++++++++++++++++++++----------
mm/page_owner.h | 9 +++++
mm/page_owner_iter.c | 17 +++++++++
4 files changed, 93 insertions(+), 19 deletions(-)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 23a2d2d71f4d..b46822eac894 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4951,6 +4951,7 @@ BTF_ID_FLAGS(func, bpf_iter_page_owner_new, KF_ITER_NEW | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_iter_page_owner_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_iter_page_owner_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_page_owner_stack_snprint, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_page_owner_get_memcg_info, KF_SLEEPABLE)
#endif
BTF_ID_FLAGS(func, __bpf_trap)
BTF_ID_FLAGS(func, bpf_strcmp, KF_PERFMON);
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 113e4e7e0b3a..664121be6686 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -538,16 +538,22 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
#ifdef CONFIG_MEMCG
/*
- * Looking for memcg information and print it out
+ * Collect memcg information of @page into @info; when @buf and a
+ * nonzero @size are given, also copy the full cgroup path of the
+ * charged memcg into @buf.
+ *
+ * Return: 0 if @page is charged to an attributable memcg; -ENODATA
+ * otherwise.
*/
-static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
- struct page *page)
+int get_page_memcg_info(struct page *page, struct memcg_info *info,
+ char *buf, int size)
{
unsigned long memcg_data;
struct obj_cgroup *objcg;
struct mem_cgroup *memcg;
- bool online;
- char name[80];
+ int ret = -ENODATA;
+
+ *info = (struct memcg_info){};
rcu_read_lock();
memcg_data = READ_ONCE(page->memcg_data);
@@ -555,31 +561,68 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
goto out_unlock;
if (memcg_data & MEMCG_DATA_OBJEXTS) {
- ret += scnprintf(kbuf + ret, count - ret,
- "Slab cache page\n");
+ info->is_slab = true;
goto out_unlock;
}
objcg = (void *)(memcg_data & ~OBJEXTS_FLAGS_MASK);
- memcg = objcg ? obj_cgroup_memcg(objcg) : NULL;
+ if (!objcg)
+ goto out_unlock;
+
+ memcg = obj_cgroup_memcg(objcg);
if (!memcg)
goto out_unlock;
- online = css_is_online(&memcg->css);
- cgroup_name(memcg->css.cgroup, name, sizeof(name));
- ret += scnprintf(kbuf + ret, count - ret,
- "Charged %sto %smemcg %s\n",
- (memcg_data & MEMCG_DATA_KMEM) ? "(via objcg) " : "",
- online ? "" : "offline ",
- name);
+ info->is_kmem = (memcg_data & MEMCG_DATA_KMEM) != 0;
+ info->is_online = css_is_online(&memcg->css);
+ cgroup_name(memcg->css.cgroup, info->name, sizeof(info->name));
+
+ if (buf && size)
+ cgroup_path(memcg->css.cgroup, buf, size);
+ ret = 0;
out_unlock:
rcu_read_unlock();
+ if (ret < 0 && buf && size)
+ buf[0] = '\0';
+
+ return ret;
+}
+
+/*
+ * Print memcg information from memcg_info
+ */
+static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
+ const struct memcg_info *info)
+{
+ if (!info)
+ return ret;
+
+ if (info->is_slab)
+ ret += scnprintf(kbuf + ret, count - ret,
+ "Slab cache page\n");
+
+ if (info->name[0])
+ ret += scnprintf(kbuf + ret, count - ret,
+ "Charged %sto %smemcg %s\n",
+ info->is_kmem ? "(via objcg) " : "",
+ info->is_online ? "" : "offline ",
+ info->name);
+
return ret;
}
#else
+int get_page_memcg_info(struct page *page, struct memcg_info *info,
+ char *buf, int size)
+{
+ *info = (struct memcg_info){};
+ if (buf && size)
+ buf[0] = '\0';
+ return -EOPNOTSUPP;
+}
+
static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
- struct page *page)
+ const struct memcg_info *info)
{
return ret;
}
@@ -589,7 +632,8 @@ static ssize_t
print_page_owner(char __user *buf, size_t count, unsigned long pfn,
struct page *page, struct page_owner *page_owner,
depot_stack_handle_t handle,
- struct page_owner_filter_state *state)
+ struct page_owner_filter_state *state,
+ const struct memcg_info *memcg_info)
{
int ret, pageblock_mt, page_mt;
char *kbuf;
@@ -639,7 +683,7 @@ print_page_owner(char __user *buf, size_t count, unsigned long pfn,
migrate_reason_names[page_owner->last_migrate_reason]);
}
- ret = print_page_owner_memcg(kbuf, count, ret, page);
+ ret = print_page_owner_memcg(kbuf, count, ret, memcg_info);
ret += snprintf(kbuf + ret, count - ret, "\n");
if (ret >= count)
@@ -811,6 +855,7 @@ static ssize_t
read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct page_owner_scan scan;
+ struct memcg_info memcg_info;
struct page_owner_filter_state *state = file->private_data;
if (!static_branch_unlikely(&page_owner_inited))
@@ -832,8 +877,10 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
/* Record the next PFN to read in the file offset */
*ppos = scan.pfn + 1;
+ get_page_memcg_info(scan.page, &memcg_info, NULL, 0);
return print_page_owner(buf, count, scan.pfn, scan.page,
- &scan.po_snap, scan.po_snap.handle, state);
+ &scan.po_snap, scan.po_snap.handle, state,
+ &memcg_info);
skip_continue:
scan.pfn++;
cond_resched();
diff --git a/mm/page_owner.h b/mm/page_owner.h
index e8e52d22abed..febf1c2807de 100644
--- a/mm/page_owner.h
+++ b/mm/page_owner.h
@@ -39,7 +39,16 @@ struct page_owner_scan {
int nid;
};
+struct memcg_info {
+ char name[80];
+ bool is_slab;
+ bool is_kmem;
+ bool is_online;
+};
+
bool page_owner_next_eligible(struct page_owner_scan *scan);
+int get_page_memcg_info(struct page *page, struct memcg_info *info,
+ char *buf, int size);
/*
* mm/page_owner_iter.c
diff --git a/mm/page_owner_iter.c b/mm/page_owner_iter.c
index a2b90c874852..86e0b4b52bfc 100644
--- a/mm/page_owner_iter.c
+++ b/mm/page_owner_iter.c
@@ -177,4 +177,21 @@ __bpf_kfunc int bpf_page_owner_stack_snprint(struct page_owner_scan *scan,
0);
}
+__bpf_kfunc int bpf_page_owner_get_memcg_info(struct page_owner_scan *scan,
+ struct memcg_info *info__uninit,
+ char *buf__uninit, u32 buf__sz)
+{
+ /* Guard against the section being offlined after scan. */
+ struct page *page = pfn_to_online_page(scan->pfn);
+
+ if (!page) {
+ *info__uninit = (struct memcg_info){};
+ if (buf__uninit && buf__sz)
+ buf__uninit[0] = '\0';
+ return -ENODATA;
+ }
+
+ return get_page_memcg_info(page, info__uninit, buf__uninit, buf__sz);
+}
+
__bpf_kfunc_end_defs();
--
2.20.1
next prev parent reply other threads:[~2026-10-13 12:27 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-13 12:21 [PATCH v2 0/6] mm/page_owner: add bpf_iter and kfuncs Zhen Ni
2026-10-13 12:21 ` [PATCH v2 1/6] mm/page_owner: extract page_owner_next_eligible() from read loop Zhen Ni
2026-10-13 12:21 ` [PATCH v2 2/6] mm/page_owner: add bpf_iter target "page_owner" Zhen Ni
2026-10-13 13:51 ` bot+bpf-ci
2026-10-13 12:21 ` [PATCH v2 3/6] mm/page_owner: add open-coded page_owner iterator Zhen Ni
2026-10-13 12:49 ` sashiko-bot
2026-10-14 8:01 ` zhen.ni
2026-10-13 13:51 ` bot+bpf-ci
2026-10-13 12:21 ` [PATCH v2 4/6] mm/page_owner: record the NUMA node in the scan cursor Zhen Ni
2026-10-13 12:36 ` sashiko-bot
2026-10-14 7:39 ` zhen.ni
2026-10-13 12:21 ` Zhen Ni [this message]
2026-10-13 12:49 ` [PATCH v2 5/6] mm/page_owner: add bpf_page_owner_get_memcg_info() kfunc sashiko-bot
2026-10-14 7:47 ` zhen.ni
2026-10-13 12:21 ` [PATCH v2 6/6] selftests/bpf: add tests for the page_owner bpf_iter target Zhen Ni
2026-10-13 12:38 ` sashiko-bot
2026-10-14 8:53 ` zhen.ni
2026-10-13 17:17 ` [PATCH v2 0/6] mm/page_owner: add bpf_iter and kfuncs Zi Yan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261013122125.843451-6-zhen.ni@easystack.cn \
--to=zhen.ni@easystack.cn \
--cc=akpm@linux-foundation.org \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=brendan.jackman@linux.dev \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=hannes@cmpxchg.org \
--cc=ihor.solodrai@linux.dev \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=mhocko@suse.com \
--cc=shuah@kernel.org \
--cc=song@kernel.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=yonghong.song@linux.dev \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.