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