[PATCH 7/8] tools/mm: Add memory cgroup filtering support to page_owner_filter

From: Zhen Ni

Date: Thu Aug 27 2026 - 23:20:14 EST


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@xxxxxxxxxxxx>
---
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