diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/perf/Documentation/perf-record.txt | 30 | ||||
-rw-r--r-- | tools/perf/Documentation/perf-report.txt | 10 | ||||
-rw-r--r-- | tools/perf/builtin-record.c | 95 | ||||
-rw-r--r-- | tools/perf/builtin-report.c | 178 | ||||
-rw-r--r-- | tools/perf/perf.h | 18 | ||||
-rw-r--r-- | tools/perf/util/event.h | 1 | ||||
-rw-r--r-- | tools/perf/util/evsel.c | 14 | ||||
-rw-r--r-- | tools/perf/util/header.c | 207 | ||||
-rw-r--r-- | tools/perf/util/header.h | 2 | ||||
-rw-r--r-- | tools/perf/util/hist.c | 122 | ||||
-rw-r--r-- | tools/perf/util/hist.h | 11 | ||||
-rw-r--r-- | tools/perf/util/session.c | 77 | ||||
-rw-r--r-- | tools/perf/util/session.h | 4 | ||||
-rw-r--r-- | tools/perf/util/sort.c | 287 | ||||
-rw-r--r-- | tools/perf/util/sort.h | 11 | ||||
-rw-r--r-- | tools/perf/util/symbol.h | 20 | ||||
-rw-r--r-- | tools/perf/util/ui/browsers/hists.c | 102 |
17 files changed, 1023 insertions, 166 deletions
diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt index a5766b4b0125..a1386b2fff00 100644 --- a/tools/perf/Documentation/perf-record.txt +++ b/tools/perf/Documentation/perf-record.txt | |||
@@ -152,6 +152,36 @@ an empty cgroup (monitor all the time) using, e.g., -G foo,,bar. Cgroups must ha | |||
152 | corresponding events, i.e., they always refer to events defined earlier on the command | 152 | corresponding events, i.e., they always refer to events defined earlier on the command |
153 | line. | 153 | line. |
154 | 154 | ||
155 | -b:: | ||
156 | --branch-any:: | ||
157 | Enable taken branch stack sampling. Any type of taken branch may be sampled. | ||
158 | This is a shortcut for --branch-filter any. See --branch-filter for more infos. | ||
159 | |||
160 | -j:: | ||
161 | --branch-filter:: | ||
162 | Enable taken branch stack sampling. Each sample captures a series of consecutive | ||
163 | taken branches. The number of branches captured with each sample depends on the | ||
164 | underlying hardware, the type of branches of interest, and the executed code. | ||
165 | It is possible to select the types of branches captured by enabling filters. The | ||
166 | following filters are defined: | ||
167 | |||
168 | - any: any type of branches | ||
169 | - any_call: any function call or system call | ||
170 | - any_ret: any function return or system call return | ||
171 | - any_ind: any indirect branch | ||
172 | - u: only when the branch target is at the user level | ||
173 | - k: only when the branch target is in the kernel | ||
174 | - hv: only when the target is at the hypervisor level | ||
175 | |||
176 | + | ||
177 | The option requires at least one branch type among any, any_call, any_ret, ind_call. | ||
178 | The privilege levels may be ommitted, in which case, the privilege levels of the associated | ||
179 | event are applied to the branch filter. Both kernel (k) and hypervisor (hv) privilege | ||
180 | levels are subject to permissions. When sampling on multiple events, branch stack sampling | ||
181 | is enabled for all the sampling events. The sampled branch type is the same for all events. | ||
182 | The various filters must be specified as a comma separated list: --branch-filter any_ret,u,k | ||
183 | Note that this feature may not be available on all processors. | ||
184 | |||
155 | SEE ALSO | 185 | SEE ALSO |
156 | -------- | 186 | -------- |
157 | linkperf:perf-stat[1], linkperf:perf-list[1] | 187 | linkperf:perf-stat[1], linkperf:perf-list[1] |
diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 9b430e98712e..87feeee8b90c 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt | |||
@@ -153,6 +153,16 @@ OPTIONS | |||
153 | information which may be very large and thus may clutter the display. | 153 | information which may be very large and thus may clutter the display. |
154 | It currently includes: cpu and numa topology of the host system. | 154 | It currently includes: cpu and numa topology of the host system. |
155 | 155 | ||
156 | -b:: | ||
157 | --branch-stack:: | ||
158 | Use the addresses of sampled taken branches instead of the instruction | ||
159 | address to build the histograms. To generate meaningful output, the | ||
160 | perf.data file must have been obtained using perf record -b or | ||
161 | perf record --branch-filter xxx where xxx is a branch filter option. | ||
162 | perf report is able to auto-detect whether a perf.data file contains | ||
163 | branch stacks and it will automatically switch to the branch view mode, | ||
164 | unless --no-branch-stack is used. | ||
165 | |||
156 | SEE ALSO | 166 | SEE ALSO |
157 | -------- | 167 | -------- |
158 | linkperf:perf-stat[1], linkperf:perf-annotate[1] | 168 | linkperf:perf-stat[1], linkperf:perf-annotate[1] |
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 75d230fef202..be4e1eee782e 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c | |||
@@ -473,6 +473,9 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) | |||
473 | if (!have_tracepoints(&evsel_list->entries)) | 473 | if (!have_tracepoints(&evsel_list->entries)) |
474 | perf_header__clear_feat(&session->header, HEADER_TRACE_INFO); | 474 | perf_header__clear_feat(&session->header, HEADER_TRACE_INFO); |
475 | 475 | ||
476 | if (!rec->opts.branch_stack) | ||
477 | perf_header__clear_feat(&session->header, HEADER_BRANCH_STACK); | ||
478 | |||
476 | if (!rec->file_new) { | 479 | if (!rec->file_new) { |
477 | err = perf_session__read_header(session, output); | 480 | err = perf_session__read_header(session, output); |
478 | if (err < 0) | 481 | if (err < 0) |
@@ -638,6 +641,90 @@ out_delete_session: | |||
638 | return err; | 641 | return err; |
639 | } | 642 | } |
640 | 643 | ||
644 | #define BRANCH_OPT(n, m) \ | ||
645 | { .name = n, .mode = (m) } | ||
646 | |||
647 | #define BRANCH_END { .name = NULL } | ||
648 | |||
649 | struct branch_mode { | ||
650 | const char *name; | ||
651 | int mode; | ||
652 | }; | ||
653 | |||
654 | static const struct branch_mode branch_modes[] = { | ||
655 | BRANCH_OPT("u", PERF_SAMPLE_BRANCH_USER), | ||
656 | BRANCH_OPT("k", PERF_SAMPLE_BRANCH_KERNEL), | ||
657 | BRANCH_OPT("hv", PERF_SAMPLE_BRANCH_HV), | ||
658 | BRANCH_OPT("any", PERF_SAMPLE_BRANCH_ANY), | ||
659 | BRANCH_OPT("any_call", PERF_SAMPLE_BRANCH_ANY_CALL), | ||
660 | BRANCH_OPT("any_ret", PERF_SAMPLE_BRANCH_ANY_RETURN), | ||
661 | BRANCH_OPT("ind_call", PERF_SAMPLE_BRANCH_IND_CALL), | ||
662 | BRANCH_END | ||
663 | }; | ||
664 | |||
665 | static int | ||
666 | parse_branch_stack(const struct option *opt, const char *str, int unset) | ||
667 | { | ||
668 | #define ONLY_PLM \ | ||
669 | (PERF_SAMPLE_BRANCH_USER |\ | ||
670 | PERF_SAMPLE_BRANCH_KERNEL |\ | ||
671 | PERF_SAMPLE_BRANCH_HV) | ||
672 | |||
673 | uint64_t *mode = (uint64_t *)opt->value; | ||
674 | const struct branch_mode *br; | ||
675 | char *s, *os = NULL, *p; | ||
676 | int ret = -1; | ||
677 | |||
678 | if (unset) | ||
679 | return 0; | ||
680 | |||
681 | /* | ||
682 | * cannot set it twice, -b + --branch-filter for instance | ||
683 | */ | ||
684 | if (*mode) | ||
685 | return -1; | ||
686 | |||
687 | /* str may be NULL in case no arg is passed to -b */ | ||
688 | if (str) { | ||
689 | /* because str is read-only */ | ||
690 | s = os = strdup(str); | ||
691 | if (!s) | ||
692 | return -1; | ||
693 | |||
694 | for (;;) { | ||
695 | p = strchr(s, ','); | ||
696 | if (p) | ||
697 | *p = '\0'; | ||
698 | |||
699 | for (br = branch_modes; br->name; br++) { | ||
700 | if (!strcasecmp(s, br->name)) | ||
701 | break; | ||
702 | } | ||
703 | if (!br->name) { | ||
704 | ui__warning("unknown branch filter %s," | ||
705 | " check man page\n", s); | ||
706 | goto error; | ||
707 | } | ||
708 | |||
709 | *mode |= br->mode; | ||
710 | |||
711 | if (!p) | ||
712 | break; | ||
713 | |||
714 | s = p + 1; | ||
715 | } | ||
716 | } | ||
717 | ret = 0; | ||
718 | |||
719 | /* default to any branch */ | ||
720 | if ((*mode & ~ONLY_PLM) == 0) { | ||
721 | *mode = PERF_SAMPLE_BRANCH_ANY; | ||
722 | } | ||
723 | error: | ||
724 | free(os); | ||
725 | return ret; | ||
726 | } | ||
727 | |||
641 | static const char * const record_usage[] = { | 728 | static const char * const record_usage[] = { |
642 | "perf record [<options>] [<command>]", | 729 | "perf record [<options>] [<command>]", |
643 | "perf record [<options>] -- <command> [<options>]", | 730 | "perf record [<options>] -- <command> [<options>]", |
@@ -727,6 +814,14 @@ const struct option record_options[] = { | |||
727 | "monitor event in cgroup name only", | 814 | "monitor event in cgroup name only", |
728 | parse_cgroups), | 815 | parse_cgroups), |
729 | OPT_STRING('u', "uid", &record.uid_str, "user", "user to profile"), | 816 | OPT_STRING('u', "uid", &record.uid_str, "user", "user to profile"), |
817 | |||
818 | OPT_CALLBACK_NOOPT('b', "branch-any", &record.opts.branch_stack, | ||
819 | "branch any", "sample any taken branches", | ||
820 | parse_branch_stack), | ||
821 | |||
822 | OPT_CALLBACK('j', "branch-filter", &record.opts.branch_stack, | ||
823 | "branch filter mask", "branch stack filter modes", | ||
824 | parse_branch_stack), | ||
730 | OPT_END() | 825 | OPT_END() |
731 | }; | 826 | }; |
732 | 827 | ||
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 25d34d483e49..8e91c6eba18a 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c | |||
@@ -53,6 +53,82 @@ struct perf_report { | |||
53 | DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); | 53 | DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); |
54 | }; | 54 | }; |
55 | 55 | ||
56 | static int perf_report__add_branch_hist_entry(struct perf_tool *tool, | ||
57 | struct addr_location *al, | ||
58 | struct perf_sample *sample, | ||
59 | struct perf_evsel *evsel, | ||
60 | struct machine *machine) | ||
61 | { | ||
62 | struct perf_report *rep = container_of(tool, struct perf_report, tool); | ||
63 | struct symbol *parent = NULL; | ||
64 | int err = 0; | ||
65 | unsigned i; | ||
66 | struct hist_entry *he; | ||
67 | struct branch_info *bi, *bx; | ||
68 | |||
69 | if ((sort__has_parent || symbol_conf.use_callchain) | ||
70 | && sample->callchain) { | ||
71 | err = machine__resolve_callchain(machine, evsel, al->thread, | ||
72 | sample->callchain, &parent); | ||
73 | if (err) | ||
74 | return err; | ||
75 | } | ||
76 | |||
77 | bi = machine__resolve_bstack(machine, al->thread, | ||
78 | sample->branch_stack); | ||
79 | if (!bi) | ||
80 | return -ENOMEM; | ||
81 | |||
82 | for (i = 0; i < sample->branch_stack->nr; i++) { | ||
83 | if (rep->hide_unresolved && !(bi[i].from.sym && bi[i].to.sym)) | ||
84 | continue; | ||
85 | /* | ||
86 | * The report shows the percentage of total branches captured | ||
87 | * and not events sampled. Thus we use a pseudo period of 1. | ||
88 | */ | ||
89 | he = __hists__add_branch_entry(&evsel->hists, al, parent, | ||
90 | &bi[i], 1); | ||
91 | if (he) { | ||
92 | struct annotation *notes; | ||
93 | err = -ENOMEM; | ||
94 | bx = he->branch_info; | ||
95 | if (bx->from.sym && use_browser > 0) { | ||
96 | notes = symbol__annotation(bx->from.sym); | ||
97 | if (!notes->src | ||
98 | && symbol__alloc_hist(bx->from.sym) < 0) | ||
99 | goto out; | ||
100 | |||
101 | err = symbol__inc_addr_samples(bx->from.sym, | ||
102 | bx->from.map, | ||
103 | evsel->idx, | ||
104 | bx->from.al_addr); | ||
105 | if (err) | ||
106 | goto out; | ||
107 | } | ||
108 | |||
109 | if (bx->to.sym && use_browser > 0) { | ||
110 | notes = symbol__annotation(bx->to.sym); | ||
111 | if (!notes->src | ||
112 | && symbol__alloc_hist(bx->to.sym) < 0) | ||
113 | goto out; | ||
114 | |||
115 | err = symbol__inc_addr_samples(bx->to.sym, | ||
116 | bx->to.map, | ||
117 | evsel->idx, | ||
118 | bx->to.al_addr); | ||
119 | if (err) | ||
120 | goto out; | ||
121 | } | ||
122 | evsel->hists.stats.total_period += 1; | ||
123 | hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); | ||
124 | err = 0; | ||
125 | } else | ||
126 | return -ENOMEM; | ||
127 | } | ||
128 | out: | ||
129 | return err; | ||
130 | } | ||
131 | |||
56 | static int perf_evsel__add_hist_entry(struct perf_evsel *evsel, | 132 | static int perf_evsel__add_hist_entry(struct perf_evsel *evsel, |
57 | struct addr_location *al, | 133 | struct addr_location *al, |
58 | struct perf_sample *sample, | 134 | struct perf_sample *sample, |
@@ -126,14 +202,21 @@ static int process_sample_event(struct perf_tool *tool, | |||
126 | if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap)) | 202 | if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap)) |
127 | return 0; | 203 | return 0; |
128 | 204 | ||
129 | if (al.map != NULL) | 205 | if (sort__branch_mode == 1) { |
130 | al.map->dso->hit = 1; | 206 | if (perf_report__add_branch_hist_entry(tool, &al, sample, |
207 | evsel, machine)) { | ||
208 | pr_debug("problem adding lbr entry, skipping event\n"); | ||
209 | return -1; | ||
210 | } | ||
211 | } else { | ||
212 | if (al.map != NULL) | ||
213 | al.map->dso->hit = 1; | ||
131 | 214 | ||
132 | if (perf_evsel__add_hist_entry(evsel, &al, sample, machine)) { | 215 | if (perf_evsel__add_hist_entry(evsel, &al, sample, machine)) { |
133 | pr_debug("problem incrementing symbol period, skipping event\n"); | 216 | pr_debug("problem incrementing symbol period, skipping event\n"); |
134 | return -1; | 217 | return -1; |
218 | } | ||
135 | } | 219 | } |
136 | |||
137 | return 0; | 220 | return 0; |
138 | } | 221 | } |
139 | 222 | ||
@@ -188,6 +271,15 @@ static int perf_report__setup_sample_type(struct perf_report *rep) | |||
188 | } | 271 | } |
189 | } | 272 | } |
190 | 273 | ||
274 | if (sort__branch_mode == 1) { | ||
275 | if (!(self->sample_type & PERF_SAMPLE_BRANCH_STACK)) { | ||
276 | fprintf(stderr, "selected -b but no branch data." | ||
277 | " Did you call perf record without" | ||
278 | " -b?\n"); | ||
279 | return -1; | ||
280 | } | ||
281 | } | ||
282 | |||
191 | return 0; | 283 | return 0; |
192 | } | 284 | } |
193 | 285 | ||
@@ -246,7 +338,7 @@ static int __cmd_report(struct perf_report *rep) | |||
246 | { | 338 | { |
247 | int ret = -EINVAL; | 339 | int ret = -EINVAL; |
248 | u64 nr_samples; | 340 | u64 nr_samples; |
249 | struct perf_session *session; | 341 | struct perf_session *session = rep->session; |
250 | struct perf_evsel *pos; | 342 | struct perf_evsel *pos; |
251 | struct map *kernel_map; | 343 | struct map *kernel_map; |
252 | struct kmap *kernel_kmap; | 344 | struct kmap *kernel_kmap; |
@@ -254,13 +346,6 @@ static int __cmd_report(struct perf_report *rep) | |||
254 | 346 | ||
255 | signal(SIGINT, sig_handler); | 347 | signal(SIGINT, sig_handler); |
256 | 348 | ||
257 | session = perf_session__new(rep->input_name, O_RDONLY, | ||
258 | rep->force, false, &rep->tool); | ||
259 | if (session == NULL) | ||
260 | return -ENOMEM; | ||
261 | |||
262 | rep->session = session; | ||
263 | |||
264 | if (rep->cpu_list) { | 349 | if (rep->cpu_list) { |
265 | ret = perf_session__cpu_bitmap(session, rep->cpu_list, | 350 | ret = perf_session__cpu_bitmap(session, rep->cpu_list, |
266 | rep->cpu_bitmap); | 351 | rep->cpu_bitmap); |
@@ -427,9 +512,19 @@ setup: | |||
427 | return 0; | 512 | return 0; |
428 | } | 513 | } |
429 | 514 | ||
515 | static int | ||
516 | parse_branch_mode(const struct option *opt __used, const char *str __used, int unset) | ||
517 | { | ||
518 | sort__branch_mode = !unset; | ||
519 | return 0; | ||
520 | } | ||
521 | |||
430 | int cmd_report(int argc, const char **argv, const char *prefix __used) | 522 | int cmd_report(int argc, const char **argv, const char *prefix __used) |
431 | { | 523 | { |
524 | struct perf_session *session; | ||
432 | struct stat st; | 525 | struct stat st; |
526 | bool has_br_stack = false; | ||
527 | int ret = -1; | ||
433 | char callchain_default_opt[] = "fractal,0.5,callee"; | 528 | char callchain_default_opt[] = "fractal,0.5,callee"; |
434 | const char * const report_usage[] = { | 529 | const char * const report_usage[] = { |
435 | "perf report [<options>]", | 530 | "perf report [<options>]", |
@@ -477,7 +572,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) | |||
477 | OPT_BOOLEAN(0, "stdio", &report.use_stdio, | 572 | OPT_BOOLEAN(0, "stdio", &report.use_stdio, |
478 | "Use the stdio interface"), | 573 | "Use the stdio interface"), |
479 | OPT_STRING('s', "sort", &sort_order, "key[,key2...]", | 574 | OPT_STRING('s', "sort", &sort_order, "key[,key2...]", |
480 | "sort by key(s): pid, comm, dso, symbol, parent"), | 575 | "sort by key(s): pid, comm, dso, symbol, parent, dso_to," |
576 | " dso_from, symbol_to, symbol_from, mispredict"), | ||
481 | OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization, | 577 | OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization, |
482 | "Show sample percentage for different cpu modes"), | 578 | "Show sample percentage for different cpu modes"), |
483 | OPT_STRING('p', "parent", &parent_pattern, "regex", | 579 | OPT_STRING('p', "parent", &parent_pattern, "regex", |
@@ -517,6 +613,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) | |||
517 | "Specify disassembler style (e.g. -M intel for intel syntax)"), | 613 | "Specify disassembler style (e.g. -M intel for intel syntax)"), |
518 | OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period, | 614 | OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period, |
519 | "Show a column with the sum of periods"), | 615 | "Show a column with the sum of periods"), |
616 | OPT_CALLBACK_NOOPT('b', "branch-stack", &sort__branch_mode, "", | ||
617 | "use branch records for histogram filling", parse_branch_mode), | ||
520 | OPT_END() | 618 | OPT_END() |
521 | }; | 619 | }; |
522 | 620 | ||
@@ -536,11 +634,36 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) | |||
536 | else | 634 | else |
537 | report.input_name = "perf.data"; | 635 | report.input_name = "perf.data"; |
538 | } | 636 | } |
637 | session = perf_session__new(report.input_name, O_RDONLY, | ||
638 | report.force, false, &report.tool); | ||
639 | if (session == NULL) | ||
640 | return -ENOMEM; | ||
539 | 641 | ||
540 | if (strcmp(report.input_name, "-") != 0) | 642 | report.session = session; |
643 | |||
644 | has_br_stack = perf_header__has_feat(&session->header, | ||
645 | HEADER_BRANCH_STACK); | ||
646 | |||
647 | if (sort__branch_mode == -1 && has_br_stack) | ||
648 | sort__branch_mode = 1; | ||
649 | |||
650 | /* sort__branch_mode could be 0 if --no-branch-stack */ | ||
651 | if (sort__branch_mode == 1) { | ||
652 | /* | ||
653 | * if no sort_order is provided, then specify | ||
654 | * branch-mode specific order | ||
655 | */ | ||
656 | if (sort_order == default_sort_order) | ||
657 | sort_order = "comm,dso_from,symbol_from," | ||
658 | "dso_to,symbol_to"; | ||
659 | |||
660 | } | ||
661 | |||
662 | if (strcmp(report.input_name, "-") != 0) { | ||
541 | setup_browser(true); | 663 | setup_browser(true); |
542 | else | 664 | } else { |
543 | use_browser = 0; | 665 | use_browser = 0; |
666 | } | ||
544 | 667 | ||
545 | /* | 668 | /* |
546 | * Only in the newt browser we are doing integrated annotation, | 669 | * Only in the newt browser we are doing integrated annotation, |
@@ -568,13 +691,13 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) | |||
568 | } | 691 | } |
569 | 692 | ||
570 | if (symbol__init() < 0) | 693 | if (symbol__init() < 0) |
571 | return -1; | 694 | goto error; |
572 | 695 | ||
573 | setup_sorting(report_usage, options); | 696 | setup_sorting(report_usage, options); |
574 | 697 | ||
575 | if (parent_pattern != default_parent_pattern) { | 698 | if (parent_pattern != default_parent_pattern) { |
576 | if (sort_dimension__add("parent") < 0) | 699 | if (sort_dimension__add("parent") < 0) |
577 | return -1; | 700 | goto error; |
578 | 701 | ||
579 | /* | 702 | /* |
580 | * Only show the parent fields if we explicitly | 703 | * Only show the parent fields if we explicitly |
@@ -592,9 +715,20 @@ int cmd_report(int argc, const char **argv, const char *prefix __used) | |||
592 | if (argc) | 715 | if (argc) |
593 | usage_with_options(report_usage, options); | 716 | usage_with_options(report_usage, options); |
594 | 717 | ||
595 | sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", stdout); | ||
596 | sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", stdout); | 718 | sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", stdout); |
597 | sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", stdout); | ||
598 | 719 | ||
599 | return __cmd_report(&report); | 720 | if (sort__branch_mode == 1) { |
721 | sort_entry__setup_elide(&sort_dso_from, symbol_conf.dso_from_list, "dso_from", stdout); | ||
722 | sort_entry__setup_elide(&sort_dso_to, symbol_conf.dso_to_list, "dso_to", stdout); | ||
723 | sort_entry__setup_elide(&sort_sym_from, symbol_conf.sym_from_list, "sym_from", stdout); | ||
724 | sort_entry__setup_elide(&sort_sym_to, symbol_conf.sym_to_list, "sym_to", stdout); | ||
725 | } else { | ||
726 | sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", stdout); | ||
727 | sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", stdout); | ||
728 | } | ||
729 | |||
730 | ret = __cmd_report(&report); | ||
731 | error: | ||
732 | perf_session__delete(session); | ||
733 | return ret; | ||
600 | } | 734 | } |
diff --git a/tools/perf/perf.h b/tools/perf/perf.h index f0227e93665d..eec392e48067 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h | |||
@@ -179,6 +179,23 @@ struct ip_callchain { | |||
179 | u64 ips[0]; | 179 | u64 ips[0]; |
180 | }; | 180 | }; |
181 | 181 | ||
182 | struct branch_flags { | ||
183 | u64 mispred:1; | ||
184 | u64 predicted:1; | ||
185 | u64 reserved:62; | ||
186 | }; | ||
187 | |||
188 | struct branch_entry { | ||
189 | u64 from; | ||
190 | u64 to; | ||
191 | struct branch_flags flags; | ||
192 | }; | ||
193 | |||
194 | struct branch_stack { | ||
195 | u64 nr; | ||
196 | struct branch_entry entries[0]; | ||
197 | }; | ||
198 | |||
182 | extern bool perf_host, perf_guest; | 199 | extern bool perf_host, perf_guest; |
183 | extern const char perf_version_string[]; | 200 | extern const char perf_version_string[]; |
184 | 201 | ||
@@ -205,6 +222,7 @@ struct perf_record_opts { | |||
205 | unsigned int freq; | 222 | unsigned int freq; |
206 | unsigned int mmap_pages; | 223 | unsigned int mmap_pages; |
207 | unsigned int user_freq; | 224 | unsigned int user_freq; |
225 | int branch_stack; | ||
208 | u64 default_interval; | 226 | u64 default_interval; |
209 | u64 user_interval; | 227 | u64 user_interval; |
210 | const char *cpu_list; | 228 | const char *cpu_list; |
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index cbdeaad9c5e5..1b197280c621 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h | |||
@@ -81,6 +81,7 @@ struct perf_sample { | |||
81 | u32 raw_size; | 81 | u32 raw_size; |
82 | void *raw_data; | 82 | void *raw_data; |
83 | struct ip_callchain *callchain; | 83 | struct ip_callchain *callchain; |
84 | struct branch_stack *branch_stack; | ||
84 | }; | 85 | }; |
85 | 86 | ||
86 | #define BUILD_ID_SIZE 20 | 87 | #define BUILD_ID_SIZE 20 |
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 302d49a9f985..f421f7cbc0d3 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c | |||
@@ -126,6 +126,10 @@ void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts) | |||
126 | attr->watermark = 0; | 126 | attr->watermark = 0; |
127 | attr->wakeup_events = 1; | 127 | attr->wakeup_events = 1; |
128 | } | 128 | } |
129 | if (opts->branch_stack) { | ||
130 | attr->sample_type |= PERF_SAMPLE_BRANCH_STACK; | ||
131 | attr->branch_sample_type = opts->branch_stack; | ||
132 | } | ||
129 | 133 | ||
130 | attr->mmap = track; | 134 | attr->mmap = track; |
131 | attr->comm = track; | 135 | attr->comm = track; |
@@ -576,6 +580,16 @@ int perf_event__parse_sample(const union perf_event *event, u64 type, | |||
576 | data->raw_data = (void *) pdata; | 580 | data->raw_data = (void *) pdata; |
577 | } | 581 | } |
578 | 582 | ||
583 | if (type & PERF_SAMPLE_BRANCH_STACK) { | ||
584 | u64 sz; | ||
585 | |||
586 | data->branch_stack = (struct branch_stack *)array; | ||
587 | array++; /* nr */ | ||
588 | |||
589 | sz = data->branch_stack->nr * sizeof(struct branch_entry); | ||
590 | sz /= sizeof(u64); | ||
591 | array += sz; | ||
592 | } | ||
579 | return 0; | 593 | return 0; |
580 | } | 594 | } |
581 | 595 | ||
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 9f867d96c6a5..0d9b6da86a39 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c | |||
@@ -1023,6 +1023,12 @@ write_it: | |||
1023 | return do_write_string(fd, buffer); | 1023 | return do_write_string(fd, buffer); |
1024 | } | 1024 | } |
1025 | 1025 | ||
1026 | static int write_branch_stack(int fd __used, struct perf_header *h __used, | ||
1027 | struct perf_evlist *evlist __used) | ||
1028 | { | ||
1029 | return 0; | ||
1030 | } | ||
1031 | |||
1026 | static void print_hostname(struct perf_header *ph, int fd, FILE *fp) | 1032 | static void print_hostname(struct perf_header *ph, int fd, FILE *fp) |
1027 | { | 1033 | { |
1028 | char *str = do_read_string(fd, ph); | 1034 | char *str = do_read_string(fd, ph); |
@@ -1144,8 +1150,9 @@ static void print_event_desc(struct perf_header *ph, int fd, FILE *fp) | |||
1144 | uint64_t id; | 1150 | uint64_t id; |
1145 | void *buf = NULL; | 1151 | void *buf = NULL; |
1146 | char *str; | 1152 | char *str; |
1147 | u32 nre, sz, nr, i, j, msz; | 1153 | u32 nre, sz, nr, i, j; |
1148 | int ret; | 1154 | ssize_t ret; |
1155 | size_t msz; | ||
1149 | 1156 | ||
1150 | /* number of events */ | 1157 | /* number of events */ |
1151 | ret = read(fd, &nre, sizeof(nre)); | 1158 | ret = read(fd, &nre, sizeof(nre)); |
@@ -1162,25 +1169,23 @@ static void print_event_desc(struct perf_header *ph, int fd, FILE *fp) | |||
1162 | if (ph->needs_swap) | 1169 | if (ph->needs_swap) |
1163 | sz = bswap_32(sz); | 1170 | sz = bswap_32(sz); |
1164 | 1171 | ||
1165 | /* | ||
1166 | * ensure it is at least to our ABI rev | ||
1167 | */ | ||
1168 | if (sz < (u32)sizeof(attr)) | ||
1169 | goto error; | ||
1170 | |||
1171 | memset(&attr, 0, sizeof(attr)); | 1172 | memset(&attr, 0, sizeof(attr)); |
1172 | 1173 | ||
1173 | /* read entire region to sync up to next field */ | 1174 | /* buffer to hold on file attr struct */ |
1174 | buf = malloc(sz); | 1175 | buf = malloc(sz); |
1175 | if (!buf) | 1176 | if (!buf) |
1176 | goto error; | 1177 | goto error; |
1177 | 1178 | ||
1178 | msz = sizeof(attr); | 1179 | msz = sizeof(attr); |
1179 | if (sz < msz) | 1180 | if (sz < (ssize_t)msz) |
1180 | msz = sz; | 1181 | msz = sz; |
1181 | 1182 | ||
1182 | for (i = 0 ; i < nre; i++) { | 1183 | for (i = 0 ; i < nre; i++) { |
1183 | 1184 | ||
1185 | /* | ||
1186 | * must read entire on-file attr struct to | ||
1187 | * sync up with layout. | ||
1188 | */ | ||
1184 | ret = read(fd, buf, sz); | 1189 | ret = read(fd, buf, sz); |
1185 | if (ret != (ssize_t)sz) | 1190 | if (ret != (ssize_t)sz) |
1186 | goto error; | 1191 | goto error; |
@@ -1316,6 +1321,12 @@ static void print_cpuid(struct perf_header *ph, int fd, FILE *fp) | |||
1316 | free(str); | 1321 | free(str); |
1317 | } | 1322 | } |
1318 | 1323 | ||
1324 | static void print_branch_stack(struct perf_header *ph __used, int fd __used, | ||
1325 | FILE *fp) | ||
1326 | { | ||
1327 | fprintf(fp, "# contains samples with branch stack\n"); | ||
1328 | } | ||
1329 | |||
1319 | static int __event_process_build_id(struct build_id_event *bev, | 1330 | static int __event_process_build_id(struct build_id_event *bev, |
1320 | char *filename, | 1331 | char *filename, |
1321 | struct perf_session *session) | 1332 | struct perf_session *session) |
@@ -1520,6 +1531,7 @@ static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = { | |||
1520 | FEAT_OPA(HEADER_CMDLINE, cmdline), | 1531 | FEAT_OPA(HEADER_CMDLINE, cmdline), |
1521 | FEAT_OPF(HEADER_CPU_TOPOLOGY, cpu_topology), | 1532 | FEAT_OPF(HEADER_CPU_TOPOLOGY, cpu_topology), |
1522 | FEAT_OPF(HEADER_NUMA_TOPOLOGY, numa_topology), | 1533 | FEAT_OPF(HEADER_NUMA_TOPOLOGY, numa_topology), |
1534 | FEAT_OPA(HEADER_BRANCH_STACK, branch_stack), | ||
1523 | }; | 1535 | }; |
1524 | 1536 | ||
1525 | struct header_print_data { | 1537 | struct header_print_data { |
@@ -1804,35 +1816,101 @@ out_free: | |||
1804 | return err; | 1816 | return err; |
1805 | } | 1817 | } |
1806 | 1818 | ||
1807 | static int check_magic_endian(u64 *magic, struct perf_file_header *header, | 1819 | static const int attr_file_abi_sizes[] = { |
1808 | struct perf_header *ph) | 1820 | [0] = PERF_ATTR_SIZE_VER0, |
1821 | [1] = PERF_ATTR_SIZE_VER1, | ||
1822 | 0, | ||
1823 | }; | ||
1824 | |||
1825 | /* | ||
1826 | * In the legacy file format, the magic number is not used to encode endianness. | ||
1827 | * hdr_sz was used to encode endianness. But given that hdr_sz can vary based | ||
1828 | * on ABI revisions, we need to try all combinations for all endianness to | ||
1829 | * detect the endianness. | ||
1830 | */ | ||
1831 | static int try_all_file_abis(uint64_t hdr_sz, struct perf_header *ph) | ||
1809 | { | 1832 | { |
1810 | int ret; | 1833 | uint64_t ref_size, attr_size; |
1834 | int i; | ||
1811 | 1835 | ||
1812 | /* check for legacy format */ | 1836 | for (i = 0 ; attr_file_abi_sizes[i]; i++) { |
1813 | ret = memcmp(magic, __perf_magic1, sizeof(*magic)); | 1837 | ref_size = attr_file_abi_sizes[i] |
1814 | if (ret == 0) { | 1838 | + sizeof(struct perf_file_section); |
1815 | pr_debug("legacy perf.data format\n"); | 1839 | if (hdr_sz != ref_size) { |
1816 | if (!header) | 1840 | attr_size = bswap_64(hdr_sz); |
1817 | return -1; | 1841 | if (attr_size != ref_size) |
1842 | continue; | ||
1818 | 1843 | ||
1819 | if (header->attr_size != sizeof(struct perf_file_attr)) { | 1844 | ph->needs_swap = true; |
1820 | u64 attr_size = bswap_64(header->attr_size); | 1845 | } |
1846 | pr_debug("ABI%d perf.data file detected, need_swap=%d\n", | ||
1847 | i, | ||
1848 | ph->needs_swap); | ||
1849 | return 0; | ||
1850 | } | ||
1851 | /* could not determine endianness */ | ||
1852 | return -1; | ||
1853 | } | ||
1821 | 1854 | ||
1822 | if (attr_size != sizeof(struct perf_file_attr)) | 1855 | #define PERF_PIPE_HDR_VER0 16 |
1823 | return -1; | 1856 | |
1857 | static const size_t attr_pipe_abi_sizes[] = { | ||
1858 | [0] = PERF_PIPE_HDR_VER0, | ||
1859 | 0, | ||
1860 | }; | ||
1861 | |||
1862 | /* | ||
1863 | * In the legacy pipe format, there is an implicit assumption that endiannesss | ||
1864 | * between host recording the samples, and host parsing the samples is the | ||
1865 | * same. This is not always the case given that the pipe output may always be | ||
1866 | * redirected into a file and analyzed on a different machine with possibly a | ||
1867 | * different endianness and perf_event ABI revsions in the perf tool itself. | ||
1868 | */ | ||
1869 | static int try_all_pipe_abis(uint64_t hdr_sz, struct perf_header *ph) | ||
1870 | { | ||
1871 | u64 attr_size; | ||
1872 | int i; | ||
1873 | |||
1874 | for (i = 0 ; attr_pipe_abi_sizes[i]; i++) { | ||
1875 | if (hdr_sz != attr_pipe_abi_sizes[i]) { | ||
1876 | attr_size = bswap_64(hdr_sz); | ||
1877 | if (attr_size != hdr_sz) | ||
1878 | continue; | ||
1824 | 1879 | ||
1825 | ph->needs_swap = true; | 1880 | ph->needs_swap = true; |
1826 | } | 1881 | } |
1882 | pr_debug("Pipe ABI%d perf.data file detected\n", i); | ||
1827 | return 0; | 1883 | return 0; |
1828 | } | 1884 | } |
1885 | return -1; | ||
1886 | } | ||
1887 | |||
1888 | static int check_magic_endian(u64 magic, uint64_t hdr_sz, | ||
1889 | bool is_pipe, struct perf_header *ph) | ||
1890 | { | ||
1891 | int ret; | ||
1892 | |||
1893 | /* check for legacy format */ | ||
1894 | ret = memcmp(&magic, __perf_magic1, sizeof(magic)); | ||
1895 | if (ret == 0) { | ||
1896 | pr_debug("legacy perf.data format\n"); | ||
1897 | if (is_pipe) | ||
1898 | return try_all_pipe_abis(hdr_sz, ph); | ||
1899 | |||
1900 | return try_all_file_abis(hdr_sz, ph); | ||
1901 | } | ||
1902 | /* | ||
1903 | * the new magic number serves two purposes: | ||
1904 | * - unique number to identify actual perf.data files | ||
1905 | * - encode endianness of file | ||
1906 | */ | ||
1829 | 1907 | ||
1830 | /* check magic number with same endianness */ | 1908 | /* check magic number with one endianness */ |
1831 | if (*magic == __perf_magic2) | 1909 | if (magic == __perf_magic2) |
1832 | return 0; | 1910 | return 0; |
1833 | 1911 | ||
1834 | /* check magic number but opposite endianness */ | 1912 | /* check magic number with opposite endianness */ |
1835 | if (*magic != __perf_magic2_sw) | 1913 | if (magic != __perf_magic2_sw) |
1836 | return -1; | 1914 | return -1; |
1837 | 1915 | ||
1838 | ph->needs_swap = true; | 1916 | ph->needs_swap = true; |
@@ -1851,8 +1929,11 @@ int perf_file_header__read(struct perf_file_header *header, | |||
1851 | if (ret <= 0) | 1929 | if (ret <= 0) |
1852 | return -1; | 1930 | return -1; |
1853 | 1931 | ||
1854 | if (check_magic_endian(&header->magic, header, ph) < 0) | 1932 | if (check_magic_endian(header->magic, |
1933 | header->attr_size, false, ph) < 0) { | ||
1934 | pr_debug("magic/endian check failed\n"); | ||
1855 | return -1; | 1935 | return -1; |
1936 | } | ||
1856 | 1937 | ||
1857 | if (ph->needs_swap) { | 1938 | if (ph->needs_swap) { |
1858 | mem_bswap_64(header, offsetof(struct perf_file_header, | 1939 | mem_bswap_64(header, offsetof(struct perf_file_header, |
@@ -1939,21 +2020,17 @@ static int perf_file_header__read_pipe(struct perf_pipe_file_header *header, | |||
1939 | if (ret <= 0) | 2020 | if (ret <= 0) |
1940 | return -1; | 2021 | return -1; |
1941 | 2022 | ||
1942 | if (check_magic_endian(&header->magic, NULL, ph) < 0) | 2023 | if (check_magic_endian(header->magic, header->size, true, ph) < 0) { |
2024 | pr_debug("endian/magic failed\n"); | ||
1943 | return -1; | 2025 | return -1; |
2026 | } | ||
2027 | |||
2028 | if (ph->needs_swap) | ||
2029 | header->size = bswap_64(header->size); | ||
1944 | 2030 | ||
1945 | if (repipe && do_write(STDOUT_FILENO, header, sizeof(*header)) < 0) | 2031 | if (repipe && do_write(STDOUT_FILENO, header, sizeof(*header)) < 0) |
1946 | return -1; | 2032 | return -1; |
1947 | 2033 | ||
1948 | if (header->size != sizeof(*header)) { | ||
1949 | u64 size = bswap_64(header->size); | ||
1950 | |||
1951 | if (size != sizeof(*header)) | ||
1952 | return -1; | ||
1953 | |||
1954 | ph->needs_swap = true; | ||
1955 | } | ||
1956 | |||
1957 | return 0; | 2034 | return 0; |
1958 | } | 2035 | } |
1959 | 2036 | ||
@@ -1973,6 +2050,52 @@ static int perf_header__read_pipe(struct perf_session *session, int fd) | |||
1973 | return 0; | 2050 | return 0; |
1974 | } | 2051 | } |
1975 | 2052 | ||
2053 | static int read_attr(int fd, struct perf_header *ph, | ||
2054 | struct perf_file_attr *f_attr) | ||
2055 | { | ||
2056 | struct perf_event_attr *attr = &f_attr->attr; | ||
2057 | size_t sz, left; | ||
2058 | size_t our_sz = sizeof(f_attr->attr); | ||
2059 | int ret; | ||
2060 | |||
2061 | memset(f_attr, 0, sizeof(*f_attr)); | ||
2062 | |||
2063 | /* read minimal guaranteed structure */ | ||
2064 | ret = readn(fd, attr, PERF_ATTR_SIZE_VER0); | ||
2065 | if (ret <= 0) { | ||
2066 | pr_debug("cannot read %d bytes of header attr\n", | ||
2067 | PERF_ATTR_SIZE_VER0); | ||
2068 | return -1; | ||
2069 | } | ||
2070 | |||
2071 | /* on file perf_event_attr size */ | ||
2072 | sz = attr->size; | ||
2073 | |||
2074 | if (ph->needs_swap) | ||
2075 | sz = bswap_32(sz); | ||
2076 | |||
2077 | if (sz == 0) { | ||
2078 | /* assume ABI0 */ | ||
2079 | sz = PERF_ATTR_SIZE_VER0; | ||
2080 | } else if (sz > our_sz) { | ||
2081 | pr_debug("file uses a more recent and unsupported ABI" | ||
2082 | " (%zu bytes extra)\n", sz - our_sz); | ||
2083 | return -1; | ||
2084 | } | ||
2085 | /* what we have not yet read and that we know about */ | ||
2086 | left = sz - PERF_ATTR_SIZE_VER0; | ||
2087 | if (left) { | ||
2088 | void *ptr = attr; | ||
2089 | ptr += PERF_ATTR_SIZE_VER0; | ||
2090 | |||
2091 | ret = readn(fd, ptr, left); | ||
2092 | } | ||
2093 | /* read perf_file_section, ids are read in caller */ | ||
2094 | ret = readn(fd, &f_attr->ids, sizeof(f_attr->ids)); | ||
2095 | |||
2096 | return ret <= 0 ? -1 : 0; | ||
2097 | } | ||
2098 | |||
1976 | int perf_session__read_header(struct perf_session *session, int fd) | 2099 | int perf_session__read_header(struct perf_session *session, int fd) |
1977 | { | 2100 | { |
1978 | struct perf_header *header = &session->header; | 2101 | struct perf_header *header = &session->header; |
@@ -1988,19 +2111,17 @@ int perf_session__read_header(struct perf_session *session, int fd) | |||
1988 | if (session->fd_pipe) | 2111 | if (session->fd_pipe) |
1989 | return perf_header__read_pipe(session, fd); | 2112 | return perf_header__read_pipe(session, fd); |
1990 | 2113 | ||
1991 | if (perf_file_header__read(&f_header, header, fd) < 0) { | 2114 | if (perf_file_header__read(&f_header, header, fd) < 0) |
1992 | pr_debug("incompatible file format\n"); | ||
1993 | return -EINVAL; | 2115 | return -EINVAL; |
1994 | } | ||
1995 | 2116 | ||
1996 | nr_attrs = f_header.attrs.size / sizeof(f_attr); | 2117 | nr_attrs = f_header.attrs.size / f_header.attr_size; |
1997 | lseek(fd, f_header.attrs.offset, SEEK_SET); | 2118 | lseek(fd, f_header.attrs.offset, SEEK_SET); |
1998 | 2119 | ||
1999 | for (i = 0; i < nr_attrs; i++) { | 2120 | for (i = 0; i < nr_attrs; i++) { |
2000 | struct perf_evsel *evsel; | 2121 | struct perf_evsel *evsel; |
2001 | off_t tmp; | 2122 | off_t tmp; |
2002 | 2123 | ||
2003 | if (readn(fd, &f_attr, sizeof(f_attr)) <= 0) | 2124 | if (read_attr(fd, header, &f_attr) < 0) |
2004 | goto out_errno; | 2125 | goto out_errno; |
2005 | 2126 | ||
2006 | if (header->needs_swap) | 2127 | if (header->needs_swap) |
diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index e68f617d082f..21a6be09c129 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h | |||
@@ -27,7 +27,7 @@ enum { | |||
27 | HEADER_EVENT_DESC, | 27 | HEADER_EVENT_DESC, |
28 | HEADER_CPU_TOPOLOGY, | 28 | HEADER_CPU_TOPOLOGY, |
29 | HEADER_NUMA_TOPOLOGY, | 29 | HEADER_NUMA_TOPOLOGY, |
30 | 30 | HEADER_BRANCH_STACK, | |
31 | HEADER_LAST_FEATURE, | 31 | HEADER_LAST_FEATURE, |
32 | HEADER_FEAT_BITS = 256, | 32 | HEADER_FEAT_BITS = 256, |
33 | }; | 33 | }; |
diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 6f505d1abac7..8380c3db1c92 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c | |||
@@ -50,21 +50,25 @@ static void hists__reset_col_len(struct hists *hists) | |||
50 | hists__set_col_len(hists, col, 0); | 50 | hists__set_col_len(hists, col, 0); |
51 | } | 51 | } |
52 | 52 | ||
53 | static void hists__set_unres_dso_col_len(struct hists *hists, int dso) | ||
54 | { | ||
55 | const unsigned int unresolved_col_width = BITS_PER_LONG / 4; | ||
56 | |||
57 | if (hists__col_len(hists, dso) < unresolved_col_width && | ||
58 | !symbol_conf.col_width_list_str && !symbol_conf.field_sep && | ||
59 | !symbol_conf.dso_list) | ||
60 | hists__set_col_len(hists, dso, unresolved_col_width); | ||
61 | } | ||
62 | |||
53 | static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) | 63 | static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) |
54 | { | 64 | { |
65 | const unsigned int unresolved_col_width = BITS_PER_LONG / 4; | ||
55 | u16 len; | 66 | u16 len; |
56 | 67 | ||
57 | if (h->ms.sym) | 68 | if (h->ms.sym) |
58 | hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen); | 69 | hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen + 4); |
59 | else { | 70 | else |
60 | const unsigned int unresolved_col_width = BITS_PER_LONG / 4; | 71 | hists__set_unres_dso_col_len(hists, HISTC_DSO); |
61 | |||
62 | if (hists__col_len(hists, HISTC_DSO) < unresolved_col_width && | ||
63 | !symbol_conf.col_width_list_str && !symbol_conf.field_sep && | ||
64 | !symbol_conf.dso_list) | ||
65 | hists__set_col_len(hists, HISTC_DSO, | ||
66 | unresolved_col_width); | ||
67 | } | ||
68 | 72 | ||
69 | len = thread__comm_len(h->thread); | 73 | len = thread__comm_len(h->thread); |
70 | if (hists__new_col_len(hists, HISTC_COMM, len)) | 74 | if (hists__new_col_len(hists, HISTC_COMM, len)) |
@@ -74,6 +78,37 @@ static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) | |||
74 | len = dso__name_len(h->ms.map->dso); | 78 | len = dso__name_len(h->ms.map->dso); |
75 | hists__new_col_len(hists, HISTC_DSO, len); | 79 | hists__new_col_len(hists, HISTC_DSO, len); |
76 | } | 80 | } |
81 | |||
82 | if (h->branch_info) { | ||
83 | int symlen; | ||
84 | /* | ||
85 | * +4 accounts for '[x] ' priv level info | ||
86 | * +2 account of 0x prefix on raw addresses | ||
87 | */ | ||
88 | if (h->branch_info->from.sym) { | ||
89 | symlen = (int)h->branch_info->from.sym->namelen + 4; | ||
90 | hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen); | ||
91 | |||
92 | symlen = dso__name_len(h->branch_info->from.map->dso); | ||
93 | hists__new_col_len(hists, HISTC_DSO_FROM, symlen); | ||
94 | } else { | ||
95 | symlen = unresolved_col_width + 4 + 2; | ||
96 | hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen); | ||
97 | hists__set_unres_dso_col_len(hists, HISTC_DSO_FROM); | ||
98 | } | ||
99 | |||
100 | if (h->branch_info->to.sym) { | ||
101 | symlen = (int)h->branch_info->to.sym->namelen + 4; | ||
102 | hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen); | ||
103 | |||
104 | symlen = dso__name_len(h->branch_info->to.map->dso); | ||
105 | hists__new_col_len(hists, HISTC_DSO_TO, symlen); | ||
106 | } else { | ||
107 | symlen = unresolved_col_width + 4 + 2; | ||
108 | hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen); | ||
109 | hists__set_unres_dso_col_len(hists, HISTC_DSO_TO); | ||
110 | } | ||
111 | } | ||
77 | } | 112 | } |
78 | 113 | ||
79 | static void hist_entry__add_cpumode_period(struct hist_entry *he, | 114 | static void hist_entry__add_cpumode_period(struct hist_entry *he, |
@@ -195,26 +230,14 @@ static u8 symbol__parent_filter(const struct symbol *parent) | |||
195 | return 0; | 230 | return 0; |
196 | } | 231 | } |
197 | 232 | ||
198 | struct hist_entry *__hists__add_entry(struct hists *hists, | 233 | static struct hist_entry *add_hist_entry(struct hists *hists, |
234 | struct hist_entry *entry, | ||
199 | struct addr_location *al, | 235 | struct addr_location *al, |
200 | struct symbol *sym_parent, u64 period) | 236 | u64 period) |
201 | { | 237 | { |
202 | struct rb_node **p; | 238 | struct rb_node **p; |
203 | struct rb_node *parent = NULL; | 239 | struct rb_node *parent = NULL; |
204 | struct hist_entry *he; | 240 | struct hist_entry *he; |
205 | struct hist_entry entry = { | ||
206 | .thread = al->thread, | ||
207 | .ms = { | ||
208 | .map = al->map, | ||
209 | .sym = al->sym, | ||
210 | }, | ||
211 | .cpu = al->cpu, | ||
212 | .ip = al->addr, | ||
213 | .level = al->level, | ||
214 | .period = period, | ||
215 | .parent = sym_parent, | ||
216 | .filtered = symbol__parent_filter(sym_parent), | ||
217 | }; | ||
218 | int cmp; | 241 | int cmp; |
219 | 242 | ||
220 | pthread_mutex_lock(&hists->lock); | 243 | pthread_mutex_lock(&hists->lock); |
@@ -225,7 +248,7 @@ struct hist_entry *__hists__add_entry(struct hists *hists, | |||
225 | parent = *p; | 248 | parent = *p; |
226 | he = rb_entry(parent, struct hist_entry, rb_node_in); | 249 | he = rb_entry(parent, struct hist_entry, rb_node_in); |
227 | 250 | ||
228 | cmp = hist_entry__cmp(&entry, he); | 251 | cmp = hist_entry__cmp(entry, he); |
229 | 252 | ||
230 | if (!cmp) { | 253 | if (!cmp) { |
231 | he->period += period; | 254 | he->period += period; |
@@ -239,7 +262,7 @@ struct hist_entry *__hists__add_entry(struct hists *hists, | |||
239 | p = &(*p)->rb_right; | 262 | p = &(*p)->rb_right; |
240 | } | 263 | } |
241 | 264 | ||
242 | he = hist_entry__new(&entry); | 265 | he = hist_entry__new(entry); |
243 | if (!he) | 266 | if (!he) |
244 | goto out_unlock; | 267 | goto out_unlock; |
245 | 268 | ||
@@ -252,6 +275,51 @@ out_unlock: | |||
252 | return he; | 275 | return he; |
253 | } | 276 | } |
254 | 277 | ||
278 | struct hist_entry *__hists__add_branch_entry(struct hists *self, | ||
279 | struct addr_location *al, | ||
280 | struct symbol *sym_parent, | ||
281 | struct branch_info *bi, | ||
282 | u64 period) | ||
283 | { | ||
284 | struct hist_entry entry = { | ||
285 | .thread = al->thread, | ||
286 | .ms = { | ||
287 | .map = bi->to.map, | ||
288 | .sym = bi->to.sym, | ||
289 | }, | ||
290 | .cpu = al->cpu, | ||
291 | .ip = bi->to.addr, | ||
292 | .level = al->level, | ||
293 | .period = period, | ||
294 | .parent = sym_parent, | ||
295 | .filtered = symbol__parent_filter(sym_parent), | ||
296 | .branch_info = bi, | ||
297 | }; | ||
298 | |||
299 | return add_hist_entry(self, &entry, al, period); | ||
300 | } | ||
301 | |||
302 | struct hist_entry *__hists__add_entry(struct hists *self, | ||
303 | struct addr_location *al, | ||
304 | struct symbol *sym_parent, u64 period) | ||
305 | { | ||
306 | struct hist_entry entry = { | ||
307 | .thread = al->thread, | ||
308 | .ms = { | ||
309 | .map = al->map, | ||
310 | .sym = al->sym, | ||
311 | }, | ||
312 | .cpu = al->cpu, | ||
313 | .ip = al->addr, | ||
314 | .level = al->level, | ||
315 | .period = period, | ||
316 | .parent = sym_parent, | ||
317 | .filtered = symbol__parent_filter(sym_parent), | ||
318 | }; | ||
319 | |||
320 | return add_hist_entry(self, &entry, al, period); | ||
321 | } | ||
322 | |||
255 | int64_t | 323 | int64_t |
256 | hist_entry__cmp(struct hist_entry *left, struct hist_entry *right) | 324 | hist_entry__cmp(struct hist_entry *left, struct hist_entry *right) |
257 | { | 325 | { |
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 48e5acd1e862..9413f3e31fea 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h | |||
@@ -42,6 +42,11 @@ enum hist_column { | |||
42 | HISTC_COMM, | 42 | HISTC_COMM, |
43 | HISTC_PARENT, | 43 | HISTC_PARENT, |
44 | HISTC_CPU, | 44 | HISTC_CPU, |
45 | HISTC_MISPREDICT, | ||
46 | HISTC_SYMBOL_FROM, | ||
47 | HISTC_SYMBOL_TO, | ||
48 | HISTC_DSO_FROM, | ||
49 | HISTC_DSO_TO, | ||
45 | HISTC_NR_COLS, /* Last entry */ | 50 | HISTC_NR_COLS, /* Last entry */ |
46 | }; | 51 | }; |
47 | 52 | ||
@@ -74,6 +79,12 @@ int hist_entry__snprintf(struct hist_entry *self, char *bf, size_t size, | |||
74 | struct hists *hists); | 79 | struct hists *hists); |
75 | void hist_entry__free(struct hist_entry *); | 80 | void hist_entry__free(struct hist_entry *); |
76 | 81 | ||
82 | struct hist_entry *__hists__add_branch_entry(struct hists *self, | ||
83 | struct addr_location *al, | ||
84 | struct symbol *sym_parent, | ||
85 | struct branch_info *bi, | ||
86 | u64 period); | ||
87 | |||
77 | void hists__output_resort(struct hists *self); | 88 | void hists__output_resort(struct hists *self); |
78 | void hists__output_resort_threaded(struct hists *hists); | 89 | void hists__output_resort_threaded(struct hists *hists); |
79 | void hists__collapse_resort(struct hists *self); | 90 | void hists__collapse_resort(struct hists *self); |
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 9f833cf9c6a9..002ebbf59f48 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c | |||
@@ -24,7 +24,7 @@ static int perf_session__open(struct perf_session *self, bool force) | |||
24 | self->fd = STDIN_FILENO; | 24 | self->fd = STDIN_FILENO; |
25 | 25 | ||
26 | if (perf_session__read_header(self, self->fd) < 0) | 26 | if (perf_session__read_header(self, self->fd) < 0) |
27 | pr_err("incompatible file format"); | 27 | pr_err("incompatible file format (rerun with -v to learn more)"); |
28 | 28 | ||
29 | return 0; | 29 | return 0; |
30 | } | 30 | } |
@@ -56,7 +56,7 @@ static int perf_session__open(struct perf_session *self, bool force) | |||
56 | } | 56 | } |
57 | 57 | ||
58 | if (perf_session__read_header(self, self->fd) < 0) { | 58 | if (perf_session__read_header(self, self->fd) < 0) { |
59 | pr_err("incompatible file format"); | 59 | pr_err("incompatible file format (rerun with -v to learn more)"); |
60 | goto out_close; | 60 | goto out_close; |
61 | } | 61 | } |
62 | 62 | ||
@@ -229,6 +229,64 @@ static bool symbol__match_parent_regex(struct symbol *sym) | |||
229 | return 0; | 229 | return 0; |
230 | } | 230 | } |
231 | 231 | ||
232 | static const u8 cpumodes[] = { | ||
233 | PERF_RECORD_MISC_USER, | ||
234 | PERF_RECORD_MISC_KERNEL, | ||
235 | PERF_RECORD_MISC_GUEST_USER, | ||
236 | PERF_RECORD_MISC_GUEST_KERNEL | ||
237 | }; | ||
238 | #define NCPUMODES (sizeof(cpumodes)/sizeof(u8)) | ||
239 | |||
240 | static void ip__resolve_ams(struct machine *self, struct thread *thread, | ||
241 | struct addr_map_symbol *ams, | ||
242 | u64 ip) | ||
243 | { | ||
244 | struct addr_location al; | ||
245 | size_t i; | ||
246 | u8 m; | ||
247 | |||
248 | memset(&al, 0, sizeof(al)); | ||
249 | |||
250 | for (i = 0; i < NCPUMODES; i++) { | ||
251 | m = cpumodes[i]; | ||
252 | /* | ||
253 | * We cannot use the header.misc hint to determine whether a | ||
254 | * branch stack address is user, kernel, guest, hypervisor. | ||
255 | * Branches may straddle the kernel/user/hypervisor boundaries. | ||
256 | * Thus, we have to try consecutively until we find a match | ||
257 | * or else, the symbol is unknown | ||
258 | */ | ||
259 | thread__find_addr_location(thread, self, m, MAP__FUNCTION, | ||
260 | ip, &al, NULL); | ||
261 | if (al.sym) | ||
262 | goto found; | ||
263 | } | ||
264 | found: | ||
265 | ams->addr = ip; | ||
266 | ams->al_addr = al.addr; | ||
267 | ams->sym = al.sym; | ||
268 | ams->map = al.map; | ||
269 | } | ||
270 | |||
271 | struct branch_info *machine__resolve_bstack(struct machine *self, | ||
272 | struct thread *thr, | ||
273 | struct branch_stack *bs) | ||
274 | { | ||
275 | struct branch_info *bi; | ||
276 | unsigned int i; | ||
277 | |||
278 | bi = calloc(bs->nr, sizeof(struct branch_info)); | ||
279 | if (!bi) | ||
280 | return NULL; | ||
281 | |||
282 | for (i = 0; i < bs->nr; i++) { | ||
283 | ip__resolve_ams(self, thr, &bi[i].to, bs->entries[i].to); | ||
284 | ip__resolve_ams(self, thr, &bi[i].from, bs->entries[i].from); | ||
285 | bi[i].flags = bs->entries[i].flags; | ||
286 | } | ||
287 | return bi; | ||
288 | } | ||
289 | |||
232 | int machine__resolve_callchain(struct machine *self, struct perf_evsel *evsel, | 290 | int machine__resolve_callchain(struct machine *self, struct perf_evsel *evsel, |
233 | struct thread *thread, | 291 | struct thread *thread, |
234 | struct ip_callchain *chain, | 292 | struct ip_callchain *chain, |
@@ -697,6 +755,18 @@ static void callchain__printf(struct perf_sample *sample) | |||
697 | i, sample->callchain->ips[i]); | 755 | i, sample->callchain->ips[i]); |
698 | } | 756 | } |
699 | 757 | ||
758 | static void branch_stack__printf(struct perf_sample *sample) | ||
759 | { | ||
760 | uint64_t i; | ||
761 | |||
762 | printf("... branch stack: nr:%" PRIu64 "\n", sample->branch_stack->nr); | ||
763 | |||
764 | for (i = 0; i < sample->branch_stack->nr; i++) | ||
765 | printf("..... %2"PRIu64": %016" PRIx64 " -> %016" PRIx64 "\n", | ||
766 | i, sample->branch_stack->entries[i].from, | ||
767 | sample->branch_stack->entries[i].to); | ||
768 | } | ||
769 | |||
700 | static void perf_session__print_tstamp(struct perf_session *session, | 770 | static void perf_session__print_tstamp(struct perf_session *session, |
701 | union perf_event *event, | 771 | union perf_event *event, |
702 | struct perf_sample *sample) | 772 | struct perf_sample *sample) |
@@ -744,6 +814,9 @@ static void dump_sample(struct perf_session *session, union perf_event *event, | |||
744 | 814 | ||
745 | if (session->sample_type & PERF_SAMPLE_CALLCHAIN) | 815 | if (session->sample_type & PERF_SAMPLE_CALLCHAIN) |
746 | callchain__printf(sample); | 816 | callchain__printf(sample); |
817 | |||
818 | if (session->sample_type & PERF_SAMPLE_BRANCH_STACK) | ||
819 | branch_stack__printf(sample); | ||
747 | } | 820 | } |
748 | 821 | ||
749 | static struct machine * | 822 | static struct machine * |
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index c8d90178e7de..7a5434c00565 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h | |||
@@ -73,6 +73,10 @@ int perf_session__resolve_callchain(struct perf_session *self, struct perf_evsel | |||
73 | struct ip_callchain *chain, | 73 | struct ip_callchain *chain, |
74 | struct symbol **parent); | 74 | struct symbol **parent); |
75 | 75 | ||
76 | struct branch_info *machine__resolve_bstack(struct machine *self, | ||
77 | struct thread *thread, | ||
78 | struct branch_stack *bs); | ||
79 | |||
76 | bool perf_session__has_traces(struct perf_session *self, const char *msg); | 80 | bool perf_session__has_traces(struct perf_session *self, const char *msg); |
77 | 81 | ||
78 | void mem_bswap_64(void *src, int byte_size); | 82 | void mem_bswap_64(void *src, int byte_size); |
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 16da30d8d765..88dbcf6f9575 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c | |||
@@ -8,6 +8,7 @@ const char default_sort_order[] = "comm,dso,symbol"; | |||
8 | const char *sort_order = default_sort_order; | 8 | const char *sort_order = default_sort_order; |
9 | int sort__need_collapse = 0; | 9 | int sort__need_collapse = 0; |
10 | int sort__has_parent = 0; | 10 | int sort__has_parent = 0; |
11 | int sort__branch_mode = -1; /* -1 = means not set */ | ||
11 | 12 | ||
12 | enum sort_type sort__first_dimension; | 13 | enum sort_type sort__first_dimension; |
13 | 14 | ||
@@ -94,6 +95,26 @@ static int hist_entry__comm_snprintf(struct hist_entry *self, char *bf, | |||
94 | return repsep_snprintf(bf, size, "%*s", width, self->thread->comm); | 95 | return repsep_snprintf(bf, size, "%*s", width, self->thread->comm); |
95 | } | 96 | } |
96 | 97 | ||
98 | static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r) | ||
99 | { | ||
100 | struct dso *dso_l = map_l ? map_l->dso : NULL; | ||
101 | struct dso *dso_r = map_r ? map_r->dso : NULL; | ||
102 | const char *dso_name_l, *dso_name_r; | ||
103 | |||
104 | if (!dso_l || !dso_r) | ||
105 | return cmp_null(dso_l, dso_r); | ||
106 | |||
107 | if (verbose) { | ||
108 | dso_name_l = dso_l->long_name; | ||
109 | dso_name_r = dso_r->long_name; | ||
110 | } else { | ||
111 | dso_name_l = dso_l->short_name; | ||
112 | dso_name_r = dso_r->short_name; | ||
113 | } | ||
114 | |||
115 | return strcmp(dso_name_l, dso_name_r); | ||
116 | } | ||
117 | |||
97 | struct sort_entry sort_comm = { | 118 | struct sort_entry sort_comm = { |
98 | .se_header = "Command", | 119 | .se_header = "Command", |
99 | .se_cmp = sort__comm_cmp, | 120 | .se_cmp = sort__comm_cmp, |
@@ -107,36 +128,74 @@ struct sort_entry sort_comm = { | |||
107 | static int64_t | 128 | static int64_t |
108 | sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) | 129 | sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) |
109 | { | 130 | { |
110 | struct dso *dso_l = left->ms.map ? left->ms.map->dso : NULL; | 131 | return _sort__dso_cmp(left->ms.map, right->ms.map); |
111 | struct dso *dso_r = right->ms.map ? right->ms.map->dso : NULL; | 132 | } |
112 | const char *dso_name_l, *dso_name_r; | ||
113 | 133 | ||
114 | if (!dso_l || !dso_r) | ||
115 | return cmp_null(dso_l, dso_r); | ||
116 | 134 | ||
117 | if (verbose) { | 135 | static int64_t _sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r, |
118 | dso_name_l = dso_l->long_name; | 136 | u64 ip_l, u64 ip_r) |
119 | dso_name_r = dso_r->long_name; | 137 | { |
120 | } else { | 138 | if (!sym_l || !sym_r) |
121 | dso_name_l = dso_l->short_name; | 139 | return cmp_null(sym_l, sym_r); |
122 | dso_name_r = dso_r->short_name; | 140 | |
141 | if (sym_l == sym_r) | ||
142 | return 0; | ||
143 | |||
144 | if (sym_l) | ||
145 | ip_l = sym_l->start; | ||
146 | if (sym_r) | ||
147 | ip_r = sym_r->start; | ||
148 | |||
149 | return (int64_t)(ip_r - ip_l); | ||
150 | } | ||
151 | |||
152 | static int _hist_entry__dso_snprintf(struct map *map, char *bf, | ||
153 | size_t size, unsigned int width) | ||
154 | { | ||
155 | if (map && map->dso) { | ||
156 | const char *dso_name = !verbose ? map->dso->short_name : | ||
157 | map->dso->long_name; | ||
158 | return repsep_snprintf(bf, size, "%-*s", width, dso_name); | ||
123 | } | 159 | } |
124 | 160 | ||
125 | return strcmp(dso_name_l, dso_name_r); | 161 | return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); |
126 | } | 162 | } |
127 | 163 | ||
128 | static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf, | 164 | static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf, |
129 | size_t size, unsigned int width) | 165 | size_t size, unsigned int width) |
130 | { | 166 | { |
131 | if (self->ms.map && self->ms.map->dso) { | 167 | return _hist_entry__dso_snprintf(self->ms.map, bf, size, width); |
132 | const char *dso_name = !verbose ? self->ms.map->dso->short_name : | 168 | } |
133 | self->ms.map->dso->long_name; | 169 | |
134 | return repsep_snprintf(bf, size, "%-*s", width, dso_name); | 170 | static int _hist_entry__sym_snprintf(struct map *map, struct symbol *sym, |
171 | u64 ip, char level, char *bf, size_t size, | ||
172 | unsigned int width __used) | ||
173 | { | ||
174 | size_t ret = 0; | ||
175 | |||
176 | if (verbose) { | ||
177 | char o = map ? dso__symtab_origin(map->dso) : '!'; | ||
178 | ret += repsep_snprintf(bf, size, "%-#*llx %c ", | ||
179 | BITS_PER_LONG / 4, ip, o); | ||
135 | } | 180 | } |
136 | 181 | ||
137 | return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); | 182 | ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", level); |
183 | if (sym) | ||
184 | ret += repsep_snprintf(bf + ret, size - ret, "%-*s", | ||
185 | width - ret, | ||
186 | sym->name); | ||
187 | else { | ||
188 | size_t len = BITS_PER_LONG / 4; | ||
189 | ret += repsep_snprintf(bf + ret, size - ret, "%-#.*llx", | ||
190 | len, ip); | ||
191 | ret += repsep_snprintf(bf + ret, size - ret, "%-*s", | ||
192 | width - ret, ""); | ||
193 | } | ||
194 | |||
195 | return ret; | ||
138 | } | 196 | } |
139 | 197 | ||
198 | |||
140 | struct sort_entry sort_dso = { | 199 | struct sort_entry sort_dso = { |
141 | .se_header = "Shared Object", | 200 | .se_header = "Shared Object", |
142 | .se_cmp = sort__dso_cmp, | 201 | .se_cmp = sort__dso_cmp, |
@@ -144,8 +203,14 @@ struct sort_entry sort_dso = { | |||
144 | .se_width_idx = HISTC_DSO, | 203 | .se_width_idx = HISTC_DSO, |
145 | }; | 204 | }; |
146 | 205 | ||
147 | /* --sort symbol */ | 206 | static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, |
207 | size_t size, unsigned int width __used) | ||
208 | { | ||
209 | return _hist_entry__sym_snprintf(self->ms.map, self->ms.sym, self->ip, | ||
210 | self->level, bf, size, width); | ||
211 | } | ||
148 | 212 | ||
213 | /* --sort symbol */ | ||
149 | static int64_t | 214 | static int64_t |
150 | sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) | 215 | sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) |
151 | { | 216 | { |
@@ -163,31 +228,7 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) | |||
163 | ip_l = left->ms.sym->start; | 228 | ip_l = left->ms.sym->start; |
164 | ip_r = right->ms.sym->start; | 229 | ip_r = right->ms.sym->start; |
165 | 230 | ||
166 | return (int64_t)(ip_r - ip_l); | 231 | return _sort__sym_cmp(left->ms.sym, right->ms.sym, ip_l, ip_r); |
167 | } | ||
168 | |||
169 | static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, | ||
170 | size_t size, unsigned int width __used) | ||
171 | { | ||
172 | size_t ret = 0; | ||
173 | |||
174 | if (verbose) { | ||
175 | char o = self->ms.map ? dso__symtab_origin(self->ms.map->dso) : '!'; | ||
176 | ret += repsep_snprintf(bf, size, "%-#*llx %c ", | ||
177 | BITS_PER_LONG / 4, self->ip, o); | ||
178 | } | ||
179 | |||
180 | if (!sort_dso.elide) | ||
181 | ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level); | ||
182 | |||
183 | if (self->ms.sym) | ||
184 | ret += repsep_snprintf(bf + ret, size - ret, "%s", | ||
185 | self->ms.sym->name); | ||
186 | else | ||
187 | ret += repsep_snprintf(bf + ret, size - ret, "%-#*llx", | ||
188 | BITS_PER_LONG / 4, self->ip); | ||
189 | |||
190 | return ret; | ||
191 | } | 232 | } |
192 | 233 | ||
193 | struct sort_entry sort_sym = { | 234 | struct sort_entry sort_sym = { |
@@ -246,19 +287,155 @@ struct sort_entry sort_cpu = { | |||
246 | .se_width_idx = HISTC_CPU, | 287 | .se_width_idx = HISTC_CPU, |
247 | }; | 288 | }; |
248 | 289 | ||
290 | static int64_t | ||
291 | sort__dso_from_cmp(struct hist_entry *left, struct hist_entry *right) | ||
292 | { | ||
293 | return _sort__dso_cmp(left->branch_info->from.map, | ||
294 | right->branch_info->from.map); | ||
295 | } | ||
296 | |||
297 | static int hist_entry__dso_from_snprintf(struct hist_entry *self, char *bf, | ||
298 | size_t size, unsigned int width) | ||
299 | { | ||
300 | return _hist_entry__dso_snprintf(self->branch_info->from.map, | ||
301 | bf, size, width); | ||
302 | } | ||
303 | |||
304 | struct sort_entry sort_dso_from = { | ||
305 | .se_header = "Source Shared Object", | ||
306 | .se_cmp = sort__dso_from_cmp, | ||
307 | .se_snprintf = hist_entry__dso_from_snprintf, | ||
308 | .se_width_idx = HISTC_DSO_FROM, | ||
309 | }; | ||
310 | |||
311 | static int64_t | ||
312 | sort__dso_to_cmp(struct hist_entry *left, struct hist_entry *right) | ||
313 | { | ||
314 | return _sort__dso_cmp(left->branch_info->to.map, | ||
315 | right->branch_info->to.map); | ||
316 | } | ||
317 | |||
318 | static int hist_entry__dso_to_snprintf(struct hist_entry *self, char *bf, | ||
319 | size_t size, unsigned int width) | ||
320 | { | ||
321 | return _hist_entry__dso_snprintf(self->branch_info->to.map, | ||
322 | bf, size, width); | ||
323 | } | ||
324 | |||
325 | static int64_t | ||
326 | sort__sym_from_cmp(struct hist_entry *left, struct hist_entry *right) | ||
327 | { | ||
328 | struct addr_map_symbol *from_l = &left->branch_info->from; | ||
329 | struct addr_map_symbol *from_r = &right->branch_info->from; | ||
330 | |||
331 | if (!from_l->sym && !from_r->sym) | ||
332 | return right->level - left->level; | ||
333 | |||
334 | return _sort__sym_cmp(from_l->sym, from_r->sym, from_l->addr, | ||
335 | from_r->addr); | ||
336 | } | ||
337 | |||
338 | static int64_t | ||
339 | sort__sym_to_cmp(struct hist_entry *left, struct hist_entry *right) | ||
340 | { | ||
341 | struct addr_map_symbol *to_l = &left->branch_info->to; | ||
342 | struct addr_map_symbol *to_r = &right->branch_info->to; | ||
343 | |||
344 | if (!to_l->sym && !to_r->sym) | ||
345 | return right->level - left->level; | ||
346 | |||
347 | return _sort__sym_cmp(to_l->sym, to_r->sym, to_l->addr, to_r->addr); | ||
348 | } | ||
349 | |||
350 | static int hist_entry__sym_from_snprintf(struct hist_entry *self, char *bf, | ||
351 | size_t size, unsigned int width __used) | ||
352 | { | ||
353 | struct addr_map_symbol *from = &self->branch_info->from; | ||
354 | return _hist_entry__sym_snprintf(from->map, from->sym, from->addr, | ||
355 | self->level, bf, size, width); | ||
356 | |||
357 | } | ||
358 | |||
359 | static int hist_entry__sym_to_snprintf(struct hist_entry *self, char *bf, | ||
360 | size_t size, unsigned int width __used) | ||
361 | { | ||
362 | struct addr_map_symbol *to = &self->branch_info->to; | ||
363 | return _hist_entry__sym_snprintf(to->map, to->sym, to->addr, | ||
364 | self->level, bf, size, width); | ||
365 | |||
366 | } | ||
367 | |||
368 | struct sort_entry sort_dso_to = { | ||
369 | .se_header = "Target Shared Object", | ||
370 | .se_cmp = sort__dso_to_cmp, | ||
371 | .se_snprintf = hist_entry__dso_to_snprintf, | ||
372 | .se_width_idx = HISTC_DSO_TO, | ||
373 | }; | ||
374 | |||
375 | struct sort_entry sort_sym_from = { | ||
376 | .se_header = "Source Symbol", | ||
377 | .se_cmp = sort__sym_from_cmp, | ||
378 | .se_snprintf = hist_entry__sym_from_snprintf, | ||
379 | .se_width_idx = HISTC_SYMBOL_FROM, | ||
380 | }; | ||
381 | |||
382 | struct sort_entry sort_sym_to = { | ||
383 | .se_header = "Target Symbol", | ||
384 | .se_cmp = sort__sym_to_cmp, | ||
385 | .se_snprintf = hist_entry__sym_to_snprintf, | ||
386 | .se_width_idx = HISTC_SYMBOL_TO, | ||
387 | }; | ||
388 | |||
389 | static int64_t | ||
390 | sort__mispredict_cmp(struct hist_entry *left, struct hist_entry *right) | ||
391 | { | ||
392 | const unsigned char mp = left->branch_info->flags.mispred != | ||
393 | right->branch_info->flags.mispred; | ||
394 | const unsigned char p = left->branch_info->flags.predicted != | ||
395 | right->branch_info->flags.predicted; | ||
396 | |||
397 | return mp || p; | ||
398 | } | ||
399 | |||
400 | static int hist_entry__mispredict_snprintf(struct hist_entry *self, char *bf, | ||
401 | size_t size, unsigned int width){ | ||
402 | static const char *out = "N/A"; | ||
403 | |||
404 | if (self->branch_info->flags.predicted) | ||
405 | out = "N"; | ||
406 | else if (self->branch_info->flags.mispred) | ||
407 | out = "Y"; | ||
408 | |||
409 | return repsep_snprintf(bf, size, "%-*s", width, out); | ||
410 | } | ||
411 | |||
412 | struct sort_entry sort_mispredict = { | ||
413 | .se_header = "Branch Mispredicted", | ||
414 | .se_cmp = sort__mispredict_cmp, | ||
415 | .se_snprintf = hist_entry__mispredict_snprintf, | ||
416 | .se_width_idx = HISTC_MISPREDICT, | ||
417 | }; | ||
418 | |||
249 | struct sort_dimension { | 419 | struct sort_dimension { |
250 | const char *name; | 420 | const char *name; |
251 | struct sort_entry *entry; | 421 | struct sort_entry *entry; |
252 | int taken; | 422 | int taken; |
253 | }; | 423 | }; |
254 | 424 | ||
425 | #define DIM(d, n, func) [d] = { .name = n, .entry = &(func) } | ||
426 | |||
255 | static struct sort_dimension sort_dimensions[] = { | 427 | static struct sort_dimension sort_dimensions[] = { |
256 | { .name = "pid", .entry = &sort_thread, }, | 428 | DIM(SORT_PID, "pid", sort_thread), |
257 | { .name = "comm", .entry = &sort_comm, }, | 429 | DIM(SORT_COMM, "comm", sort_comm), |
258 | { .name = "dso", .entry = &sort_dso, }, | 430 | DIM(SORT_DSO, "dso", sort_dso), |
259 | { .name = "symbol", .entry = &sort_sym, }, | 431 | DIM(SORT_DSO_FROM, "dso_from", sort_dso_from), |
260 | { .name = "parent", .entry = &sort_parent, }, | 432 | DIM(SORT_DSO_TO, "dso_to", sort_dso_to), |
261 | { .name = "cpu", .entry = &sort_cpu, }, | 433 | DIM(SORT_SYM, "symbol", sort_sym), |
434 | DIM(SORT_SYM_TO, "symbol_from", sort_sym_from), | ||
435 | DIM(SORT_SYM_FROM, "symbol_to", sort_sym_to), | ||
436 | DIM(SORT_PARENT, "parent", sort_parent), | ||
437 | DIM(SORT_CPU, "cpu", sort_cpu), | ||
438 | DIM(SORT_MISPREDICT, "mispredict", sort_mispredict), | ||
262 | }; | 439 | }; |
263 | 440 | ||
264 | int sort_dimension__add(const char *tok) | 441 | int sort_dimension__add(const char *tok) |
@@ -270,7 +447,6 @@ int sort_dimension__add(const char *tok) | |||
270 | 447 | ||
271 | if (strncasecmp(tok, sd->name, strlen(tok))) | 448 | if (strncasecmp(tok, sd->name, strlen(tok))) |
272 | continue; | 449 | continue; |
273 | |||
274 | if (sd->entry == &sort_parent) { | 450 | if (sd->entry == &sort_parent) { |
275 | int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED); | 451 | int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED); |
276 | if (ret) { | 452 | if (ret) { |
@@ -302,6 +478,16 @@ int sort_dimension__add(const char *tok) | |||
302 | sort__first_dimension = SORT_PARENT; | 478 | sort__first_dimension = SORT_PARENT; |
303 | else if (!strcmp(sd->name, "cpu")) | 479 | else if (!strcmp(sd->name, "cpu")) |
304 | sort__first_dimension = SORT_CPU; | 480 | sort__first_dimension = SORT_CPU; |
481 | else if (!strcmp(sd->name, "symbol_from")) | ||
482 | sort__first_dimension = SORT_SYM_FROM; | ||
483 | else if (!strcmp(sd->name, "symbol_to")) | ||
484 | sort__first_dimension = SORT_SYM_TO; | ||
485 | else if (!strcmp(sd->name, "dso_from")) | ||
486 | sort__first_dimension = SORT_DSO_FROM; | ||
487 | else if (!strcmp(sd->name, "dso_to")) | ||
488 | sort__first_dimension = SORT_DSO_TO; | ||
489 | else if (!strcmp(sd->name, "mispredict")) | ||
490 | sort__first_dimension = SORT_MISPREDICT; | ||
305 | } | 491 | } |
306 | 492 | ||
307 | list_add_tail(&sd->entry->list, &hist_entry__sort_list); | 493 | list_add_tail(&sd->entry->list, &hist_entry__sort_list); |
@@ -309,7 +495,6 @@ int sort_dimension__add(const char *tok) | |||
309 | 495 | ||
310 | return 0; | 496 | return 0; |
311 | } | 497 | } |
312 | |||
313 | return -ESRCH; | 498 | return -ESRCH; |
314 | } | 499 | } |
315 | 500 | ||
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 3f67ae395752..472aa5a63a58 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h | |||
@@ -31,11 +31,16 @@ extern const char *parent_pattern; | |||
31 | extern const char default_sort_order[]; | 31 | extern const char default_sort_order[]; |
32 | extern int sort__need_collapse; | 32 | extern int sort__need_collapse; |
33 | extern int sort__has_parent; | 33 | extern int sort__has_parent; |
34 | extern int sort__branch_mode; | ||
34 | extern char *field_sep; | 35 | extern char *field_sep; |
35 | extern struct sort_entry sort_comm; | 36 | extern struct sort_entry sort_comm; |
36 | extern struct sort_entry sort_dso; | 37 | extern struct sort_entry sort_dso; |
37 | extern struct sort_entry sort_sym; | 38 | extern struct sort_entry sort_sym; |
38 | extern struct sort_entry sort_parent; | 39 | extern struct sort_entry sort_parent; |
40 | extern struct sort_entry sort_dso_from; | ||
41 | extern struct sort_entry sort_dso_to; | ||
42 | extern struct sort_entry sort_sym_from; | ||
43 | extern struct sort_entry sort_sym_to; | ||
39 | extern enum sort_type sort__first_dimension; | 44 | extern enum sort_type sort__first_dimension; |
40 | 45 | ||
41 | /** | 46 | /** |
@@ -72,6 +77,7 @@ struct hist_entry { | |||
72 | struct hist_entry *pair; | 77 | struct hist_entry *pair; |
73 | struct rb_root sorted_chain; | 78 | struct rb_root sorted_chain; |
74 | }; | 79 | }; |
80 | struct branch_info *branch_info; | ||
75 | struct callchain_root callchain[0]; | 81 | struct callchain_root callchain[0]; |
76 | }; | 82 | }; |
77 | 83 | ||
@@ -82,6 +88,11 @@ enum sort_type { | |||
82 | SORT_SYM, | 88 | SORT_SYM, |
83 | SORT_PARENT, | 89 | SORT_PARENT, |
84 | SORT_CPU, | 90 | SORT_CPU, |
91 | SORT_DSO_FROM, | ||
92 | SORT_DSO_TO, | ||
93 | SORT_SYM_FROM, | ||
94 | SORT_SYM_TO, | ||
95 | SORT_MISPREDICT, | ||
85 | }; | 96 | }; |
86 | 97 | ||
87 | /* | 98 | /* |
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 2a683d4fc918..ac49ef208a5f 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h | |||
@@ -5,6 +5,7 @@ | |||
5 | #include <stdbool.h> | 5 | #include <stdbool.h> |
6 | #include <stdint.h> | 6 | #include <stdint.h> |
7 | #include "map.h" | 7 | #include "map.h" |
8 | #include "../perf.h" | ||
8 | #include <linux/list.h> | 9 | #include <linux/list.h> |
9 | #include <linux/rbtree.h> | 10 | #include <linux/rbtree.h> |
10 | #include <stdio.h> | 11 | #include <stdio.h> |
@@ -96,7 +97,11 @@ struct symbol_conf { | |||
96 | *col_width_list_str; | 97 | *col_width_list_str; |
97 | struct strlist *dso_list, | 98 | struct strlist *dso_list, |
98 | *comm_list, | 99 | *comm_list, |
99 | *sym_list; | 100 | *sym_list, |
101 | *dso_from_list, | ||
102 | *dso_to_list, | ||
103 | *sym_from_list, | ||
104 | *sym_to_list; | ||
100 | const char *symfs; | 105 | const char *symfs; |
101 | }; | 106 | }; |
102 | 107 | ||
@@ -120,6 +125,19 @@ struct map_symbol { | |||
120 | bool has_children; | 125 | bool has_children; |
121 | }; | 126 | }; |
122 | 127 | ||
128 | struct addr_map_symbol { | ||
129 | struct map *map; | ||
130 | struct symbol *sym; | ||
131 | u64 addr; | ||
132 | u64 al_addr; | ||
133 | }; | ||
134 | |||
135 | struct branch_info { | ||
136 | struct addr_map_symbol from; | ||
137 | struct addr_map_symbol to; | ||
138 | struct branch_flags flags; | ||
139 | }; | ||
140 | |||
123 | struct addr_location { | 141 | struct addr_location { |
124 | struct thread *thread; | 142 | struct thread *thread; |
125 | struct map *map; | 143 | struct map *map; |
diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/util/ui/browsers/hists.c index bfba0490c098..de8ece8bcce3 100644 --- a/tools/perf/util/ui/browsers/hists.c +++ b/tools/perf/util/ui/browsers/hists.c | |||
@@ -805,8 +805,11 @@ static struct hist_browser *hist_browser__new(struct hists *hists) | |||
805 | self->hists = hists; | 805 | self->hists = hists; |
806 | self->b.refresh = hist_browser__refresh; | 806 | self->b.refresh = hist_browser__refresh; |
807 | self->b.seek = ui_browser__hists_seek; | 807 | self->b.seek = ui_browser__hists_seek; |
808 | self->b.use_navkeypressed = true, | 808 | self->b.use_navkeypressed = true; |
809 | self->has_symbols = sort_sym.list.next != NULL; | 809 | if (sort__branch_mode == 1) |
810 | self->has_symbols = sort_sym_from.list.next != NULL; | ||
811 | else | ||
812 | self->has_symbols = sort_sym.list.next != NULL; | ||
810 | } | 813 | } |
811 | 814 | ||
812 | return self; | 815 | return self; |
@@ -853,6 +856,16 @@ static int hists__browser_title(struct hists *self, char *bf, size_t size, | |||
853 | return printed; | 856 | return printed; |
854 | } | 857 | } |
855 | 858 | ||
859 | static inline void free_popup_options(char **options, int n) | ||
860 | { | ||
861 | int i; | ||
862 | |||
863 | for (i = 0; i < n; ++i) { | ||
864 | free(options[i]); | ||
865 | options[i] = NULL; | ||
866 | } | ||
867 | } | ||
868 | |||
856 | static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | 869 | static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, |
857 | const char *helpline, const char *ev_name, | 870 | const char *helpline, const char *ev_name, |
858 | bool left_exits, | 871 | bool left_exits, |
@@ -861,7 +874,10 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
861 | { | 874 | { |
862 | struct hists *self = &evsel->hists; | 875 | struct hists *self = &evsel->hists; |
863 | struct hist_browser *browser = hist_browser__new(self); | 876 | struct hist_browser *browser = hist_browser__new(self); |
877 | struct branch_info *bi; | ||
864 | struct pstack *fstack; | 878 | struct pstack *fstack; |
879 | char *options[16]; | ||
880 | int nr_options = 0; | ||
865 | int key = -1; | 881 | int key = -1; |
866 | 882 | ||
867 | if (browser == NULL) | 883 | if (browser == NULL) |
@@ -873,13 +889,16 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
873 | 889 | ||
874 | ui_helpline__push(helpline); | 890 | ui_helpline__push(helpline); |
875 | 891 | ||
892 | memset(options, 0, sizeof(options)); | ||
893 | |||
876 | while (1) { | 894 | while (1) { |
877 | const struct thread *thread = NULL; | 895 | const struct thread *thread = NULL; |
878 | const struct dso *dso = NULL; | 896 | const struct dso *dso = NULL; |
879 | char *options[16]; | 897 | int choice = 0, |
880 | int nr_options = 0, choice = 0, i, | ||
881 | annotate = -2, zoom_dso = -2, zoom_thread = -2, | 898 | annotate = -2, zoom_dso = -2, zoom_thread = -2, |
882 | browse_map = -2; | 899 | annotate_f = -2, annotate_t = -2, browse_map = -2; |
900 | |||
901 | nr_options = 0; | ||
883 | 902 | ||
884 | key = hist_browser__run(browser, ev_name, timer, arg, delay_secs); | 903 | key = hist_browser__run(browser, ev_name, timer, arg, delay_secs); |
885 | 904 | ||
@@ -887,7 +906,6 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
887 | thread = hist_browser__selected_thread(browser); | 906 | thread = hist_browser__selected_thread(browser); |
888 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | 907 | dso = browser->selection->map ? browser->selection->map->dso : NULL; |
889 | } | 908 | } |
890 | |||
891 | switch (key) { | 909 | switch (key) { |
892 | case K_TAB: | 910 | case K_TAB: |
893 | case K_UNTAB: | 911 | case K_UNTAB: |
@@ -902,7 +920,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
902 | if (!browser->has_symbols) { | 920 | if (!browser->has_symbols) { |
903 | ui_browser__warning(&browser->b, delay_secs * 2, | 921 | ui_browser__warning(&browser->b, delay_secs * 2, |
904 | "Annotation is only available for symbolic views, " | 922 | "Annotation is only available for symbolic views, " |
905 | "include \"sym\" in --sort to use it."); | 923 | "include \"sym*\" in --sort to use it."); |
906 | continue; | 924 | continue; |
907 | } | 925 | } |
908 | 926 | ||
@@ -972,12 +990,34 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
972 | if (!browser->has_symbols) | 990 | if (!browser->has_symbols) |
973 | goto add_exit_option; | 991 | goto add_exit_option; |
974 | 992 | ||
975 | if (browser->selection != NULL && | 993 | if (sort__branch_mode == 1) { |
976 | browser->selection->sym != NULL && | 994 | bi = browser->he_selection->branch_info; |
977 | !browser->selection->map->dso->annotate_warned && | 995 | if (browser->selection != NULL && |
978 | asprintf(&options[nr_options], "Annotate %s", | 996 | bi && |
979 | browser->selection->sym->name) > 0) | 997 | bi->from.sym != NULL && |
980 | annotate = nr_options++; | 998 | !bi->from.map->dso->annotate_warned && |
999 | asprintf(&options[nr_options], "Annotate %s", | ||
1000 | bi->from.sym->name) > 0) | ||
1001 | annotate_f = nr_options++; | ||
1002 | |||
1003 | if (browser->selection != NULL && | ||
1004 | bi && | ||
1005 | bi->to.sym != NULL && | ||
1006 | !bi->to.map->dso->annotate_warned && | ||
1007 | (bi->to.sym != bi->from.sym || | ||
1008 | bi->to.map->dso != bi->from.map->dso) && | ||
1009 | asprintf(&options[nr_options], "Annotate %s", | ||
1010 | bi->to.sym->name) > 0) | ||
1011 | annotate_t = nr_options++; | ||
1012 | } else { | ||
1013 | |||
1014 | if (browser->selection != NULL && | ||
1015 | browser->selection->sym != NULL && | ||
1016 | !browser->selection->map->dso->annotate_warned && | ||
1017 | asprintf(&options[nr_options], "Annotate %s", | ||
1018 | browser->selection->sym->name) > 0) | ||
1019 | annotate = nr_options++; | ||
1020 | } | ||
981 | 1021 | ||
982 | if (thread != NULL && | 1022 | if (thread != NULL && |
983 | asprintf(&options[nr_options], "Zoom %s %s(%d) thread", | 1023 | asprintf(&options[nr_options], "Zoom %s %s(%d) thread", |
@@ -998,25 +1038,39 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
998 | browse_map = nr_options++; | 1038 | browse_map = nr_options++; |
999 | add_exit_option: | 1039 | add_exit_option: |
1000 | options[nr_options++] = (char *)"Exit"; | 1040 | options[nr_options++] = (char *)"Exit"; |
1001 | 1041 | retry_popup_menu: | |
1002 | choice = ui__popup_menu(nr_options, options); | 1042 | choice = ui__popup_menu(nr_options, options); |
1003 | 1043 | ||
1004 | for (i = 0; i < nr_options - 1; ++i) | ||
1005 | free(options[i]); | ||
1006 | |||
1007 | if (choice == nr_options - 1) | 1044 | if (choice == nr_options - 1) |
1008 | break; | 1045 | break; |
1009 | 1046 | ||
1010 | if (choice == -1) | 1047 | if (choice == -1) { |
1048 | free_popup_options(options, nr_options - 1); | ||
1011 | continue; | 1049 | continue; |
1050 | } | ||
1012 | 1051 | ||
1013 | if (choice == annotate) { | 1052 | if (choice == annotate || choice == annotate_t || choice == annotate_f) { |
1014 | struct hist_entry *he; | 1053 | struct hist_entry *he; |
1015 | int err; | 1054 | int err; |
1016 | do_annotate: | 1055 | do_annotate: |
1017 | he = hist_browser__selected_entry(browser); | 1056 | he = hist_browser__selected_entry(browser); |
1018 | if (he == NULL) | 1057 | if (he == NULL) |
1019 | continue; | 1058 | continue; |
1059 | |||
1060 | /* | ||
1061 | * we stash the branch_info symbol + map into the | ||
1062 | * the ms so we don't have to rewrite all the annotation | ||
1063 | * code to use branch_info. | ||
1064 | * in branch mode, the ms struct is not used | ||
1065 | */ | ||
1066 | if (choice == annotate_f) { | ||
1067 | he->ms.sym = he->branch_info->from.sym; | ||
1068 | he->ms.map = he->branch_info->from.map; | ||
1069 | } else if (choice == annotate_t) { | ||
1070 | he->ms.sym = he->branch_info->to.sym; | ||
1071 | he->ms.map = he->branch_info->to.map; | ||
1072 | } | ||
1073 | |||
1020 | /* | 1074 | /* |
1021 | * Don't let this be freed, say, by hists__decay_entry. | 1075 | * Don't let this be freed, say, by hists__decay_entry. |
1022 | */ | 1076 | */ |
@@ -1024,9 +1078,18 @@ do_annotate: | |||
1024 | err = hist_entry__tui_annotate(he, evsel->idx, | 1078 | err = hist_entry__tui_annotate(he, evsel->idx, |
1025 | timer, arg, delay_secs); | 1079 | timer, arg, delay_secs); |
1026 | he->used = false; | 1080 | he->used = false; |
1081 | /* | ||
1082 | * offer option to annotate the other branch source or target | ||
1083 | * (if they exists) when returning from annotate | ||
1084 | */ | ||
1085 | if ((err == 'q' || err == CTRL('c')) | ||
1086 | && annotate_t != -2 && annotate_f != -2) | ||
1087 | goto retry_popup_menu; | ||
1088 | |||
1027 | ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); | 1089 | ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); |
1028 | if (err) | 1090 | if (err) |
1029 | ui_browser__handle_resize(&browser->b); | 1091 | ui_browser__handle_resize(&browser->b); |
1092 | |||
1030 | } else if (choice == browse_map) | 1093 | } else if (choice == browse_map) |
1031 | map__browse(browser->selection->map); | 1094 | map__browse(browser->selection->map); |
1032 | else if (choice == zoom_dso) { | 1095 | else if (choice == zoom_dso) { |
@@ -1072,6 +1135,7 @@ out_free_stack: | |||
1072 | pstack__delete(fstack); | 1135 | pstack__delete(fstack); |
1073 | out: | 1136 | out: |
1074 | hist_browser__delete(browser); | 1137 | hist_browser__delete(browser); |
1138 | free_popup_options(options, nr_options - 1); | ||
1075 | return key; | 1139 | return key; |
1076 | } | 1140 | } |
1077 | 1141 | ||