[PATCH 6/8] tools/mm: Add PID/TGID/COMM filtering support to page_owner_filter
From: Zhen Ni
Date: Fri Aug 28 2026 - 02:46:30 EST
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@xxxxxxxxxxxx>
---
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