2 * Copyright (C) 2008 Eduard - Gabriel Munteanu
4 * This file is released under GPL version 2.
18 #include <hashtable.h>
19 #include <kmemtrace.h>
21 #include <addr2line.h>
23 #define A2L_QUERY_LEN 512
26 uint64_t call_site
; /* Hash key */
33 #define STILL_ALIVE INT_MAX
34 struct allocation_stats
{
37 uint64_t ptr
; /* Hash key */
45 static struct ht_table
*caller_ht
, *alloc_ht
;
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
);
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
);
76 printf("\tlast touched by %s\n\n", prev_name
);
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
)
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
);
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
;
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
))
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
);
132 alloc_stats
->died_on
= ev
->seq_num
;
135 static int parse_event(struct kmemtrace_event
*ev
,
137 unsigned long origin_cpu
)
139 struct kallsyms_symbol
*sym
;
140 struct symbol_stats
*caller_stats
= NULL
;
141 struct allocation_stats
*alloc_stats
= NULL
;
147 curr_alloc
[origin_cpu
]++;
149 sym
= kallsyms_lookup(ev
->call_site
);
151 printf("parse_event: Unknown symbol with address %p!\n",
152 (void *) ev
->call_site
);
155 name_len
= strlen(sym
->name
);
157 if (last_timestamp
> ev
->seq_num
) {
158 printf("parse_event: Bad timestamp, caller %s!\n", sym
->name
);
161 last_timestamp
= ev
->seq_num
;
164 caller_stats
= ht_update_entry(caller_ht
, ev
->call_site
)->data
;
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
;
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
);
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);
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
);
215 static void show_callers(void) {
216 struct symbol_stats
**vec
;
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
));
236 ht_action(caller_ht
, ht_free_entry
, NULL
);
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
);
258 static void free_alloc(struct ht_entry
*entry
, int freeable
, void *private)
260 struct allocation_stats
*stats
= entry
->data
;
264 ht_free_entry(entry
, freeable
, NULL
);
267 static void show_allocations(void) {
268 char a2l_result
[A2L_QUERY_LEN
];
270 struct allocation_stats
**vec
;
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
)
294 printf("%lld\n", (long long) vec
[i
]->died_on
-
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
)
306 printf("%lld\n", (long long) vec
[i
]->died_on
-
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'},
354 c
= getopt_long(argc
, argv
, "acp:s:h",
355 long_opts
, &option_index
);
368 /* strncpy(prefix, optarg, PREFIX_MAXLEN); */
372 snprintf(vmlinux_path
,
373 FILENAME_MAX
, "%s", optarg
);
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
);
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
);