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