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 5/6] mm/page_owner: add bpf_page_owner_get_memcg_info() kfunc
Date: Fri, 9 Oct 2026 19:23:07 +0800 [thread overview]
Message-ID: <20261009112308.240769-6-zhen.ni@easystack.cn> (raw)
In-Reply-To: <20261009112308.240769-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.
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
kernel/bpf/helpers.c | 1 +
mm/page_owner.c | 85 ++++++++++++++++++++++++++++++++++----------
mm/page_owner.h | 9 +++++
mm/page_owner_iter.c | 7 ++++
4 files changed, 83 insertions(+), 19 deletions(-)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 3e8afd19a1f4..b07a4dc46b58 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4952,6 +4952,7 @@ BTF_ID_FLAGS(func, bpf_iter_page_owner_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLE
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_nid, 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 43a722678acc..ad503422b0c4 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)
@@ -810,6 +854,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 c57859d18dc3..e74fc664d9b1 100644
--- a/mm/page_owner.h
+++ b/mm/page_owner.h
@@ -38,8 +38,17 @@ struct page_owner_scan {
struct page_owner po_snap;
};
+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 page_owner_get_nid(struct page *page);
+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 907517a29144..e7a8ca1e90e8 100644
--- a/mm/page_owner_iter.c
+++ b/mm/page_owner_iter.c
@@ -170,4 +170,11 @@ __bpf_kfunc int bpf_page_owner_get_nid(struct page_owner_scan *scan)
return page_owner_get_nid(scan->page);
}
+__bpf_kfunc int bpf_page_owner_get_memcg_info(struct page_owner_scan *scan,
+ struct memcg_info *info,
+ char *buf, u32 size)
+{
+ return get_page_memcg_info(scan->page, info, buf, size);
+}
+
__bpf_kfunc_end_defs();
--
2.20.1
next prev parent reply other threads:[~2026-10-09 13:45 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-09 11:23 [PATCH 0/6] mm/page_owner: add bpf_iter and kfuncs Zhen Ni
2026-10-09 11:23 ` [PATCH 2/6] mm/page_owner: add bpf_iter target "page_owner" Zhen Ni
2026-10-09 11:23 ` [PATCH 3/6] mm/page_owner: add open-coded page_owner iterator Zhen Ni
2026-10-09 11:23 ` [PATCH 4/6] mm/page_owner: add bpf_page_owner_get_nid() kfunc Zhen Ni
2026-10-09 11:23 ` Zhen Ni [this message]
2026-10-09 11:23 ` [PATCH 6/6] selftests/bpf: add tests for the page_owner bpf_iter target Zhen Ni
2026-10-09 13:48 ` bot+bpf-ci
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=20261009112308.240769-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 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®