diff options
author | Namhyung Kim <namhyung@kernel.org> | 2015-04-06 01:36:10 -0400 |
---|---|---|
committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2015-04-13 10:44:52 -0400 |
commit | 0d68bc92c48167130b61b449f08be27dc862dba2 (patch) | |
tree | 4452eccf64339786bd7ff503719e35ec76b36dd7 /tools/perf | |
parent | 9fdd8a875c6f3b02af48d5fa426206ca009b2b06 (diff) |
perf kmem: Analyze page allocator events also
The perf kmem command records and analyze kernel memory allocation only
for SLAB objects. This patch implement a simple page allocator analyzer
using kmem:mm_page_alloc and kmem:mm_page_free events.
It adds two new options of --slab and --page. The --slab option is for
analyzing SLAB allocator and that's what perf kmem currently does.
The new --page option enables page allocator events and analyze kernel
memory usage in page unit. Currently, 'stat --alloc' subcommand is
implemented only.
If none of these --slab nor --page is specified, --slab is implied.
First run 'perf kmem record' to generate a suitable perf.data file:
# perf kmem record --page sleep 5
Then run 'perf kmem stat' to postprocess the perf.data file:
# perf kmem stat --page --alloc --line 10
-------------------------------------------------------------------------------
PFN | Total alloc (KB) | Hits | Order | Mig.type | GFP flags
-------------------------------------------------------------------------------
4045014 | 16 | 1 | 2 | RECLAIM | 00285250
4143980 | 16 | 1 | 2 | RECLAIM | 00285250
3938658 | 16 | 1 | 2 | RECLAIM | 00285250
4045400 | 16 | 1 | 2 | RECLAIM | 00285250
3568708 | 16 | 1 | 2 | RECLAIM | 00285250
3729824 | 16 | 1 | 2 | RECLAIM | 00285250
3657210 | 16 | 1 | 2 | RECLAIM | 00285250
4120750 | 16 | 1 | 2 | RECLAIM | 00285250
3678850 | 16 | 1 | 2 | RECLAIM | 00285250
3693874 | 16 | 1 | 2 | RECLAIM | 00285250
... | ... | ... | ... | ... | ...
-------------------------------------------------------------------------------
SUMMARY (page allocator)
========================
Total allocation requests : 44,260 [ 177,256 KB ]
Total free requests : 117 [ 468 KB ]
Total alloc+freed requests : 49 [ 196 KB ]
Total alloc-only requests : 44,211 [ 177,060 KB ]
Total free-only requests : 68 [ 272 KB ]
Total allocation failures : 0 [ 0 KB ]
Order Unmovable Reclaimable Movable Reserved CMA/Isolated
----- ------------ ------------ ------------ ------------ ------------
0 32 . 44,210 . .
1 . . . . .
2 . 18 . . .
3 . . . . .
4 . . . . .
5 . . . . .
6 . . . . .
7 . . . . .
8 . . . . .
9 . . . . .
10 . . . . .
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Joonsoo Kim <js1304@gmail.com>
Cc: Minchan Kim <minchan@kernel.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: linux-mm@kvack.org
Link: http://lkml.kernel.org/r/1428298576-9785-4-git-send-email-namhyung@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf')
-rw-r--r-- | tools/perf/Documentation/perf-kmem.txt | 8 | ||||
-rw-r--r-- | tools/perf/builtin-kmem.c | 500 |
2 files changed, 491 insertions, 17 deletions
diff --git a/tools/perf/Documentation/perf-kmem.txt b/tools/perf/Documentation/perf-kmem.txt index 150253cc3c97..23219c65c16f 100644 --- a/tools/perf/Documentation/perf-kmem.txt +++ b/tools/perf/Documentation/perf-kmem.txt | |||
@@ -3,7 +3,7 @@ perf-kmem(1) | |||
3 | 3 | ||
4 | NAME | 4 | NAME |
5 | ---- | 5 | ---- |
6 | perf-kmem - Tool to trace/measure kernel memory(slab) properties | 6 | perf-kmem - Tool to trace/measure kernel memory properties |
7 | 7 | ||
8 | SYNOPSIS | 8 | SYNOPSIS |
9 | -------- | 9 | -------- |
@@ -46,6 +46,12 @@ OPTIONS | |||
46 | --raw-ip:: | 46 | --raw-ip:: |
47 | Print raw ip instead of symbol | 47 | Print raw ip instead of symbol |
48 | 48 | ||
49 | --slab:: | ||
50 | Analyze SLAB allocator events. | ||
51 | |||
52 | --page:: | ||
53 | Analyze page allocator events | ||
54 | |||
49 | SEE ALSO | 55 | SEE ALSO |
50 | -------- | 56 | -------- |
51 | linkperf:perf-record[1] | 57 | linkperf:perf-record[1] |
diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index 4ebf65c79434..63ea01349b6e 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c | |||
@@ -22,6 +22,11 @@ | |||
22 | #include <linux/string.h> | 22 | #include <linux/string.h> |
23 | #include <locale.h> | 23 | #include <locale.h> |
24 | 24 | ||
25 | static int kmem_slab; | ||
26 | static int kmem_page; | ||
27 | |||
28 | static long kmem_page_size; | ||
29 | |||
25 | struct alloc_stat; | 30 | struct alloc_stat; |
26 | typedef int (*sort_fn_t)(struct alloc_stat *, struct alloc_stat *); | 31 | typedef int (*sort_fn_t)(struct alloc_stat *, struct alloc_stat *); |
27 | 32 | ||
@@ -226,6 +231,244 @@ static int perf_evsel__process_free_event(struct perf_evsel *evsel, | |||
226 | return 0; | 231 | return 0; |
227 | } | 232 | } |
228 | 233 | ||
234 | static u64 total_page_alloc_bytes; | ||
235 | static u64 total_page_free_bytes; | ||
236 | static u64 total_page_nomatch_bytes; | ||
237 | static u64 total_page_fail_bytes; | ||
238 | static unsigned long nr_page_allocs; | ||
239 | static unsigned long nr_page_frees; | ||
240 | static unsigned long nr_page_fails; | ||
241 | static unsigned long nr_page_nomatch; | ||
242 | |||
243 | static bool use_pfn; | ||
244 | |||
245 | #define MAX_MIGRATE_TYPES 6 | ||
246 | #define MAX_PAGE_ORDER 11 | ||
247 | |||
248 | static int order_stats[MAX_PAGE_ORDER][MAX_MIGRATE_TYPES]; | ||
249 | |||
250 | struct page_stat { | ||
251 | struct rb_node node; | ||
252 | u64 page; | ||
253 | int order; | ||
254 | unsigned gfp_flags; | ||
255 | unsigned migrate_type; | ||
256 | u64 alloc_bytes; | ||
257 | u64 free_bytes; | ||
258 | int nr_alloc; | ||
259 | int nr_free; | ||
260 | }; | ||
261 | |||
262 | static struct rb_root page_tree; | ||
263 | static struct rb_root page_alloc_tree; | ||
264 | static struct rb_root page_alloc_sorted; | ||
265 | |||
266 | static struct page_stat *search_page(unsigned long page, bool create) | ||
267 | { | ||
268 | struct rb_node **node = &page_tree.rb_node; | ||
269 | struct rb_node *parent = NULL; | ||
270 | struct page_stat *data; | ||
271 | |||
272 | while (*node) { | ||
273 | s64 cmp; | ||
274 | |||
275 | parent = *node; | ||
276 | data = rb_entry(*node, struct page_stat, node); | ||
277 | |||
278 | cmp = data->page - page; | ||
279 | if (cmp < 0) | ||
280 | node = &parent->rb_left; | ||
281 | else if (cmp > 0) | ||
282 | node = &parent->rb_right; | ||
283 | else | ||
284 | return data; | ||
285 | } | ||
286 | |||
287 | if (!create) | ||
288 | return NULL; | ||
289 | |||
290 | data = zalloc(sizeof(*data)); | ||
291 | if (data != NULL) { | ||
292 | data->page = page; | ||
293 | |||
294 | rb_link_node(&data->node, parent, node); | ||
295 | rb_insert_color(&data->node, &page_tree); | ||
296 | } | ||
297 | |||
298 | return data; | ||
299 | } | ||
300 | |||
301 | static int page_stat_cmp(struct page_stat *a, struct page_stat *b) | ||
302 | { | ||
303 | if (a->page > b->page) | ||
304 | return -1; | ||
305 | if (a->page < b->page) | ||
306 | return 1; | ||
307 | if (a->order > b->order) | ||
308 | return -1; | ||
309 | if (a->order < b->order) | ||
310 | return 1; | ||
311 | if (a->migrate_type > b->migrate_type) | ||
312 | return -1; | ||
313 | if (a->migrate_type < b->migrate_type) | ||
314 | return 1; | ||
315 | if (a->gfp_flags > b->gfp_flags) | ||
316 | return -1; | ||
317 | if (a->gfp_flags < b->gfp_flags) | ||
318 | return 1; | ||
319 | return 0; | ||
320 | } | ||
321 | |||
322 | static struct page_stat *search_page_alloc_stat(struct page_stat *stat, bool create) | ||
323 | { | ||
324 | struct rb_node **node = &page_alloc_tree.rb_node; | ||
325 | struct rb_node *parent = NULL; | ||
326 | struct page_stat *data; | ||
327 | |||
328 | while (*node) { | ||
329 | s64 cmp; | ||
330 | |||
331 | parent = *node; | ||
332 | data = rb_entry(*node, struct page_stat, node); | ||
333 | |||
334 | cmp = page_stat_cmp(data, stat); | ||
335 | if (cmp < 0) | ||
336 | node = &parent->rb_left; | ||
337 | else if (cmp > 0) | ||
338 | node = &parent->rb_right; | ||
339 | else | ||
340 | return data; | ||
341 | } | ||
342 | |||
343 | if (!create) | ||
344 | return NULL; | ||
345 | |||
346 | data = zalloc(sizeof(*data)); | ||
347 | if (data != NULL) { | ||
348 | data->page = stat->page; | ||
349 | data->order = stat->order; | ||
350 | data->gfp_flags = stat->gfp_flags; | ||
351 | data->migrate_type = stat->migrate_type; | ||
352 | |||
353 | rb_link_node(&data->node, parent, node); | ||
354 | rb_insert_color(&data->node, &page_alloc_tree); | ||
355 | } | ||
356 | |||
357 | return data; | ||
358 | } | ||
359 | |||
360 | static bool valid_page(u64 pfn_or_page) | ||
361 | { | ||
362 | if (use_pfn && pfn_or_page == -1UL) | ||
363 | return false; | ||
364 | if (!use_pfn && pfn_or_page == 0) | ||
365 | return false; | ||
366 | return true; | ||
367 | } | ||
368 | |||
369 | static int perf_evsel__process_page_alloc_event(struct perf_evsel *evsel, | ||
370 | struct perf_sample *sample) | ||
371 | { | ||
372 | u64 page; | ||
373 | unsigned int order = perf_evsel__intval(evsel, sample, "order"); | ||
374 | unsigned int gfp_flags = perf_evsel__intval(evsel, sample, "gfp_flags"); | ||
375 | unsigned int migrate_type = perf_evsel__intval(evsel, sample, | ||
376 | "migratetype"); | ||
377 | u64 bytes = kmem_page_size << order; | ||
378 | struct page_stat *stat; | ||
379 | struct page_stat this = { | ||
380 | .order = order, | ||
381 | .gfp_flags = gfp_flags, | ||
382 | .migrate_type = migrate_type, | ||
383 | }; | ||
384 | |||
385 | if (use_pfn) | ||
386 | page = perf_evsel__intval(evsel, sample, "pfn"); | ||
387 | else | ||
388 | page = perf_evsel__intval(evsel, sample, "page"); | ||
389 | |||
390 | nr_page_allocs++; | ||
391 | total_page_alloc_bytes += bytes; | ||
392 | |||
393 | if (!valid_page(page)) { | ||
394 | nr_page_fails++; | ||
395 | total_page_fail_bytes += bytes; | ||
396 | |||
397 | return 0; | ||
398 | } | ||
399 | |||
400 | /* | ||
401 | * This is to find the current page (with correct gfp flags and | ||
402 | * migrate type) at free event. | ||
403 | */ | ||
404 | stat = search_page(page, true); | ||
405 | if (stat == NULL) | ||
406 | return -ENOMEM; | ||
407 | |||
408 | stat->order = order; | ||
409 | stat->gfp_flags = gfp_flags; | ||
410 | stat->migrate_type = migrate_type; | ||
411 | |||
412 | this.page = page; | ||
413 | stat = search_page_alloc_stat(&this, true); | ||
414 | if (stat == NULL) | ||
415 | return -ENOMEM; | ||
416 | |||
417 | stat->nr_alloc++; | ||
418 | stat->alloc_bytes += bytes; | ||
419 | |||
420 | order_stats[order][migrate_type]++; | ||
421 | |||
422 | return 0; | ||
423 | } | ||
424 | |||
425 | static int perf_evsel__process_page_free_event(struct perf_evsel *evsel, | ||
426 | struct perf_sample *sample) | ||
427 | { | ||
428 | u64 page; | ||
429 | unsigned int order = perf_evsel__intval(evsel, sample, "order"); | ||
430 | u64 bytes = kmem_page_size << order; | ||
431 | struct page_stat *stat; | ||
432 | struct page_stat this = { | ||
433 | .order = order, | ||
434 | }; | ||
435 | |||
436 | if (use_pfn) | ||
437 | page = perf_evsel__intval(evsel, sample, "pfn"); | ||
438 | else | ||
439 | page = perf_evsel__intval(evsel, sample, "page"); | ||
440 | |||
441 | nr_page_frees++; | ||
442 | total_page_free_bytes += bytes; | ||
443 | |||
444 | stat = search_page(page, false); | ||
445 | if (stat == NULL) { | ||
446 | pr_debug2("missing free at page %"PRIx64" (order: %d)\n", | ||
447 | page, order); | ||
448 | |||
449 | nr_page_nomatch++; | ||
450 | total_page_nomatch_bytes += bytes; | ||
451 | |||
452 | return 0; | ||
453 | } | ||
454 | |||
455 | this.page = page; | ||
456 | this.gfp_flags = stat->gfp_flags; | ||
457 | this.migrate_type = stat->migrate_type; | ||
458 | |||
459 | rb_erase(&stat->node, &page_tree); | ||
460 | free(stat); | ||
461 | |||
462 | stat = search_page_alloc_stat(&this, false); | ||
463 | if (stat == NULL) | ||
464 | return -ENOENT; | ||
465 | |||
466 | stat->nr_free++; | ||
467 | stat->free_bytes += bytes; | ||
468 | |||
469 | return 0; | ||
470 | } | ||
471 | |||
229 | typedef int (*tracepoint_handler)(struct perf_evsel *evsel, | 472 | typedef int (*tracepoint_handler)(struct perf_evsel *evsel, |
230 | struct perf_sample *sample); | 473 | struct perf_sample *sample); |
231 | 474 | ||
@@ -270,8 +513,9 @@ static double fragmentation(unsigned long n_req, unsigned long n_alloc) | |||
270 | return 100.0 - (100.0 * n_req / n_alloc); | 513 | return 100.0 - (100.0 * n_req / n_alloc); |
271 | } | 514 | } |
272 | 515 | ||
273 | static void __print_result(struct rb_root *root, struct perf_session *session, | 516 | static void __print_slab_result(struct rb_root *root, |
274 | int n_lines, int is_caller) | 517 | struct perf_session *session, |
518 | int n_lines, int is_caller) | ||
275 | { | 519 | { |
276 | struct rb_node *next; | 520 | struct rb_node *next; |
277 | struct machine *machine = &session->machines.host; | 521 | struct machine *machine = &session->machines.host; |
@@ -323,9 +567,56 @@ static void __print_result(struct rb_root *root, struct perf_session *session, | |||
323 | printf("%.105s\n", graph_dotted_line); | 567 | printf("%.105s\n", graph_dotted_line); |
324 | } | 568 | } |
325 | 569 | ||
326 | static void print_summary(void) | 570 | static const char * const migrate_type_str[] = { |
571 | "UNMOVABL", | ||
572 | "RECLAIM", | ||
573 | "MOVABLE", | ||
574 | "RESERVED", | ||
575 | "CMA/ISLT", | ||
576 | "UNKNOWN", | ||
577 | }; | ||
578 | |||
579 | static void __print_page_result(struct rb_root *root, | ||
580 | struct perf_session *session __maybe_unused, | ||
581 | int n_lines) | ||
582 | { | ||
583 | struct rb_node *next = rb_first(root); | ||
584 | const char *format; | ||
585 | |||
586 | printf("\n%.80s\n", graph_dotted_line); | ||
587 | printf(" %-16s | Total alloc (KB) | Hits | Order | Mig.type | GFP flags\n", | ||
588 | use_pfn ? "PFN" : "Page"); | ||
589 | printf("%.80s\n", graph_dotted_line); | ||
590 | |||
591 | if (use_pfn) | ||
592 | format = " %16llu | %'16llu | %'9d | %5d | %8s | %08lx\n"; | ||
593 | else | ||
594 | format = " %016llx | %'16llu | %'9d | %5d | %8s | %08lx\n"; | ||
595 | |||
596 | while (next && n_lines--) { | ||
597 | struct page_stat *data; | ||
598 | |||
599 | data = rb_entry(next, struct page_stat, node); | ||
600 | |||
601 | printf(format, (unsigned long long)data->page, | ||
602 | (unsigned long long)data->alloc_bytes / 1024, | ||
603 | data->nr_alloc, data->order, | ||
604 | migrate_type_str[data->migrate_type], | ||
605 | (unsigned long)data->gfp_flags); | ||
606 | |||
607 | next = rb_next(next); | ||
608 | } | ||
609 | |||
610 | if (n_lines == -1) | ||
611 | printf(" ... | ... | ... | ... | ... | ... \n"); | ||
612 | |||
613 | printf("%.80s\n", graph_dotted_line); | ||
614 | } | ||
615 | |||
616 | static void print_slab_summary(void) | ||
327 | { | 617 | { |
328 | printf("\nSUMMARY\n=======\n"); | 618 | printf("\nSUMMARY (SLAB allocator)"); |
619 | printf("\n========================\n"); | ||
329 | printf("Total bytes requested: %'lu\n", total_requested); | 620 | printf("Total bytes requested: %'lu\n", total_requested); |
330 | printf("Total bytes allocated: %'lu\n", total_allocated); | 621 | printf("Total bytes allocated: %'lu\n", total_allocated); |
331 | printf("Total bytes wasted on internal fragmentation: %'lu\n", | 622 | printf("Total bytes wasted on internal fragmentation: %'lu\n", |
@@ -335,13 +626,73 @@ static void print_summary(void) | |||
335 | printf("Cross CPU allocations: %'lu/%'lu\n", nr_cross_allocs, nr_allocs); | 626 | printf("Cross CPU allocations: %'lu/%'lu\n", nr_cross_allocs, nr_allocs); |
336 | } | 627 | } |
337 | 628 | ||
338 | static void print_result(struct perf_session *session) | 629 | static void print_page_summary(void) |
630 | { | ||
631 | int o, m; | ||
632 | u64 nr_alloc_freed = nr_page_frees - nr_page_nomatch; | ||
633 | u64 total_alloc_freed_bytes = total_page_free_bytes - total_page_nomatch_bytes; | ||
634 | |||
635 | printf("\nSUMMARY (page allocator)"); | ||
636 | printf("\n========================\n"); | ||
637 | printf("%-30s: %'16lu [ %'16"PRIu64" KB ]\n", "Total allocation requests", | ||
638 | nr_page_allocs, total_page_alloc_bytes / 1024); | ||
639 | printf("%-30s: %'16lu [ %'16"PRIu64" KB ]\n", "Total free requests", | ||
640 | nr_page_frees, total_page_free_bytes / 1024); | ||
641 | printf("\n"); | ||
642 | |||
643 | printf("%-30s: %'16lu [ %'16"PRIu64" KB ]\n", "Total alloc+freed requests", | ||
644 | nr_alloc_freed, (total_alloc_freed_bytes) / 1024); | ||
645 | printf("%-30s: %'16lu [ %'16"PRIu64" KB ]\n", "Total alloc-only requests", | ||
646 | nr_page_allocs - nr_alloc_freed, | ||
647 | (total_page_alloc_bytes - total_alloc_freed_bytes) / 1024); | ||
648 | printf("%-30s: %'16lu [ %'16"PRIu64" KB ]\n", "Total free-only requests", | ||
649 | nr_page_nomatch, total_page_nomatch_bytes / 1024); | ||
650 | printf("\n"); | ||
651 | |||
652 | printf("%-30s: %'16lu [ %'16"PRIu64" KB ]\n", "Total allocation failures", | ||
653 | nr_page_fails, total_page_fail_bytes / 1024); | ||
654 | printf("\n"); | ||
655 | |||
656 | printf("%5s %12s %12s %12s %12s %12s\n", "Order", "Unmovable", | ||
657 | "Reclaimable", "Movable", "Reserved", "CMA/Isolated"); | ||
658 | printf("%.5s %.12s %.12s %.12s %.12s %.12s\n", graph_dotted_line, | ||
659 | graph_dotted_line, graph_dotted_line, graph_dotted_line, | ||
660 | graph_dotted_line, graph_dotted_line); | ||
661 | |||
662 | for (o = 0; o < MAX_PAGE_ORDER; o++) { | ||
663 | printf("%5d", o); | ||
664 | for (m = 0; m < MAX_MIGRATE_TYPES - 1; m++) { | ||
665 | if (order_stats[o][m]) | ||
666 | printf(" %'12d", order_stats[o][m]); | ||
667 | else | ||
668 | printf(" %12c", '.'); | ||
669 | } | ||
670 | printf("\n"); | ||
671 | } | ||
672 | } | ||
673 | |||
674 | static void print_slab_result(struct perf_session *session) | ||
339 | { | 675 | { |
340 | if (caller_flag) | 676 | if (caller_flag) |
341 | __print_result(&root_caller_sorted, session, caller_lines, 1); | 677 | __print_slab_result(&root_caller_sorted, session, caller_lines, 1); |
678 | if (alloc_flag) | ||
679 | __print_slab_result(&root_alloc_sorted, session, alloc_lines, 0); | ||
680 | print_slab_summary(); | ||
681 | } | ||
682 | |||
683 | static void print_page_result(struct perf_session *session) | ||
684 | { | ||
342 | if (alloc_flag) | 685 | if (alloc_flag) |
343 | __print_result(&root_alloc_sorted, session, alloc_lines, 0); | 686 | __print_page_result(&page_alloc_sorted, session, alloc_lines); |
344 | print_summary(); | 687 | print_page_summary(); |
688 | } | ||
689 | |||
690 | static void print_result(struct perf_session *session) | ||
691 | { | ||
692 | if (kmem_slab) | ||
693 | print_slab_result(session); | ||
694 | if (kmem_page) | ||
695 | print_page_result(session); | ||
345 | } | 696 | } |
346 | 697 | ||
347 | struct sort_dimension { | 698 | struct sort_dimension { |
@@ -353,8 +704,8 @@ struct sort_dimension { | |||
353 | static LIST_HEAD(caller_sort); | 704 | static LIST_HEAD(caller_sort); |
354 | static LIST_HEAD(alloc_sort); | 705 | static LIST_HEAD(alloc_sort); |
355 | 706 | ||
356 | static void sort_insert(struct rb_root *root, struct alloc_stat *data, | 707 | static void sort_slab_insert(struct rb_root *root, struct alloc_stat *data, |
357 | struct list_head *sort_list) | 708 | struct list_head *sort_list) |
358 | { | 709 | { |
359 | struct rb_node **new = &(root->rb_node); | 710 | struct rb_node **new = &(root->rb_node); |
360 | struct rb_node *parent = NULL; | 711 | struct rb_node *parent = NULL; |
@@ -383,8 +734,8 @@ static void sort_insert(struct rb_root *root, struct alloc_stat *data, | |||
383 | rb_insert_color(&data->node, root); | 734 | rb_insert_color(&data->node, root); |
384 | } | 735 | } |
385 | 736 | ||
386 | static void __sort_result(struct rb_root *root, struct rb_root *root_sorted, | 737 | static void __sort_slab_result(struct rb_root *root, struct rb_root *root_sorted, |
387 | struct list_head *sort_list) | 738 | struct list_head *sort_list) |
388 | { | 739 | { |
389 | struct rb_node *node; | 740 | struct rb_node *node; |
390 | struct alloc_stat *data; | 741 | struct alloc_stat *data; |
@@ -396,26 +747,79 @@ static void __sort_result(struct rb_root *root, struct rb_root *root_sorted, | |||
396 | 747 | ||
397 | rb_erase(node, root); | 748 | rb_erase(node, root); |
398 | data = rb_entry(node, struct alloc_stat, node); | 749 | data = rb_entry(node, struct alloc_stat, node); |
399 | sort_insert(root_sorted, data, sort_list); | 750 | sort_slab_insert(root_sorted, data, sort_list); |
751 | } | ||
752 | } | ||
753 | |||
754 | static void sort_page_insert(struct rb_root *root, struct page_stat *data) | ||
755 | { | ||
756 | struct rb_node **new = &root->rb_node; | ||
757 | struct rb_node *parent = NULL; | ||
758 | |||
759 | while (*new) { | ||
760 | struct page_stat *this; | ||
761 | int cmp = 0; | ||
762 | |||
763 | this = rb_entry(*new, struct page_stat, node); | ||
764 | parent = *new; | ||
765 | |||
766 | /* TODO: support more sort key */ | ||
767 | cmp = data->alloc_bytes - this->alloc_bytes; | ||
768 | |||
769 | if (cmp > 0) | ||
770 | new = &parent->rb_left; | ||
771 | else | ||
772 | new = &parent->rb_right; | ||
773 | } | ||
774 | |||
775 | rb_link_node(&data->node, parent, new); | ||
776 | rb_insert_color(&data->node, root); | ||
777 | } | ||
778 | |||
779 | static void __sort_page_result(struct rb_root *root, struct rb_root *root_sorted) | ||
780 | { | ||
781 | struct rb_node *node; | ||
782 | struct page_stat *data; | ||
783 | |||
784 | for (;;) { | ||
785 | node = rb_first(root); | ||
786 | if (!node) | ||
787 | break; | ||
788 | |||
789 | rb_erase(node, root); | ||
790 | data = rb_entry(node, struct page_stat, node); | ||
791 | sort_page_insert(root_sorted, data); | ||
400 | } | 792 | } |
401 | } | 793 | } |
402 | 794 | ||
403 | static void sort_result(void) | 795 | static void sort_result(void) |
404 | { | 796 | { |
405 | __sort_result(&root_alloc_stat, &root_alloc_sorted, &alloc_sort); | 797 | if (kmem_slab) { |
406 | __sort_result(&root_caller_stat, &root_caller_sorted, &caller_sort); | 798 | __sort_slab_result(&root_alloc_stat, &root_alloc_sorted, |
799 | &alloc_sort); | ||
800 | __sort_slab_result(&root_caller_stat, &root_caller_sorted, | ||
801 | &caller_sort); | ||
802 | } | ||
803 | if (kmem_page) { | ||
804 | __sort_page_result(&page_alloc_tree, &page_alloc_sorted); | ||
805 | } | ||
407 | } | 806 | } |
408 | 807 | ||
409 | static int __cmd_kmem(struct perf_session *session) | 808 | static int __cmd_kmem(struct perf_session *session) |
410 | { | 809 | { |
411 | int err = -EINVAL; | 810 | int err = -EINVAL; |
811 | struct perf_evsel *evsel; | ||
412 | const struct perf_evsel_str_handler kmem_tracepoints[] = { | 812 | const struct perf_evsel_str_handler kmem_tracepoints[] = { |
813 | /* slab allocator */ | ||
413 | { "kmem:kmalloc", perf_evsel__process_alloc_event, }, | 814 | { "kmem:kmalloc", perf_evsel__process_alloc_event, }, |
414 | { "kmem:kmem_cache_alloc", perf_evsel__process_alloc_event, }, | 815 | { "kmem:kmem_cache_alloc", perf_evsel__process_alloc_event, }, |
415 | { "kmem:kmalloc_node", perf_evsel__process_alloc_node_event, }, | 816 | { "kmem:kmalloc_node", perf_evsel__process_alloc_node_event, }, |
416 | { "kmem:kmem_cache_alloc_node", perf_evsel__process_alloc_node_event, }, | 817 | { "kmem:kmem_cache_alloc_node", perf_evsel__process_alloc_node_event, }, |
417 | { "kmem:kfree", perf_evsel__process_free_event, }, | 818 | { "kmem:kfree", perf_evsel__process_free_event, }, |
418 | { "kmem:kmem_cache_free", perf_evsel__process_free_event, }, | 819 | { "kmem:kmem_cache_free", perf_evsel__process_free_event, }, |
820 | /* page allocator */ | ||
821 | { "kmem:mm_page_alloc", perf_evsel__process_page_alloc_event, }, | ||
822 | { "kmem:mm_page_free", perf_evsel__process_page_free_event, }, | ||
419 | }; | 823 | }; |
420 | 824 | ||
421 | if (!perf_session__has_traces(session, "kmem record")) | 825 | if (!perf_session__has_traces(session, "kmem record")) |
@@ -426,10 +830,20 @@ static int __cmd_kmem(struct perf_session *session) | |||
426 | goto out; | 830 | goto out; |
427 | } | 831 | } |
428 | 832 | ||
833 | evlist__for_each(session->evlist, evsel) { | ||
834 | if (!strcmp(perf_evsel__name(evsel), "kmem:mm_page_alloc") && | ||
835 | perf_evsel__field(evsel, "pfn")) { | ||
836 | use_pfn = true; | ||
837 | break; | ||
838 | } | ||
839 | } | ||
840 | |||
429 | setup_pager(); | 841 | setup_pager(); |
430 | err = perf_session__process_events(session); | 842 | err = perf_session__process_events(session); |
431 | if (err != 0) | 843 | if (err != 0) { |
844 | pr_err("error during process events: %d\n", err); | ||
432 | goto out; | 845 | goto out; |
846 | } | ||
433 | sort_result(); | 847 | sort_result(); |
434 | print_result(session); | 848 | print_result(session); |
435 | out: | 849 | out: |
@@ -612,6 +1026,22 @@ static int parse_alloc_opt(const struct option *opt __maybe_unused, | |||
612 | return 0; | 1026 | return 0; |
613 | } | 1027 | } |
614 | 1028 | ||
1029 | static int parse_slab_opt(const struct option *opt __maybe_unused, | ||
1030 | const char *arg __maybe_unused, | ||
1031 | int unset __maybe_unused) | ||
1032 | { | ||
1033 | kmem_slab = (kmem_page + 1); | ||
1034 | return 0; | ||
1035 | } | ||
1036 | |||
1037 | static int parse_page_opt(const struct option *opt __maybe_unused, | ||
1038 | const char *arg __maybe_unused, | ||
1039 | int unset __maybe_unused) | ||
1040 | { | ||
1041 | kmem_page = (kmem_slab + 1); | ||
1042 | return 0; | ||
1043 | } | ||
1044 | |||
615 | static int parse_line_opt(const struct option *opt __maybe_unused, | 1045 | static int parse_line_opt(const struct option *opt __maybe_unused, |
616 | const char *arg, int unset __maybe_unused) | 1046 | const char *arg, int unset __maybe_unused) |
617 | { | 1047 | { |
@@ -634,6 +1064,8 @@ static int __cmd_record(int argc, const char **argv) | |||
634 | { | 1064 | { |
635 | const char * const record_args[] = { | 1065 | const char * const record_args[] = { |
636 | "record", "-a", "-R", "-c", "1", | 1066 | "record", "-a", "-R", "-c", "1", |
1067 | }; | ||
1068 | const char * const slab_events[] = { | ||
637 | "-e", "kmem:kmalloc", | 1069 | "-e", "kmem:kmalloc", |
638 | "-e", "kmem:kmalloc_node", | 1070 | "-e", "kmem:kmalloc_node", |
639 | "-e", "kmem:kfree", | 1071 | "-e", "kmem:kfree", |
@@ -641,10 +1073,19 @@ static int __cmd_record(int argc, const char **argv) | |||
641 | "-e", "kmem:kmem_cache_alloc_node", | 1073 | "-e", "kmem:kmem_cache_alloc_node", |
642 | "-e", "kmem:kmem_cache_free", | 1074 | "-e", "kmem:kmem_cache_free", |
643 | }; | 1075 | }; |
1076 | const char * const page_events[] = { | ||
1077 | "-e", "kmem:mm_page_alloc", | ||
1078 | "-e", "kmem:mm_page_free", | ||
1079 | }; | ||
644 | unsigned int rec_argc, i, j; | 1080 | unsigned int rec_argc, i, j; |
645 | const char **rec_argv; | 1081 | const char **rec_argv; |
646 | 1082 | ||
647 | rec_argc = ARRAY_SIZE(record_args) + argc - 1; | 1083 | rec_argc = ARRAY_SIZE(record_args) + argc - 1; |
1084 | if (kmem_slab) | ||
1085 | rec_argc += ARRAY_SIZE(slab_events); | ||
1086 | if (kmem_page) | ||
1087 | rec_argc += ARRAY_SIZE(page_events); | ||
1088 | |||
648 | rec_argv = calloc(rec_argc + 1, sizeof(char *)); | 1089 | rec_argv = calloc(rec_argc + 1, sizeof(char *)); |
649 | 1090 | ||
650 | if (rec_argv == NULL) | 1091 | if (rec_argv == NULL) |
@@ -653,6 +1094,15 @@ static int __cmd_record(int argc, const char **argv) | |||
653 | for (i = 0; i < ARRAY_SIZE(record_args); i++) | 1094 | for (i = 0; i < ARRAY_SIZE(record_args); i++) |
654 | rec_argv[i] = strdup(record_args[i]); | 1095 | rec_argv[i] = strdup(record_args[i]); |
655 | 1096 | ||
1097 | if (kmem_slab) { | ||
1098 | for (j = 0; j < ARRAY_SIZE(slab_events); j++, i++) | ||
1099 | rec_argv[i] = strdup(slab_events[j]); | ||
1100 | } | ||
1101 | if (kmem_page) { | ||
1102 | for (j = 0; j < ARRAY_SIZE(page_events); j++, i++) | ||
1103 | rec_argv[i] = strdup(page_events[j]); | ||
1104 | } | ||
1105 | |||
656 | for (j = 1; j < (unsigned int)argc; j++, i++) | 1106 | for (j = 1; j < (unsigned int)argc; j++, i++) |
657 | rec_argv[i] = argv[j]; | 1107 | rec_argv[i] = argv[j]; |
658 | 1108 | ||
@@ -679,6 +1129,10 @@ int cmd_kmem(int argc, const char **argv, const char *prefix __maybe_unused) | |||
679 | OPT_CALLBACK('l', "line", NULL, "num", "show n lines", parse_line_opt), | 1129 | OPT_CALLBACK('l', "line", NULL, "num", "show n lines", parse_line_opt), |
680 | OPT_BOOLEAN(0, "raw-ip", &raw_ip, "show raw ip instead of symbol"), | 1130 | OPT_BOOLEAN(0, "raw-ip", &raw_ip, "show raw ip instead of symbol"), |
681 | OPT_BOOLEAN('f', "force", &file.force, "don't complain, do it"), | 1131 | OPT_BOOLEAN('f', "force", &file.force, "don't complain, do it"), |
1132 | OPT_CALLBACK_NOOPT(0, "slab", NULL, NULL, "Analyze slab allocator", | ||
1133 | parse_slab_opt), | ||
1134 | OPT_CALLBACK_NOOPT(0, "page", NULL, NULL, "Analyze page allocator", | ||
1135 | parse_page_opt), | ||
682 | OPT_END() | 1136 | OPT_END() |
683 | }; | 1137 | }; |
684 | const char *const kmem_subcommands[] = { "record", "stat", NULL }; | 1138 | const char *const kmem_subcommands[] = { "record", "stat", NULL }; |
@@ -695,6 +1149,9 @@ int cmd_kmem(int argc, const char **argv, const char *prefix __maybe_unused) | |||
695 | if (!argc) | 1149 | if (!argc) |
696 | usage_with_options(kmem_usage, kmem_options); | 1150 | usage_with_options(kmem_usage, kmem_options); |
697 | 1151 | ||
1152 | if (kmem_slab == 0 && kmem_page == 0) | ||
1153 | kmem_slab = 1; /* for backward compatibility */ | ||
1154 | |||
698 | if (!strncmp(argv[0], "rec", 3)) { | 1155 | if (!strncmp(argv[0], "rec", 3)) { |
699 | symbol__init(NULL); | 1156 | symbol__init(NULL); |
700 | return __cmd_record(argc, argv); | 1157 | return __cmd_record(argc, argv); |
@@ -706,6 +1163,17 @@ int cmd_kmem(int argc, const char **argv, const char *prefix __maybe_unused) | |||
706 | if (session == NULL) | 1163 | if (session == NULL) |
707 | return -1; | 1164 | return -1; |
708 | 1165 | ||
1166 | if (kmem_page) { | ||
1167 | struct perf_evsel *evsel = perf_evlist__first(session->evlist); | ||
1168 | |||
1169 | if (evsel == NULL || evsel->tp_format == NULL) { | ||
1170 | pr_err("invalid event found.. aborting\n"); | ||
1171 | return -1; | ||
1172 | } | ||
1173 | |||
1174 | kmem_page_size = pevent_get_page_size(evsel->tp_format->pevent); | ||
1175 | } | ||
1176 | |||
709 | symbol__init(&session->header.env); | 1177 | symbol__init(&session->header.env); |
710 | 1178 | ||
711 | if (!strcmp(argv[0], "stat")) { | 1179 | if (!strcmp(argv[0], "stat")) { |