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
);
386 static int get_arg_type(const char *arg
)
388 if (!strcmp(arg
, "pid") || !strcmp(arg
, "p"))
390 else if (!strcmp(arg
, "tgid") || !strcmp(arg
, "tg"))
392 else if (!strcmp(arg
, "name") || !strcmp(arg
, "n"))
394 else if (!strcmp(arg
, "stacktrace") || !strcmp(arg
, "st"))
395 return ARG_STACKTRACE
;
396 else if (!strcmp(arg
, "txt") || !strcmp(arg
, "T"))
398 else if (!strcmp(arg
, "alloc_ts") || !strcmp(arg
, "at"))
400 else if (!strcmp(arg
, "allocator") || !strcmp(arg
, "ator"))
401 return ARG_ALLOCATOR
;
407 static int get_allocator(const char *buf
, const char *migrate_info
)
409 char *tmp
, *first_line
, *second_line
;
412 if (strstr(migrate_info
, "CMA"))
413 allocator
|= ALLOCATOR_CMA
;
414 if (strstr(migrate_info
, "slab"))
415 allocator
|= ALLOCATOR_SLAB
;
416 tmp
= strstr(buf
, "__vmalloc_node_range");
425 tmp
= strstr(tmp
, "alloc_pages");
426 if (tmp
&& first_line
<= tmp
&& tmp
< second_line
)
427 allocator
|= ALLOCATOR_VMALLOC
;
430 allocator
= ALLOCATOR_OTHERS
;
434 static bool match_num_list(int num
, int *list
, int list_size
)
436 for (int i
= 0; i
< list_size
; ++i
)
442 static bool match_str_list(const char *str
, char **list
, int list_size
)
444 for (int i
= 0; i
< list_size
; ++i
)
445 if (!strcmp(list
[i
], str
))
450 static bool is_need(char *buf
)
452 if ((filter
& FILTER_PID
) && !match_num_list(get_pid(buf
), fc
.pids
, fc
.pids_size
))
454 if ((filter
& FILTER_TGID
) &&
455 !match_num_list(get_tgid(buf
), fc
.tgids
, fc
.tgids_size
))
458 char *comm
= get_comm(buf
);
460 if ((filter
& FILTER_COMM
) &&
461 !match_str_list(comm
, fc
.comms
, fc
.comms_size
)) {
469 static bool add_list(char *buf
, int len
, char *ext_buf
)
471 if (list_size
== max_size
) {
472 fprintf(stderr
, "max_size too small??\n");
477 list
[list_size
].pid
= get_pid(buf
);
478 list
[list_size
].tgid
= get_tgid(buf
);
479 list
[list_size
].comm
= get_comm(buf
);
480 list
[list_size
].txt
= malloc(len
+1);
481 if (!list
[list_size
].txt
) {
482 fprintf(stderr
, "Out of memory\n");
485 memcpy(list
[list_size
].txt
, buf
, len
);
486 if (sc
.cmps
[0] != compare_ts
) {
487 len
= remove_pattern(&ts_nsec_pattern
, list
[list_size
].txt
, len
);
489 list
[list_size
].txt
[len
] = 0;
490 list
[list_size
].len
= len
;
491 list
[list_size
].num
= 1;
492 list
[list_size
].page_num
= get_page_num(buf
);
494 list
[list_size
].stacktrace
= strchr(list
[list_size
].txt
, '\n') ?: "";
495 if (*list
[list_size
].stacktrace
== '\n')
496 list
[list_size
].stacktrace
++;
497 list
[list_size
].ts_nsec
= get_ts_nsec(buf
);
498 list
[list_size
].allocator
= get_allocator(buf
, ext_buf
);
500 if (list_size
% 1000 == 0) {
501 printf("loaded %d\r", list_size
);
507 static bool parse_cull_args(const char *arg_str
)
510 char **args
= explode(',', arg_str
, &size
);
512 for (int i
= 0; i
< size
; ++i
) {
513 int arg_type
= get_arg_type(args
[i
]);
515 if (arg_type
== ARG_PID
)
517 else if (arg_type
== ARG_TGID
)
519 else if (arg_type
== ARG_COMM
)
521 else if (arg_type
== ARG_STACKTRACE
)
522 cull
|= CULL_STACKTRACE
;
523 else if (arg_type
== ARG_ALLOCATOR
)
524 cull
|= CULL_ALLOCATOR
;
526 free_explode(args
, size
);
530 free_explode(args
, size
);
532 set_single_cmp(compare_num
, SORT_DESC
);
536 static void set_single_cmp(int (*cmp
)(const void *, const void *), int sign
)
538 if (sc
.signs
== NULL
|| sc
.size
< 1)
539 sc
.signs
= calloc(1, sizeof(int));
541 if (sc
.cmps
== NULL
|| sc
.size
< 1)
542 sc
.cmps
= calloc(1, sizeof(int *));
547 static bool parse_sort_args(const char *arg_str
)
551 if (sc
.size
!= 0) { /* reset sort_condition */
557 char **args
= explode(',', arg_str
, &size
);
559 sc
.signs
= calloc(size
, sizeof(int));
560 sc
.cmps
= calloc(size
, sizeof(int *));
561 for (int i
= 0; i
< size
; ++i
) {
564 sc
.signs
[i
] = SORT_ASC
;
565 if (args
[i
][0] == '-' || args
[i
][0] == '+') {
566 if (args
[i
][0] == '-')
567 sc
.signs
[i
] = SORT_DESC
;
571 int arg_type
= get_arg_type(args
[i
]+offset
);
573 if (arg_type
== ARG_PID
)
574 sc
.cmps
[i
] = compare_pid
;
575 else if (arg_type
== ARG_TGID
)
576 sc
.cmps
[i
] = compare_tgid
;
577 else if (arg_type
== ARG_COMM
)
578 sc
.cmps
[i
] = compare_comm
;
579 else if (arg_type
== ARG_STACKTRACE
)
580 sc
.cmps
[i
] = compare_stacktrace
;
581 else if (arg_type
== ARG_ALLOC_TS
)
582 sc
.cmps
[i
] = compare_ts
;
583 else if (arg_type
== ARG_TXT
)
584 sc
.cmps
[i
] = compare_txt
;
585 else if (arg_type
== ARG_ALLOCATOR
)
586 sc
.cmps
[i
] = compare_allocator
;
588 free_explode(args
, size
);
594 free_explode(args
, size
);
598 static int *parse_nums_list(char *arg_str
, int *list_size
)
601 char **args
= explode(',', arg_str
, &size
);
602 int *list
= calloc(size
, sizeof(int));
605 for (int i
= 0; i
< size
; ++i
) {
608 list
[i
] = strtol(args
[i
], &endptr
, 10);
609 if (errno
!= 0 || endptr
== args
[i
] || *endptr
!= '\0') {
615 free_explode(args
, size
);
619 static void print_allocator(FILE *out
, int allocator
)
621 fprintf(out
, "allocated by ");
622 if (allocator
& ALLOCATOR_CMA
)
623 fprintf(out
, "CMA ");
624 if (allocator
& ALLOCATOR_SLAB
)
625 fprintf(out
, "SLAB ");
626 if (allocator
& ALLOCATOR_VMALLOC
)
627 fprintf(out
, "VMALLOC ");
628 if (allocator
& ALLOCATOR_OTHERS
)
629 fprintf(out
, "OTHERS ");
632 #define BUF_SIZE (128 * 1024)
634 static void usage(void)
636 printf("Usage: ./page_owner_sort [OPTIONS] <input> <output>\n"
637 "-a\t\t\tSort by memory allocation time.\n"
638 "-m\t\t\tSort by total memory.\n"
639 "-n\t\t\tSort by task command name.\n"
640 "-p\t\t\tSort by pid.\n"
641 "-P\t\t\tSort by tgid.\n"
642 "-s\t\t\tSort by the stacktrace.\n"
643 "-t\t\t\tSort by number of times record is seen (default).\n\n"
644 "--pid <pidlist>\t\tSelect by pid. This selects the information"
645 " of\n\t\t\tblocks whose process ID numbers appear in <pidlist>.\n"
646 "--tgid <tgidlist>\tSelect by tgid. This selects the information"
647 " of\n\t\t\tblocks whose Thread Group ID numbers appear in "
649 "--name <cmdlist>\tSelect by command name. This selects the"
650 " information\n\t\t\tof blocks whose command name appears in"
652 "--cull <rules>\t\tCull by user-defined rules. <rules> is a "
653 "single\n\t\t\targument in the form of a comma-separated list "
654 "with some\n\t\t\tcommon fields predefined (pid, tgid, comm, "
655 "stacktrace, allocator)\n"
656 "--sort <order>\t\tSpecify sort order as: [+|-]key[,[+|-]key[,...]]\n"
660 int main(int argc
, char **argv
)
664 int i
, count
, compare_flag
;
667 struct option longopts
[] = {
668 { "pid", required_argument
, NULL
, 1 },
669 { "tgid", required_argument
, NULL
, 2 },
670 { "name", required_argument
, NULL
, 3 },
671 { "cull", required_argument
, NULL
, 4 },
672 { "sort", required_argument
, NULL
, 5 },
676 compare_flag
= COMP_NO_FLAG
;
678 while ((opt
= getopt_long(argc
, argv
, "admnpstP", longopts
, NULL
)) != -1)
681 compare_flag
|= COMP_ALLOC
;
687 compare_flag
|= COMP_PAGE_NUM
;
690 compare_flag
|= COMP_PID
;
693 compare_flag
|= COMP_STACK
;
696 compare_flag
|= COMP_NUM
;
699 compare_flag
|= COMP_TGID
;
702 compare_flag
|= COMP_COMM
;
705 filter
= filter
| FILTER_PID
;
706 fc
.pids
= parse_nums_list(optarg
, &fc
.pids_size
);
707 if (fc
.pids
== NULL
) {
708 fprintf(stderr
, "wrong/invalid pid in from the command line:%s\n",
714 filter
= filter
| FILTER_TGID
;
715 fc
.tgids
= parse_nums_list(optarg
, &fc
.tgids_size
);
716 if (fc
.tgids
== NULL
) {
717 fprintf(stderr
, "wrong/invalid tgid in from the command line:%s\n",
723 filter
= filter
| FILTER_COMM
;
724 fc
.comms
= explode(',', optarg
, &fc
.comms_size
);
727 if (!parse_cull_args(optarg
)) {
728 fprintf(stderr
, "wrong argument after --cull option:%s\n",
734 if (!parse_sort_args(optarg
)) {
735 fprintf(stderr
, "wrong argument after --sort option:%s\n",
745 if (optind
>= (argc
- 1)) {
750 /* Only one compare option is allowed, yet we also want handle the
751 * default case were no option is provided, but we still want to
752 * match the behavior of the -t option (compare by number of times
755 switch (compare_flag
) {
757 set_single_cmp(compare_ts
, SORT_ASC
);
760 set_single_cmp(compare_page_num
, SORT_DESC
);
763 set_single_cmp(compare_pid
, SORT_ASC
);
766 set_single_cmp(compare_stacktrace
, SORT_ASC
);
770 set_single_cmp(compare_num
, SORT_DESC
);
773 set_single_cmp(compare_tgid
, SORT_ASC
);
776 set_single_cmp(compare_comm
, SORT_ASC
);
783 fin
= fopen(argv
[optind
], "r");
784 fout
= fopen(argv
[optind
+ 1], "w");
791 if (!check_regcomp(&order_pattern
, "order\\s*([0-9]*),"))
793 if (!check_regcomp(&pid_pattern
, "pid\\s*([0-9]*),"))
795 if (!check_regcomp(&tgid_pattern
, "tgid\\s*([0-9]*) "))
797 if (!check_regcomp(&comm_pattern
, "tgid\\s*[0-9]*\\s*\\((.*)\\),\\s*ts"))
799 if (!check_regcomp(&ts_nsec_pattern
, "ts\\s*([0-9]*)\\s*ns"))
802 fstat(fileno(fin
), &st
);
803 max_size
= st
.st_size
/ 100; /* hack ... */
805 list
= malloc(max_size
* sizeof(*list
));
806 buf
= malloc(BUF_SIZE
);
807 ext_buf
= malloc(BUF_SIZE
);
808 if (!list
|| !buf
|| !ext_buf
) {
809 fprintf(stderr
, "Out of memory\n");
814 int buf_len
= read_block(buf
, ext_buf
, BUF_SIZE
, fin
);
818 if (!add_list(buf
, buf_len
, ext_buf
))
822 printf("loaded %d\n", list_size
);
824 printf("sorting ....\n");
826 qsort(list
, list_size
, sizeof(list
[0]), compare_cull_condition
);
830 for (i
= count
= 0; i
< list_size
; i
++) {
832 compare_cull_condition((void *)(&list
[count
-1]), (void *)(&list
[i
])) != 0) {
833 list
[count
++] = list
[i
];
835 list
[count
-1].num
+= list
[i
].num
;
836 list
[count
-1].page_num
+= list
[i
].page_num
;
840 qsort(list
, count
, sizeof(list
[0]), compare_sort_condition
);
842 for (i
= 0; i
< count
; i
++) {
844 fprintf(fout
, "%d times, %d pages, ", list
[i
].num
, list
[i
].page_num
);
845 print_allocator(fout
, list
[i
].allocator
);
846 fprintf(fout
, ":\n%s\n", list
[i
].txt
);
849 fprintf(fout
, "%d times, %d pages",
850 list
[i
].num
, list
[i
].page_num
);
851 if (cull
& CULL_PID
|| filter
& FILTER_PID
)
852 fprintf(fout
, ", PID %d", list
[i
].pid
);
853 if (cull
& CULL_TGID
|| filter
& FILTER_TGID
)
854 fprintf(fout
, ", TGID %d", list
[i
].tgid
);
855 if (cull
& CULL_COMM
|| filter
& FILTER_COMM
)
856 fprintf(fout
, ", task_comm_name: %s", list
[i
].comm
);
857 if (cull
& CULL_ALLOCATOR
) {
859 print_allocator(fout
, list
[i
].allocator
);
861 if (cull
& CULL_STACKTRACE
)
862 fprintf(fout
, ":\n%s", list
[i
].stacktrace
);
875 regfree(&ts_nsec_pattern
);
877 regfree(&comm_pattern
);
879 regfree(&tgid_pattern
);
881 regfree(&pid_pattern
);
883 regfree(&order_pattern
);