b8d2867b5b184afcbc2be851566cbaff42a5c4bb
[linux-2.6-microblaze.git] / tools / vm / page_owner_sort.c
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/vm/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
25 struct block_list {
26         char *txt;
27         char *stacktrace;
28         __u64 ts_nsec;
29         __u64 free_ts_nsec;
30         int len;
31         int num;
32         int page_num;
33         pid_t pid;
34         pid_t tgid;
35 };
36
37 static regex_t order_pattern;
38 static regex_t pid_pattern;
39 static regex_t tgid_pattern;
40 static regex_t ts_nsec_pattern;
41 static regex_t free_ts_nsec_pattern;
42 static struct block_list *list;
43 static int list_size;
44 static int max_size;
45 static int cull_st;
46 static int filter;
47
48 int read_block(char *buf, int buf_size, FILE *fin)
49 {
50         char *curr = buf, *const buf_end = buf + buf_size;
51
52         while (buf_end - curr > 1 && fgets(curr, buf_end - curr, fin)) {
53                 if (*curr == '\n') /* empty line */
54                         return curr - buf;
55                 if (!strncmp(curr, "PFN", 3))
56                         continue;
57                 curr += strlen(curr);
58         }
59
60         return -1; /* EOF or no space left in buf. */
61 }
62
63 static int compare_txt(const void *p1, const void *p2)
64 {
65         const struct block_list *l1 = p1, *l2 = p2;
66
67         return strcmp(l1->txt, l2->txt);
68 }
69
70 static int compare_stacktrace(const void *p1, const void *p2)
71 {
72         const struct block_list *l1 = p1, *l2 = p2;
73
74         return strcmp(l1->stacktrace, l2->stacktrace);
75 }
76
77 static int compare_num(const void *p1, const void *p2)
78 {
79         const struct block_list *l1 = p1, *l2 = p2;
80
81         return l2->num - l1->num;
82 }
83
84 static int compare_page_num(const void *p1, const void *p2)
85 {
86         const struct block_list *l1 = p1, *l2 = p2;
87
88         return l2->page_num - l1->page_num;
89 }
90
91 static int compare_pid(const void *p1, const void *p2)
92 {
93         const struct block_list *l1 = p1, *l2 = p2;
94
95         return l1->pid - l2->pid;
96 }
97
98 static int compare_tgid(const void *p1, const void *p2)
99 {
100         const struct block_list *l1 = p1, *l2 = p2;
101
102         return l1->tgid - l2->tgid;
103 }
104
105 static int compare_ts(const void *p1, const void *p2)
106 {
107         const struct block_list *l1 = p1, *l2 = p2;
108
109         return l1->ts_nsec < l2->ts_nsec ? -1 : 1;
110 }
111
112 static int compare_free_ts(const void *p1, const void *p2)
113 {
114         const struct block_list *l1 = p1, *l2 = p2;
115
116         return l1->free_ts_nsec < l2->free_ts_nsec ? -1 : 1;
117 }
118
119 static int search_pattern(regex_t *pattern, char *pattern_str, char *buf)
120 {
121         int err, val_len;
122         regmatch_t pmatch[2];
123
124         err = regexec(pattern, buf, 2, pmatch, REG_NOTBOL);
125         if (err != 0 || pmatch[1].rm_so == -1) {
126                 printf("no matching pattern in %s\n", buf);
127                 return -1;
128         }
129         val_len = pmatch[1].rm_eo - pmatch[1].rm_so;
130
131         memcpy(pattern_str, buf + pmatch[1].rm_so, val_len);
132
133         return 0;
134 }
135
136 static void check_regcomp(regex_t *pattern, const char *regex)
137 {
138         int err;
139
140         err = regcomp(pattern, regex, REG_EXTENDED | REG_NEWLINE);
141         if (err != 0 || pattern->re_nsub != 1) {
142                 printf("Invalid pattern %s code %d\n", regex, err);
143                 exit(1);
144         }
145 }
146
147 # define FIELD_BUFF 25
148
149 static int get_page_num(char *buf)
150 {
151         int order_val;
152         char order_str[FIELD_BUFF] = {0};
153         char *endptr;
154
155         search_pattern(&order_pattern, order_str, buf);
156         errno = 0;
157         order_val = strtol(order_str, &endptr, 10);
158         if (order_val > 64 || errno != 0 || endptr == order_str || *endptr != '\0') {
159                 printf("wrong order in follow buf:\n%s\n", buf);
160                 return 0;
161         }
162
163         return 1 << order_val;
164 }
165
166 static pid_t get_pid(char *buf)
167 {
168         pid_t pid;
169         char pid_str[FIELD_BUFF] = {0};
170         char *endptr;
171
172         search_pattern(&pid_pattern, pid_str, buf);
173         errno = 0;
174         pid = strtol(pid_str, &endptr, 10);
175         if (errno != 0 || endptr == pid_str || *endptr != '\0') {
176                 printf("wrong/invalid pid in follow buf:\n%s\n", buf);
177                 return -1;
178         }
179
180         return pid;
181
182 }
183
184 static pid_t get_tgid(char *buf)
185 {
186         pid_t tgid;
187         char tgid_str[FIELD_BUFF] = {0};
188         char *endptr;
189
190         search_pattern(&tgid_pattern, tgid_str, buf);
191         errno = 0;
192         tgid = strtol(tgid_str, &endptr, 10);
193         if (errno != 0 || endptr == tgid_str || *endptr != '\0') {
194                 printf("wrong/invalid tgid in follow buf:\n%s\n", buf);
195                 return -1;
196         }
197
198         return tgid;
199
200 }
201
202 static __u64 get_ts_nsec(char *buf)
203 {
204         __u64 ts_nsec;
205         char ts_nsec_str[FIELD_BUFF] = {0};
206         char *endptr;
207
208         search_pattern(&ts_nsec_pattern, ts_nsec_str, buf);
209         errno = 0;
210         ts_nsec = strtoull(ts_nsec_str, &endptr, 10);
211         if (errno != 0 || endptr == ts_nsec_str || *endptr != '\0') {
212                 printf("wrong ts_nsec in follow buf:\n%s\n", buf);
213                 return -1;
214         }
215
216         return ts_nsec;
217 }
218
219 static __u64 get_free_ts_nsec(char *buf)
220 {
221         __u64 free_ts_nsec;
222         char free_ts_nsec_str[FIELD_BUFF] = {0};
223         char *endptr;
224
225         search_pattern(&free_ts_nsec_pattern, free_ts_nsec_str, buf);
226         errno = 0;
227         free_ts_nsec = strtoull(free_ts_nsec_str, &endptr, 10);
228         if (errno != 0 || endptr == free_ts_nsec_str || *endptr != '\0') {
229                 printf("wrong free_ts_nsec in follow buf:\n%s\n", buf);
230                 return -1;
231         }
232
233         return free_ts_nsec;
234 }
235
236 static void add_list(char *buf, int len)
237 {
238         if (list_size != 0 &&
239             len == list[list_size-1].len &&
240             memcmp(buf, list[list_size-1].txt, len) == 0) {
241                 list[list_size-1].num++;
242                 list[list_size-1].page_num += get_page_num(buf);
243                 return;
244         }
245         if (list_size == max_size) {
246                 printf("max_size too small??\n");
247                 exit(1);
248         }
249
250         list[list_size].free_ts_nsec = get_free_ts_nsec(buf);
251         if (filter == 1 && list[list_size].free_ts_nsec != 0)
252                 return;
253         list[list_size].txt = malloc(len+1);
254         if (!list[list_size].txt) {
255                 printf("Out of memory\n");
256                 exit(1);
257         }
258
259         list[list_size].len = len;
260         list[list_size].num = 1;
261         list[list_size].page_num = get_page_num(buf);
262         memcpy(list[list_size].txt, buf, len);
263         list[list_size].txt[len] = 0;
264         list[list_size].stacktrace = strchr(list[list_size].txt, '\n') ?: "";
265         if (*list[list_size].stacktrace == '\n')
266                 list[list_size].stacktrace++;
267         list[list_size].pid = get_pid(buf);
268         list[list_size].tgid = get_tgid(buf);
269         list[list_size].ts_nsec = get_ts_nsec(buf);
270         list_size++;
271         if (list_size % 1000 == 0) {
272                 printf("loaded %d\r", list_size);
273                 fflush(stdout);
274         }
275 }
276
277 #define BUF_SIZE        (128 * 1024)
278
279 static void usage(void)
280 {
281         printf("Usage: ./page_owner_sort [OPTIONS] <input> <output>\n"
282                 "-m     Sort by total memory.\n"
283                 "-s     Sort by the stack trace.\n"
284                 "-t     Sort by times (default).\n"
285                 "-p     Sort by pid.\n"
286                 "-P     Sort by tgid.\n"
287                 "-a     Sort by memory allocate time.\n"
288                 "-r     Sort by memory release time.\n"
289                 "-c     Cull by comparing stacktrace instead of total block.\n"
290                 "-f     Filter out the information of blocks whose memory has been released.\n"
291         );
292 }
293
294 int main(int argc, char **argv)
295 {
296         int (*cmp)(const void *, const void *) = compare_num;
297         FILE *fin, *fout;
298         char *buf;
299         int ret, i, count;
300         struct stat st;
301         int opt;
302
303         while ((opt = getopt(argc, argv, "acfmprstP")) != -1)
304                 switch (opt) {
305                 case 'a':
306                         cmp = compare_ts;
307                         break;
308                 case 'c':
309                         cull_st = 1;
310                         break;
311                 case 'f':
312                         filter = 1;
313                         break;
314                 case 'm':
315                         cmp = compare_page_num;
316                         break;
317                 case 'p':
318                         cmp = compare_pid;
319                         break;
320                 case 'r':
321                         cmp = compare_free_ts;
322                         break;
323                 case 's':
324                         cmp = compare_stacktrace;
325                         break;
326                 case 't':
327                         cmp = compare_num;
328                         break;
329                 case 'P':
330                         cmp = compare_tgid;
331                         break;
332                 default:
333                         usage();
334                         exit(1);
335                 }
336
337         if (optind >= (argc - 1)) {
338                 usage();
339                 exit(1);
340         }
341
342         fin = fopen(argv[optind], "r");
343         fout = fopen(argv[optind + 1], "w");
344         if (!fin || !fout) {
345                 usage();
346                 perror("open: ");
347                 exit(1);
348         }
349
350         check_regcomp(&order_pattern, "order\\s*([0-9]*),");
351         check_regcomp(&pid_pattern, "pid\\s*([0-9]*),");
352         check_regcomp(&tgid_pattern, "tgid\\s*([0-9]*) ");
353         check_regcomp(&ts_nsec_pattern, "ts\\s*([0-9]*)\\s*ns,");
354         check_regcomp(&free_ts_nsec_pattern, "free_ts\\s*([0-9]*)\\s*ns");
355         fstat(fileno(fin), &st);
356         max_size = st.st_size / 100; /* hack ... */
357
358         list = malloc(max_size * sizeof(*list));
359         buf = malloc(BUF_SIZE);
360         if (!list || !buf) {
361                 printf("Out of memory\n");
362                 exit(1);
363         }
364
365         for ( ; ; ) {
366                 ret = read_block(buf, BUF_SIZE, fin);
367                 if (ret < 0)
368                         break;
369
370                 add_list(buf, ret);
371         }
372
373         printf("loaded %d\n", list_size);
374
375         printf("sorting ....\n");
376
377         if (cull_st == 1)
378                 qsort(list, list_size, sizeof(list[0]), compare_stacktrace);
379         else
380                 qsort(list, list_size, sizeof(list[0]), compare_txt);
381
382
383
384         printf("culling\n");
385
386         long offset = cull_st ? &list[0].stacktrace - &list[0].txt : 0;
387
388         for (i = count = 0; i < list_size; i++) {
389                 if (count == 0 ||
390                     strcmp(*(&list[count-1].txt+offset), *(&list[i].txt+offset)) != 0) {
391                         list[count++] = list[i];
392                 } else {
393                         list[count-1].num += list[i].num;
394                         list[count-1].page_num += list[i].page_num;
395                 }
396         }
397
398         qsort(list, count, sizeof(list[0]), cmp);
399
400         for (i = 0; i < count; i++) {
401                 if (cull_st == 0)
402                         fprintf(fout, "%d times, %d pages:\n%s\n",
403                                         list[i].num, list[i].page_num, list[i].txt);
404                 else
405                         fprintf(fout, "%d times, %d pages:\n%s\n",
406                                         list[i].num, list[i].page_num, list[i].stacktrace);
407         }
408         regfree(&order_pattern);
409         regfree(&pid_pattern);
410         regfree(&tgid_pattern);
411         regfree(&ts_nsec_pattern);
412         regfree(&free_ts_nsec_pattern);
413         return 0;
414 }