kmemtrace-report: pretty formatting.
[kmemtrace-user.git] / kmemtrace-report.c
blob59d85d507abdcfdbcdfeb2b7e0c8c84e83be1e5c
1 /*
2 * Copyright (C) 2008 Eduard - Gabriel Munteanu
4 * This file is released under GPL version 2.
5 */
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <stdint.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13 #include <getopt.h>
14 #include <limits.h>
16 #include <common.h>
17 #include <kallsyms.h>
18 #include <hashtable.h>
19 #include <kmemtrace.h>
20 #include <dataset.h>
21 #include <addr2line.h>
23 #define A2L_QUERY_LEN 512
25 struct symbol_stats {
26 uint64_t call_site; /* Hash key */
27 char *name;
28 uint64_t n_req;
29 uint64_t n_alloc;
30 uint64_t n_freed;
33 #define STILL_ALIVE INT_MAX
34 struct allocation_stats {
35 uint64_t call_site;
36 char *name;
37 uint64_t ptr; /* Hash key */
38 uint64_t n_req;
39 uint64_t n_alloc;
41 int32_t born_on;
42 int32_t died_on;
45 static struct ht_table *caller_ht, *alloc_ht;
47 /* Statistics */
48 static unsigned long total_requested, total_allocated;
49 static unsigned long n_cross_allocs, n_cross_frees;
50 static unsigned long n_ignored;
51 static int32_t last_timestamp = INT_MIN;
53 static int callers_flag, allocs_flag, addr2line_flag;
54 static char vmlinux_path[FILENAME_MAX];
56 static inline int is_cross_cpu(unsigned long origin, unsigned long target)
58 return (target != -1 && origin != target);
61 unsigned long *curr_alloc;
63 static inline void warn_event(struct kmemtrace_event *ev,
64 const char *name, const char *prev_name)
66 char a2l_result[A2L_QUERY_LEN];
68 printf("\tby %s (%llx)\n", name, (unsigned long long) ev->call_site);
69 if (addr2line_flag) {
70 if (addr2line_query((void *) ev->call_site,
71 a2l_result, A2L_QUERY_LEN) <= 0)
72 panic("Couldn't resolve callsite!\n");
73 printf("%s\n", a2l_result);
75 if (name)
76 printf("\tlast touched by %s\n\n", prev_name);
77 else
78 printf("\tnever seen before!\n\n");
81 static void parse_event_alloc(struct kmemtrace_event *ev,
82 struct kmemtrace_allocstats *evstats,
83 struct symbol_stats *caller_stats,
84 struct allocation_stats *alloc_stats,
85 struct kallsyms_symbol *symbol,
86 unsigned long origin_cpu)
88 if (alloc_stats) {
89 if (alloc_stats->died_on == STILL_ALIVE) {
90 printf("Allocation #%lu (CPU%lu) already exists!\n",
91 curr_alloc[origin_cpu], origin_cpu);
92 warn_event(ev, symbol->name, alloc_stats->name);
93 n_ignored++;
94 return;
97 alloc_stats->call_site = ev->call_site;
98 /* alloc_stats->name already stored. */
99 /* alloc_stats->ptr automatically stored when registering. */
100 alloc_stats->n_req = evstats->bytes_req;
101 alloc_stats->n_alloc = evstats->bytes_alloc;
102 alloc_stats->born_on = ev->seq_num;
103 alloc_stats->died_on = STILL_ALIVE;
106 if (caller_stats) {
107 caller_stats->n_req += evstats->bytes_req;
108 caller_stats->n_alloc += evstats->bytes_alloc;
111 total_requested += evstats->bytes_req;
112 total_allocated += evstats->bytes_alloc;
114 if (is_cross_cpu(origin_cpu, evstats->numa_node))
115 n_cross_allocs++;
118 static void parse_event_free(struct kmemtrace_event *ev,
119 struct symbol_stats *caller_stats,
120 struct allocation_stats *alloc_stats,
121 struct kallsyms_symbol *symbol,
122 unsigned long origin_cpu)
124 if (alloc_stats->died_on != STILL_ALIVE) {
125 printf("Allocation #%lu (CPU%lu) already dead!\n",
126 curr_alloc[origin_cpu], origin_cpu);
127 warn_event(ev, symbol->name, alloc_stats->name);
128 n_ignored++;
129 return;
132 alloc_stats->died_on = ev->seq_num;
135 static int parse_event(struct kmemtrace_event *ev,
136 void *extra,
137 unsigned long origin_cpu)
139 struct kallsyms_symbol *sym;
140 struct symbol_stats *caller_stats = NULL;
141 struct allocation_stats *alloc_stats = NULL;
142 size_t name_len;
144 if (!ev->ptr)
145 return 0;
147 curr_alloc[origin_cpu]++;
149 sym = kallsyms_lookup(ev->call_site);
150 if (!sym) {
151 printf("parse_event: Unknown symbol with address %p!\n",
152 (void *) ev->call_site);
153 return 1;
155 name_len = strlen(sym->name);
157 if (last_timestamp > ev->seq_num) {
158 printf("parse_event: Bad timestamp, caller %s!\n", sym->name);
159 return 0;
161 last_timestamp = ev->seq_num;
163 if (callers_flag) {
164 caller_stats = ht_update_entry(caller_ht, ev->call_site)->data;
165 if (!caller_stats)
166 panic("parse_event: Could not register caller!\n");
167 if (!caller_stats->name) {
168 caller_stats->name = malloc(name_len + 1);
169 strncpy(caller_stats->name, sym->name, name_len + 1);
173 alloc_stats = ht_update_entry(alloc_ht, ev->ptr)->data;
174 if (!alloc_stats)
175 panic("parse_event: Could not register allocation!\n");
177 if (ev->event_id == KMEMTRACE_EVENT_ALLOC)
178 parse_event_alloc(ev, extra, caller_stats,
179 alloc_stats, sym, origin_cpu);
180 else if (ev->event_id == KMEMTRACE_EVENT_FREE)
181 parse_event_free(ev, caller_stats,
182 alloc_stats, sym, origin_cpu);
183 else
184 panic("parse_event: unknown event %d!\n", ev->event_id);
186 if (!alloc_stats->name) {
187 alloc_stats->name = malloc(name_len + 1);
188 strncpy(alloc_stats->name, sym->name, name_len + 1);
191 return 0;
194 static inline float fragmentation(unsigned long n_req, unsigned long n_alloc)
196 return n_alloc ? (100. - (100. * n_req / n_alloc)) : 0.;
199 static int compare_caller_frag(const void *a, const void *b)
201 const struct symbol_stats * const sym_a =
202 *((const struct symbol_stats **) a);
203 const struct symbol_stats * const sym_b =
204 *((const struct symbol_stats **) b);
205 float frag_a = fragmentation(sym_a->n_req, sym_a->n_alloc);
206 float frag_b = fragmentation(sym_b->n_req, sym_b->n_alloc);
208 if (frag_a < frag_b)
209 return -1;
210 if (frag_a > frag_b)
211 return 1;
212 return 0;
215 static void show_callers(void) {
216 struct symbol_stats **vec;
217 size_t size, i;
219 if (!callers_flag)
220 return;
222 vec = ht_malloc_vec(caller_ht, 0);
223 size = ht_to_vec(caller_ht, 0, vec);
224 qsort(vec, size, sizeof(void *), compare_caller_frag);
226 printf("Stats by caller\n");
227 printf("Name\tRequested\tAllocated\tFragmentation\n");
228 for (i = 0; i < size; i++) {
229 printf("%32s\t%10llu\t%10llu\t%.1f%%\n", vec[i]->name,
230 (unsigned long long) vec[i]->n_req,
231 (unsigned long long) vec[i]->n_alloc,
232 fragmentation(vec[i]->n_req, vec[i]->n_alloc));
233 free(vec[i]->name);
236 ht_action(caller_ht, ht_free_entry, NULL);
238 ht_free_vec(vec);
239 ht_destroy(caller_ht);
242 static int compare_alloc_frag(const void *a, const void *b)
244 const struct allocation_stats * const alloc_a =
245 *((const struct allocation_stats **) a);
246 const struct allocation_stats * const alloc_b =
247 *((const struct allocation_stats **) b);
248 float frag_a = fragmentation(alloc_a->n_req, alloc_a->n_alloc);
249 float frag_b = fragmentation(alloc_b->n_req, alloc_b->n_alloc);
251 if (frag_a < frag_b)
252 return -1;
253 if (frag_a > frag_b)
254 return 1;
255 return 0;
258 static void free_alloc(struct ht_entry *entry, int freeable, void *private)
260 struct allocation_stats *stats = entry->data;
262 free(stats->name);
264 ht_free_entry(entry, freeable, NULL);
267 static void show_allocations(void) {
268 char a2l_result[A2L_QUERY_LEN];
269 size_t size, i;
270 struct allocation_stats **vec;
272 if (!allocs_flag)
273 goto no_show;
275 vec = ht_malloc_vec(alloc_ht, 0);
276 size = ht_to_vec(alloc_ht, 0, vec);
277 qsort(vec, size, sizeof(void *), compare_alloc_frag);
279 printf("Stats by allocation\n");
280 printf("Owner\tRequested\tAllocated\tFragmentation\tLifetime\n");
281 if (addr2line_flag) {
282 for (i = 0; i < size; i++) {
283 if (addr2line_query((void *) vec[i]->call_site,
284 a2l_result, A2L_QUERY_LEN) <= 0)
285 panic("Couldn't resolve callsite!\n");
286 printf("%s\n%32s\t%7llu\t%7llu\t%.1f%%\t",
287 a2l_result, vec[i]->name,
288 (unsigned long long) vec[i]->n_req,
289 (unsigned long long) vec[i]->n_alloc,
290 fragmentation(vec[i]->n_req, vec[i]->n_alloc));
291 if (vec[i]->died_on == STILL_ALIVE)
292 printf("(alive)\n");
293 else
294 printf("%lld\n", (long long) vec[i]->died_on -
295 vec[i]->born_on);
297 } else {
298 for (i = 0; i < size; i++) {
299 printf("%32s\t%7llu\t%7llu\t%.1f%%\t", vec[i]->name,
300 (unsigned long long) vec[i]->n_req,
301 (unsigned long long) vec[i]->n_alloc,
302 fragmentation(vec[i]->n_req, vec[i]->n_alloc));
303 if (vec[i]->died_on == STILL_ALIVE)
304 printf("(alive)\n");
305 else
306 printf("%lld\n", (long long) vec[i]->died_on -
307 vec[i]->born_on);
311 ht_free_vec(vec);
313 no_show:
314 ht_action(alloc_ht, free_alloc, NULL);
315 ht_destroy(alloc_ht);
318 static void show_summary(void)
320 printf("SUMMARY\n=======\n");
321 printf("(Ignored erroneous events: %lu)\n", n_ignored);
322 printf("Total bytes requested: %lu\n", total_requested);
323 printf("Total bytes allocated: %lu\n", total_allocated);
324 printf("Total bytes wasted on internal fragmentation: %lu\n",
325 total_allocated - total_requested);
326 printf("Internal fragmentation: %f%%\n",
327 fragmentation(total_requested, total_allocated));
328 printf("Cross CPU allocations, frees: %lu, %lu\n",
329 n_cross_allocs, n_cross_frees);
332 static void print_usage(void)
334 printf("kmemtrace reporting tool\nUsage:\n");
335 printf("kmemtrace_report [-a | --show-allocs] [-c | --show-callers]\n"
336 " [<-p | --prefix> <prefix>]\n"
337 " [<-s | --vmlinux> <vmlinux>]\n"
338 "kmemtrace_report [-h | --help])\n");
341 static void parse_cmdline(int argc, char **argv)
343 int c, option_index = 0;
344 struct option long_opts[] = {
345 {"show-allocs", no_argument, &allocs_flag, 1},
346 {"show-callers", no_argument, &callers_flag, 1},
347 {"prefix", required_argument, 0, 'p'},
348 {"vmlinux", required_argument, 0, 's'},
349 {"help", no_argument, 0, 'h'},
350 {0, 0, 0, 0}
353 for (;;) {
354 c = getopt_long(argc, argv, "acp:s:h",
355 long_opts, &option_index);
357 if (c == -1)
358 break;
360 switch (c) {
361 case 'a':
362 allocs_flag = 1;
363 break;
364 case 'c':
365 callers_flag = 1;
366 break;
367 case 'p':
368 /* strncpy(prefix, optarg, PREFIX_MAXLEN); */
369 break;
370 case 's':
371 addr2line_flag = 1;
372 snprintf(vmlinux_path,
373 FILENAME_MAX, "%s", optarg);
374 break;
375 case 'h':
376 print_usage();
377 exit(0);
378 default:
379 exit(-1);
384 int main(int argc, char **argv)
386 unsigned long n_cpus = sysconf(_SC_NPROCESSORS_ONLN);
387 struct dataset_group *group;
389 curr_alloc = calloc(n_cpus, sizeof(unsigned long));
391 parse_cmdline(argc, argv);
393 if (kallsyms_init("kallsyms") <= 0)
394 panic("Error while reading kallsyms!\n");
395 if (addr2line_flag && addr2line_init(vmlinux_path))
396 panic("Could not start addr2line!\n");
398 alloc_ht = ht_create(12, struct allocation_stats, ptr);
399 if (callers_flag)
400 caller_ht = ht_create(8, struct symbol_stats, call_site);
402 group = dataset_group_create(n_cpus);
403 if (dataset_group_add_auto(group, "cpu%lu.out"))
404 panic("Could not open input files!\n");
405 dataset_iter_ordered(group, parse_event);
406 dataset_group_destroy(group);
408 show_callers();
409 show_allocations();
410 show_summary();
412 if (addr2line_flag)
413 addr2line_exit();
414 kallsyms_exit();
416 free(curr_alloc);
418 return EXIT_SUCCESS;