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