* [PATCH 1/8] mm/page_owner: Add PID filtering support
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
@ 2026-08-28 3:13 ` Zhen Ni
2026-08-28 3:13 ` [PATCH 2/8] mm/page_owner: Add TGID " Zhen Ni
` (7 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Zhen Ni @ 2026-08-28 3:13 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel,
Zhen Ni
Add PID filtering support. Users can filter page_owner output by process
IDs using the "pid=<pid_list>" command format. The filter supports up to
16 PIDs specified as a comma-separated list. PIDs are stored in sorted
order for efficient binary search matching during page owner iteration.
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
mm/page_owner.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 83 insertions(+), 2 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index fbbda7ba914b..33b9e12a019b 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -12,6 +12,8 @@
#include <linux/seq_file.h>
#include <linux/memcontrol.h>
#include <linux/sched/clock.h>
+#include <linux/bsearch.h>
+#include <linux/sort.h>
#include "page_alloc.h"
@@ -66,12 +68,24 @@ static const char * const page_owner_print_mode_strings[] = {
[PAGE_OWNER_PRINT_STACK_HANDLE] = "stack_handle",
};
+/* PID_MAX_LIMIT = 4,194,304 (7 decimal digits) */
+#define PID_MAX_DIGITS 7
+#define MAX_FILTER_PIDS 16
+
struct page_owner_filter_state {
enum page_owner_print_mode print_mode;
- nodemask_t nid_filter;
bool nid_filter_enabled;
+ bool proc_filter_enabled;
+ nodemask_t nid_filter;
+ int pid_count;
+ pid_t pid_list[MAX_FILTER_PIDS];
};
+static int cmp_pid_t(const void *a, const void *b)
+{
+ return *(pid_t *)a - *(pid_t *)b;
+}
+
static bool page_owner_enabled __initdata;
DEFINE_STATIC_KEY_FALSE(page_owner_inited);
@@ -820,6 +834,19 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
goto ext_put_continue;
}
+ if (state->proc_filter_enabled) {
+ bool proc_match = false;
+
+ proc_match = bsearch(&page_owner->pid,
+ state->pid_list,
+ state->pid_count,
+ sizeof(pid_t),
+ cmp_pid_t) != NULL;
+
+ if (!proc_match)
+ goto ext_put_continue;
+ }
+
/* Record the next PFN to read in the file offset */
*ppos = pfn + 1;
@@ -927,6 +954,7 @@ static int page_owner_open(struct inode *inode, struct file *file)
state->print_mode = PAGE_OWNER_PRINT_STACK;
nodes_clear(state->nid_filter);
state->nid_filter_enabled = false;
+ state->proc_filter_enabled = false;
file->private_data = state;
return 0;
}
@@ -937,6 +965,37 @@ static int page_owner_release(struct inode *inode, struct file *file)
return 0;
}
+static int parse_pid_t_list(const char *str, pid_t *list, int *count)
+{
+ char *str_copy, *token;
+ int i = 0;
+ unsigned int pid;
+ int ret = 0;
+
+ str_copy = kstrdup(str, GFP_KERNEL);
+ if (!str_copy)
+ return -ENOMEM;
+
+ while ((token = strsep(&str_copy, ",")) != NULL) {
+ if (*token == '\0')
+ continue;
+ if (i >= MAX_FILTER_PIDS) {
+ ret = -E2BIG;
+ goto out_free;
+ }
+ if (kstrtouint(token, 10, &pid) != 0) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+ list[i++] = (pid_t)pid;
+ }
+
+ *count = i;
+out_free:
+ kfree(str_copy);
+ return ret;
+}
+
static ssize_t page_owner_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
@@ -949,6 +1008,9 @@ static ssize_t page_owner_write(struct file *file,
enum page_owner_print_mode new_print_mode;
nodemask_t new_nid_filter;
bool new_nid_filter_enabled;
+ bool new_proc_filter_enabled;
+ pid_t new_pid_list[MAX_FILTER_PIDS];
+ int new_pid_count = 0;
/*
* Maximum input length for filter commands:
@@ -956,8 +1018,10 @@ static ssize_t page_owner_write(struct file *file,
* with sufficient buffer
* - 6 * MAX_NUMNODES: worst case for nid list
* Worst case per node: ",NNNNN" (comma + 5-digit node number) = 6 bytes
+ * - For list filters: (digit+comma) * count + prefix
*/
- if (count > 32 + 6 * MAX_NUMNODES)
+ if (count > 32 + 6 * MAX_NUMNODES +
+ (PID_MAX_DIGITS + 1) * MAX_FILTER_PIDS + 4)
return -EINVAL;
kbuf = memdup_user_nul(buf, count);
@@ -969,6 +1033,11 @@ static ssize_t page_owner_write(struct file *file,
new_print_mode = state->print_mode;
new_nid_filter = state->nid_filter;
new_nid_filter_enabled = state->nid_filter_enabled;
+ new_proc_filter_enabled = state->proc_filter_enabled;
+ if (state->pid_count > 0) {
+ memcpy(new_pid_list, state->pid_list, sizeof(state->pid_list));
+ new_pid_count = state->pid_count;
+ }
while ((token = strsep(&kbuf, " \t\n")) != NULL) {
if (*token == '\0')
@@ -1000,6 +1069,10 @@ static ssize_t page_owner_write(struct file *file,
}
new_nid_filter_enabled = true;
+ } else if (!strncmp(token, "pid=", 4)) {
+ ret = parse_pid_t_list(token + 4, new_pid_list, &new_pid_count);
+ if (ret < 0)
+ goto out_free;
} else {
ret = -EINVAL;
goto out_free;
@@ -1010,6 +1083,14 @@ static ssize_t page_owner_write(struct file *file,
state->print_mode = new_print_mode;
state->nid_filter = new_nid_filter;
state->nid_filter_enabled = new_nid_filter_enabled;
+ state->proc_filter_enabled = new_pid_count > 0;
+ if (new_pid_count > 0) {
+ memcpy(state->pid_list, new_pid_list, sizeof(state->pid_list));
+ state->pid_count = new_pid_count;
+ }
+ if (state->pid_count > 1)
+ sort(state->pid_list, state->pid_count, sizeof(pid_t),
+ cmp_pid_t, NULL);
ret = count;
--
2.20.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 2/8] mm/page_owner: Add TGID filtering support
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
2026-08-28 3:13 ` [PATCH 1/8] mm/page_owner: Add PID filtering support Zhen Ni
@ 2026-08-28 3:13 ` Zhen Ni
2026-08-28 3:13 ` [PATCH 3/8] mm/page_owner: Add COMM filtering with wildcard support Zhen Ni
` (6 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Zhen Ni @ 2026-08-28 3:13 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel,
Zhen Ni
Extend filter to support thread group ID (TGID) filtering alongside
PID filtering. Reuses existing PID parsing and binary search
infrastructure with separate TGID list and count fields.
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
mm/page_owner.c | 43 ++++++++++++++++++++++++++++++++++++-------
1 file changed, 36 insertions(+), 7 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 33b9e12a019b..077867e9e572 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -71,6 +71,7 @@ static const char * const page_owner_print_mode_strings[] = {
/* PID_MAX_LIMIT = 4,194,304 (7 decimal digits) */
#define PID_MAX_DIGITS 7
#define MAX_FILTER_PIDS 16
+#define MAX_FILTER_TGIDS 16
struct page_owner_filter_state {
enum page_owner_print_mode print_mode;
@@ -78,7 +79,9 @@ struct page_owner_filter_state {
bool proc_filter_enabled;
nodemask_t nid_filter;
int pid_count;
+ int tgid_count;
pid_t pid_list[MAX_FILTER_PIDS];
+ pid_t tgid_list[MAX_FILTER_TGIDS];
};
static int cmp_pid_t(const void *a, const void *b)
@@ -837,11 +840,19 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
if (state->proc_filter_enabled) {
bool proc_match = false;
- proc_match = bsearch(&page_owner->pid,
- state->pid_list,
- state->pid_count,
- sizeof(pid_t),
- cmp_pid_t) != NULL;
+ if (state->pid_count > 0)
+ proc_match = bsearch(&page_owner->pid,
+ state->pid_list,
+ state->pid_count,
+ sizeof(pid_t),
+ cmp_pid_t) != NULL;
+
+ if (!proc_match && state->tgid_count > 0)
+ proc_match = bsearch(&page_owner->tgid,
+ state->tgid_list,
+ state->tgid_count,
+ sizeof(pid_t),
+ cmp_pid_t) != NULL;
if (!proc_match)
goto ext_put_continue;
@@ -1010,7 +1021,9 @@ static ssize_t page_owner_write(struct file *file,
bool new_nid_filter_enabled;
bool new_proc_filter_enabled;
pid_t new_pid_list[MAX_FILTER_PIDS];
+ pid_t new_tgid_list[MAX_FILTER_TGIDS];
int new_pid_count = 0;
+ int new_tgid_count = 0;
/*
* Maximum input length for filter commands:
@@ -1021,7 +1034,8 @@ static ssize_t page_owner_write(struct file *file,
* - For list filters: (digit+comma) * count + prefix
*/
if (count > 32 + 6 * MAX_NUMNODES +
- (PID_MAX_DIGITS + 1) * MAX_FILTER_PIDS + 4)
+ (PID_MAX_DIGITS + 1) * MAX_FILTER_PIDS + 4 +
+ (PID_MAX_DIGITS + 1) * MAX_FILTER_TGIDS + 5)
return -EINVAL;
kbuf = memdup_user_nul(buf, count);
@@ -1038,6 +1052,10 @@ static ssize_t page_owner_write(struct file *file,
memcpy(new_pid_list, state->pid_list, sizeof(state->pid_list));
new_pid_count = state->pid_count;
}
+ if (state->tgid_count > 0) {
+ memcpy(new_tgid_list, state->tgid_list, sizeof(state->tgid_list));
+ new_tgid_count = state->tgid_count;
+ }
while ((token = strsep(&kbuf, " \t\n")) != NULL) {
if (*token == '\0')
@@ -1073,6 +1091,10 @@ static ssize_t page_owner_write(struct file *file,
ret = parse_pid_t_list(token + 4, new_pid_list, &new_pid_count);
if (ret < 0)
goto out_free;
+ } else if (!strncmp(token, "tgid=", 5)) {
+ ret = parse_pid_t_list(token + 5, new_tgid_list, &new_tgid_count);
+ if (ret < 0)
+ goto out_free;
} else {
ret = -EINVAL;
goto out_free;
@@ -1083,7 +1105,7 @@ static ssize_t page_owner_write(struct file *file,
state->print_mode = new_print_mode;
state->nid_filter = new_nid_filter;
state->nid_filter_enabled = new_nid_filter_enabled;
- state->proc_filter_enabled = new_pid_count > 0;
+ state->proc_filter_enabled = new_pid_count > 0 || new_tgid_count > 0;
if (new_pid_count > 0) {
memcpy(state->pid_list, new_pid_list, sizeof(state->pid_list));
state->pid_count = new_pid_count;
@@ -1091,6 +1113,13 @@ static ssize_t page_owner_write(struct file *file,
if (state->pid_count > 1)
sort(state->pid_list, state->pid_count, sizeof(pid_t),
cmp_pid_t, NULL);
+ if (new_tgid_count > 0) {
+ memcpy(state->tgid_list, new_tgid_list, sizeof(state->tgid_list));
+ state->tgid_count = new_tgid_count;
+ }
+ if (state->tgid_count > 1)
+ sort(state->tgid_list, state->tgid_count, sizeof(pid_t),
+ cmp_pid_t, NULL);
ret = count;
--
2.20.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 3/8] mm/page_owner: Add COMM filtering with wildcard support
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
2026-08-28 3:13 ` [PATCH 1/8] mm/page_owner: Add PID filtering support Zhen Ni
2026-08-28 3:13 ` [PATCH 2/8] mm/page_owner: Add TGID " Zhen Ni
@ 2026-08-28 3:13 ` Zhen Ni
2026-08-28 3:13 ` [PATCH 4/8] mm/page_owner: Refactor memcg handling for cgroup filter support Zhen Ni
` (5 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Zhen Ni @ 2026-08-28 3:13 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel,
Zhen Ni
Add process name (COMM) filtering to page_owner with glob-style wildcard
pattern matching support. Users can now filter page_owner output by
process names using flexible patterns.
Supported wildcards:
* : matches any sequence of characters
? : matches any single character
[abc]: matches any character in the set
[a-z]: matches any character in the range
Examples:
comm=python* : matches python, python3, python3.9, etc.
comm=*sh : matches bash, zsh, dash, etc.
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
mm/page_owner.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 79 insertions(+), 3 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 077867e9e572..7cd533682054 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -14,6 +14,7 @@
#include <linux/sched/clock.h>
#include <linux/bsearch.h>
#include <linux/sort.h>
+#include <linux/glob.h>
#include "page_alloc.h"
@@ -72,6 +73,7 @@ static const char * const page_owner_print_mode_strings[] = {
#define PID_MAX_DIGITS 7
#define MAX_FILTER_PIDS 16
#define MAX_FILTER_TGIDS 16
+#define MAX_FILTER_COMMS 8
struct page_owner_filter_state {
enum page_owner_print_mode print_mode;
@@ -80,8 +82,10 @@ struct page_owner_filter_state {
nodemask_t nid_filter;
int pid_count;
int tgid_count;
+ int comm_count;
pid_t pid_list[MAX_FILTER_PIDS];
pid_t tgid_list[MAX_FILTER_TGIDS];
+ char comm_list[MAX_FILTER_COMMS][TASK_COMM_LEN];
};
static int cmp_pid_t(const void *a, const void *b)
@@ -854,6 +858,20 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
sizeof(pid_t),
cmp_pid_t) != NULL;
+ if (!proc_match && state->comm_count > 0) {
+ bool comm_match = false;
+ int i;
+
+ for (i = 0; i < state->comm_count; i++) {
+ if (glob_match(state->comm_list[i],
+ page_owner->comm)) {
+ comm_match = true;
+ break;
+ }
+ }
+ proc_match = comm_match;
+ }
+
if (!proc_match)
goto ext_put_continue;
}
@@ -1007,6 +1025,38 @@ static int parse_pid_t_list(const char *str, pid_t *list, int *count)
return ret;
}
+static int parse_comm_list(const char *str, char (*list)[TASK_COMM_LEN], int *count)
+{
+ char *str_copy, *token;
+ int i = 0;
+ int ret = 0;
+
+ str_copy = kstrdup(str, GFP_KERNEL);
+ if (!str_copy)
+ return -ENOMEM;
+
+ while ((token = strsep(&str_copy, ",")) != NULL) {
+ token = strstrip(token);
+ if (*token == '\0')
+ continue;
+ if (i >= MAX_FILTER_COMMS) {
+ ret = -E2BIG;
+ goto out_free;
+ }
+ strscpy(list[i], token, TASK_COMM_LEN);
+ i++;
+ }
+
+ *count = i;
+
+ if (i == 0)
+ ret = -EINVAL;
+
+out_free:
+ kfree(str_copy);
+ return ret;
+}
+
static ssize_t page_owner_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
@@ -1022,8 +1072,10 @@ static ssize_t page_owner_write(struct file *file,
bool new_proc_filter_enabled;
pid_t new_pid_list[MAX_FILTER_PIDS];
pid_t new_tgid_list[MAX_FILTER_TGIDS];
+ char (*new_comm_list)[TASK_COMM_LEN] = NULL;
int new_pid_count = 0;
int new_tgid_count = 0;
+ int new_comm_count = 0;
/*
* Maximum input length for filter commands:
@@ -1035,12 +1087,19 @@ static ssize_t page_owner_write(struct file *file,
*/
if (count > 32 + 6 * MAX_NUMNODES +
(PID_MAX_DIGITS + 1) * MAX_FILTER_PIDS + 4 +
- (PID_MAX_DIGITS + 1) * MAX_FILTER_TGIDS + 5)
+ (PID_MAX_DIGITS + 1) * MAX_FILTER_TGIDS + 5 +
+ TASK_COMM_LEN * MAX_FILTER_COMMS + 5)
return -EINVAL;
+ new_comm_list = kmalloc_array(MAX_FILTER_COMMS, TASK_COMM_LEN, GFP_KERNEL);
+ if (!new_comm_list)
+ return -ENOMEM;
+
kbuf = memdup_user_nul(buf, count);
- if (IS_ERR(kbuf))
+ if (IS_ERR(kbuf)) {
+ kfree(new_comm_list);
return PTR_ERR(kbuf);
+ }
orig = kbuf;
@@ -1056,6 +1115,11 @@ static ssize_t page_owner_write(struct file *file,
memcpy(new_tgid_list, state->tgid_list, sizeof(state->tgid_list));
new_tgid_count = state->tgid_count;
}
+ if (state->comm_count > 0) {
+ memcpy(new_comm_list, state->comm_list,
+ state->comm_count * TASK_COMM_LEN);
+ new_comm_count = state->comm_count;
+ }
while ((token = strsep(&kbuf, " \t\n")) != NULL) {
if (*token == '\0')
@@ -1095,6 +1159,10 @@ static ssize_t page_owner_write(struct file *file,
ret = parse_pid_t_list(token + 5, new_tgid_list, &new_tgid_count);
if (ret < 0)
goto out_free;
+ } else if (!strncmp(token, "comm=", 5)) {
+ ret = parse_comm_list(token + 5, new_comm_list, &new_comm_count);
+ if (ret < 0)
+ goto out_free;
} else {
ret = -EINVAL;
goto out_free;
@@ -1105,7 +1173,9 @@ static ssize_t page_owner_write(struct file *file,
state->print_mode = new_print_mode;
state->nid_filter = new_nid_filter;
state->nid_filter_enabled = new_nid_filter_enabled;
- state->proc_filter_enabled = new_pid_count > 0 || new_tgid_count > 0;
+ state->proc_filter_enabled = new_pid_count > 0 ||
+ new_tgid_count > 0 ||
+ new_comm_count > 0;
if (new_pid_count > 0) {
memcpy(state->pid_list, new_pid_list, sizeof(state->pid_list));
state->pid_count = new_pid_count;
@@ -1120,10 +1190,16 @@ static ssize_t page_owner_write(struct file *file,
if (state->tgid_count > 1)
sort(state->tgid_list, state->tgid_count, sizeof(pid_t),
cmp_pid_t, NULL);
+ if (new_comm_count > 0) {
+ memcpy(state->comm_list, new_comm_list,
+ new_comm_count * TASK_COMM_LEN);
+ state->comm_count = new_comm_count;
+ }
ret = count;
out_free:
+ kfree(new_comm_list);
kfree(orig);
return ret;
}
--
2.20.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 4/8] mm/page_owner: Refactor memcg handling for cgroup filter support
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
` (2 preceding siblings ...)
2026-08-28 3:13 ` [PATCH 3/8] mm/page_owner: Add COMM filtering with wildcard support Zhen Ni
@ 2026-08-28 3:13 ` Zhen Ni
2026-08-28 3:13 ` [PATCH 5/8] mm/page_owner: Add memcg " Zhen Ni
` (4 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Zhen Ni @ 2026-08-28 3:13 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel,
Zhen Ni
Extract memcg information retrieval from printing logic to prepare for
cgroup filtering support. Introduce struct memcg_info to hold cgroup
data that can be reused for both display output and filtering
decisions.
No functional change - output behavior unchanged.
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
mm/page_owner.c | 62 +++++++++++++++++++++++++++++++++++--------------
1 file changed, 44 insertions(+), 18 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 7cd533682054..09cfad10a7df 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -69,6 +69,13 @@ static const char * const page_owner_print_mode_strings[] = {
[PAGE_OWNER_PRINT_STACK_HANDLE] = "stack_handle",
};
+struct memcg_info {
+ char name[80];
+ bool is_slab;
+ bool is_objcg;
+ bool is_online;
+};
+
/* PID_MAX_LIMIT = 4,194,304 (7 decimal digits) */
#define PID_MAX_DIGITS 7
#define MAX_FILTER_PIDS 16
@@ -573,16 +580,13 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
#ifdef CONFIG_MEMCG
/*
- * Looking for memcg information and print it out
+ * Get memcg information from page
*/
-static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
- struct page *page)
+static void get_page_memcg_info(struct page *page, struct memcg_info *info)
{
unsigned long memcg_data;
struct obj_cgroup *objcg;
struct mem_cgroup *memcg;
- bool online;
- char name[80];
rcu_read_lock();
memcg_data = READ_ONCE(page->memcg_data);
@@ -590,8 +594,7 @@ 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;
}
@@ -600,21 +603,38 @@ static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
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_objcg = (memcg_data & MEMCG_DATA_KMEM) != 0;
+ info->is_online = css_is_online(&memcg->css);
+ cgroup_name(memcg->css.cgroup, info->name, sizeof(info->name));
out_unlock:
rcu_read_unlock();
+}
+
+/*
+ * 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_objcg ? "(via objcg) " : "",
+ info->is_online ? "" : "offline ",
+ info->name);
return ret;
}
#else
static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret,
- struct page *page)
+ const struct memcg_info *info)
{
return ret;
}
@@ -624,7 +644,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;
@@ -674,7 +695,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)
@@ -777,6 +798,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
* user through copy_to_user() or GFP_KERNEL allocations.
*/
struct page_owner page_owner_tmp;
+ struct memcg_info memcg_info = {};
/*
* If the new page is in a new MAX_ORDER_NR_PAGES area,
@@ -876,13 +898,17 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
goto ext_put_continue;
}
+#ifdef CONFIG_MEMCG
+ get_page_memcg_info(page, &memcg_info);
+#endif
+
/* Record the next PFN to read in the file offset */
*ppos = pfn + 1;
page_owner_tmp = *page_owner;
page_ext_put(page_ext);
return print_page_owner(buf, count, pfn, page,
- &page_owner_tmp, handle, state);
+ &page_owner_tmp, handle, state, &memcg_info);
ext_put_continue:
page_ext_put(page_ext);
cond_resched();
--
2.20.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 5/8] mm/page_owner: Add memcg filter support
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
` (3 preceding siblings ...)
2026-08-28 3:13 ` [PATCH 4/8] mm/page_owner: Refactor memcg handling for cgroup filter support Zhen Ni
@ 2026-08-28 3:13 ` Zhen Ni
2026-08-28 3:13 ` [PATCH 6/8] tools/mm: Add PID/TGID/COMM filtering support to page_owner_filter Zhen Ni
` (3 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Zhen Ni @ 2026-08-28 3:13 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel,
Zhen Ni
Add memory cgroup filtering to page_owner to allow filtering pages by
their memcg path. This helps debug memory usage patterns for specific
cgroups. Users can now filter page_owner output to show only pages
belonging to a particular memory cgroup.
Collect cgroup path in memcg_info using cgroup_path() and store the
filter state in page_owner_filter_state. When the user sets memcg filter
via "memcg=<path>" command, compare each page's cgroup path against
the specified path and skip non-matching pages using strcmp.
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
mm/page_owner.c | 79 +++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 74 insertions(+), 5 deletions(-)
diff --git a/mm/page_owner.c b/mm/page_owner.c
index 09cfad10a7df..21b82e927eaf 100644
--- a/mm/page_owner.c
+++ b/mm/page_owner.c
@@ -71,6 +71,7 @@ static const char * const page_owner_print_mode_strings[] = {
struct memcg_info {
char name[80];
+ char *path;
bool is_slab;
bool is_objcg;
bool is_online;
@@ -86,6 +87,7 @@ struct page_owner_filter_state {
enum page_owner_print_mode print_mode;
bool nid_filter_enabled;
bool proc_filter_enabled;
+ bool memcg_filter_enabled;
nodemask_t nid_filter;
int pid_count;
int tgid_count;
@@ -93,6 +95,7 @@ struct page_owner_filter_state {
pid_t pid_list[MAX_FILTER_PIDS];
pid_t tgid_list[MAX_FILTER_TGIDS];
char comm_list[MAX_FILTER_COMMS][TASK_COMM_LEN];
+ char *memcg_path;
};
static int cmp_pid_t(const void *a, const void *b)
@@ -582,12 +585,21 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
/*
* Get memcg information from page
*/
-static void get_page_memcg_info(struct page *page, struct memcg_info *info)
+static void get_page_memcg_info(struct page *page, struct memcg_info *info,
+ bool need_path)
{
unsigned long memcg_data;
struct obj_cgroup *objcg;
struct mem_cgroup *memcg;
+ if (need_path) {
+ info->path = kzalloc(PATH_MAX, GFP_KERNEL);
+ if (!info->path)
+ return;
+ } else {
+ info->path = NULL;
+ }
+
rcu_read_lock();
memcg_data = READ_ONCE(page->memcg_data);
if (!memcg_data || PageTail(page))
@@ -606,8 +618,14 @@ static void get_page_memcg_info(struct page *page, struct memcg_info *info)
info->is_objcg = (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 (need_path)
+ cgroup_path(memcg->css.cgroup, info->path, PATH_MAX);
out_unlock:
rcu_read_unlock();
+ if (info->path && !info->path[0]) {
+ kfree(info->path);
+ info->path = NULL;
+ }
}
/*
@@ -776,6 +794,7 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
struct page_owner *page_owner;
depot_stack_handle_t handle;
struct page_owner_filter_state *state = file->private_data;
+ ssize_t ret;
if (!static_branch_unlikely(&page_owner_inited))
return -EINVAL;
@@ -899,7 +918,12 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
}
#ifdef CONFIG_MEMCG
- get_page_memcg_info(page, &memcg_info);
+ get_page_memcg_info(page, &memcg_info,
+ state->memcg_filter_enabled);
+ if (state->memcg_filter_enabled)
+ if (!memcg_info.path ||
+ strcmp(memcg_info.path, state->memcg_path) != 0)
+ goto ext_put_continue;
#endif
/* Record the next PFN to read in the file offset */
@@ -907,9 +931,12 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
page_owner_tmp = *page_owner;
page_ext_put(page_ext);
- return print_page_owner(buf, count, pfn, page,
+ ret = print_page_owner(buf, count, pfn, page,
&page_owner_tmp, handle, state, &memcg_info);
+ kfree(memcg_info.path);
+ return ret;
ext_put_continue:
+ kfree(memcg_info.path);
page_ext_put(page_ext);
cond_resched();
}
@@ -1016,7 +1043,10 @@ static int page_owner_open(struct inode *inode, struct file *file)
static int page_owner_release(struct inode *inode, struct file *file)
{
- kfree(file->private_data);
+ struct page_owner_filter_state *state = file->private_data;
+
+ kfree(state->memcg_path);
+ kfree(state);
return 0;
}
@@ -1096,9 +1126,11 @@ static ssize_t page_owner_write(struct file *file,
nodemask_t new_nid_filter;
bool new_nid_filter_enabled;
bool new_proc_filter_enabled;
+ bool new_memcg_filter_enabled;
pid_t new_pid_list[MAX_FILTER_PIDS];
pid_t new_tgid_list[MAX_FILTER_TGIDS];
char (*new_comm_list)[TASK_COMM_LEN] = NULL;
+ char *new_memcg_path;
int new_pid_count = 0;
int new_tgid_count = 0;
int new_comm_count = 0;
@@ -1114,16 +1146,24 @@ static ssize_t page_owner_write(struct file *file,
if (count > 32 + 6 * MAX_NUMNODES +
(PID_MAX_DIGITS + 1) * MAX_FILTER_PIDS + 4 +
(PID_MAX_DIGITS + 1) * MAX_FILTER_TGIDS + 5 +
- TASK_COMM_LEN * MAX_FILTER_COMMS + 5)
+ TASK_COMM_LEN * MAX_FILTER_COMMS + 5 +
+ PATH_MAX + 6)
return -EINVAL;
new_comm_list = kmalloc_array(MAX_FILTER_COMMS, TASK_COMM_LEN, GFP_KERNEL);
if (!new_comm_list)
return -ENOMEM;
+ new_memcg_path = kmalloc(PATH_MAX, GFP_KERNEL);
+ if (!new_memcg_path) {
+ kfree(new_comm_list);
+ return -ENOMEM;
+ }
+
kbuf = memdup_user_nul(buf, count);
if (IS_ERR(kbuf)) {
kfree(new_comm_list);
+ kfree(new_memcg_path);
return PTR_ERR(kbuf);
}
@@ -1146,6 +1186,9 @@ static ssize_t page_owner_write(struct file *file,
state->comm_count * TASK_COMM_LEN);
new_comm_count = state->comm_count;
}
+ new_memcg_filter_enabled = state->memcg_filter_enabled;
+ if (state->memcg_filter_enabled && state->memcg_path)
+ strscpy(new_memcg_path, state->memcg_path, PATH_MAX);
while ((token = strsep(&kbuf, " \t\n")) != NULL) {
if (*token == '\0')
@@ -1189,12 +1232,33 @@ static ssize_t page_owner_write(struct file *file,
ret = parse_comm_list(token + 5, new_comm_list, &new_comm_count);
if (ret < 0)
goto out_free;
+ } else if (!strncmp(token, "memcg=", 6)) {
+ if (token[6] == '\0') {
+ ret = -EINVAL;
+ goto out_free;
+ }
+ ret = strscpy(new_memcg_path, token + 6, PATH_MAX);
+ if (ret < 0)
+ goto out_free;
+ new_memcg_filter_enabled = true;
} else {
ret = -EINVAL;
goto out_free;
}
}
+ if (new_memcg_filter_enabled) {
+ if (!state->memcg_path) {
+ state->memcg_path = kzalloc(PATH_MAX, GFP_KERNEL);
+ if (!state->memcg_path) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+ } else {
+ memset(state->memcg_path, 0, PATH_MAX);
+ }
+ }
+
/* Commit all filter changes */
state->print_mode = new_print_mode;
state->nid_filter = new_nid_filter;
@@ -1221,11 +1285,16 @@ static ssize_t page_owner_write(struct file *file,
new_comm_count * TASK_COMM_LEN);
state->comm_count = new_comm_count;
}
+ if (new_memcg_filter_enabled) {
+ strscpy(state->memcg_path, new_memcg_path, PATH_MAX);
+ state->memcg_filter_enabled = true;
+ }
ret = count;
out_free:
kfree(new_comm_list);
+ kfree(new_memcg_path);
kfree(orig);
return ret;
}
--
2.20.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 6/8] tools/mm: Add PID/TGID/COMM filtering support to page_owner_filter
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
` (4 preceding siblings ...)
2026-08-28 3:13 ` [PATCH 5/8] mm/page_owner: Add memcg " Zhen Ni
@ 2026-08-28 3:13 ` Zhen Ni
2026-08-28 3:13 ` [PATCH 7/8] tools/mm: Add memory cgroup " Zhen Ni
` (2 subsequent siblings)
8 siblings, 0 replies; 11+ messages in thread
From: Zhen Ni @ 2026-08-28 3:13 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel,
Zhen Ni
Add command-line options for filtering by process ID (PID), thread group
ID (TGID), and process name (COMM) to the page_owner_filter userspace tool.
New options:
-p, --pid PID_LIST : Process IDs (comma-separated, max 16)
-t, --tgid TGID_LIST : Thread Group IDs (comma-separated, max 16)
-c, --comm COMM_LIST : Process names (comma-separated, max 8)
Supports wildcards: * ? [a-z]
Usage examples:
page_owner_filter -p 1234,5678
page_owner_filter -c "python*"
page_owner_filter -n 0 -c kworker* -o output.txt
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
tools/mm/page_owner_filter.c | 168 ++++++++++++++++++++++++++++++-----
1 file changed, 147 insertions(+), 21 deletions(-)
diff --git a/tools/mm/page_owner_filter.c b/tools/mm/page_owner_filter.c
index 1d1f0a38678a..9256ffad4939 100644
--- a/tools/mm/page_owner_filter.c
+++ b/tools/mm/page_owner_filter.c
@@ -21,22 +21,24 @@
#include <signal.h>
#define MAX_CMD_LEN 512
+#define TASK_COMM_LEN 16
static void usage(const char *prog)
{
fprintf(stderr, "Usage: %s [OPTIONS]\n", prog);
fprintf(stderr, "\nOptions:\n");
- fprintf(stderr, " -m, --mode MODE : print_mode (stack, handle, or stack_handle)\n");
- fprintf(stderr, " -n, --nid NID_LIST : NUMA node IDs (comma-separated or ranges)\n");
- fprintf(stderr, " -o, --output FILE : output file (default: stdout)\n");
- fprintf(stderr, " -h, --help : show this help message\n");
+ fprintf(stderr, " -m, --mode MODE : print_mode (stack, handle, stack_handle)\n");
+ fprintf(stderr, " -n, --nid NID_LIST : NUMA nodes (comma-separated or ranges)\n");
+ fprintf(stderr, " -p, --pid PID_LIST : Process IDs (comma-separated, max 16)\n");
+ fprintf(stderr, " -t, --tgid TGID_LIST : Thread Group IDs (comma-separated, max 16)\n");
+ fprintf(stderr, " -c, --comm COMM_LIST : Process names (comma-separated, max 8)\n");
+ fprintf(stderr, " Supports wildcards: * ? [a-z]\n");
+ fprintf(stderr, " -o, --output FILE : output file (default: stdout)\n");
+ fprintf(stderr, " -h, --help : show this help message\n");
fprintf(stderr, "\nExamples:\n");
- fprintf(stderr, " %s -m stack\n", prog);
- fprintf(stderr, " %s -m handle\n", prog);
- fprintf(stderr, " %s -m stack_handle\n", prog);
- fprintf(stderr, " %s -m stack -o output.txt\n", prog);
- fprintf(stderr, " %s -n 0,1,2\n", prog);
- fprintf(stderr, " %s -m stack -n 0\n", prog);
+ fprintf(stderr, " %s -m handle -o output.txt\n", prog);
+ fprintf(stderr, " %s -n 0,1 -c bash\n", prog);
+ fprintf(stderr, " %s -c \"python*\" -t 1\n", prog);
}
static int validate_mode(const char *mode)
@@ -132,6 +134,93 @@ static int validate_nid_list(const char *nid_list)
return 0;
}
+static int validate_pid_list(const char *pid_list)
+{
+ const char *p;
+ int count = 0;
+
+ if (!pid_list || strlen(pid_list) == 0)
+ return -1;
+
+ for (p = pid_list; *p; p++) {
+ if (*p == ',') {
+ count++;
+ continue;
+ }
+ if (!isdigit((unsigned char)*p)) {
+ fprintf(stderr,
+ "Error: Invalid character '%c' in pid_list (only digits allowed)\n",
+ *p);
+ return -1;
+ }
+ }
+
+ if (++count > 16) {
+ fprintf(stderr, "Error: Too many PIDs (max 16)\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int validate_tgid_list(const char *tgid_list)
+{
+ return validate_pid_list(tgid_list);
+}
+
+static int validate_comm_list(const char *comm_list)
+{
+ const char *p;
+ const char *comm_start;
+ int count = 0;
+ int comm_len = 0;
+
+ if (!comm_list || strlen(comm_list) == 0)
+ return -1;
+
+ comm_start = comm_list;
+ for (p = comm_list; *p; p++) {
+ if (*p == ',') {
+ /* Check COMM length before separator */
+ if (comm_len == 0) {
+ fprintf(stderr, "Error: Empty COMM in list\n");
+ return -1;
+ }
+ if (comm_len >= TASK_COMM_LEN) {
+ fprintf(stderr,
+ "Error: COMM too long (max %d chars)\n",
+ TASK_COMM_LEN - 1);
+ fprintf(stderr, " Near: %.15s...\n", comm_start);
+ return -1;
+ }
+ count++;
+ comm_len = 0;
+ comm_start = p + 1;
+ continue;
+ }
+ comm_len++;
+ }
+
+ /* Check last COMM */
+ if (comm_len == 0) {
+ fprintf(stderr, "Error: Empty COMM at end of list\n");
+ return -1;
+ }
+ if (comm_len >= TASK_COMM_LEN) {
+ fprintf(stderr, "Error: COMM too long (max %d chars)\n",
+ TASK_COMM_LEN - 1);
+ fprintf(stderr, " Near: %.15s...\n", comm_start);
+ return -1;
+ }
+
+ if (++count > 8) {
+ fprintf(stderr, "Error: Too many COMMs (max 8)\n");
+ return -1;
+ }
+
+ return 0;
+}
+
int main(int argc, char *argv[])
{
const char *output_file = NULL;
@@ -148,6 +237,9 @@ int main(int argc, char *argv[])
static struct option long_options[] = {
{"mode", required_argument, 0, 'm'},
{"nid", required_argument, 0, 'n'},
+ {"pid", required_argument, 0, 'p'},
+ {"tgid", required_argument, 0, 't'},
+ {"comm", required_argument, 0, 'c'},
{"output", required_argument, 0, 'o'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
@@ -174,7 +266,7 @@ int main(int argc, char *argv[])
return 1;
}
- while ((opt = getopt_long(argc, argv, "m:n:o:h", long_options, NULL)) != -1) {
+ while ((opt = getopt_long(argc, argv, "m:n:p:t:c:o:h", long_options, NULL)) != -1) {
int len;
switch (opt) {
@@ -206,6 +298,48 @@ int main(int argc, char *argv[])
cmd_len += len;
break;
}
+ case 'p': {
+ const char *pid_list = optarg;
+
+ if (validate_pid_list(pid_list) < 0)
+ return 1;
+ len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,
+ "%spid=%s", cmd_len > 0 ? " " : "", pid_list);
+ if (len < 0 || cmd_len + len >= MAX_CMD_LEN) {
+ fprintf(stderr, "Error: Command too long\n");
+ return 1;
+ }
+ cmd_len += len;
+ break;
+ }
+ case 't': {
+ const char *tgid_list = optarg;
+
+ if (validate_tgid_list(tgid_list) < 0)
+ return 1;
+ len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,
+ "%stgid=%s", cmd_len > 0 ? " " : "", tgid_list);
+ if (len < 0 || cmd_len + len >= MAX_CMD_LEN) {
+ fprintf(stderr, "Error: Command too long\n");
+ return 1;
+ }
+ cmd_len += len;
+ break;
+ }
+ case 'c': {
+ const char *comm_list = optarg;
+
+ if (validate_comm_list(comm_list) < 0)
+ return 1;
+ len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,
+ "%scomm=%s", cmd_len > 0 ? " " : "", comm_list);
+ if (len < 0 || cmd_len + len >= MAX_CMD_LEN) {
+ fprintf(stderr, "Error: Command too long\n");
+ return 1;
+ }
+ cmd_len += len;
+ break;
+ }
case 'o':
output_file = optarg;
break;
@@ -220,7 +354,7 @@ int main(int argc, char *argv[])
/* At least one filter must be specified */
if (cmd_len == 0) {
- fprintf(stderr, "Error: At least one filter (-m or -n) must be specified\n\n");
+ fprintf(stderr, "Error: At least one filter must be specified\n\n");
usage(argv[0]);
return 1;
}
@@ -255,15 +389,7 @@ int main(int argc, char *argv[])
ret = write(fd, filter_cmd, strlen(filter_cmd));
if (ret < 0) {
- if (errno == EINVAL) {
- fprintf(stderr, "Error: Kernel rejected the filter command.\n");
- fprintf(stderr, "Possible causes:\n");
- fprintf(stderr, " - Kernel does not support per-fd filtering\n");
- fprintf(stderr, " - NUMA node has no memory\n");
- fprintf(stderr, " - Unknown reason\n");
- } else {
- perror("write filter command");
- }
+ perror("write filter command");
goto out;
}
--
2.20.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 7/8] tools/mm: Add memory cgroup filtering support to page_owner_filter
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
` (5 preceding siblings ...)
2026-08-28 3:13 ` [PATCH 6/8] tools/mm: Add PID/TGID/COMM filtering support to page_owner_filter Zhen Ni
@ 2026-08-28 3:13 ` Zhen Ni
2026-08-28 3:13 ` [PATCH 8/8] Documentation: page_owner: Document PID/TGID/COMM and cgroup filters Zhen Ni
2026-08-28 7:03 ` [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Lorenzo Stoakes (ARM)
8 siblings, 0 replies; 11+ messages in thread
From: Zhen Ni @ 2026-08-28 3:13 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel,
Zhen Ni
Add filtering capability for page_owner to allow filtering by
memory cgroup path.
Filter page_owner output by cgroup path:
./page_owner_filter -g /
./page_owner_filter -g /user.slice
./page_owner_filter -g /user.slice -c systemd
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
tools/mm/page_owner_filter.c | 56 ++++++++++++++++++++++++++++++++++--
1 file changed, 53 insertions(+), 3 deletions(-)
diff --git a/tools/mm/page_owner_filter.c b/tools/mm/page_owner_filter.c
index 9256ffad4939..1ffbe4387854 100644
--- a/tools/mm/page_owner_filter.c
+++ b/tools/mm/page_owner_filter.c
@@ -20,7 +20,7 @@
#include <getopt.h>
#include <signal.h>
-#define MAX_CMD_LEN 512
+#define MAX_CMD_LEN 2048
#define TASK_COMM_LEN 16
static void usage(const char *prog)
@@ -33,12 +33,13 @@ static void usage(const char *prog)
fprintf(stderr, " -t, --tgid TGID_LIST : Thread Group IDs (comma-separated, max 16)\n");
fprintf(stderr, " -c, --comm COMM_LIST : Process names (comma-separated, max 8)\n");
fprintf(stderr, " Supports wildcards: * ? [a-z]\n");
+ fprintf(stderr, " -g, --cgroup PATH : Memory cgroup path\n");
fprintf(stderr, " -o, --output FILE : output file (default: stdout)\n");
fprintf(stderr, " -h, --help : show this help message\n");
fprintf(stderr, "\nExamples:\n");
fprintf(stderr, " %s -m handle -o output.txt\n", prog);
fprintf(stderr, " %s -n 0,1 -c bash\n", prog);
- fprintf(stderr, " %s -c \"python*\" -t 1\n", prog);
+ fprintf(stderr, " %s -c \"python*\" -g user.slice\n", prog);
}
static int validate_mode(const char *mode)
@@ -221,6 +222,38 @@ static int validate_comm_list(const char *comm_list)
return 0;
}
+static int validate_cgroup_path(const char *path)
+{
+ char cgroup_path[512];
+ const char *input_path = path;
+ int is_cgroup_v2 = 0;
+
+ if (!path || strlen(path) == 0)
+ return -1;
+
+ if (path[0] == '/')
+ input_path++;
+
+ /* Check if v1 memory controller exists */
+ if (access("/sys/fs/cgroup/memory", F_OK) != 0)
+ is_cgroup_v2 = 1;
+
+ if (is_cgroup_v2)
+ snprintf(cgroup_path, sizeof(cgroup_path),
+ "/sys/fs/cgroup/%s/memory.stat", input_path);
+ else
+ snprintf(cgroup_path, sizeof(cgroup_path),
+ "/sys/fs/cgroup/memory/%s/memory.stat", input_path);
+
+ if (access(cgroup_path, F_OK) != 0) {
+ fprintf(stderr, "Error: Cgroup path '%s': not found or no memory controller\n",
+ path);
+ return -1;
+ }
+
+ return 0;
+}
+
int main(int argc, char *argv[])
{
const char *output_file = NULL;
@@ -240,6 +273,7 @@ int main(int argc, char *argv[])
{"pid", required_argument, 0, 'p'},
{"tgid", required_argument, 0, 't'},
{"comm", required_argument, 0, 'c'},
+ {"cgroup", required_argument, 0, 'g'},
{"output", required_argument, 0, 'o'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
@@ -266,7 +300,7 @@ int main(int argc, char *argv[])
return 1;
}
- while ((opt = getopt_long(argc, argv, "m:n:p:t:c:o:h", long_options, NULL)) != -1) {
+ while ((opt = getopt_long(argc, argv, "m:n:p:t:c:g:o:h", long_options, NULL)) != -1) {
int len;
switch (opt) {
@@ -340,6 +374,22 @@ int main(int argc, char *argv[])
cmd_len += len;
break;
}
+ case 'g': {
+ const char *cgroup_path = optarg;
+
+ if (validate_cgroup_path(cgroup_path) < 0)
+ return 1;
+ const char *path = (cgroup_path[0] == '/') ? cgroup_path + 1 : cgroup_path;
+
+ len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,
+ "%smemcg=/%s", cmd_len > 0 ? " " : "", path);
+ if (len < 0 || cmd_len + len >= MAX_CMD_LEN) {
+ fprintf(stderr, "Error: Command too long\n");
+ return 1;
+ }
+ cmd_len += len;
+ break;
+ }
case 'o':
output_file = optarg;
break;
--
2.20.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 8/8] Documentation: page_owner: Document PID/TGID/COMM and cgroup filters
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
` (6 preceding siblings ...)
2026-08-28 3:13 ` [PATCH 7/8] tools/mm: Add memory cgroup " Zhen Ni
@ 2026-08-28 3:13 ` Zhen Ni
2026-08-28 7:03 ` [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Lorenzo Stoakes (ARM)
8 siblings, 0 replies; 11+ messages in thread
From: Zhen Ni @ 2026-08-28 3:13 UTC (permalink / raw)
To: Andrew Morton
Cc: David Hildenbrand, Lorenzo Stoakes, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel,
Zhen Ni
Update page_owner.rst to document process and cgroup filtering
support in page_owner_filter tool. Add usage examples for PID, TGID,
COMM (with wildcard support), and cgroup filters along with their
respective limits.
Signed-off-by: Zhen Ni <zhen.ni@easystack.cn>
---
Documentation/mm/page_owner.rst | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/Documentation/mm/page_owner.rst b/Documentation/mm/page_owner.rst
index a6bd3fe6423a..c8a7e6c242c5 100644
--- a/Documentation/mm/page_owner.rst
+++ b/Documentation/mm/page_owner.rst
@@ -283,7 +283,7 @@ page_owner supports filtering output at the kernel level before reading,
which reduces the amount of data that needs to be processed in userspace.
The page_owner_filter tool provides a convenient interface for this filtering
-capability. It supports two types of filters:
+capability. It supports the following types of filters:
1. **print_mode filter**: Control what information is printed for each page
- ``stack``: Print full stack traces (default, compatible with existing usage)
@@ -300,6 +300,16 @@ capability. It supports two types of filters:
- Ranges: ``-n 0-3``
- Mixed format: ``-n 0,2-3,5``
+3. **Process filters**: Filter pages by process identifiers
+ - Filter by process ID: ``-p PID_LIST`` (comma-separated, max 16)
+ - Filter by thread group ID: ``-t TGID_LIST`` (comma-separated, max 16)
+ - Filter by task command name: ``-c COMM_LIST`` (comma-separated, max 8)
+ - Name matching supports wildcards: ``*``, ``?``, ``[a-z]``
+
+4. **Cgroup (memcg) filter**: Filter pages by memory cgroup
+ - Filter by cgroup path: ``-g CGROUP``
+ - Useful for containerized environments and multi-tenant systems
+
Usage examples::
# Filter by print mode
@@ -310,9 +320,19 @@ Usage examples::
./page_owner_filter -n 0
./page_owner_filter -n 0-3
+ # Filter by process
+ ./page_owner_filter -p 1234
+ ./page_owner_filter -t 1,2,3
+ ./page_owner_filter -c python*
+
+ # Filter by cgroup
+ ./page_owner_filter -g system.slice
+ ./page_owner_filter -g kubepods/besteffort/pod123
+
# Combined filters
./page_owner_filter -m stack -n 0,1,2
./page_owner_filter -m handle -n 0,2-3
+ ./page_owner_filter -g user.slice -c bash -n 0
# Save to file
./page_owner_filter -m handle -o filtered_output.txt
@@ -323,6 +343,9 @@ reduce output size by ~66% (84MB vs 244MB) and improve read performance by ~4.4x
compared to full stack output.
The NUMA node filter is useful for NUMA-aware memory allocation analysis and debugging.
+Process filters help isolate memory allocations for specific processes or tasks.
+The cgroup filter is essential for containerized environments where you need to
+analyze memory usage per container or service.
Behind the scenes, page_owner_filter opens /sys/kernel/debug/page_owner and
writes filter commands before reading the filtered output. The filtering uses
--
2.20.1
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering
2026-08-28 3:13 [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Zhen Ni
` (7 preceding siblings ...)
2026-08-28 3:13 ` [PATCH 8/8] Documentation: page_owner: Document PID/TGID/COMM and cgroup filters Zhen Ni
@ 2026-08-28 7:03 ` Lorenzo Stoakes (ARM)
2026-08-28 18:36 ` Andrew Morton
8 siblings, 1 reply; 11+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-08-28 7:03 UTC (permalink / raw)
To: Zhen Ni
Cc: Andrew Morton, David Hildenbrand, Liam R . Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Brendan Jackman,
Johannes Weiner, Zi Yan, linux-mm, linux-doc, linux-kernel
On Fri, Aug 28, 2026 at 11:13:31AM +0800, Zhen Ni wrote:
> This patch series adds process and memory cgroup filtering support to
> page_owner. Following the previous series that introduced print_mode and
> NUMA node filters:
> https://lore.kernel.org/linux-mm/20260707115411.1714314-1-zhen.ni@easystack.cn/
>
> This series adds filtering capabilities to page_owner, allowing users to
> filter output by specific processes and memory cgroups. Users can now
> filter page_owner output by PID, TGID, COMM (with wildcard support), and
> memory cgroup path. This makes page_owner debugging more focused and
> efficient for tracking memory allocations in specific contexts.
The majority of this cover letter feels like it should have been
documentation put somewhere :)
I mean I like to be verbose, but this is slightly excessive :P
Remember that mm places cover letters in the first commit in merged
series...
Andrew - could we not do that here, or at least heavily truncate it?
[...]
> Test: Cgroup path exceeding PATH_MAX
> Generated path length: 4101
> ./page_owner_filter -g '<very long path>'
> Error: Cgroup path '/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa': not found or no memory controller
Also let's maybe refrain from >4000 char 'aaa...[rgh]'s, I think people get
the picture without needing all the chars :)
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering
2026-08-28 7:03 ` [PATCH 0/8] mm/page_owner: Add PID/TGID/COMM and cgroup filtering Lorenzo Stoakes (ARM)
@ 2026-08-28 18:36 ` Andrew Morton
0 siblings, 0 replies; 11+ messages in thread
From: Andrew Morton @ 2026-08-28 18:36 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Zhen Ni, David Hildenbrand, Liam R . Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Brendan Jackman, Johannes Weiner,
Zi Yan, linux-mm, linux-doc, linux-kernel
On Fri, 28 Aug 2026 08:03:38 +0100 "Lorenzo Stoakes (ARM)" <ljs@kernel.org> wrote:
> On Fri, Aug 28, 2026 at 11:13:31AM +0800, Zhen Ni wrote:
> > This patch series adds process and memory cgroup filtering support to
> > page_owner. Following the previous series that introduced print_mode and
> > NUMA node filters:
> > https://lore.kernel.org/linux-mm/20260707115411.1714314-1-zhen.ni@easystack.cn/
> >
> > This series adds filtering capabilities to page_owner, allowing users to
> > filter output by specific processes and memory cgroups. Users can now
> > filter page_owner output by PID, TGID, COMM (with wildcard support), and
> > memory cgroup path. This makes page_owner debugging more focused and
> > efficient for tracking memory allocations in specific contexts.
>
> The majority of this cover letter feels like it should have been
> documentation put somewhere :)
yes please.
The only thing longer than the cover letter is the Sashiko report ;)
https://sashiko.dev/#/patchset/20260828031339.1270699-1-zhen.ni@easystack.cn
^ permalink raw reply [flat|nested] 11+ messages in thread