1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * User-space helper to sort the output of /sys/kernel/debug/page_owner
4  *
5  * Example use:
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
10  *
11  * See Documentation/mm/page_owner.rst
12 */
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <string.h>
21 #include <regex.h>
22 #include <errno.h>
23 #include <linux/types.h>
24 #include <getopt.h>
25 
26 #define bool int
27 #define true 1
28 #define false 0
29 #define TASK_COMM_LEN 16
30 
31 struct block_list {
32 	char *txt;
33 	char *comm; // task command name
34 	char *stacktrace;
35 	__u64 ts_nsec;
36 	__u64 free_ts_nsec;
37 	int len;
38 	int num;
39 	int page_num;
40 	pid_t pid;
41 	pid_t tgid;
42 	int allocator;
43 };
44 enum FILTER_BIT {
45 	FILTER_UNRELEASE = 1<<1,
46 	FILTER_PID = 1<<2,
47 	FILTER_TGID = 1<<3,
48 	FILTER_COMM = 1<<4
49 };
50 enum CULL_BIT {
51 	CULL_UNRELEASE = 1<<1,
52 	CULL_PID = 1<<2,
53 	CULL_TGID = 1<<3,
54 	CULL_COMM = 1<<4,
55 	CULL_STACKTRACE = 1<<5,
56 	CULL_ALLOCATOR = 1<<6
57 };
58 enum ALLOCATOR_BIT {
59 	ALLOCATOR_CMA = 1<<1,
60 	ALLOCATOR_SLAB = 1<<2,
61 	ALLOCATOR_VMALLOC = 1<<3,
62 	ALLOCATOR_OTHERS = 1<<4
63 };
64 enum ARG_TYPE {
65 	ARG_TXT, ARG_COMM, ARG_STACKTRACE, ARG_ALLOC_TS, ARG_FREE_TS,
66 	ARG_CULL_TIME, ARG_PAGE_NUM, ARG_PID, ARG_TGID, ARG_UNKNOWN, ARG_FREE,
67 	ARG_ALLOCATOR
68 };
69 enum SORT_ORDER {
70 	SORT_ASC = 1,
71 	SORT_DESC = -1,
72 };
73 struct filter_condition {
74 	pid_t *pids;
75 	pid_t *tgids;
76 	char **comms;
77 	int pids_size;
78 	int tgids_size;
79 	int comms_size;
80 };
81 struct sort_condition {
82 	int (**cmps)(const void *, const void *);
83 	int *signs;
84 	int size;
85 };
86 static struct filter_condition fc;
87 static struct sort_condition sc;
88 static regex_t order_pattern;
89 static regex_t pid_pattern;
90 static regex_t tgid_pattern;
91 static regex_t comm_pattern;
92 static regex_t ts_nsec_pattern;
93 static regex_t free_ts_nsec_pattern;
94 static struct block_list *list;
95 static int list_size;
96 static int max_size;
97 static int cull;
98 static int filter;
99 static bool debug_on;
100 
101 static void set_single_cmp(int (*cmp)(const void *, const void *), int sign);
102 
read_block(char * buf,char * ext_buf,int buf_size,FILE * fin)103 int read_block(char *buf, char *ext_buf, int buf_size, FILE *fin)
104 {
105 	char *curr = buf, *const buf_end = buf + buf_size;
106 
107 	while (buf_end - curr > 1 && fgets(curr, buf_end - curr, fin)) {
108 		if (*curr == '\n') { /* empty line */
109 			return curr - buf;
110 		}
111 		if (!strncmp(curr, "PFN", 3)) {
112 			strcpy(ext_buf, curr);
113 			continue;
114 		}
115 		curr += strlen(curr);
116 	}
117 
118 	return -1; /* EOF or no space left in buf. */
119 }
120 
compare_txt(const void * p1,const void * p2)121 static int compare_txt(const void *p1, const void *p2)
122 {
123 	const struct block_list *l1 = p1, *l2 = p2;
124 
125 	return strcmp(l1->txt, l2->txt);
126 }
127 
compare_stacktrace(const void * p1,const void * p2)128 static int compare_stacktrace(const void *p1, const void *p2)
129 {
130 	const struct block_list *l1 = p1, *l2 = p2;
131 
132 	return strcmp(l1->stacktrace, l2->stacktrace);
133 }
134 
compare_num(const void * p1,const void * p2)135 static int compare_num(const void *p1, const void *p2)
136 {
137 	const struct block_list *l1 = p1, *l2 = p2;
138 
139 	return l1->num - l2->num;
140 }
141 
compare_page_num(const void * p1,const void * p2)142 static int compare_page_num(const void *p1, const void *p2)
143 {
144 	const struct block_list *l1 = p1, *l2 = p2;
145 
146 	return l1->page_num - l2->page_num;
147 }
148 
compare_pid(const void * p1,const void * p2)149 static int compare_pid(const void *p1, const void *p2)
150 {
151 	const struct block_list *l1 = p1, *l2 = p2;
152 
153 	return l1->pid - l2->pid;
154 }
155 
compare_tgid(const void * p1,const void * p2)156 static int compare_tgid(const void *p1, const void *p2)
157 {
158 	const struct block_list *l1 = p1, *l2 = p2;
159 
160 	return l1->tgid - l2->tgid;
161 }
162 
compare_allocator(const void * p1,const void * p2)163 static int compare_allocator(const void *p1, const void *p2)
164 {
165 	const struct block_list *l1 = p1, *l2 = p2;
166 
167 	return l1->allocator - l2->allocator;
168 }
169 
compare_comm(const void * p1,const void * p2)170 static int compare_comm(const void *p1, const void *p2)
171 {
172 	const struct block_list *l1 = p1, *l2 = p2;
173 
174 	return strcmp(l1->comm, l2->comm);
175 }
176 
compare_ts(const void * p1,const void * p2)177 static int compare_ts(const void *p1, const void *p2)
178 {
179 	const struct block_list *l1 = p1, *l2 = p2;
180 
181 	return l1->ts_nsec < l2->ts_nsec ? -1 : 1;
182 }
183 
compare_free_ts(const void * p1,const void * p2)184 static int compare_free_ts(const void *p1, const void *p2)
185 {
186 	const struct block_list *l1 = p1, *l2 = p2;
187 
188 	return l1->free_ts_nsec < l2->free_ts_nsec ? -1 : 1;
189 }
190 
compare_release(const void * p1,const void * p2)191 static int compare_release(const void *p1, const void *p2)
192 {
193 	const struct block_list *l1 = p1, *l2 = p2;
194 
195 	if (!l1->free_ts_nsec && !l2->free_ts_nsec)
196 		return 0;
197 	if (l1->free_ts_nsec && l2->free_ts_nsec)
198 		return 0;
199 	return l1->free_ts_nsec ? 1 : -1;
200 }
201 
compare_cull_condition(const void * p1,const void * p2)202 static int compare_cull_condition(const void *p1, const void *p2)
203 {
204 	if (cull == 0)
205 		return compare_txt(p1, p2);
206 	if ((cull & CULL_STACKTRACE) && compare_stacktrace(p1, p2))
207 		return compare_stacktrace(p1, p2);
208 	if ((cull & CULL_PID) && compare_pid(p1, p2))
209 		return compare_pid(p1, p2);
210 	if ((cull & CULL_TGID) && compare_tgid(p1, p2))
211 		return compare_tgid(p1, p2);
212 	if ((cull & CULL_COMM) && compare_comm(p1, p2))
213 		return compare_comm(p1, p2);
214 	if ((cull & CULL_UNRELEASE) && compare_release(p1, p2))
215 		return compare_release(p1, p2);
216 	if ((cull & CULL_ALLOCATOR) && compare_allocator(p1, p2))
217 		return compare_allocator(p1, p2);
218 	return 0;
219 }
220 
compare_sort_condition(const void * p1,const void * p2)221 static int compare_sort_condition(const void *p1, const void *p2)
222 {
223 	int cmp = 0;
224 
225 	for (int i = 0; i < sc.size; ++i)
226 		if (cmp == 0)
227 			cmp = sc.signs[i] * sc.cmps[i](p1, p2);
228 	return cmp;
229 }
230 
search_pattern(regex_t * pattern,char * pattern_str,char * buf)231 static int search_pattern(regex_t *pattern, char *pattern_str, char *buf)
232 {
233 	int err, val_len;
234 	regmatch_t pmatch[2];
235 
236 	err = regexec(pattern, buf, 2, pmatch, REG_NOTBOL);
237 	if (err != 0 || pmatch[1].rm_so == -1) {
238 		if (debug_on)
239 			fprintf(stderr, "no matching pattern in %s\n", buf);
240 		return -1;
241 	}
242 	val_len = pmatch[1].rm_eo - pmatch[1].rm_so;
243 
244 	memcpy(pattern_str, buf + pmatch[1].rm_so, val_len);
245 
246 	return 0;
247 }
248 
check_regcomp(regex_t * pattern,const char * regex)249 static void check_regcomp(regex_t *pattern, const char *regex)
250 {
251 	int err;
252 
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);
256 		exit(1);
257 	}
258 }
259 
explode(char sep,const char * str,int * size)260 static char **explode(char sep, const char *str, int *size)
261 {
262 	int count = 0, len = strlen(str);
263 	int lastindex = -1, j = 0;
264 
265 	for (int i = 0; i < len; i++)
266 		if (str[i] == sep)
267 			count++;
268 	char **ret = calloc(++count, sizeof(char *));
269 
270 	for (int i = 0; i < len; i++) {
271 		if (str[i] == sep) {
272 			ret[j] = calloc(i - lastindex, sizeof(char));
273 			memcpy(ret[j++], str + lastindex + 1, i - lastindex - 1);
274 			lastindex = i;
275 		}
276 	}
277 	if (lastindex <= len - 1) {
278 		ret[j] = calloc(len - lastindex, sizeof(char));
279 		memcpy(ret[j++], str + lastindex + 1, strlen(str) - 1 - lastindex);
280 	}
281 	*size = j;
282 	return ret;
283 }
284 
free_explode(char ** arr,int size)285 static void free_explode(char **arr, int size)
286 {
287 	for (int i = 0; i < size; i++)
288 		free(arr[i]);
289 	free(arr);
290 }
291 
292 # define FIELD_BUFF 25
293 
get_page_num(char * buf)294 static int get_page_num(char *buf)
295 {
296 	int order_val;
297 	char order_str[FIELD_BUFF] = {0};
298 	char *endptr;
299 
300 	search_pattern(&order_pattern, order_str, buf);
301 	errno = 0;
302 	order_val = strtol(order_str, &endptr, 10);
303 	if (order_val > 64 || errno != 0 || endptr == order_str || *endptr != '\0') {
304 		if (debug_on)
305 			fprintf(stderr, "wrong order in follow buf:\n%s\n", buf);
306 		return 0;
307 	}
308 
309 	return 1 << order_val;
310 }
311 
get_pid(char * buf)312 static pid_t get_pid(char *buf)
313 {
314 	pid_t pid;
315 	char pid_str[FIELD_BUFF] = {0};
316 	char *endptr;
317 
318 	search_pattern(&pid_pattern, pid_str, buf);
319 	errno = 0;
320 	pid = strtol(pid_str, &endptr, 10);
321 	if (errno != 0 || endptr == pid_str || *endptr != '\0') {
322 		if (debug_on)
323 			fprintf(stderr, "wrong/invalid pid in follow buf:\n%s\n", buf);
324 		return -1;
325 	}
326 
327 	return pid;
328 
329 }
330 
get_tgid(char * buf)331 static pid_t get_tgid(char *buf)
332 {
333 	pid_t tgid;
334 	char tgid_str[FIELD_BUFF] = {0};
335 	char *endptr;
336 
337 	search_pattern(&tgid_pattern, tgid_str, buf);
338 	errno = 0;
339 	tgid = strtol(tgid_str, &endptr, 10);
340 	if (errno != 0 || endptr == tgid_str || *endptr != '\0') {
341 		if (debug_on)
342 			fprintf(stderr, "wrong/invalid tgid in follow buf:\n%s\n", buf);
343 		return -1;
344 	}
345 
346 	return tgid;
347 
348 }
349 
get_ts_nsec(char * buf)350 static __u64 get_ts_nsec(char *buf)
351 {
352 	__u64 ts_nsec;
353 	char ts_nsec_str[FIELD_BUFF] = {0};
354 	char *endptr;
355 
356 	search_pattern(&ts_nsec_pattern, ts_nsec_str, buf);
357 	errno = 0;
358 	ts_nsec = strtoull(ts_nsec_str, &endptr, 10);
359 	if (errno != 0 || endptr == ts_nsec_str || *endptr != '\0') {
360 		if (debug_on)
361 			fprintf(stderr, "wrong ts_nsec in follow buf:\n%s\n", buf);
362 		return -1;
363 	}
364 
365 	return ts_nsec;
366 }
367 
get_free_ts_nsec(char * buf)368 static __u64 get_free_ts_nsec(char *buf)
369 {
370 	__u64 free_ts_nsec;
371 	char free_ts_nsec_str[FIELD_BUFF] = {0};
372 	char *endptr;
373 
374 	search_pattern(&free_ts_nsec_pattern, free_ts_nsec_str, buf);
375 	errno = 0;
376 	free_ts_nsec = strtoull(free_ts_nsec_str, &endptr, 10);
377 	if (errno != 0 || endptr == free_ts_nsec_str || *endptr != '\0') {
378 		if (debug_on)
379 			fprintf(stderr, "wrong free_ts_nsec in follow buf:\n%s\n", buf);
380 		return -1;
381 	}
382 
383 	return free_ts_nsec;
384 }
385 
get_comm(char * buf)386 static char *get_comm(char *buf)
387 {
388 	char *comm_str = malloc(TASK_COMM_LEN);
389 
390 	memset(comm_str, 0, TASK_COMM_LEN);
391 
392 	search_pattern(&comm_pattern, comm_str, buf);
393 	errno = 0;
394 	if (errno != 0) {
395 		if (debug_on)
396 			fprintf(stderr, "wrong comm in follow buf:\n%s\n", buf);
397 		return NULL;
398 	}
399 
400 	return comm_str;
401 }
402 
get_arg_type(const char * arg)403 static int get_arg_type(const char *arg)
404 {
405 	if (!strcmp(arg, "pid") || !strcmp(arg, "p"))
406 		return ARG_PID;
407 	else if (!strcmp(arg, "tgid") || !strcmp(arg, "tg"))
408 		return ARG_TGID;
409 	else if (!strcmp(arg, "name") || !strcmp(arg, "n"))
410 		return  ARG_COMM;
411 	else if (!strcmp(arg, "stacktrace") || !strcmp(arg, "st"))
412 		return ARG_STACKTRACE;
413 	else if (!strcmp(arg, "free") || !strcmp(arg, "f"))
414 		return ARG_FREE;
415 	else if (!strcmp(arg, "txt") || !strcmp(arg, "T"))
416 		return ARG_TXT;
417 	else if (!strcmp(arg, "free_ts") || !strcmp(arg, "ft"))
418 		return ARG_FREE_TS;
419 	else if (!strcmp(arg, "alloc_ts") || !strcmp(arg, "at"))
420 		return ARG_ALLOC_TS;
421 	else if (!strcmp(arg, "allocator") || !strcmp(arg, "ator"))
422 		return ARG_ALLOCATOR;
423 	else {
424 		return ARG_UNKNOWN;
425 	}
426 }
427 
get_allocator(const char * buf,const char * migrate_info)428 static int get_allocator(const char *buf, const char *migrate_info)
429 {
430 	char *tmp, *first_line, *second_line;
431 	int allocator = 0;
432 
433 	if (strstr(migrate_info, "CMA"))
434 		allocator |= ALLOCATOR_CMA;
435 	if (strstr(migrate_info, "slab"))
436 		allocator |= ALLOCATOR_SLAB;
437 	tmp = strstr(buf, "__vmalloc_node_range");
438 	if (tmp) {
439 		second_line = tmp;
440 		while (*tmp != '\n')
441 			tmp--;
442 		tmp--;
443 		while (*tmp != '\n')
444 			tmp--;
445 		first_line = ++tmp;
446 		tmp = strstr(tmp, "alloc_pages");
447 		if (tmp && first_line <= tmp && tmp < second_line)
448 			allocator |= ALLOCATOR_VMALLOC;
449 	}
450 	if (allocator == 0)
451 		allocator = ALLOCATOR_OTHERS;
452 	return allocator;
453 }
454 
match_num_list(int num,int * list,int list_size)455 static bool match_num_list(int num, int *list, int list_size)
456 {
457 	for (int i = 0; i < list_size; ++i)
458 		if (list[i] == num)
459 			return true;
460 	return false;
461 }
462 
match_str_list(const char * str,char ** list,int list_size)463 static bool match_str_list(const char *str, char **list, int list_size)
464 {
465 	for (int i = 0; i < list_size; ++i)
466 		if (!strcmp(list[i], str))
467 			return true;
468 	return false;
469 }
470 
is_need(char * buf)471 static bool is_need(char *buf)
472 {
473 	__u64 ts_nsec, free_ts_nsec;
474 
475 	ts_nsec = get_ts_nsec(buf);
476 	free_ts_nsec = get_free_ts_nsec(buf);
477 
478 	if ((filter & FILTER_UNRELEASE) && free_ts_nsec != 0 && ts_nsec < free_ts_nsec)
479 		return false;
480 	if ((filter & FILTER_PID) && !match_num_list(get_pid(buf), fc.pids, fc.pids_size))
481 		return false;
482 	if ((filter & FILTER_TGID) &&
483 		!match_num_list(get_tgid(buf), fc.tgids, fc.tgids_size))
484 		return false;
485 
486 	char *comm = get_comm(buf);
487 
488 	if ((filter & FILTER_COMM) &&
489 	!match_str_list(comm, fc.comms, fc.comms_size)) {
490 		free(comm);
491 		return false;
492 	}
493 	free(comm);
494 	return true;
495 }
496 
add_list(char * buf,int len,char * ext_buf)497 static void add_list(char *buf, int len, char *ext_buf)
498 {
499 	if (list_size != 0 &&
500 		len == list[list_size-1].len &&
501 		memcmp(buf, list[list_size-1].txt, len) == 0) {
502 		list[list_size-1].num++;
503 		list[list_size-1].page_num += get_page_num(buf);
504 		return;
505 	}
506 	if (list_size == max_size) {
507 		fprintf(stderr, "max_size too small??\n");
508 		exit(1);
509 	}
510 	if (!is_need(buf))
511 		return;
512 	list[list_size].pid = get_pid(buf);
513 	list[list_size].tgid = get_tgid(buf);
514 	list[list_size].comm = get_comm(buf);
515 	list[list_size].txt = malloc(len+1);
516 	if (!list[list_size].txt) {
517 		fprintf(stderr, "Out of memory\n");
518 		exit(1);
519 	}
520 	memcpy(list[list_size].txt, buf, len);
521 	list[list_size].txt[len] = 0;
522 	list[list_size].len = len;
523 	list[list_size].num = 1;
524 	list[list_size].page_num = get_page_num(buf);
525 
526 	list[list_size].stacktrace = strchr(list[list_size].txt, '\n') ?: "";
527 	if (*list[list_size].stacktrace == '\n')
528 		list[list_size].stacktrace++;
529 	list[list_size].ts_nsec = get_ts_nsec(buf);
530 	list[list_size].free_ts_nsec = get_free_ts_nsec(buf);
531 	list[list_size].allocator = get_allocator(buf, ext_buf);
532 	list_size++;
533 	if (list_size % 1000 == 0) {
534 		printf("loaded %d\r", list_size);
535 		fflush(stdout);
536 	}
537 }
538 
parse_cull_args(const char * arg_str)539 static bool parse_cull_args(const char *arg_str)
540 {
541 	int size = 0;
542 	char **args = explode(',', arg_str, &size);
543 
544 	for (int i = 0; i < size; ++i) {
545 		int arg_type = get_arg_type(args[i]);
546 
547 		if (arg_type == ARG_PID)
548 			cull |= CULL_PID;
549 		else if (arg_type == ARG_TGID)
550 			cull |= CULL_TGID;
551 		else if (arg_type == ARG_COMM)
552 			cull |= CULL_COMM;
553 		else if (arg_type == ARG_STACKTRACE)
554 			cull |= CULL_STACKTRACE;
555 		else if (arg_type == ARG_FREE)
556 			cull |= CULL_UNRELEASE;
557 		else if (arg_type == ARG_ALLOCATOR)
558 			cull |= CULL_ALLOCATOR;
559 		else {
560 			free_explode(args, size);
561 			return false;
562 		}
563 	}
564 	free_explode(args, size);
565 	if (sc.size == 0)
566 		set_single_cmp(compare_num, SORT_DESC);
567 	return true;
568 }
569 
set_single_cmp(int (* cmp)(const void *,const void *),int sign)570 static void set_single_cmp(int (*cmp)(const void *, const void *), int sign)
571 {
572 	if (sc.signs == NULL || sc.size < 1)
573 		sc.signs = calloc(1, sizeof(int));
574 	sc.signs[0] = sign;
575 	if (sc.cmps == NULL || sc.size < 1)
576 		sc.cmps = calloc(1, sizeof(int *));
577 	sc.cmps[0] = cmp;
578 	sc.size = 1;
579 }
580 
parse_sort_args(const char * arg_str)581 static bool parse_sort_args(const char *arg_str)
582 {
583 	int size = 0;
584 
585 	if (sc.size != 0) { /* reset sort_condition */
586 		free(sc.signs);
587 		free(sc.cmps);
588 		size = 0;
589 	}
590 
591 	char **args = explode(',', arg_str, &size);
592 
593 	sc.signs = calloc(size, sizeof(int));
594 	sc.cmps = calloc(size, sizeof(int *));
595 	for (int i = 0; i < size; ++i) {
596 		int offset = 0;
597 
598 		sc.signs[i] = SORT_ASC;
599 		if (args[i][0] == '-' || args[i][0] == '+') {
600 			if (args[i][0] == '-')
601 				sc.signs[i] = SORT_DESC;
602 			offset = 1;
603 		}
604 
605 		int arg_type = get_arg_type(args[i]+offset);
606 
607 		if (arg_type == ARG_PID)
608 			sc.cmps[i] = compare_pid;
609 		else if (arg_type == ARG_TGID)
610 			sc.cmps[i] = compare_tgid;
611 		else if (arg_type == ARG_COMM)
612 			sc.cmps[i] = compare_comm;
613 		else if (arg_type == ARG_STACKTRACE)
614 			sc.cmps[i] = compare_stacktrace;
615 		else if (arg_type == ARG_ALLOC_TS)
616 			sc.cmps[i] = compare_ts;
617 		else if (arg_type == ARG_FREE_TS)
618 			sc.cmps[i] = compare_free_ts;
619 		else if (arg_type == ARG_TXT)
620 			sc.cmps[i] = compare_txt;
621 		else if (arg_type == ARG_ALLOCATOR)
622 			sc.cmps[i] = compare_allocator;
623 		else {
624 			free_explode(args, size);
625 			sc.size = 0;
626 			return false;
627 		}
628 	}
629 	sc.size = size;
630 	free_explode(args, size);
631 	return true;
632 }
633 
parse_nums_list(char * arg_str,int * list_size)634 static int *parse_nums_list(char *arg_str, int *list_size)
635 {
636 	int size = 0;
637 	char **args = explode(',', arg_str, &size);
638 	int *list = calloc(size, sizeof(int));
639 
640 	errno = 0;
641 	for (int i = 0; i < size; ++i) {
642 		char *endptr = NULL;
643 
644 		list[i] = strtol(args[i], &endptr, 10);
645 		if (errno != 0 || endptr == args[i] || *endptr != '\0') {
646 			free(list);
647 			return NULL;
648 		}
649 	}
650 	*list_size = size;
651 	free_explode(args, size);
652 	return list;
653 }
654 
print_allocator(FILE * out,int allocator)655 static void print_allocator(FILE *out, int allocator)
656 {
657 	fprintf(out, "allocated by ");
658 	if (allocator & ALLOCATOR_CMA)
659 		fprintf(out, "CMA ");
660 	if (allocator & ALLOCATOR_SLAB)
661 		fprintf(out, "SLAB ");
662 	if (allocator & ALLOCATOR_VMALLOC)
663 		fprintf(out, "VMALLOC ");
664 	if (allocator & ALLOCATOR_OTHERS)
665 		fprintf(out, "OTHERS ");
666 }
667 
668 #define BUF_SIZE	(128 * 1024)
669 
usage(void)670 static void usage(void)
671 {
672 	printf("Usage: ./page_owner_sort [OPTIONS] <input> <output>\n"
673 		"-m\t\tSort by total memory.\n"
674 		"-s\t\tSort by the stack trace.\n"
675 		"-t\t\tSort by times (default).\n"
676 		"-p\t\tSort by pid.\n"
677 		"-P\t\tSort by tgid.\n"
678 		"-n\t\tSort by task command name.\n"
679 		"-a\t\tSort by memory allocate time.\n"
680 		"-r\t\tSort by memory release time.\n"
681 		"-f\t\tFilter out the information of blocks whose memory has been released.\n"
682 		"-d\t\tPrint debug information.\n"
683 		"--pid <pidlist>\tSelect by pid. This selects the information of blocks whose process ID numbers appear in <pidlist>.\n"
684 		"--tgid <tgidlist>\tSelect by tgid. This selects the information of blocks whose Thread Group ID numbers appear in <tgidlist>.\n"
685 		"--name <cmdlist>\n\t\tSelect by command name. This selects the information of blocks whose command name appears in <cmdlist>.\n"
686 		"--cull <rules>\tCull by user-defined rules.<rules> is a single argument in the form of a comma-separated list with some common fields predefined\n"
687 		"--sort <order>\tSpecify sort order as: [+|-]key[,[+|-]key[,...]]\n"
688 	);
689 }
690 
main(int argc,char ** argv)691 int main(int argc, char **argv)
692 {
693 	FILE *fin, *fout;
694 	char *buf, *ext_buf;
695 	int i, count;
696 	struct stat st;
697 	int opt;
698 	struct option longopts[] = {
699 		{ "pid", required_argument, NULL, 1 },
700 		{ "tgid", required_argument, NULL, 2 },
701 		{ "name", required_argument, NULL, 3 },
702 		{ "cull",  required_argument, NULL, 4 },
703 		{ "sort",  required_argument, NULL, 5 },
704 		{ 0, 0, 0, 0},
705 	};
706 
707 	while ((opt = getopt_long(argc, argv, "adfmnprstP", longopts, NULL)) != -1)
708 		switch (opt) {
709 		case 'a':
710 			set_single_cmp(compare_ts, SORT_ASC);
711 			break;
712 		case 'd':
713 			debug_on = true;
714 			break;
715 		case 'f':
716 			filter = filter | FILTER_UNRELEASE;
717 			break;
718 		case 'm':
719 			set_single_cmp(compare_page_num, SORT_DESC);
720 			break;
721 		case 'p':
722 			set_single_cmp(compare_pid, SORT_ASC);
723 			break;
724 		case 'r':
725 			set_single_cmp(compare_free_ts, SORT_ASC);
726 			break;
727 		case 's':
728 			set_single_cmp(compare_stacktrace, SORT_ASC);
729 			break;
730 		case 't':
731 			set_single_cmp(compare_num, SORT_DESC);
732 			break;
733 		case 'P':
734 			set_single_cmp(compare_tgid, SORT_ASC);
735 			break;
736 		case 'n':
737 			set_single_cmp(compare_comm, SORT_ASC);
738 			break;
739 		case 1:
740 			filter = filter | FILTER_PID;
741 			fc.pids = parse_nums_list(optarg, &fc.pids_size);
742 			if (fc.pids == NULL) {
743 				fprintf(stderr, "wrong/invalid pid in from the command line:%s\n",
744 						optarg);
745 				exit(1);
746 			}
747 			break;
748 		case 2:
749 			filter = filter | FILTER_TGID;
750 			fc.tgids = parse_nums_list(optarg, &fc.tgids_size);
751 			if (fc.tgids == NULL) {
752 				fprintf(stderr, "wrong/invalid tgid in from the command line:%s\n",
753 						optarg);
754 				exit(1);
755 			}
756 			break;
757 		case 3:
758 			filter = filter | FILTER_COMM;
759 			fc.comms = explode(',', optarg, &fc.comms_size);
760 			break;
761 		case 4:
762 			if (!parse_cull_args(optarg)) {
763 				fprintf(stderr, "wrong argument after --cull option:%s\n",
764 						optarg);
765 				exit(1);
766 			}
767 			break;
768 		case 5:
769 			if (!parse_sort_args(optarg)) {
770 				fprintf(stderr, "wrong argument after --sort option:%s\n",
771 						optarg);
772 				exit(1);
773 			}
774 			break;
775 		default:
776 			usage();
777 			exit(1);
778 		}
779 
780 	if (optind >= (argc - 1)) {
781 		usage();
782 		exit(1);
783 	}
784 
785 	fin = fopen(argv[optind], "r");
786 	fout = fopen(argv[optind + 1], "w");
787 	if (!fin || !fout) {
788 		usage();
789 		perror("open: ");
790 		exit(1);
791 	}
792 
793 	check_regcomp(&order_pattern, "order\\s*([0-9]*),");
794 	check_regcomp(&pid_pattern, "pid\\s*([0-9]*),");
795 	check_regcomp(&tgid_pattern, "tgid\\s*([0-9]*) ");
796 	check_regcomp(&comm_pattern, "tgid\\s*[0-9]*\\s*\\((.*)\\),\\s*ts");
797 	check_regcomp(&ts_nsec_pattern, "ts\\s*([0-9]*)\\s*ns,");
798 	check_regcomp(&free_ts_nsec_pattern, "free_ts\\s*([0-9]*)\\s*ns");
799 	fstat(fileno(fin), &st);
800 	max_size = st.st_size / 100; /* hack ... */
801 
802 	list = malloc(max_size * sizeof(*list));
803 	buf = malloc(BUF_SIZE);
804 	ext_buf = malloc(BUF_SIZE);
805 	if (!list || !buf || !ext_buf) {
806 		fprintf(stderr, "Out of memory\n");
807 		exit(1);
808 	}
809 
810 	for ( ; ; ) {
811 		int buf_len = read_block(buf, ext_buf, BUF_SIZE, fin);
812 
813 		if (buf_len < 0)
814 			break;
815 		add_list(buf, buf_len, ext_buf);
816 	}
817 
818 	printf("loaded %d\n", list_size);
819 
820 	printf("sorting ....\n");
821 
822 	qsort(list, list_size, sizeof(list[0]), compare_cull_condition);
823 
824 	printf("culling\n");
825 
826 	for (i = count = 0; i < list_size; i++) {
827 		if (count == 0 ||
828 		    compare_cull_condition((void *)(&list[count-1]), (void *)(&list[i])) != 0) {
829 			list[count++] = list[i];
830 		} else {
831 			list[count-1].num += list[i].num;
832 			list[count-1].page_num += list[i].page_num;
833 		}
834 	}
835 
836 	qsort(list, count, sizeof(list[0]), compare_sort_condition);
837 
838 	for (i = 0; i < count; i++) {
839 		if (cull == 0) {
840 			fprintf(fout, "%d times, %d pages, ", list[i].num, list[i].page_num);
841 			print_allocator(fout, list[i].allocator);
842 			fprintf(fout, ":\n%s\n", list[i].txt);
843 		}
844 		else {
845 			fprintf(fout, "%d times, %d pages",
846 					list[i].num, list[i].page_num);
847 			if (cull & CULL_PID || filter & FILTER_PID)
848 				fprintf(fout, ", PID %d", list[i].pid);
849 			if (cull & CULL_TGID || filter & FILTER_TGID)
850 				fprintf(fout, ", TGID %d", list[i].pid);
851 			if (cull & CULL_COMM || filter & FILTER_COMM)
852 				fprintf(fout, ", task_comm_name: %s", list[i].comm);
853 			if (cull & CULL_ALLOCATOR) {
854 				fprintf(fout, ", ");
855 				print_allocator(fout, list[i].allocator);
856 			}
857 			if (cull & CULL_UNRELEASE)
858 				fprintf(fout, " (%s)",
859 						list[i].free_ts_nsec ? "UNRELEASED" : "RELEASED");
860 			if (cull & CULL_STACKTRACE)
861 				fprintf(fout, ":\n%s", list[i].stacktrace);
862 			fprintf(fout, "\n");
863 		}
864 	}
865 	regfree(&order_pattern);
866 	regfree(&pid_pattern);
867 	regfree(&tgid_pattern);
868 	regfree(&comm_pattern);
869 	regfree(&ts_nsec_pattern);
870 	regfree(&free_ts_nsec_pattern);
871 	return 0;
872 }
873