diff options
author | Peter Zijlstra <a.p.zijlstra@chello.nl> | 2009-06-17 09:51:44 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-06-17 13:23:52 -0400 |
commit | 6e7d6fdcbeefa9434653b5e5da12909636ea1d52 (patch) | |
tree | f224c67b032841da2d58130c1d36beed8e5598aa /tools | |
parent | a3d06cc6aa3e765dc2bf98626f87272dcf641dca (diff) |
perf report: Add --sort <call> --call <$regex>
Implement sorting by callchain symbols, --sort <call>.
It will create a new column which will show a match to
--call $regex or "[unmatched]".
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
LKML-Reference: <new-submission>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/perf/builtin-report.c | 209 |
1 files changed, 158 insertions, 51 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index f86bb07c0e84..cd74b2e58adb 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c | |||
@@ -40,11 +40,13 @@ static int dump_trace = 0; | |||
40 | 40 | ||
41 | static int verbose; | 41 | static int verbose; |
42 | static int full_paths; | 42 | static int full_paths; |
43 | static int collapse_syscalls; | ||
44 | 43 | ||
45 | static unsigned long page_size; | 44 | static unsigned long page_size; |
46 | static unsigned long mmap_window = 32; | 45 | static unsigned long mmap_window = 32; |
47 | 46 | ||
47 | static char *call = "^sys_"; | ||
48 | static regex_t call_regex; | ||
49 | |||
48 | struct ip_chain_event { | 50 | struct ip_chain_event { |
49 | __u16 nr; | 51 | __u16 nr; |
50 | __u16 hv; | 52 | __u16 hv; |
@@ -463,6 +465,7 @@ struct hist_entry { | |||
463 | struct map *map; | 465 | struct map *map; |
464 | struct dso *dso; | 466 | struct dso *dso; |
465 | struct symbol *sym; | 467 | struct symbol *sym; |
468 | struct symbol *call; | ||
466 | __u64 ip; | 469 | __u64 ip; |
467 | char level; | 470 | char level; |
468 | 471 | ||
@@ -483,6 +486,16 @@ struct sort_entry { | |||
483 | size_t (*print)(FILE *fp, struct hist_entry *); | 486 | size_t (*print)(FILE *fp, struct hist_entry *); |
484 | }; | 487 | }; |
485 | 488 | ||
489 | static int64_t cmp_null(void *l, void *r) | ||
490 | { | ||
491 | if (!l && !r) | ||
492 | return 0; | ||
493 | else if (!l) | ||
494 | return -1; | ||
495 | else | ||
496 | return 1; | ||
497 | } | ||
498 | |||
486 | /* --sort pid */ | 499 | /* --sort pid */ |
487 | 500 | ||
488 | static int64_t | 501 | static int64_t |
@@ -517,14 +530,8 @@ sort__comm_collapse(struct hist_entry *left, struct hist_entry *right) | |||
517 | char *comm_l = left->thread->comm; | 530 | char *comm_l = left->thread->comm; |
518 | char *comm_r = right->thread->comm; | 531 | char *comm_r = right->thread->comm; |
519 | 532 | ||
520 | if (!comm_l || !comm_r) { | 533 | if (!comm_l || !comm_r) |
521 | if (!comm_l && !comm_r) | 534 | return cmp_null(comm_l, comm_r); |
522 | return 0; | ||
523 | else if (!comm_l) | ||
524 | return -1; | ||
525 | else | ||
526 | return 1; | ||
527 | } | ||
528 | 535 | ||
529 | return strcmp(comm_l, comm_r); | 536 | return strcmp(comm_l, comm_r); |
530 | } | 537 | } |
@@ -550,14 +557,8 @@ sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) | |||
550 | struct dso *dso_l = left->dso; | 557 | struct dso *dso_l = left->dso; |
551 | struct dso *dso_r = right->dso; | 558 | struct dso *dso_r = right->dso; |
552 | 559 | ||
553 | if (!dso_l || !dso_r) { | 560 | if (!dso_l || !dso_r) |
554 | if (!dso_l && !dso_r) | 561 | return cmp_null(dso_l, dso_r); |
555 | return 0; | ||
556 | else if (!dso_l) | ||
557 | return -1; | ||
558 | else | ||
559 | return 1; | ||
560 | } | ||
561 | 562 | ||
562 | return strcmp(dso_l->name, dso_r->name); | 563 | return strcmp(dso_l->name, dso_r->name); |
563 | } | 564 | } |
@@ -617,7 +618,38 @@ static struct sort_entry sort_sym = { | |||
617 | .print = sort__sym_print, | 618 | .print = sort__sym_print, |
618 | }; | 619 | }; |
619 | 620 | ||
621 | /* --sort call */ | ||
622 | |||
623 | static int64_t | ||
624 | sort__call_cmp(struct hist_entry *left, struct hist_entry *right) | ||
625 | { | ||
626 | struct symbol *sym_l = left->call; | ||
627 | struct symbol *sym_r = right->call; | ||
628 | |||
629 | if (!sym_l || !sym_r) | ||
630 | return cmp_null(sym_l, sym_r); | ||
631 | |||
632 | return strcmp(sym_l->name, sym_r->name); | ||
633 | } | ||
634 | |||
635 | static size_t | ||
636 | sort__call_print(FILE *fp, struct hist_entry *self) | ||
637 | { | ||
638 | size_t ret = 0; | ||
639 | |||
640 | ret += fprintf(fp, "%-20s", self->call ? self->call->name : "[unmatched]"); | ||
641 | |||
642 | return ret; | ||
643 | } | ||
644 | |||
645 | static struct sort_entry sort_call = { | ||
646 | .header = "Callchain symbol ", | ||
647 | .cmp = sort__call_cmp, | ||
648 | .print = sort__call_print, | ||
649 | }; | ||
650 | |||
620 | static int sort__need_collapse = 0; | 651 | static int sort__need_collapse = 0; |
652 | static int sort__has_call = 0; | ||
621 | 653 | ||
622 | struct sort_dimension { | 654 | struct sort_dimension { |
623 | char *name; | 655 | char *name; |
@@ -630,6 +662,7 @@ static struct sort_dimension sort_dimensions[] = { | |||
630 | { .name = "comm", .entry = &sort_comm, }, | 662 | { .name = "comm", .entry = &sort_comm, }, |
631 | { .name = "dso", .entry = &sort_dso, }, | 663 | { .name = "dso", .entry = &sort_dso, }, |
632 | { .name = "symbol", .entry = &sort_sym, }, | 664 | { .name = "symbol", .entry = &sort_sym, }, |
665 | { .name = "call", .entry = &sort_call, }, | ||
633 | }; | 666 | }; |
634 | 667 | ||
635 | static LIST_HEAD(hist_entry__sort_list); | 668 | static LIST_HEAD(hist_entry__sort_list); |
@@ -650,6 +683,18 @@ static int sort_dimension__add(char *tok) | |||
650 | if (sd->entry->collapse) | 683 | if (sd->entry->collapse) |
651 | sort__need_collapse = 1; | 684 | sort__need_collapse = 1; |
652 | 685 | ||
686 | if (sd->entry == &sort_call) { | ||
687 | int ret = regcomp(&call_regex, call, REG_EXTENDED); | ||
688 | if (ret) { | ||
689 | char err[BUFSIZ]; | ||
690 | |||
691 | regerror(ret, &call_regex, err, sizeof(err)); | ||
692 | fprintf(stderr, "Invalid regex: %s\n%s", call, err); | ||
693 | exit(-1); | ||
694 | } | ||
695 | sort__has_call = 1; | ||
696 | } | ||
697 | |||
653 | list_add_tail(&sd->entry->list, &hist_entry__sort_list); | 698 | list_add_tail(&sd->entry->list, &hist_entry__sort_list); |
654 | sd->taken = 1; | 699 | sd->taken = 1; |
655 | 700 | ||
@@ -731,12 +776,75 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, __u64 total_samples) | |||
731 | } | 776 | } |
732 | 777 | ||
733 | /* | 778 | /* |
779 | * | ||
780 | */ | ||
781 | |||
782 | static struct symbol * | ||
783 | resolve_symbol(struct thread *thread, struct map **mapp, | ||
784 | struct dso **dsop, __u64 *ipp) | ||
785 | { | ||
786 | struct dso *dso = dsop ? *dsop : NULL; | ||
787 | struct map *map = mapp ? *mapp : NULL; | ||
788 | uint64_t ip = *ipp; | ||
789 | |||
790 | if (!thread) | ||
791 | return NULL; | ||
792 | |||
793 | if (dso) | ||
794 | goto got_dso; | ||
795 | |||
796 | if (map) | ||
797 | goto got_map; | ||
798 | |||
799 | map = thread__find_map(thread, ip); | ||
800 | if (map != NULL) { | ||
801 | if (mapp) | ||
802 | *mapp = map; | ||
803 | got_map: | ||
804 | ip = map->map_ip(map, ip); | ||
805 | *ipp = ip; | ||
806 | |||
807 | dso = map->dso; | ||
808 | } else { | ||
809 | /* | ||
810 | * If this is outside of all known maps, | ||
811 | * and is a negative address, try to look it | ||
812 | * up in the kernel dso, as it might be a | ||
813 | * vsyscall (which executes in user-mode): | ||
814 | */ | ||
815 | if ((long long)ip < 0) | ||
816 | dso = kernel_dso; | ||
817 | } | ||
818 | dprintf(" ...... dso: %s\n", dso ? dso->name : "<not found>"); | ||
819 | |||
820 | if (dsop) | ||
821 | *dsop = dso; | ||
822 | |||
823 | if (!dso) | ||
824 | return NULL; | ||
825 | got_dso: | ||
826 | return dso->find_symbol(dso, ip); | ||
827 | } | ||
828 | |||
829 | static struct symbol *call__match(struct symbol *sym) | ||
830 | { | ||
831 | if (!sym) | ||
832 | return NULL; | ||
833 | |||
834 | if (sym->name && !regexec(&call_regex, sym->name, 0, NULL, 0)) | ||
835 | return sym; | ||
836 | |||
837 | return NULL; | ||
838 | } | ||
839 | |||
840 | /* | ||
734 | * collect histogram counts | 841 | * collect histogram counts |
735 | */ | 842 | */ |
736 | 843 | ||
737 | static int | 844 | static int |
738 | hist_entry__add(struct thread *thread, struct map *map, struct dso *dso, | 845 | hist_entry__add(struct thread *thread, struct map *map, struct dso *dso, |
739 | struct symbol *sym, __u64 ip, char level, __u64 count) | 846 | struct symbol *sym, __u64 ip, struct ip_chain_event *chain, |
847 | char level, __u64 count) | ||
740 | { | 848 | { |
741 | struct rb_node **p = &hist.rb_node; | 849 | struct rb_node **p = &hist.rb_node; |
742 | struct rb_node *parent = NULL; | 850 | struct rb_node *parent = NULL; |
@@ -752,6 +860,33 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso, | |||
752 | }; | 860 | }; |
753 | int cmp; | 861 | int cmp; |
754 | 862 | ||
863 | if (sort__has_call && chain) { | ||
864 | int i, nr = chain->hv; | ||
865 | struct symbol *sym; | ||
866 | struct dso *dso; | ||
867 | __u64 ip; | ||
868 | |||
869 | for (i = 0; i < chain->kernel; i++) { | ||
870 | ip = chain->ips[nr + i]; | ||
871 | dso = kernel_dso; | ||
872 | sym = resolve_symbol(thread, NULL, &dso, &ip); | ||
873 | entry.call = call__match(sym); | ||
874 | if (entry.call) | ||
875 | goto got_call; | ||
876 | } | ||
877 | nr += i; | ||
878 | |||
879 | for (i = 0; i < chain->user; i++) { | ||
880 | ip = chain->ips[nr + i]; | ||
881 | sym = resolve_symbol(thread, NULL, NULL, &ip); | ||
882 | entry.call = call__match(sym); | ||
883 | if (entry.call) | ||
884 | goto got_call; | ||
885 | } | ||
886 | nr += i; | ||
887 | } | ||
888 | got_call: | ||
889 | |||
755 | while (*p != NULL) { | 890 | while (*p != NULL) { |
756 | parent = *p; | 891 | parent = *p; |
757 | he = rb_entry(parent, struct hist_entry, rb_node); | 892 | he = rb_entry(parent, struct hist_entry, rb_node); |
@@ -955,7 +1090,7 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
955 | __u64 period = 1; | 1090 | __u64 period = 1; |
956 | struct map *map = NULL; | 1091 | struct map *map = NULL; |
957 | void *more_data = event->ip.__more_data; | 1092 | void *more_data = event->ip.__more_data; |
958 | struct ip_chain_event *chain; | 1093 | struct ip_chain_event *chain = NULL; |
959 | 1094 | ||
960 | if (event->header.type & PERF_SAMPLE_PERIOD) { | 1095 | if (event->header.type & PERF_SAMPLE_PERIOD) { |
961 | period = *(__u64 *)more_data; | 1096 | period = *(__u64 *)more_data; |
@@ -984,15 +1119,6 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
984 | for (i = 0; i < chain->nr; i++) | 1119 | for (i = 0; i < chain->nr; i++) |
985 | dprintf("..... %2d: %016Lx\n", i, chain->ips[i]); | 1120 | dprintf("..... %2d: %016Lx\n", i, chain->ips[i]); |
986 | } | 1121 | } |
987 | if (collapse_syscalls) { | ||
988 | /* | ||
989 | * Find the all-but-last kernel entry | ||
990 | * amongst the call-chains - to get | ||
991 | * to the level of system calls: | ||
992 | */ | ||
993 | if (chain->kernel >= 2) | ||
994 | ip = chain->ips[chain->kernel-2]; | ||
995 | } | ||
996 | } | 1122 | } |
997 | 1123 | ||
998 | dprintf(" ... thread: %s:%d\n", thread->comm, thread->pid); | 1124 | dprintf(" ... thread: %s:%d\n", thread->comm, thread->pid); |
@@ -1016,22 +1142,6 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
1016 | show = SHOW_USER; | 1142 | show = SHOW_USER; |
1017 | level = '.'; | 1143 | level = '.'; |
1018 | 1144 | ||
1019 | map = thread__find_map(thread, ip); | ||
1020 | if (map != NULL) { | ||
1021 | ip = map->map_ip(map, ip); | ||
1022 | dso = map->dso; | ||
1023 | } else { | ||
1024 | /* | ||
1025 | * If this is outside of all known maps, | ||
1026 | * and is a negative address, try to look it | ||
1027 | * up in the kernel dso, as it might be a | ||
1028 | * vsyscall (which executes in user-mode): | ||
1029 | */ | ||
1030 | if ((long long)ip < 0) | ||
1031 | dso = kernel_dso; | ||
1032 | } | ||
1033 | dprintf(" ...... dso: %s\n", dso ? dso->name : "<not found>"); | ||
1034 | |||
1035 | } else { | 1145 | } else { |
1036 | show = SHOW_HV; | 1146 | show = SHOW_HV; |
1037 | level = 'H'; | 1147 | level = 'H'; |
@@ -1039,12 +1149,9 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
1039 | } | 1149 | } |
1040 | 1150 | ||
1041 | if (show & show_mask) { | 1151 | if (show & show_mask) { |
1042 | struct symbol *sym = NULL; | 1152 | struct symbol *sym = resolve_symbol(thread, &map, &dso, &ip); |
1043 | |||
1044 | if (dso) | ||
1045 | sym = dso->find_symbol(dso, ip); | ||
1046 | 1153 | ||
1047 | if (hist_entry__add(thread, map, dso, sym, ip, level, period)) { | 1154 | if (hist_entry__add(thread, map, dso, sym, ip, chain, level, period)) { |
1048 | fprintf(stderr, | 1155 | fprintf(stderr, |
1049 | "problem incrementing symbol count, skipping event\n"); | 1156 | "problem incrementing symbol count, skipping event\n"); |
1050 | return -1; | 1157 | return -1; |
@@ -1353,8 +1460,8 @@ static const struct option options[] = { | |||
1353 | "sort by key(s): pid, comm, dso, symbol. Default: pid,symbol"), | 1460 | "sort by key(s): pid, comm, dso, symbol. Default: pid,symbol"), |
1354 | OPT_BOOLEAN('P', "full-paths", &full_paths, | 1461 | OPT_BOOLEAN('P', "full-paths", &full_paths, |
1355 | "Don't shorten the pathnames taking into account the cwd"), | 1462 | "Don't shorten the pathnames taking into account the cwd"), |
1356 | OPT_BOOLEAN('S', "syscalls", &collapse_syscalls, | 1463 | OPT_STRING('c', "call", &call, "regex", |
1357 | "show per syscall summary overhead, using call graph"), | 1464 | "regex to use for --sort call"), |
1358 | OPT_END() | 1465 | OPT_END() |
1359 | }; | 1466 | }; |
1360 | 1467 | ||