diff options
Diffstat (limited to 'tools/perf/builtin-report.c')
-rw-r--r-- | tools/perf/builtin-report.c | 210 |
1 files changed, 64 insertions, 146 deletions
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index bc0eec1ce4be..21d830bafff3 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c | |||
@@ -72,6 +72,10 @@ static int report__config(const char *var, const char *value, void *cb) | |||
72 | rep->min_percent = strtof(value, NULL); | 72 | rep->min_percent = strtof(value, NULL); |
73 | return 0; | 73 | return 0; |
74 | } | 74 | } |
75 | if (!strcmp(var, "report.children")) { | ||
76 | symbol_conf.cumulate_callchain = perf_config_bool(var, value); | ||
77 | return 0; | ||
78 | } | ||
75 | 79 | ||
76 | return perf_default_config(var, value, cb); | 80 | return perf_default_config(var, value, cb); |
77 | } | 81 | } |
@@ -85,156 +89,52 @@ static void report__inc_stats(struct report *rep, struct hist_entry *he) | |||
85 | */ | 89 | */ |
86 | if (he->stat.nr_events == 1) | 90 | if (he->stat.nr_events == 1) |
87 | rep->nr_entries++; | 91 | rep->nr_entries++; |
88 | |||
89 | /* | ||
90 | * Only counts number of samples at this stage as it's more | ||
91 | * natural to do it here and non-sample events are also | ||
92 | * counted in perf_session_deliver_event(). The dump_trace | ||
93 | * requires this info is ready before going to the output tree. | ||
94 | */ | ||
95 | hists__inc_nr_events(he->hists, PERF_RECORD_SAMPLE); | ||
96 | if (!he->filtered) | ||
97 | he->hists->stats.nr_non_filtered_samples++; | ||
98 | } | 92 | } |
99 | 93 | ||
100 | static int report__add_mem_hist_entry(struct report *rep, struct addr_location *al, | 94 | static int hist_iter__report_callback(struct hist_entry_iter *iter, |
101 | struct perf_sample *sample, struct perf_evsel *evsel) | 95 | struct addr_location *al, bool single, |
96 | void *arg) | ||
102 | { | 97 | { |
103 | struct symbol *parent = NULL; | 98 | int err = 0; |
104 | struct hist_entry *he; | 99 | struct report *rep = arg; |
105 | struct mem_info *mi, *mx; | 100 | struct hist_entry *he = iter->he; |
106 | uint64_t cost; | 101 | struct perf_evsel *evsel = iter->evsel; |
107 | int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); | 102 | struct mem_info *mi; |
108 | 103 | struct branch_info *bi; | |
109 | if (err) | ||
110 | return err; | ||
111 | 104 | ||
112 | mi = sample__resolve_mem(sample, al); | 105 | report__inc_stats(rep, he); |
113 | if (!mi) | ||
114 | return -ENOMEM; | ||
115 | 106 | ||
116 | if (rep->hide_unresolved && !al->sym) | 107 | if (!ui__has_annotation()) |
117 | return 0; | 108 | return 0; |
118 | 109 | ||
119 | cost = sample->weight; | 110 | if (sort__mode == SORT_MODE__BRANCH) { |
120 | if (!cost) | 111 | bi = he->branch_info; |
121 | cost = 1; | 112 | err = addr_map_symbol__inc_samples(&bi->from, evsel->idx); |
122 | |||
123 | /* | ||
124 | * must pass period=weight in order to get the correct | ||
125 | * sorting from hists__collapse_resort() which is solely | ||
126 | * based on periods. We want sorting be done on nr_events * weight | ||
127 | * and this is indirectly achieved by passing period=weight here | ||
128 | * and the he_stat__add_period() function. | ||
129 | */ | ||
130 | he = __hists__add_entry(&evsel->hists, al, parent, NULL, mi, | ||
131 | cost, cost, 0); | ||
132 | if (!he) | ||
133 | return -ENOMEM; | ||
134 | |||
135 | if (ui__has_annotation()) { | ||
136 | err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); | ||
137 | if (err) | ||
138 | goto out; | ||
139 | |||
140 | mx = he->mem_info; | ||
141 | err = addr_map_symbol__inc_samples(&mx->daddr, evsel->idx); | ||
142 | if (err) | 113 | if (err) |
143 | goto out; | 114 | goto out; |
144 | } | ||
145 | |||
146 | report__inc_stats(rep, he); | ||
147 | |||
148 | err = hist_entry__append_callchain(he, sample); | ||
149 | out: | ||
150 | return err; | ||
151 | } | ||
152 | |||
153 | static int report__add_branch_hist_entry(struct report *rep, struct addr_location *al, | ||
154 | struct perf_sample *sample, struct perf_evsel *evsel) | ||
155 | { | ||
156 | struct symbol *parent = NULL; | ||
157 | unsigned i; | ||
158 | struct hist_entry *he; | ||
159 | struct branch_info *bi, *bx; | ||
160 | int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); | ||
161 | 115 | ||
162 | if (err) | 116 | err = addr_map_symbol__inc_samples(&bi->to, evsel->idx); |
163 | return err; | ||
164 | |||
165 | bi = sample__resolve_bstack(sample, al); | ||
166 | if (!bi) | ||
167 | return -ENOMEM; | ||
168 | |||
169 | for (i = 0; i < sample->branch_stack->nr; i++) { | ||
170 | if (rep->hide_unresolved && !(bi[i].from.sym && bi[i].to.sym)) | ||
171 | continue; | ||
172 | 117 | ||
173 | err = -ENOMEM; | 118 | } else if (rep->mem_mode) { |
174 | 119 | mi = he->mem_info; | |
175 | /* overwrite the 'al' to branch-to info */ | 120 | err = addr_map_symbol__inc_samples(&mi->daddr, evsel->idx); |
176 | al->map = bi[i].to.map; | 121 | if (err) |
177 | al->sym = bi[i].to.sym; | ||
178 | al->addr = bi[i].to.addr; | ||
179 | /* | ||
180 | * The report shows the percentage of total branches captured | ||
181 | * and not events sampled. Thus we use a pseudo period of 1. | ||
182 | */ | ||
183 | he = __hists__add_entry(&evsel->hists, al, parent, &bi[i], NULL, | ||
184 | 1, 1, 0); | ||
185 | if (he) { | ||
186 | if (ui__has_annotation()) { | ||
187 | bx = he->branch_info; | ||
188 | err = addr_map_symbol__inc_samples(&bx->from, | ||
189 | evsel->idx); | ||
190 | if (err) | ||
191 | goto out; | ||
192 | |||
193 | err = addr_map_symbol__inc_samples(&bx->to, | ||
194 | evsel->idx); | ||
195 | if (err) | ||
196 | goto out; | ||
197 | } | ||
198 | report__inc_stats(rep, he); | ||
199 | } else | ||
200 | goto out; | 122 | goto out; |
201 | } | ||
202 | err = 0; | ||
203 | out: | ||
204 | free(bi); | ||
205 | return err; | ||
206 | } | ||
207 | |||
208 | static int report__add_hist_entry(struct report *rep, struct perf_evsel *evsel, | ||
209 | struct addr_location *al, struct perf_sample *sample) | ||
210 | { | ||
211 | struct symbol *parent = NULL; | ||
212 | struct hist_entry *he; | ||
213 | int err = sample__resolve_callchain(sample, &parent, evsel, al, rep->max_stack); | ||
214 | |||
215 | if (err) | ||
216 | return err; | ||
217 | 123 | ||
218 | he = __hists__add_entry(&evsel->hists, al, parent, NULL, NULL, | ||
219 | sample->period, sample->weight, | ||
220 | sample->transaction); | ||
221 | if (he == NULL) | ||
222 | return -ENOMEM; | ||
223 | |||
224 | err = hist_entry__append_callchain(he, sample); | ||
225 | if (err) | ||
226 | goto out; | ||
227 | |||
228 | if (ui__has_annotation()) | ||
229 | err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); | 124 | err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); |
230 | 125 | ||
231 | report__inc_stats(rep, he); | 126 | } else if (symbol_conf.cumulate_callchain) { |
127 | if (single) | ||
128 | err = hist_entry__inc_addr_samples(he, evsel->idx, | ||
129 | al->addr); | ||
130 | } else { | ||
131 | err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr); | ||
132 | } | ||
232 | 133 | ||
233 | out: | 134 | out: |
234 | return err; | 135 | return err; |
235 | } | 136 | } |
236 | 137 | ||
237 | |||
238 | static int process_sample_event(struct perf_tool *tool, | 138 | static int process_sample_event(struct perf_tool *tool, |
239 | union perf_event *event, | 139 | union perf_event *event, |
240 | struct perf_sample *sample, | 140 | struct perf_sample *sample, |
@@ -243,6 +143,10 @@ static int process_sample_event(struct perf_tool *tool, | |||
243 | { | 143 | { |
244 | struct report *rep = container_of(tool, struct report, tool); | 144 | struct report *rep = container_of(tool, struct report, tool); |
245 | struct addr_location al; | 145 | struct addr_location al; |
146 | struct hist_entry_iter iter = { | ||
147 | .hide_unresolved = rep->hide_unresolved, | ||
148 | .add_entry_cb = hist_iter__report_callback, | ||
149 | }; | ||
246 | int ret; | 150 | int ret; |
247 | 151 | ||
248 | if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { | 152 | if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { |
@@ -257,22 +161,23 @@ static int process_sample_event(struct perf_tool *tool, | |||
257 | if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap)) | 161 | if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap)) |
258 | return 0; | 162 | return 0; |
259 | 163 | ||
260 | if (sort__mode == SORT_MODE__BRANCH) { | 164 | if (sort__mode == SORT_MODE__BRANCH) |
261 | ret = report__add_branch_hist_entry(rep, &al, sample, evsel); | 165 | iter.ops = &hist_iter_branch; |
262 | if (ret < 0) | 166 | else if (rep->mem_mode) |
263 | pr_debug("problem adding lbr entry, skipping event\n"); | 167 | iter.ops = &hist_iter_mem; |
264 | } else if (rep->mem_mode == 1) { | 168 | else if (symbol_conf.cumulate_callchain) |
265 | ret = report__add_mem_hist_entry(rep, &al, sample, evsel); | 169 | iter.ops = &hist_iter_cumulative; |
266 | if (ret < 0) | 170 | else |
267 | pr_debug("problem adding mem entry, skipping event\n"); | 171 | iter.ops = &hist_iter_normal; |
268 | } else { | 172 | |
269 | if (al.map != NULL) | 173 | if (al.map != NULL) |
270 | al.map->dso->hit = 1; | 174 | al.map->dso->hit = 1; |
175 | |||
176 | ret = hist_entry_iter__add(&iter, &al, evsel, sample, rep->max_stack, | ||
177 | rep); | ||
178 | if (ret < 0) | ||
179 | pr_debug("problem adding hist entry, skipping event\n"); | ||
271 | 180 | ||
272 | ret = report__add_hist_entry(rep, evsel, &al, sample); | ||
273 | if (ret < 0) | ||
274 | pr_debug("problem incrementing symbol period, skipping event\n"); | ||
275 | } | ||
276 | return ret; | 181 | return ret; |
277 | } | 182 | } |
278 | 183 | ||
@@ -329,6 +234,14 @@ static int report__setup_sample_type(struct report *rep) | |||
329 | } | 234 | } |
330 | } | 235 | } |
331 | 236 | ||
237 | if (symbol_conf.cumulate_callchain) { | ||
238 | /* Silently ignore if callchain is missing */ | ||
239 | if (!(sample_type & PERF_SAMPLE_CALLCHAIN)) { | ||
240 | symbol_conf.cumulate_callchain = false; | ||
241 | perf_hpp__cancel_cumulate(); | ||
242 | } | ||
243 | } | ||
244 | |||
332 | if (sort__mode == SORT_MODE__BRANCH) { | 245 | if (sort__mode == SORT_MODE__BRANCH) { |
333 | if (!is_pipe && | 246 | if (!is_pipe && |
334 | !(sample_type & PERF_SAMPLE_BRANCH_STACK)) { | 247 | !(sample_type & PERF_SAMPLE_BRANCH_STACK)) { |
@@ -712,6 +625,8 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) | |||
712 | OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order", | 625 | OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order", |
713 | "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit, callchain order, key (function or address). " | 626 | "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit, callchain order, key (function or address). " |
714 | "Default: fractal,0.5,callee,function", &report_parse_callchain_opt, callchain_default_opt), | 627 | "Default: fractal,0.5,callee,function", &report_parse_callchain_opt, callchain_default_opt), |
628 | OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain, | ||
629 | "Accumulate callchains of children and show total overhead as well"), | ||
715 | OPT_INTEGER(0, "max-stack", &report.max_stack, | 630 | OPT_INTEGER(0, "max-stack", &report.max_stack, |
716 | "Set the maximum stack depth when parsing the callchain, " | 631 | "Set the maximum stack depth when parsing the callchain, " |
717 | "anything beyond the specified depth will be ignored. " | 632 | "anything beyond the specified depth will be ignored. " |
@@ -804,8 +719,10 @@ repeat: | |||
804 | has_br_stack = perf_header__has_feat(&session->header, | 719 | has_br_stack = perf_header__has_feat(&session->header, |
805 | HEADER_BRANCH_STACK); | 720 | HEADER_BRANCH_STACK); |
806 | 721 | ||
807 | if (branch_mode == -1 && has_br_stack) | 722 | if (branch_mode == -1 && has_br_stack) { |
808 | sort__mode = SORT_MODE__BRANCH; | 723 | sort__mode = SORT_MODE__BRANCH; |
724 | symbol_conf.cumulate_callchain = false; | ||
725 | } | ||
809 | 726 | ||
810 | if (report.mem_mode) { | 727 | if (report.mem_mode) { |
811 | if (sort__mode == SORT_MODE__BRANCH) { | 728 | if (sort__mode == SORT_MODE__BRANCH) { |
@@ -813,6 +730,7 @@ repeat: | |||
813 | goto error; | 730 | goto error; |
814 | } | 731 | } |
815 | sort__mode = SORT_MODE__MEMORY; | 732 | sort__mode = SORT_MODE__MEMORY; |
733 | symbol_conf.cumulate_callchain = false; | ||
816 | } | 734 | } |
817 | 735 | ||
818 | if (setup_sorting() < 0) { | 736 | if (setup_sorting() < 0) { |