1 // SPDX-License-Identifier: GPL-2.0
3 * User-space helper to sort the output of /sys/kernel/debug/page_owner
6 * cat /sys/kernel/debug/page_owner > page_owner_full.txt
7 * ./page_owner_sort page_owner_full.txt sorted_page_owner.txt
8 * Or sort by total memory:
9 * ./page_owner_sort -m page_owner_full.txt sorted_page_owner.txt
11 * See Documentation/mm/page_owner.rst
16 #include <sys/types.h>
23 #include <linux/types.h>
29 #define TASK_COMM_LEN 16
33 char *comm
; // task command name
52 CULL_STACKTRACE
= 1<<4,
57 ALLOCATOR_SLAB
= 1<<2,
58 ALLOCATOR_VMALLOC
= 1<<3,
59 ALLOCATOR_OTHERS
= 1<<4
62 ARG_TXT
, ARG_COMM
, ARG_STACKTRACE
, ARG_ALLOC_TS
, ARG_CULL_TIME
,
63 ARG_PAGE_NUM
, ARG_PID
, ARG_TGID
, ARG_UNKNOWN
, ARG_ALLOCATOR
79 struct filter_condition
{
87 struct sort_condition
{
88 int (**cmps
)(const void *, const void *);
92 static struct filter_condition fc
;
93 static struct sort_condition sc
;
94 static regex_t order_pattern
;
95 static regex_t pid_pattern
;
96 static regex_t tgid_pattern
;
97 static regex_t comm_pattern
;
98 static regex_t ts_nsec_pattern
;
99 static struct block_list
*list
;
100 static int list_size
;
104 static bool debug_on
;
106 static void set_single_cmp(int (*cmp
)(const void *, const void *), int sign
);
108 int read_block(char *buf
, char *ext_buf
, int buf_size
, FILE *fin
)
110 char *curr
= buf
, *const buf_end
= buf
+ buf_size
;
112 while (buf_end
- curr
> 1 && fgets(curr
, buf_end
- curr
, fin
)) {
113 if (*curr
== '\n') { /* empty line */
116 if (!strncmp(curr
, "PFN", 3)) {
117 strcpy(ext_buf
, curr
);
120 curr
+= strlen(curr
);
123 return -1; /* EOF or no space left in buf. */
126 static int compare_txt(const void *p1
, const void *p2
)
128 const struct block_list
*l1
= p1
, *l2
= p2
;
130 return strcmp(l1
->txt
, l2
->txt
);
133 static int compare_stacktrace(const void *p1
, const void *p2
)
135 const struct block_list
*l1
= p1
, *l2
= p2
;
137 return strcmp(l1
->stacktrace
, l2
->stacktrace
);
140 static int compare_num(const void *p1
, const void *p2
)
142 const struct block_list
*l1
= p1
, *l2
= p2
;
144 return l1
->num
- l2
->num
;
147 static int compare_page_num(const void *p1
, const void *p2
)
149 const struct block_list
*l1
= p1
, *l2
= p2
;
151 return l1
->page_num
- l2
->page_num
;
154 static int compare_pid(const void *p1
, const void *p2
)
156 const struct block_list
*l1
= p1
, *l2
= p2
;
158 return l1
->pid
- l2
->pid
;
161 static int compare_tgid(const void *p1
, const void *p2
)
163 const struct block_list
*l1
= p1
, *l2
= p2
;
165 return l1
->tgid
- l2
->tgid
;
168 static int compare_allocator(const void *p1
, const void *p2
)
170 const struct block_list
*l1
= p1
, *l2
= p2
;
172 return l1
->allocator
- l2
->allocator
;
175 static int compare_comm(const void *p1
, const void *p2
)
177 const struct block_list
*l1
= p1
, *l2
= p2
;
179 return strcmp(l1
->comm
, l2
->comm
);
182 static int compare_ts(const void *p1
, const void *p2
)
184 const struct block_list
*l1
= p1
, *l2
= p2
;
186 return l1
->ts_nsec
< l2
->ts_nsec
? -1 : 1;
189 static int compare_cull_condition(const void *p1
, const void *p2
)
192 return compare_txt(p1
, p2
);
193 if ((cull
& CULL_STACKTRACE
) && compare_stacktrace(p1
, p2
))
194 return compare_stacktrace(p1
, p2
);
195 if ((cull
& CULL_PID
) && compare_pid(p1
, p2
))
196 return compare_pid(p1
, p2
);
197 if ((cull
& CULL_TGID
) && compare_tgid(p1
, p2
))
198 return compare_tgid(p1
, p2
);
199 if ((cull
& CULL_COMM
) && compare_comm(p1
, p2
))
200 return compare_comm(p1
, p2
);
201 if ((cull
& CULL_ALLOCATOR
) && compare_allocator(p1
, p2
))
202 return compare_allocator(p1
, p2
);
206 static int compare_sort_condition(const void *p1
, const void *p2
)
210 for (int i
= 0; i
< sc
.size
; ++i
)
212 cmp
= sc
.signs
[i
] * sc
.cmps
[i
](p1
, p2
);
216 static int remove_pattern(regex_t
*pattern
, char *buf
, int len
)
218 regmatch_t pmatch
[2];
221 err
= regexec(pattern
, buf
, 2, pmatch
, REG_NOTBOL
);
222 if (err
!= 0 || pmatch
[1].rm_so
== -1)
225 memcpy(buf
+ pmatch
[1].rm_so
,
226 buf
+ pmatch
[1].rm_eo
, len
- pmatch
[1].rm_eo
);
228 return len
- (pmatch
[1].rm_eo
- pmatch
[1].rm_so
);
231 static int search_pattern(regex_t
*pattern
, char *pattern_str
, char *buf
)
234 regmatch_t pmatch
[2];
236 err
= regexec(pattern
, buf
, 2, pmatch
, REG_NOTBOL
);
237 if (err
!= 0 || pmatch
[1].rm_so
== -1) {
239 fprintf(stderr
, "no matching pattern in %s\n", buf
);
242 val_len
= pmatch
[1].rm_eo
- pmatch
[1].rm_so
;
244 memcpy(pattern_str
, buf
+ pmatch
[1].rm_so
, val_len
);
249 static bool check_regcomp(regex_t
*pattern
, const char *regex
)
253 err
= regcomp(pattern
, regex
, REG_EXTENDED
| REG_NEWLINE
);
254 if (err
!= 0 || pattern
->re_nsub
!= 1) {
255 fprintf(stderr
, "Invalid pattern %s code %d\n", regex
, err
);
261 static char **explode(char sep
, const char *str
, int *size
)
263 int count
= 0, len
= strlen(str
);
264 int lastindex
= -1, j
= 0;
266 for (int i
= 0; i
< len
; i
++)
269 char **ret
= calloc(++count
, sizeof(char *));
271 for (int i
= 0; i
< len
; i
++) {
273 ret
[j
] = calloc(i
- lastindex
, sizeof(char));
274 memcpy(ret
[j
++], str
+ lastindex
+ 1, i
- lastindex
- 1);
278 if (lastindex
<= len
- 1) {
279 ret
[j
] = calloc(len
- lastindex
, sizeof(char));
280 memcpy(ret
[j
++], str
+ lastindex
+ 1, strlen(str
) - 1 - lastindex
);
286 static void free_explode(char **arr
, int size
)
288 for (int i
= 0; i
< size
; i
++)
293 # define FIELD_BUFF 25
295 static int get_page_num(char *buf
)
298 char order_str
[FIELD_BUFF
] = {0};
301 search_pattern(&order_pattern
, order_str
, buf
);
303 order_val
= strtol(order_str
, &endptr
, 10);
304 if (order_val
> 64 || errno
!= 0 || endptr
== order_str
|| *endptr
!= '\0') {
306 fprintf(stderr
, "wrong order in follow buf:\n%s\n", buf
);
310 return 1 << order_val
;
313 static pid_t
get_pid(char *buf
)
316 char pid_str
[FIELD_BUFF
] = {0};
319 search_pattern(&pid_pattern
, pid_str
, buf
);
321 pid
= strtol(pid_str
, &endptr
, 10);
322 if (errno
!= 0 || endptr
== pid_str
|| *endptr
!= '\0') {
324 fprintf(stderr
, "wrong/invalid pid in follow buf:\n%s\n", buf
);
332 static pid_t
get_tgid(char *buf
)
335 char tgid_str
[FIELD_BUFF
] = {0};
338 search_pattern(&tgid_pattern
, tgid_str
, buf
);
340 tgid
= strtol(tgid_str
, &endptr
, 10);
341 if (errno
!= 0 || endptr
== tgid_str
|| *endptr
!= '\0') {
343 fprintf(stderr
, "wrong/invalid tgid in follow buf:\n%s\n", buf
);
351 static __u64
get_ts_nsec(char *buf
)
354 char ts_nsec_str
[FIELD_BUFF
] = {0};
357 search_pattern(&ts_nsec_pattern
, ts_nsec_str
, buf
);
359 ts_nsec
= strtoull(ts_nsec_str
, &endptr
, 10);
360 if (errno
!= 0 || endptr
== ts_nsec_str
|| *endptr
!= '\0') {
362 fprintf(stderr
, "wrong ts_nsec in follow buf:\n%s\n", buf
);
369 static char *get_comm(char *buf
)
371 char *comm_str
= malloc(TASK_COMM_LEN
);
373 memset(comm_str
, 0, TASK_COMM_LEN
);
375 search_pattern(&comm_pattern
, comm_str
, buf
);
379 fprintf(stderr
, "wrong comm in follow buf:\n%s\n", buf
);
387 static int get_arg_type(const char *arg
)
389 if (!strcmp(arg
, "pid") || !strcmp(arg
, "p"))
391 else if (!strcmp(arg
, "tgid") || !strcmp(arg
, "tg"))
393 else if (!strcmp(arg
, "name") || !strcmp(arg
, "n"))
395 else if (!strcmp(arg
, "stacktrace") || !strcmp(arg
, "st"))
396 return ARG_STACKTRACE
;
397 else if (!strcmp(arg
, "txt") || !strcmp(arg
, "T"))
399 else if (!strcmp(arg
, "alloc_ts") || !strcmp(arg
, "at"))
401 else if (!strcmp(arg
, "allocator") || !strcmp(arg
, "ator"))
402 return ARG_ALLOCATOR
;
408 static int get_allocator(const char *buf
, const char *migrate_info
)
410 char *tmp
, *first_line
, *second_line
;
413 if (strstr(migrate_info
, "CMA"))
414 allocator
|= ALLOCATOR_CMA
;
415 if (strstr(migrate_info
, "slab"))
416 allocator
|= ALLOCATOR_SLAB
;
417 tmp
= strstr(buf
, "__vmalloc_node_range");
426 tmp
= strstr(tmp
, "alloc_pages");
427 if (tmp
&& first_line
<= tmp
&& tmp
< second_line
)
428 allocator
|= ALLOCATOR_VMALLOC
;
431 allocator
= ALLOCATOR_OTHERS
;
435 static bool match_num_list(int num
, int *list
, int list_size
)
437 for (int i
= 0; i
< list_size
; ++i
)
443 static bool match_str_list(const char *str
, char **list
, int list_size
)
445 for (int i
= 0; i
< list_size
; ++i
)
446 if (!strcmp(list
[i
], str
))
451 static bool is_need(char *buf
)
453 if ((filter
& FILTER_PID
) && !match_num_list(get_pid(buf
), fc
.pids
, fc
.pids_size
))
455 if ((filter
& FILTER_TGID
) &&
456 !match_num_list(get_tgid(buf
), fc
.tgids
, fc
.tgids_size
))
459 char *comm
= get_comm(buf
);
461 if ((filter
& FILTER_COMM
) &&
462 !match_str_list(comm
, fc
.comms
, fc
.comms_size
)) {
470 static bool add_list(char *buf
, int len
, char *ext_buf
)
472 if (list_size
== max_size
) {
473 fprintf(stderr
, "max_size too small??\n");
478 list
[list_size
].pid
= get_pid(buf
);
479 list
[list_size
].tgid
= get_tgid(buf
);
480 list
[list_size
].comm
= get_comm(buf
);
481 list
[list_size
].txt
= malloc(len
+1);
482 if (!list
[list_size
].txt
) {
483 fprintf(stderr
, "Out of memory\n");
486 memcpy(list
[list_size
].txt
, buf
, len
);
487 if (sc
.cmps
[0] != compare_ts
) {
488 len
= remove_pattern(&ts_nsec_pattern
, list
[list_size
].txt
, len
);
490 list
[list_size
].txt
[len
] = 0;
491 list
[list_size
].len
= len
;
492 list
[list_size
].num
= 1;
493 list
[list_size
].page_num
= get_page_num(buf
);
495 list
[list_size
].stacktrace
= strchr(list
[list_size
].txt
, '\n') ?: "";
496 if (*list
[list_size
].stacktrace
== '\n')
497 list
[list_size
].stacktrace
++;
498 list
[list_size
].ts_nsec
= get_ts_nsec(buf
);
499 list
[list_size
].allocator
= get_allocator(buf
, ext_buf
);
501 if (list_size
% 1000 == 0) {
502 printf("loaded %d\r", list_size
);
508 static bool parse_cull_args(const char *arg_str
)
511 char **args
= explode(',', arg_str
, &size
);
513 for (int i
= 0; i
< size
; ++i
) {
514 int arg_type
= get_arg_type(args
[i
]);
516 if (arg_type
== ARG_PID
)
518 else if (arg_type
== ARG_TGID
)
520 else if (arg_type
== ARG_COMM
)
522 else if (arg_type
== ARG_STACKTRACE
)
523 cull
|= CULL_STACKTRACE
;
524 else if (arg_type
== ARG_ALLOCATOR
)
525 cull
|= CULL_ALLOCATOR
;
527 free_explode(args
, size
);
531 free_explode(args
, size
);
533 set_single_cmp(compare_num
, SORT_DESC
);
537 static void set_single_cmp(int (*cmp
)(const void *, const void *), int sign
)
539 if (sc
.signs
== NULL
|| sc
.size
< 1)
540 sc
.signs
= calloc(1, sizeof(int));
542 if (sc
.cmps
== NULL
|| sc
.size
< 1)
543 sc
.cmps
= calloc(1, sizeof(int *));
548 static bool parse_sort_args(const char *arg_str
)
552 if (sc
.size
!= 0) { /* reset sort_condition */
558 char **args
= explode(',', arg_str
, &size
);
560 sc
.signs
= calloc(size
, sizeof(int));
561 sc
.cmps
= calloc(size
, sizeof(int *));
562 for (int i
= 0; i
< size
; ++i
) {
565 sc
.signs
[i
] = SORT_ASC
;
566 if (args
[i
][0] == '-' || args
[i
][0] == '+') {
567 if (args
[i
][0] == '-')
568 sc
.signs
[i
] = SORT_DESC
;
572 int arg_type
= get_arg_type(args
[i
]+offset
);
574 if (arg_type
== ARG_PID
)
575 sc
.cmps
[i
] = compare_pid
;
576 else if (arg_type
== ARG_TGID
)
577 sc
.cmps
[i
] = compare_tgid
;
578 else if (arg_type
== ARG_COMM
)
579 sc
.cmps
[i
] = compare_comm
;
580 else if (arg_type
== ARG_STACKTRACE
)
581 sc
.cmps
[i
] = compare_stacktrace
;
582 else if (arg_type
== ARG_ALLOC_TS
)
583 sc
.cmps
[i
] = compare_ts
;
584 else if (arg_type
== ARG_TXT
)
585 sc
.cmps
[i
] = compare_txt
;
586 else if (arg_type
== ARG_ALLOCATOR
)
587 sc
.cmps
[i
] = compare_allocator
;
589 free_explode(args
, size
);
595 free_explode(args
, size
);
599 static int *parse_nums_list(char *arg_str
, int *list_size
)
602 char **args
= explode(',', arg_str
, &size
);
603 int *list
= calloc(size
, sizeof(int));
606 for (int i
= 0; i
< size
; ++i
) {
609 list
[i
] = strtol(args
[i
], &endptr
, 10);
610 if (errno
!= 0 || endptr
== args
[i
] || *endptr
!= '\0') {
616 free_explode(args
, size
);
620 static void print_allocator(FILE *out
, int allocator
)
622 fprintf(out
, "allocated by ");
623 if (allocator
& ALLOCATOR_CMA
)
624 fprintf(out
, "CMA ");
625 if (allocator
& ALLOCATOR_SLAB
)
626 fprintf(out
, "SLAB ");
627 if (allocator
& ALLOCATOR_VMALLOC
)
628 fprintf(out
, "VMALLOC ");
629 if (allocator
& ALLOCATOR_OTHERS
)
630 fprintf(out
, "OTHERS ");
633 #define BUF_SIZE (128 * 1024)
635 static void usage(void)
637 printf("Usage: ./page_owner_sort [OPTIONS] <input> <output>\n"
638 "-a\t\t\tSort by memory allocation time.\n"
639 "-m\t\t\tSort by total memory.\n"
640 "-n\t\t\tSort by task command name.\n"
641 "-p\t\t\tSort by pid.\n"
642 "-P\t\t\tSort by tgid.\n"
643 "-s\t\t\tSort by the stacktrace.\n"
644 "-t\t\t\tSort by number of times record is seen (default).\n\n"
645 "--pid <pidlist>\t\tSelect by pid. This selects the information"
646 " of\n\t\t\tblocks whose process ID numbers appear in <pidlist>.\n"
647 "--tgid <tgidlist>\tSelect by tgid. This selects the information"
648 " of\n\t\t\tblocks whose Thread Group ID numbers appear in "
650 "--name <cmdlist>\tSelect by command name. This selects the"
651 " information\n\t\t\tof blocks whose command name appears in"
653 "--cull <rules>\t\tCull by user-defined rules. <rules> is a "
654 "single\n\t\t\targument in the form of a comma-separated list "
655 "with some\n\t\t\tcommon fields predefined (pid, tgid, comm, "
656 "stacktrace, allocator)\n"
657 "--sort <order>\t\tSpecify sort order as: [+|-]key[,[+|-]key[,...]]\n"
661 int main(int argc
, char **argv
)
665 int i
, count
, compare_flag
;
668 struct option longopts
[] = {
669 { "pid", required_argument
, NULL
, 1 },
670 { "tgid", required_argument
, NULL
, 2 },
671 { "name", required_argument
, NULL
, 3 },
672 { "cull", required_argument
, NULL
, 4 },
673 { "sort", required_argument
, NULL
, 5 },
677 compare_flag
= COMP_NO_FLAG
;
679 while ((opt
= getopt_long(argc
, argv
, "admnpstP", longopts
, NULL
)) != -1)
682 compare_flag
|= COMP_ALLOC
;
688 compare_flag
|= COMP_PAGE_NUM
;
691 compare_flag
|= COMP_PID
;
694 compare_flag
|= COMP_STACK
;
697 compare_flag
|= COMP_NUM
;
700 compare_flag
|= COMP_TGID
;
703 compare_flag
|= COMP_COMM
;
706 filter
= filter
| FILTER_PID
;
707 fc
.pids
= parse_nums_list(optarg
, &fc
.pids_size
);
708 if (fc
.pids
== NULL
) {
709 fprintf(stderr
, "wrong/invalid pid in from the command line:%s\n",
715 filter
= filter
| FILTER_TGID
;
716 fc
.tgids
= parse_nums_list(optarg
, &fc
.tgids_size
);
717 if (fc
.tgids
== NULL
) {
718 fprintf(stderr
, "wrong/invalid tgid in from the command line:%s\n",
724 filter
= filter
| FILTER_COMM
;
725 fc
.comms
= explode(',', optarg
, &fc
.comms_size
);
728 if (!parse_cull_args(optarg
)) {
729 fprintf(stderr
, "wrong argument after --cull option:%s\n",
735 if (!parse_sort_args(optarg
)) {
736 fprintf(stderr
, "wrong argument after --sort option:%s\n",
746 if (optind
>= (argc
- 1)) {
751 /* Only one compare option is allowed, yet we also want handle the
752 * default case were no option is provided, but we still want to
753 * match the behavior of the -t option (compare by number of times
756 switch (compare_flag
) {
758 set_single_cmp(compare_ts
, SORT_ASC
);
761 set_single_cmp(compare_page_num
, SORT_DESC
);
764 set_single_cmp(compare_pid
, SORT_ASC
);
767 set_single_cmp(compare_stacktrace
, SORT_ASC
);
771 set_single_cmp(compare_num
, SORT_DESC
);
774 set_single_cmp(compare_tgid
, SORT_ASC
);
777 set_single_cmp(compare_comm
, SORT_ASC
);
784 fin
= fopen(argv
[optind
], "r");
785 fout
= fopen(argv
[optind
+ 1], "w");
792 if (!check_regcomp(&order_pattern
, "order\\s*([0-9]*),"))
794 if (!check_regcomp(&pid_pattern
, "pid\\s*([0-9]*),"))
796 if (!check_regcomp(&tgid_pattern
, "tgid\\s*([0-9]*) "))
798 if (!check_regcomp(&comm_pattern
, "tgid\\s*[0-9]*\\s*\\((.*)\\),\\s*ts"))
800 if (!check_regcomp(&ts_nsec_pattern
, "ts\\s*([0-9]*)\\s*ns"))
803 fstat(fileno(fin
), &st
);
804 max_size
= st
.st_size
/ 100; /* hack ... */
806 list
= malloc(max_size
* sizeof(*list
));
807 buf
= malloc(BUF_SIZE
);
808 ext_buf
= malloc(BUF_SIZE
);
809 if (!list
|| !buf
|| !ext_buf
) {
810 fprintf(stderr
, "Out of memory\n");
815 int buf_len
= read_block(buf
, ext_buf
, BUF_SIZE
, fin
);
819 if (!add_list(buf
, buf_len
, ext_buf
))
823 printf("loaded %d\n", list_size
);
825 printf("sorting ....\n");
827 qsort(list
, list_size
, sizeof(list
[0]), compare_cull_condition
);
831 for (i
= count
= 0; i
< list_size
; i
++) {
833 compare_cull_condition((void *)(&list
[count
-1]), (void *)(&list
[i
])) != 0) {
834 list
[count
++] = list
[i
];
836 list
[count
-1].num
+= list
[i
].num
;
837 list
[count
-1].page_num
+= list
[i
].page_num
;
841 qsort(list
, count
, sizeof(list
[0]), compare_sort_condition
);
843 for (i
= 0; i
< count
; i
++) {
845 fprintf(fout
, "%d times, %d pages, ", list
[i
].num
, list
[i
].page_num
);
846 print_allocator(fout
, list
[i
].allocator
);
847 fprintf(fout
, ":\n%s\n", list
[i
].txt
);
850 fprintf(fout
, "%d times, %d pages",
851 list
[i
].num
, list
[i
].page_num
);
852 if (cull
& CULL_PID
|| filter
& FILTER_PID
)
853 fprintf(fout
, ", PID %d", list
[i
].pid
);
854 if (cull
& CULL_TGID
|| filter
& FILTER_TGID
)
855 fprintf(fout
, ", TGID %d", list
[i
].tgid
);
856 if (cull
& CULL_COMM
|| filter
& FILTER_COMM
)
857 fprintf(fout
, ", task_comm_name: %s", list
[i
].comm
);
858 if (cull
& CULL_ALLOCATOR
) {
860 print_allocator(fout
, list
[i
].allocator
);
862 if (cull
& CULL_STACKTRACE
)
863 fprintf(fout
, ":\n%s", list
[i
].stacktrace
);
876 regfree(&ts_nsec_pattern
);
878 regfree(&comm_pattern
);
880 regfree(&tgid_pattern
);
882 regfree(&pid_pattern
);
884 regfree(&order_pattern
);