Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.15.
  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
 31struct 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};
 44enum FILTER_BIT {
 45	FILTER_UNRELEASE = 1<<1,
 46	FILTER_PID = 1<<2,
 47	FILTER_TGID = 1<<3,
 48	FILTER_COMM = 1<<4
 49};
 50enum 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};
 58enum ALLOCATOR_BIT {
 59	ALLOCATOR_CMA = 1<<1,
 60	ALLOCATOR_SLAB = 1<<2,
 61	ALLOCATOR_VMALLOC = 1<<3,
 62	ALLOCATOR_OTHERS = 1<<4
 63};
 64enum 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};
 69enum SORT_ORDER {
 70	SORT_ASC = 1,
 71	SORT_DESC = -1,
 72};
 73struct 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};
 81struct sort_condition {
 82	int (**cmps)(const void *, const void *);
 83	int *signs;
 84	int size;
 85};
 86static struct filter_condition fc;
 87static struct sort_condition sc;
 88static regex_t order_pattern;
 89static regex_t pid_pattern;
 90static regex_t tgid_pattern;
 91static regex_t comm_pattern;
 92static regex_t ts_nsec_pattern;
 93static regex_t free_ts_nsec_pattern;
 94static struct block_list *list;
 95static int list_size;
 96static int max_size;
 97static int cull;
 98static int filter;
 99static bool debug_on;
100
101static void set_single_cmp(int (*cmp)(const void *, const void *), int sign);
102
103int 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
121static 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
128static 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
135static 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
142static 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
149static 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
156static 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
163static 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
170static 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
177static 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
184static 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
191static 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
202static 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
221static 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
231static 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
249static 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
260static 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
285static 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
294static 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
312static 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
331static 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
350static __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
368static __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
386static 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
403static 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
428static 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
455static 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
463static 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
471static 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
497static 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
539static 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
570static 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
581static 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
634static 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
655static 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
670static 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
691int 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}