diff options
Diffstat (limited to 'tools/perf/builtin-report.c')
-rw-r--r-- | tools/perf/builtin-report.c | 595 |
1 files changed, 488 insertions, 107 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 5eb5566f0c95..4e5cc266311e 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c | |||
@@ -10,13 +10,16 @@ | |||
10 | #include "util/util.h" | 10 | #include "util/util.h" |
11 | 11 | ||
12 | #include "util/color.h" | 12 | #include "util/color.h" |
13 | #include "util/list.h" | 13 | #include <linux/list.h> |
14 | #include "util/cache.h" | 14 | #include "util/cache.h" |
15 | #include "util/rbtree.h" | 15 | #include <linux/rbtree.h> |
16 | #include "util/symbol.h" | 16 | #include "util/symbol.h" |
17 | #include "util/string.h" | 17 | #include "util/string.h" |
18 | #include "util/callchain.h" | ||
19 | #include "util/strlist.h" | ||
18 | 20 | ||
19 | #include "perf.h" | 21 | #include "perf.h" |
22 | #include "util/header.h" | ||
20 | 23 | ||
21 | #include "util/parse-options.h" | 24 | #include "util/parse-options.h" |
22 | #include "util/parse-events.h" | 25 | #include "util/parse-events.h" |
@@ -30,6 +33,8 @@ static char *vmlinux = NULL; | |||
30 | 33 | ||
31 | static char default_sort_order[] = "comm,dso"; | 34 | static char default_sort_order[] = "comm,dso"; |
32 | static char *sort_order = default_sort_order; | 35 | static char *sort_order = default_sort_order; |
36 | static char *dso_list_str, *comm_list_str, *sym_list_str; | ||
37 | static struct strlist *dso_list, *comm_list, *sym_list; | ||
33 | 38 | ||
34 | static int input; | 39 | static int input; |
35 | static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV; | 40 | static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV; |
@@ -41,6 +46,8 @@ static int dump_trace = 0; | |||
41 | static int verbose; | 46 | static int verbose; |
42 | #define eprintf(x...) do { if (verbose) fprintf(stderr, x); } while (0) | 47 | #define eprintf(x...) do { if (verbose) fprintf(stderr, x); } while (0) |
43 | 48 | ||
49 | static int modules; | ||
50 | |||
44 | static int full_paths; | 51 | static int full_paths; |
45 | 52 | ||
46 | static unsigned long page_size; | 53 | static unsigned long page_size; |
@@ -52,6 +59,18 @@ static regex_t parent_regex; | |||
52 | 59 | ||
53 | static int exclude_other = 1; | 60 | static int exclude_other = 1; |
54 | 61 | ||
62 | static char callchain_default_opt[] = "fractal,0.5"; | ||
63 | |||
64 | static int callchain; | ||
65 | |||
66 | static | ||
67 | struct callchain_param callchain_param = { | ||
68 | .mode = CHAIN_GRAPH_ABS, | ||
69 | .min_percent = 0.5 | ||
70 | }; | ||
71 | |||
72 | static u64 sample_type; | ||
73 | |||
55 | struct ip_event { | 74 | struct ip_event { |
56 | struct perf_event_header header; | 75 | struct perf_event_header header; |
57 | u64 ip; | 76 | u64 ip; |
@@ -59,11 +78,6 @@ struct ip_event { | |||
59 | unsigned char __more_data[]; | 78 | unsigned char __more_data[]; |
60 | }; | 79 | }; |
61 | 80 | ||
62 | struct ip_callchain { | ||
63 | u64 nr; | ||
64 | u64 ips[0]; | ||
65 | }; | ||
66 | |||
67 | struct mmap_event { | 81 | struct mmap_event { |
68 | struct perf_event_header header; | 82 | struct perf_event_header header; |
69 | u32 pid, tid; | 83 | u32 pid, tid; |
@@ -97,6 +111,13 @@ struct lost_event { | |||
97 | u64 lost; | 111 | u64 lost; |
98 | }; | 112 | }; |
99 | 113 | ||
114 | struct read_event { | ||
115 | struct perf_event_header header; | ||
116 | u32 pid,tid; | ||
117 | u64 value; | ||
118 | u64 format[3]; | ||
119 | }; | ||
120 | |||
100 | typedef union event_union { | 121 | typedef union event_union { |
101 | struct perf_event_header header; | 122 | struct perf_event_header header; |
102 | struct ip_event ip; | 123 | struct ip_event ip; |
@@ -105,11 +126,13 @@ typedef union event_union { | |||
105 | struct fork_event fork; | 126 | struct fork_event fork; |
106 | struct period_event period; | 127 | struct period_event period; |
107 | struct lost_event lost; | 128 | struct lost_event lost; |
129 | struct read_event read; | ||
108 | } event_t; | 130 | } event_t; |
109 | 131 | ||
110 | static LIST_HEAD(dsos); | 132 | static LIST_HEAD(dsos); |
111 | static struct dso *kernel_dso; | 133 | static struct dso *kernel_dso; |
112 | static struct dso *vdso; | 134 | static struct dso *vdso; |
135 | static struct dso *hypervisor_dso; | ||
113 | 136 | ||
114 | static void dsos__add(struct dso *dso) | 137 | static void dsos__add(struct dso *dso) |
115 | { | 138 | { |
@@ -165,7 +188,7 @@ static void dsos__fprintf(FILE *fp) | |||
165 | 188 | ||
166 | static struct symbol *vdso__find_symbol(struct dso *dso, u64 ip) | 189 | static struct symbol *vdso__find_symbol(struct dso *dso, u64 ip) |
167 | { | 190 | { |
168 | return dso__find_symbol(kernel_dso, ip); | 191 | return dso__find_symbol(dso, ip); |
169 | } | 192 | } |
170 | 193 | ||
171 | static int load_kernel(void) | 194 | static int load_kernel(void) |
@@ -176,8 +199,8 @@ static int load_kernel(void) | |||
176 | if (!kernel_dso) | 199 | if (!kernel_dso) |
177 | return -1; | 200 | return -1; |
178 | 201 | ||
179 | err = dso__load_kernel(kernel_dso, vmlinux, NULL, verbose); | 202 | err = dso__load_kernel(kernel_dso, vmlinux, NULL, verbose, modules); |
180 | if (err) { | 203 | if (err <= 0) { |
181 | dso__delete(kernel_dso); | 204 | dso__delete(kernel_dso); |
182 | kernel_dso = NULL; | 205 | kernel_dso = NULL; |
183 | } else | 206 | } else |
@@ -191,6 +214,11 @@ static int load_kernel(void) | |||
191 | 214 | ||
192 | dsos__add(vdso); | 215 | dsos__add(vdso); |
193 | 216 | ||
217 | hypervisor_dso = dso__new("[hypervisor]", 0); | ||
218 | if (!hypervisor_dso) | ||
219 | return -1; | ||
220 | dsos__add(hypervisor_dso); | ||
221 | |||
194 | return err; | 222 | return err; |
195 | } | 223 | } |
196 | 224 | ||
@@ -222,14 +250,14 @@ static u64 map__map_ip(struct map *map, u64 ip) | |||
222 | return ip - map->start + map->pgoff; | 250 | return ip - map->start + map->pgoff; |
223 | } | 251 | } |
224 | 252 | ||
225 | static u64 vdso__map_ip(struct map *map, u64 ip) | 253 | static u64 vdso__map_ip(struct map *map __used, u64 ip) |
226 | { | 254 | { |
227 | return ip; | 255 | return ip; |
228 | } | 256 | } |
229 | 257 | ||
230 | static inline int is_anon_memory(const char *filename) | 258 | static inline int is_anon_memory(const char *filename) |
231 | { | 259 | { |
232 | return strcmp(filename, "//anon") == 0; | 260 | return strcmp(filename, "//anon") == 0; |
233 | } | 261 | } |
234 | 262 | ||
235 | static struct map *map__new(struct mmap_event *event) | 263 | static struct map *map__new(struct mmap_event *event) |
@@ -400,9 +428,27 @@ static void thread__insert_map(struct thread *self, struct map *map) | |||
400 | 428 | ||
401 | list_for_each_entry_safe(pos, tmp, &self->maps, node) { | 429 | list_for_each_entry_safe(pos, tmp, &self->maps, node) { |
402 | if (map__overlap(pos, map)) { | 430 | if (map__overlap(pos, map)) { |
403 | list_del_init(&pos->node); | 431 | if (verbose >= 2) { |
404 | /* XXX leaks dsos */ | 432 | printf("overlapping maps:\n"); |
405 | free(pos); | 433 | map__fprintf(map, stdout); |
434 | map__fprintf(pos, stdout); | ||
435 | } | ||
436 | |||
437 | if (map->start <= pos->start && map->end > pos->start) | ||
438 | pos->start = map->end; | ||
439 | |||
440 | if (map->end >= pos->end && map->start < pos->end) | ||
441 | pos->end = map->start; | ||
442 | |||
443 | if (verbose >= 2) { | ||
444 | printf("after collision:\n"); | ||
445 | map__fprintf(pos, stdout); | ||
446 | } | ||
447 | |||
448 | if (pos->start >= pos->end) { | ||
449 | list_del_init(&pos->node); | ||
450 | free(pos); | ||
451 | } | ||
406 | } | 452 | } |
407 | } | 453 | } |
408 | 454 | ||
@@ -464,17 +510,19 @@ static size_t threads__fprintf(FILE *fp) | |||
464 | static struct rb_root hist; | 510 | static struct rb_root hist; |
465 | 511 | ||
466 | struct hist_entry { | 512 | struct hist_entry { |
467 | struct rb_node rb_node; | 513 | struct rb_node rb_node; |
468 | 514 | ||
469 | struct thread *thread; | 515 | struct thread *thread; |
470 | struct map *map; | 516 | struct map *map; |
471 | struct dso *dso; | 517 | struct dso *dso; |
472 | struct symbol *sym; | 518 | struct symbol *sym; |
473 | struct symbol *parent; | 519 | struct symbol *parent; |
474 | u64 ip; | 520 | u64 ip; |
475 | char level; | 521 | char level; |
476 | 522 | struct callchain_node callchain; | |
477 | u64 count; | 523 | struct rb_root sorted_chain; |
524 | |||
525 | u64 count; | ||
478 | }; | 526 | }; |
479 | 527 | ||
480 | /* | 528 | /* |
@@ -609,7 +657,11 @@ sort__sym_print(FILE *fp, struct hist_entry *self) | |||
609 | 657 | ||
610 | if (self->sym) { | 658 | if (self->sym) { |
611 | ret += fprintf(fp, "[%c] %s", | 659 | ret += fprintf(fp, "[%c] %s", |
612 | self->dso == kernel_dso ? 'k' : '.', self->sym->name); | 660 | self->dso == kernel_dso ? 'k' : |
661 | self->dso == hypervisor_dso ? 'h' : '.', self->sym->name); | ||
662 | |||
663 | if (self->sym->module) | ||
664 | ret += fprintf(fp, "\t[%s]", self->sym->module->name); | ||
613 | } else { | 665 | } else { |
614 | ret += fprintf(fp, "%#016llx", (u64)self->ip); | 666 | ret += fprintf(fp, "%#016llx", (u64)self->ip); |
615 | } | 667 | } |
@@ -674,7 +726,7 @@ static LIST_HEAD(hist_entry__sort_list); | |||
674 | 726 | ||
675 | static int sort_dimension__add(char *tok) | 727 | static int sort_dimension__add(char *tok) |
676 | { | 728 | { |
677 | int i; | 729 | unsigned int i; |
678 | 730 | ||
679 | for (i = 0; i < ARRAY_SIZE(sort_dimensions); i++) { | 731 | for (i = 0; i < ARRAY_SIZE(sort_dimensions); i++) { |
680 | struct sort_dimension *sd = &sort_dimensions[i]; | 732 | struct sort_dimension *sd = &sort_dimensions[i]; |
@@ -744,34 +796,180 @@ hist_entry__collapse(struct hist_entry *left, struct hist_entry *right) | |||
744 | return cmp; | 796 | return cmp; |
745 | } | 797 | } |
746 | 798 | ||
799 | static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask) | ||
800 | { | ||
801 | int i; | ||
802 | size_t ret = 0; | ||
803 | |||
804 | ret += fprintf(fp, "%s", " "); | ||
805 | |||
806 | for (i = 0; i < depth; i++) | ||
807 | if (depth_mask & (1 << i)) | ||
808 | ret += fprintf(fp, "| "); | ||
809 | else | ||
810 | ret += fprintf(fp, " "); | ||
811 | |||
812 | ret += fprintf(fp, "\n"); | ||
813 | |||
814 | return ret; | ||
815 | } | ||
747 | static size_t | 816 | static size_t |
748 | hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples) | 817 | ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, int depth, |
818 | int depth_mask, int count, u64 total_samples, | ||
819 | int hits) | ||
749 | { | 820 | { |
750 | struct sort_entry *se; | 821 | int i; |
751 | size_t ret; | 822 | size_t ret = 0; |
752 | 823 | ||
753 | if (exclude_other && !self->parent) | 824 | ret += fprintf(fp, "%s", " "); |
754 | return 0; | 825 | for (i = 0; i < depth; i++) { |
826 | if (depth_mask & (1 << i)) | ||
827 | ret += fprintf(fp, "|"); | ||
828 | else | ||
829 | ret += fprintf(fp, " "); | ||
830 | if (!count && i == depth - 1) { | ||
831 | double percent; | ||
832 | |||
833 | percent = hits * 100.0 / total_samples; | ||
834 | ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent); | ||
835 | } else | ||
836 | ret += fprintf(fp, "%s", " "); | ||
837 | } | ||
838 | if (chain->sym) | ||
839 | ret += fprintf(fp, "%s\n", chain->sym->name); | ||
840 | else | ||
841 | ret += fprintf(fp, "%p\n", (void *)(long)chain->ip); | ||
842 | |||
843 | return ret; | ||
844 | } | ||
845 | |||
846 | static size_t | ||
847 | callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | ||
848 | u64 total_samples, int depth, int depth_mask) | ||
849 | { | ||
850 | struct rb_node *node, *next; | ||
851 | struct callchain_node *child; | ||
852 | struct callchain_list *chain; | ||
853 | int new_depth_mask = depth_mask; | ||
854 | u64 new_total; | ||
855 | size_t ret = 0; | ||
856 | int i; | ||
755 | 857 | ||
756 | if (total_samples) { | 858 | if (callchain_param.mode == CHAIN_GRAPH_REL) |
757 | double percent = self->count * 100.0 / total_samples; | 859 | new_total = self->cumul_hit; |
758 | char *color = PERF_COLOR_NORMAL; | 860 | else |
861 | new_total = total_samples; | ||
862 | |||
863 | node = rb_first(&self->rb_root); | ||
864 | while (node) { | ||
865 | child = rb_entry(node, struct callchain_node, rb_node); | ||
759 | 866 | ||
760 | /* | 867 | /* |
761 | * We color high-overhead entries in red, mid-overhead | 868 | * The depth mask manages the output of pipes that show |
762 | * entries in green - and keep the low overhead places | 869 | * the depth. We don't want to keep the pipes of the current |
763 | * normal: | 870 | * level for the last child of this depth |
764 | */ | 871 | */ |
765 | if (percent >= 5.0) { | 872 | next = rb_next(node); |
766 | color = PERF_COLOR_RED; | 873 | if (!next) |
767 | } else { | 874 | new_depth_mask &= ~(1 << (depth - 1)); |
768 | if (percent >= 0.5) | 875 | |
769 | color = PERF_COLOR_GREEN; | 876 | /* |
877 | * But we keep the older depth mask for the line seperator | ||
878 | * to keep the level link until we reach the last child | ||
879 | */ | ||
880 | ret += ipchain__fprintf_graph_line(fp, depth, depth_mask); | ||
881 | i = 0; | ||
882 | list_for_each_entry(chain, &child->val, list) { | ||
883 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
884 | continue; | ||
885 | ret += ipchain__fprintf_graph(fp, chain, depth, | ||
886 | new_depth_mask, i++, | ||
887 | new_total, | ||
888 | child->cumul_hit); | ||
889 | } | ||
890 | ret += callchain__fprintf_graph(fp, child, new_total, | ||
891 | depth + 1, | ||
892 | new_depth_mask | (1 << depth)); | ||
893 | node = next; | ||
894 | } | ||
895 | |||
896 | return ret; | ||
897 | } | ||
898 | |||
899 | static size_t | ||
900 | callchain__fprintf_flat(FILE *fp, struct callchain_node *self, | ||
901 | u64 total_samples) | ||
902 | { | ||
903 | struct callchain_list *chain; | ||
904 | size_t ret = 0; | ||
905 | |||
906 | if (!self) | ||
907 | return 0; | ||
908 | |||
909 | ret += callchain__fprintf_flat(fp, self->parent, total_samples); | ||
910 | |||
911 | |||
912 | list_for_each_entry(chain, &self->val, list) { | ||
913 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
914 | continue; | ||
915 | if (chain->sym) | ||
916 | ret += fprintf(fp, " %s\n", chain->sym->name); | ||
917 | else | ||
918 | ret += fprintf(fp, " %p\n", | ||
919 | (void *)(long)chain->ip); | ||
920 | } | ||
921 | |||
922 | return ret; | ||
923 | } | ||
924 | |||
925 | static size_t | ||
926 | hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, | ||
927 | u64 total_samples) | ||
928 | { | ||
929 | struct rb_node *rb_node; | ||
930 | struct callchain_node *chain; | ||
931 | size_t ret = 0; | ||
932 | |||
933 | rb_node = rb_first(&self->sorted_chain); | ||
934 | while (rb_node) { | ||
935 | double percent; | ||
936 | |||
937 | chain = rb_entry(rb_node, struct callchain_node, rb_node); | ||
938 | percent = chain->hit * 100.0 / total_samples; | ||
939 | switch (callchain_param.mode) { | ||
940 | case CHAIN_FLAT: | ||
941 | ret += percent_color_fprintf(fp, " %6.2f%%\n", | ||
942 | percent); | ||
943 | ret += callchain__fprintf_flat(fp, chain, total_samples); | ||
944 | break; | ||
945 | case CHAIN_GRAPH_ABS: /* Falldown */ | ||
946 | case CHAIN_GRAPH_REL: | ||
947 | ret += callchain__fprintf_graph(fp, chain, | ||
948 | total_samples, 1, 1); | ||
949 | default: | ||
950 | break; | ||
770 | } | 951 | } |
952 | ret += fprintf(fp, "\n"); | ||
953 | rb_node = rb_next(rb_node); | ||
954 | } | ||
955 | |||
956 | return ret; | ||
957 | } | ||
958 | |||
771 | 959 | ||
772 | ret = color_fprintf(fp, color, " %6.2f%%", | 960 | static size_t |
961 | hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples) | ||
962 | { | ||
963 | struct sort_entry *se; | ||
964 | size_t ret; | ||
965 | |||
966 | if (exclude_other && !self->parent) | ||
967 | return 0; | ||
968 | |||
969 | if (total_samples) | ||
970 | ret = percent_color_fprintf(fp, " %6.2f%%", | ||
773 | (self->count * 100.0) / total_samples); | 971 | (self->count * 100.0) / total_samples); |
774 | } else | 972 | else |
775 | ret = fprintf(fp, "%12Ld ", self->count); | 973 | ret = fprintf(fp, "%12Ld ", self->count); |
776 | 974 | ||
777 | list_for_each_entry(se, &hist_entry__sort_list, list) { | 975 | list_for_each_entry(se, &hist_entry__sort_list, list) { |
@@ -784,6 +982,9 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples) | |||
784 | 982 | ||
785 | ret += fprintf(fp, "\n"); | 983 | ret += fprintf(fp, "\n"); |
786 | 984 | ||
985 | if (callchain) | ||
986 | hist_entry_callchain__fprintf(fp, self, total_samples); | ||
987 | |||
787 | return ret; | 988 | return ret; |
788 | } | 989 | } |
789 | 990 | ||
@@ -797,7 +998,7 @@ resolve_symbol(struct thread *thread, struct map **mapp, | |||
797 | { | 998 | { |
798 | struct dso *dso = dsop ? *dsop : NULL; | 999 | struct dso *dso = dsop ? *dsop : NULL; |
799 | struct map *map = mapp ? *mapp : NULL; | 1000 | struct map *map = mapp ? *mapp : NULL; |
800 | uint64_t ip = *ipp; | 1001 | u64 ip = *ipp; |
801 | 1002 | ||
802 | if (!thread) | 1003 | if (!thread) |
803 | return NULL; | 1004 | return NULL; |
@@ -814,7 +1015,6 @@ resolve_symbol(struct thread *thread, struct map **mapp, | |||
814 | *mapp = map; | 1015 | *mapp = map; |
815 | got_map: | 1016 | got_map: |
816 | ip = map->map_ip(map, ip); | 1017 | ip = map->map_ip(map, ip); |
817 | *ipp = ip; | ||
818 | 1018 | ||
819 | dso = map->dso; | 1019 | dso = map->dso; |
820 | } else { | 1020 | } else { |
@@ -828,6 +1028,8 @@ got_map: | |||
828 | dso = kernel_dso; | 1028 | dso = kernel_dso; |
829 | } | 1029 | } |
830 | dprintf(" ...... dso: %s\n", dso ? dso->name : "<not found>"); | 1030 | dprintf(" ...... dso: %s\n", dso ? dso->name : "<not found>"); |
1031 | dprintf(" ...... map: %Lx -> %Lx\n", *ipp, ip); | ||
1032 | *ipp = ip; | ||
831 | 1033 | ||
832 | if (dsop) | 1034 | if (dsop) |
833 | *dsop = dso; | 1035 | *dsop = dso; |
@@ -846,6 +1048,58 @@ static int call__match(struct symbol *sym) | |||
846 | return 0; | 1048 | return 0; |
847 | } | 1049 | } |
848 | 1050 | ||
1051 | static struct symbol ** | ||
1052 | resolve_callchain(struct thread *thread, struct map *map __used, | ||
1053 | struct ip_callchain *chain, struct hist_entry *entry) | ||
1054 | { | ||
1055 | u64 context = PERF_CONTEXT_MAX; | ||
1056 | struct symbol **syms = NULL; | ||
1057 | unsigned int i; | ||
1058 | |||
1059 | if (callchain) { | ||
1060 | syms = calloc(chain->nr, sizeof(*syms)); | ||
1061 | if (!syms) { | ||
1062 | fprintf(stderr, "Can't allocate memory for symbols\n"); | ||
1063 | exit(-1); | ||
1064 | } | ||
1065 | } | ||
1066 | |||
1067 | for (i = 0; i < chain->nr; i++) { | ||
1068 | u64 ip = chain->ips[i]; | ||
1069 | struct dso *dso = NULL; | ||
1070 | struct symbol *sym; | ||
1071 | |||
1072 | if (ip >= PERF_CONTEXT_MAX) { | ||
1073 | context = ip; | ||
1074 | continue; | ||
1075 | } | ||
1076 | |||
1077 | switch (context) { | ||
1078 | case PERF_CONTEXT_HV: | ||
1079 | dso = hypervisor_dso; | ||
1080 | break; | ||
1081 | case PERF_CONTEXT_KERNEL: | ||
1082 | dso = kernel_dso; | ||
1083 | break; | ||
1084 | default: | ||
1085 | break; | ||
1086 | } | ||
1087 | |||
1088 | sym = resolve_symbol(thread, NULL, &dso, &ip); | ||
1089 | |||
1090 | if (sym) { | ||
1091 | if (sort__has_parent && call__match(sym) && | ||
1092 | !entry->parent) | ||
1093 | entry->parent = sym; | ||
1094 | if (!callchain) | ||
1095 | break; | ||
1096 | syms[i] = sym; | ||
1097 | } | ||
1098 | } | ||
1099 | |||
1100 | return syms; | ||
1101 | } | ||
1102 | |||
849 | /* | 1103 | /* |
850 | * collect histogram counts | 1104 | * collect histogram counts |
851 | */ | 1105 | */ |
@@ -858,6 +1112,7 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso, | |||
858 | struct rb_node **p = &hist.rb_node; | 1112 | struct rb_node **p = &hist.rb_node; |
859 | struct rb_node *parent = NULL; | 1113 | struct rb_node *parent = NULL; |
860 | struct hist_entry *he; | 1114 | struct hist_entry *he; |
1115 | struct symbol **syms = NULL; | ||
861 | struct hist_entry entry = { | 1116 | struct hist_entry entry = { |
862 | .thread = thread, | 1117 | .thread = thread, |
863 | .map = map, | 1118 | .map = map, |
@@ -867,39 +1122,12 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso, | |||
867 | .level = level, | 1122 | .level = level, |
868 | .count = count, | 1123 | .count = count, |
869 | .parent = NULL, | 1124 | .parent = NULL, |
1125 | .sorted_chain = RB_ROOT | ||
870 | }; | 1126 | }; |
871 | int cmp; | 1127 | int cmp; |
872 | 1128 | ||
873 | if (sort__has_parent && chain) { | 1129 | if ((sort__has_parent || callchain) && chain) |
874 | u64 context = PERF_CONTEXT_MAX; | 1130 | syms = resolve_callchain(thread, map, chain, &entry); |
875 | int i; | ||
876 | |||
877 | for (i = 0; i < chain->nr; i++) { | ||
878 | u64 ip = chain->ips[i]; | ||
879 | struct dso *dso = NULL; | ||
880 | struct symbol *sym; | ||
881 | |||
882 | if (ip >= PERF_CONTEXT_MAX) { | ||
883 | context = ip; | ||
884 | continue; | ||
885 | } | ||
886 | |||
887 | switch (context) { | ||
888 | case PERF_CONTEXT_KERNEL: | ||
889 | dso = kernel_dso; | ||
890 | break; | ||
891 | default: | ||
892 | break; | ||
893 | } | ||
894 | |||
895 | sym = resolve_symbol(thread, NULL, &dso, &ip); | ||
896 | |||
897 | if (sym && call__match(sym)) { | ||
898 | entry.parent = sym; | ||
899 | break; | ||
900 | } | ||
901 | } | ||
902 | } | ||
903 | 1131 | ||
904 | while (*p != NULL) { | 1132 | while (*p != NULL) { |
905 | parent = *p; | 1133 | parent = *p; |
@@ -909,6 +1137,10 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso, | |||
909 | 1137 | ||
910 | if (!cmp) { | 1138 | if (!cmp) { |
911 | he->count += count; | 1139 | he->count += count; |
1140 | if (callchain) { | ||
1141 | append_chain(&he->callchain, chain, syms); | ||
1142 | free(syms); | ||
1143 | } | ||
912 | return 0; | 1144 | return 0; |
913 | } | 1145 | } |
914 | 1146 | ||
@@ -922,6 +1154,11 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso, | |||
922 | if (!he) | 1154 | if (!he) |
923 | return -ENOMEM; | 1155 | return -ENOMEM; |
924 | *he = entry; | 1156 | *he = entry; |
1157 | if (callchain) { | ||
1158 | callchain_init(&he->callchain); | ||
1159 | append_chain(&he->callchain, chain, syms); | ||
1160 | free(syms); | ||
1161 | } | ||
925 | rb_link_node(&he->rb_node, parent, p); | 1162 | rb_link_node(&he->rb_node, parent, p); |
926 | rb_insert_color(&he->rb_node, &hist); | 1163 | rb_insert_color(&he->rb_node, &hist); |
927 | 1164 | ||
@@ -992,12 +1229,16 @@ static void collapse__resort(void) | |||
992 | 1229 | ||
993 | static struct rb_root output_hists; | 1230 | static struct rb_root output_hists; |
994 | 1231 | ||
995 | static void output__insert_entry(struct hist_entry *he) | 1232 | static void output__insert_entry(struct hist_entry *he, u64 min_callchain_hits) |
996 | { | 1233 | { |
997 | struct rb_node **p = &output_hists.rb_node; | 1234 | struct rb_node **p = &output_hists.rb_node; |
998 | struct rb_node *parent = NULL; | 1235 | struct rb_node *parent = NULL; |
999 | struct hist_entry *iter; | 1236 | struct hist_entry *iter; |
1000 | 1237 | ||
1238 | if (callchain) | ||
1239 | callchain_param.sort(&he->sorted_chain, &he->callchain, | ||
1240 | min_callchain_hits, &callchain_param); | ||
1241 | |||
1001 | while (*p != NULL) { | 1242 | while (*p != NULL) { |
1002 | parent = *p; | 1243 | parent = *p; |
1003 | iter = rb_entry(parent, struct hist_entry, rb_node); | 1244 | iter = rb_entry(parent, struct hist_entry, rb_node); |
@@ -1012,11 +1253,14 @@ static void output__insert_entry(struct hist_entry *he) | |||
1012 | rb_insert_color(&he->rb_node, &output_hists); | 1253 | rb_insert_color(&he->rb_node, &output_hists); |
1013 | } | 1254 | } |
1014 | 1255 | ||
1015 | static void output__resort(void) | 1256 | static void output__resort(u64 total_samples) |
1016 | { | 1257 | { |
1017 | struct rb_node *next; | 1258 | struct rb_node *next; |
1018 | struct hist_entry *n; | 1259 | struct hist_entry *n; |
1019 | struct rb_root *tree = &hist; | 1260 | struct rb_root *tree = &hist; |
1261 | u64 min_callchain_hits; | ||
1262 | |||
1263 | min_callchain_hits = total_samples * (callchain_param.min_percent / 100); | ||
1020 | 1264 | ||
1021 | if (sort__need_collapse) | 1265 | if (sort__need_collapse) |
1022 | tree = &collapse_hists; | 1266 | tree = &collapse_hists; |
@@ -1028,7 +1272,7 @@ static void output__resort(void) | |||
1028 | next = rb_next(&n->rb_node); | 1272 | next = rb_next(&n->rb_node); |
1029 | 1273 | ||
1030 | rb_erase(&n->rb_node, tree); | 1274 | rb_erase(&n->rb_node, tree); |
1031 | output__insert_entry(n); | 1275 | output__insert_entry(n, min_callchain_hits); |
1032 | } | 1276 | } |
1033 | } | 1277 | } |
1034 | 1278 | ||
@@ -1054,7 +1298,7 @@ static size_t output__fprintf(FILE *fp, u64 total_samples) | |||
1054 | 1298 | ||
1055 | fprintf(fp, "# ........"); | 1299 | fprintf(fp, "# ........"); |
1056 | list_for_each_entry(se, &hist_entry__sort_list, list) { | 1300 | list_for_each_entry(se, &hist_entry__sort_list, list) { |
1057 | int i; | 1301 | unsigned int i; |
1058 | 1302 | ||
1059 | if (exclude_other && (se == &sort_parent)) | 1303 | if (exclude_other && (se == &sort_parent)) |
1060 | continue; | 1304 | continue; |
@@ -1115,7 +1359,7 @@ static int validate_chain(struct ip_callchain *chain, event_t *event) | |||
1115 | } | 1359 | } |
1116 | 1360 | ||
1117 | static int | 1361 | static int |
1118 | process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | 1362 | process_sample_event(event_t *event, unsigned long offset, unsigned long head) |
1119 | { | 1363 | { |
1120 | char level; | 1364 | char level; |
1121 | int show = 0; | 1365 | int show = 0; |
@@ -1126,13 +1370,14 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
1126 | struct map *map = NULL; | 1370 | struct map *map = NULL; |
1127 | void *more_data = event->ip.__more_data; | 1371 | void *more_data = event->ip.__more_data; |
1128 | struct ip_callchain *chain = NULL; | 1372 | struct ip_callchain *chain = NULL; |
1373 | int cpumode; | ||
1129 | 1374 | ||
1130 | if (event->header.type & PERF_SAMPLE_PERIOD) { | 1375 | if (sample_type & PERF_SAMPLE_PERIOD) { |
1131 | period = *(u64 *)more_data; | 1376 | period = *(u64 *)more_data; |
1132 | more_data += sizeof(u64); | 1377 | more_data += sizeof(u64); |
1133 | } | 1378 | } |
1134 | 1379 | ||
1135 | dprintf("%p [%p]: PERF_EVENT (IP, %d): %d: %p period: %Ld\n", | 1380 | dprintf("%p [%p]: PERF_EVENT_SAMPLE (IP, %d): %d: %p period: %Ld\n", |
1136 | (void *)(offset + head), | 1381 | (void *)(offset + head), |
1137 | (void *)(long)(event->header.size), | 1382 | (void *)(long)(event->header.size), |
1138 | event->header.misc, | 1383 | event->header.misc, |
@@ -1140,8 +1385,8 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
1140 | (void *)(long)ip, | 1385 | (void *)(long)ip, |
1141 | (long long)period); | 1386 | (long long)period); |
1142 | 1387 | ||
1143 | if (event->header.type & PERF_SAMPLE_CALLCHAIN) { | 1388 | if (sample_type & PERF_SAMPLE_CALLCHAIN) { |
1144 | int i; | 1389 | unsigned int i; |
1145 | 1390 | ||
1146 | chain = (void *)more_data; | 1391 | chain = (void *)more_data; |
1147 | 1392 | ||
@@ -1166,7 +1411,12 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
1166 | return -1; | 1411 | return -1; |
1167 | } | 1412 | } |
1168 | 1413 | ||
1169 | if (event->header.misc & PERF_EVENT_MISC_KERNEL) { | 1414 | if (comm_list && !strlist__has_entry(comm_list, thread->comm)) |
1415 | return 0; | ||
1416 | |||
1417 | cpumode = event->header.misc & PERF_EVENT_MISC_CPUMODE_MASK; | ||
1418 | |||
1419 | if (cpumode == PERF_EVENT_MISC_KERNEL) { | ||
1170 | show = SHOW_KERNEL; | 1420 | show = SHOW_KERNEL; |
1171 | level = 'k'; | 1421 | level = 'k'; |
1172 | 1422 | ||
@@ -1174,7 +1424,7 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
1174 | 1424 | ||
1175 | dprintf(" ...... dso: %s\n", dso->name); | 1425 | dprintf(" ...... dso: %s\n", dso->name); |
1176 | 1426 | ||
1177 | } else if (event->header.misc & PERF_EVENT_MISC_USER) { | 1427 | } else if (cpumode == PERF_EVENT_MISC_USER) { |
1178 | 1428 | ||
1179 | show = SHOW_USER; | 1429 | show = SHOW_USER; |
1180 | level = '.'; | 1430 | level = '.'; |
@@ -1182,12 +1432,21 @@ process_overflow_event(event_t *event, unsigned long offset, unsigned long head) | |||
1182 | } else { | 1432 | } else { |
1183 | show = SHOW_HV; | 1433 | show = SHOW_HV; |
1184 | level = 'H'; | 1434 | level = 'H'; |
1435 | |||
1436 | dso = hypervisor_dso; | ||
1437 | |||
1185 | dprintf(" ...... dso: [hypervisor]\n"); | 1438 | dprintf(" ...... dso: [hypervisor]\n"); |
1186 | } | 1439 | } |
1187 | 1440 | ||
1188 | if (show & show_mask) { | 1441 | if (show & show_mask) { |
1189 | struct symbol *sym = resolve_symbol(thread, &map, &dso, &ip); | 1442 | struct symbol *sym = resolve_symbol(thread, &map, &dso, &ip); |
1190 | 1443 | ||
1444 | if (dso_list && dso && dso->name && !strlist__has_entry(dso_list, dso->name)) | ||
1445 | return 0; | ||
1446 | |||
1447 | if (sym_list && sym && !strlist__has_entry(sym_list, sym->name)) | ||
1448 | return 0; | ||
1449 | |||
1191 | if (hist_entry__add(thread, map, dso, sym, ip, chain, level, period)) { | 1450 | if (hist_entry__add(thread, map, dso, sym, ip, chain, level, period)) { |
1192 | eprintf("problem incrementing symbol count, skipping event\n"); | 1451 | eprintf("problem incrementing symbol count, skipping event\n"); |
1193 | return -1; | 1452 | return -1; |
@@ -1328,14 +1587,27 @@ static void trace_event(event_t *event) | |||
1328 | } | 1587 | } |
1329 | 1588 | ||
1330 | static int | 1589 | static int |
1590 | process_read_event(event_t *event, unsigned long offset, unsigned long head) | ||
1591 | { | ||
1592 | dprintf("%p [%p]: PERF_EVENT_READ: %d %d %Lu\n", | ||
1593 | (void *)(offset + head), | ||
1594 | (void *)(long)(event->header.size), | ||
1595 | event->read.pid, | ||
1596 | event->read.tid, | ||
1597 | event->read.value); | ||
1598 | |||
1599 | return 0; | ||
1600 | } | ||
1601 | |||
1602 | static int | ||
1331 | process_event(event_t *event, unsigned long offset, unsigned long head) | 1603 | process_event(event_t *event, unsigned long offset, unsigned long head) |
1332 | { | 1604 | { |
1333 | trace_event(event); | 1605 | trace_event(event); |
1334 | 1606 | ||
1335 | if (event->header.misc & PERF_EVENT_MISC_OVERFLOW) | ||
1336 | return process_overflow_event(event, offset, head); | ||
1337 | |||
1338 | switch (event->header.type) { | 1607 | switch (event->header.type) { |
1608 | case PERF_EVENT_SAMPLE: | ||
1609 | return process_sample_event(event, offset, head); | ||
1610 | |||
1339 | case PERF_EVENT_MMAP: | 1611 | case PERF_EVENT_MMAP: |
1340 | return process_mmap_event(event, offset, head); | 1612 | return process_mmap_event(event, offset, head); |
1341 | 1613 | ||
@@ -1351,6 +1623,9 @@ process_event(event_t *event, unsigned long offset, unsigned long head) | |||
1351 | case PERF_EVENT_LOST: | 1623 | case PERF_EVENT_LOST: |
1352 | return process_lost_event(event, offset, head); | 1624 | return process_lost_event(event, offset, head); |
1353 | 1625 | ||
1626 | case PERF_EVENT_READ: | ||
1627 | return process_read_event(event, offset, head); | ||
1628 | |||
1354 | /* | 1629 | /* |
1355 | * We dont process them right now but they are fine: | 1630 | * We dont process them right now but they are fine: |
1356 | */ | 1631 | */ |
@@ -1366,13 +1641,30 @@ process_event(event_t *event, unsigned long offset, unsigned long head) | |||
1366 | return 0; | 1641 | return 0; |
1367 | } | 1642 | } |
1368 | 1643 | ||
1369 | static struct perf_file_header file_header; | 1644 | static struct perf_header *header; |
1645 | |||
1646 | static u64 perf_header__sample_type(void) | ||
1647 | { | ||
1648 | u64 sample_type = 0; | ||
1649 | int i; | ||
1650 | |||
1651 | for (i = 0; i < header->attrs; i++) { | ||
1652 | struct perf_header_attr *attr = header->attr[i]; | ||
1653 | |||
1654 | if (!sample_type) | ||
1655 | sample_type = attr->attr.sample_type; | ||
1656 | else if (sample_type != attr->attr.sample_type) | ||
1657 | die("non matching sample_type"); | ||
1658 | } | ||
1659 | |||
1660 | return sample_type; | ||
1661 | } | ||
1370 | 1662 | ||
1371 | static int __cmd_report(void) | 1663 | static int __cmd_report(void) |
1372 | { | 1664 | { |
1373 | int ret, rc = EXIT_FAILURE; | 1665 | int ret, rc = EXIT_FAILURE; |
1374 | unsigned long offset = 0; | 1666 | unsigned long offset = 0; |
1375 | unsigned long head = sizeof(file_header); | 1667 | unsigned long head, shift; |
1376 | struct stat stat; | 1668 | struct stat stat; |
1377 | event_t *event; | 1669 | event_t *event; |
1378 | uint32_t size; | 1670 | uint32_t size; |
@@ -1400,15 +1692,24 @@ static int __cmd_report(void) | |||
1400 | exit(0); | 1692 | exit(0); |
1401 | } | 1693 | } |
1402 | 1694 | ||
1403 | if (read(input, &file_header, sizeof(file_header)) == -1) { | 1695 | header = perf_header__read(input); |
1404 | perror("failed to read file headers"); | 1696 | head = header->data_offset; |
1405 | exit(-1); | ||
1406 | } | ||
1407 | 1697 | ||
1408 | if (sort__has_parent && | 1698 | sample_type = perf_header__sample_type(); |
1409 | !(file_header.sample_type & PERF_SAMPLE_CALLCHAIN)) { | 1699 | |
1410 | fprintf(stderr, "selected --sort parent, but no callchain data\n"); | 1700 | if (!(sample_type & PERF_SAMPLE_CALLCHAIN)) { |
1411 | exit(-1); | 1701 | if (sort__has_parent) { |
1702 | fprintf(stderr, "selected --sort parent, but no" | ||
1703 | " callchain data. Did you call" | ||
1704 | " perf record without -g?\n"); | ||
1705 | exit(-1); | ||
1706 | } | ||
1707 | if (callchain) { | ||
1708 | fprintf(stderr, "selected -c but no callchain data." | ||
1709 | " Did you call perf record without" | ||
1710 | " -g?\n"); | ||
1711 | exit(-1); | ||
1712 | } | ||
1412 | } | 1713 | } |
1413 | 1714 | ||
1414 | if (load_kernel() < 0) { | 1715 | if (load_kernel() < 0) { |
@@ -1426,6 +1727,11 @@ static int __cmd_report(void) | |||
1426 | cwd = NULL; | 1727 | cwd = NULL; |
1427 | cwdlen = 0; | 1728 | cwdlen = 0; |
1428 | } | 1729 | } |
1730 | |||
1731 | shift = page_size * (head / page_size); | ||
1732 | offset += shift; | ||
1733 | head -= shift; | ||
1734 | |||
1429 | remap: | 1735 | remap: |
1430 | buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ, | 1736 | buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ, |
1431 | MAP_SHARED, input, offset); | 1737 | MAP_SHARED, input, offset); |
@@ -1442,9 +1748,10 @@ more: | |||
1442 | size = 8; | 1748 | size = 8; |
1443 | 1749 | ||
1444 | if (head + event->header.size >= page_size * mmap_window) { | 1750 | if (head + event->header.size >= page_size * mmap_window) { |
1445 | unsigned long shift = page_size * (head / page_size); | ||
1446 | int ret; | 1751 | int ret; |
1447 | 1752 | ||
1753 | shift = page_size * (head / page_size); | ||
1754 | |||
1448 | ret = munmap(buf, page_size * mmap_window); | 1755 | ret = munmap(buf, page_size * mmap_window); |
1449 | assert(ret == 0); | 1756 | assert(ret == 0); |
1450 | 1757 | ||
@@ -1482,10 +1789,10 @@ more: | |||
1482 | 1789 | ||
1483 | head += size; | 1790 | head += size; |
1484 | 1791 | ||
1485 | if (offset + head >= sizeof(file_header) + file_header.data_size) | 1792 | if (offset + head >= header->data_offset + header->data_size) |
1486 | goto done; | 1793 | goto done; |
1487 | 1794 | ||
1488 | if (offset + head < stat.st_size) | 1795 | if (offset + head < (unsigned long)stat.st_size) |
1489 | goto more; | 1796 | goto more; |
1490 | 1797 | ||
1491 | done: | 1798 | done: |
@@ -1509,12 +1816,58 @@ done: | |||
1509 | dsos__fprintf(stdout); | 1816 | dsos__fprintf(stdout); |
1510 | 1817 | ||
1511 | collapse__resort(); | 1818 | collapse__resort(); |
1512 | output__resort(); | 1819 | output__resort(total); |
1513 | output__fprintf(stdout, total); | 1820 | output__fprintf(stdout, total); |
1514 | 1821 | ||
1515 | return rc; | 1822 | return rc; |
1516 | } | 1823 | } |
1517 | 1824 | ||
1825 | static int | ||
1826 | parse_callchain_opt(const struct option *opt __used, const char *arg, | ||
1827 | int unset __used) | ||
1828 | { | ||
1829 | char *tok; | ||
1830 | char *endptr; | ||
1831 | |||
1832 | callchain = 1; | ||
1833 | |||
1834 | if (!arg) | ||
1835 | return 0; | ||
1836 | |||
1837 | tok = strtok((char *)arg, ","); | ||
1838 | if (!tok) | ||
1839 | return -1; | ||
1840 | |||
1841 | /* get the output mode */ | ||
1842 | if (!strncmp(tok, "graph", strlen(arg))) | ||
1843 | callchain_param.mode = CHAIN_GRAPH_ABS; | ||
1844 | |||
1845 | else if (!strncmp(tok, "flat", strlen(arg))) | ||
1846 | callchain_param.mode = CHAIN_FLAT; | ||
1847 | |||
1848 | else if (!strncmp(tok, "fractal", strlen(arg))) | ||
1849 | callchain_param.mode = CHAIN_GRAPH_REL; | ||
1850 | |||
1851 | else | ||
1852 | return -1; | ||
1853 | |||
1854 | /* get the min percentage */ | ||
1855 | tok = strtok(NULL, ","); | ||
1856 | if (!tok) | ||
1857 | goto setup; | ||
1858 | |||
1859 | callchain_param.min_percent = strtod(tok, &endptr); | ||
1860 | if (tok == endptr) | ||
1861 | return -1; | ||
1862 | |||
1863 | setup: | ||
1864 | if (register_callchain_param(&callchain_param) < 0) { | ||
1865 | fprintf(stderr, "Can't register callchain params\n"); | ||
1866 | return -1; | ||
1867 | } | ||
1868 | return 0; | ||
1869 | } | ||
1870 | |||
1518 | static const char * const report_usage[] = { | 1871 | static const char * const report_usage[] = { |
1519 | "perf report [<options>] <command>", | 1872 | "perf report [<options>] <command>", |
1520 | NULL | 1873 | NULL |
@@ -1528,6 +1881,8 @@ static const struct option options[] = { | |||
1528 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, | 1881 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, |
1529 | "dump raw trace in ASCII"), | 1882 | "dump raw trace in ASCII"), |
1530 | OPT_STRING('k', "vmlinux", &vmlinux, "file", "vmlinux pathname"), | 1883 | OPT_STRING('k', "vmlinux", &vmlinux, "file", "vmlinux pathname"), |
1884 | OPT_BOOLEAN('m', "modules", &modules, | ||
1885 | "load module symbols - WARNING: use only with -k and LIVE kernel"), | ||
1531 | OPT_STRING('s', "sort", &sort_order, "key[,key2...]", | 1886 | OPT_STRING('s', "sort", &sort_order, "key[,key2...]", |
1532 | "sort by key(s): pid, comm, dso, symbol, parent"), | 1887 | "sort by key(s): pid, comm, dso, symbol, parent"), |
1533 | OPT_BOOLEAN('P', "full-paths", &full_paths, | 1888 | OPT_BOOLEAN('P', "full-paths", &full_paths, |
@@ -1536,6 +1891,15 @@ static const struct option options[] = { | |||
1536 | "regex filter to identify parent, see: '--sort parent'"), | 1891 | "regex filter to identify parent, see: '--sort parent'"), |
1537 | OPT_BOOLEAN('x', "exclude-other", &exclude_other, | 1892 | OPT_BOOLEAN('x', "exclude-other", &exclude_other, |
1538 | "Only display entries with parent-match"), | 1893 | "Only display entries with parent-match"), |
1894 | OPT_CALLBACK_DEFAULT('c', "callchain", NULL, "output_type,min_percent", | ||
1895 | "Display callchains using output_type and min percent threshold. " | ||
1896 | "Default: flat,0", &parse_callchain_opt, callchain_default_opt), | ||
1897 | OPT_STRING('d', "dsos", &dso_list_str, "dso[,dso...]", | ||
1898 | "only consider symbols in these dsos"), | ||
1899 | OPT_STRING('C', "comms", &comm_list_str, "comm[,comm...]", | ||
1900 | "only consider symbols in these comms"), | ||
1901 | OPT_STRING('S', "symbols", &sym_list_str, "symbol[,symbol...]", | ||
1902 | "only consider these symbols"), | ||
1539 | OPT_END() | 1903 | OPT_END() |
1540 | }; | 1904 | }; |
1541 | 1905 | ||
@@ -1554,7 +1918,20 @@ static void setup_sorting(void) | |||
1554 | free(str); | 1918 | free(str); |
1555 | } | 1919 | } |
1556 | 1920 | ||
1557 | int cmd_report(int argc, const char **argv, const char *prefix) | 1921 | static void setup_list(struct strlist **list, const char *list_str, |
1922 | const char *list_name) | ||
1923 | { | ||
1924 | if (list_str) { | ||
1925 | *list = strlist__new(true, list_str); | ||
1926 | if (!*list) { | ||
1927 | fprintf(stderr, "problems parsing %s list\n", | ||
1928 | list_name); | ||
1929 | exit(129); | ||
1930 | } | ||
1931 | } | ||
1932 | } | ||
1933 | |||
1934 | int cmd_report(int argc, const char **argv, const char *prefix __used) | ||
1558 | { | 1935 | { |
1559 | symbol__init(); | 1936 | symbol__init(); |
1560 | 1937 | ||
@@ -1575,6 +1952,10 @@ int cmd_report(int argc, const char **argv, const char *prefix) | |||
1575 | if (argc) | 1952 | if (argc) |
1576 | usage_with_options(report_usage, options); | 1953 | usage_with_options(report_usage, options); |
1577 | 1954 | ||
1955 | setup_list(&dso_list, dso_list_str, "dso"); | ||
1956 | setup_list(&comm_list, comm_list_str, "comm"); | ||
1957 | setup_list(&sym_list, sym_list_str, "symbol"); | ||
1958 | |||
1578 | setup_pager(); | 1959 | setup_pager(); |
1579 | 1960 | ||
1580 | return __cmd_report(); | 1961 | return __cmd_report(); |