diff options
Diffstat (limited to 'tools/perf/util/ui/browsers/hists.c')
-rw-r--r-- | tools/perf/util/ui/browsers/hists.c | 502 |
1 files changed, 346 insertions, 156 deletions
diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/util/ui/browsers/hists.c index 6866aa4c41e0..5d767c622dfc 100644 --- a/tools/perf/util/ui/browsers/hists.c +++ b/tools/perf/util/ui/browsers/hists.c | |||
@@ -7,6 +7,8 @@ | |||
7 | #include <newt.h> | 7 | #include <newt.h> |
8 | #include <linux/rbtree.h> | 8 | #include <linux/rbtree.h> |
9 | 9 | ||
10 | #include "../../evsel.h" | ||
11 | #include "../../evlist.h" | ||
10 | #include "../../hist.h" | 12 | #include "../../hist.h" |
11 | #include "../../pstack.h" | 13 | #include "../../pstack.h" |
12 | #include "../../sort.h" | 14 | #include "../../sort.h" |
@@ -58,6 +60,11 @@ static char callchain_list__folded(const struct callchain_list *self) | |||
58 | return map_symbol__folded(&self->ms); | 60 | return map_symbol__folded(&self->ms); |
59 | } | 61 | } |
60 | 62 | ||
63 | static void map_symbol__set_folding(struct map_symbol *self, bool unfold) | ||
64 | { | ||
65 | self->unfolded = unfold ? self->has_children : false; | ||
66 | } | ||
67 | |||
61 | static int callchain_node__count_rows_rb_tree(struct callchain_node *self) | 68 | static int callchain_node__count_rows_rb_tree(struct callchain_node *self) |
62 | { | 69 | { |
63 | int n = 0; | 70 | int n = 0; |
@@ -129,16 +136,16 @@ static void callchain_node__init_have_children_rb_tree(struct callchain_node *se | |||
129 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | 136 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { |
130 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | 137 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); |
131 | struct callchain_list *chain; | 138 | struct callchain_list *chain; |
132 | int first = true; | 139 | bool first = true; |
133 | 140 | ||
134 | list_for_each_entry(chain, &child->val, list) { | 141 | list_for_each_entry(chain, &child->val, list) { |
135 | if (first) { | 142 | if (first) { |
136 | first = false; | 143 | first = false; |
137 | chain->ms.has_children = chain->list.next != &child->val || | 144 | chain->ms.has_children = chain->list.next != &child->val || |
138 | rb_first(&child->rb_root) != NULL; | 145 | !RB_EMPTY_ROOT(&child->rb_root); |
139 | } else | 146 | } else |
140 | chain->ms.has_children = chain->list.next == &child->val && | 147 | chain->ms.has_children = chain->list.next == &child->val && |
141 | rb_first(&child->rb_root) != NULL; | 148 | !RB_EMPTY_ROOT(&child->rb_root); |
142 | } | 149 | } |
143 | 150 | ||
144 | callchain_node__init_have_children_rb_tree(child); | 151 | callchain_node__init_have_children_rb_tree(child); |
@@ -150,7 +157,7 @@ static void callchain_node__init_have_children(struct callchain_node *self) | |||
150 | struct callchain_list *chain; | 157 | struct callchain_list *chain; |
151 | 158 | ||
152 | list_for_each_entry(chain, &self->val, list) | 159 | list_for_each_entry(chain, &self->val, list) |
153 | chain->ms.has_children = rb_first(&self->rb_root) != NULL; | 160 | chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root); |
154 | 161 | ||
155 | callchain_node__init_have_children_rb_tree(self); | 162 | callchain_node__init_have_children_rb_tree(self); |
156 | } | 163 | } |
@@ -168,6 +175,7 @@ static void callchain__init_have_children(struct rb_root *self) | |||
168 | static void hist_entry__init_have_children(struct hist_entry *self) | 175 | static void hist_entry__init_have_children(struct hist_entry *self) |
169 | { | 176 | { |
170 | if (!self->init_have_children) { | 177 | if (!self->init_have_children) { |
178 | self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain); | ||
171 | callchain__init_have_children(&self->sorted_chain); | 179 | callchain__init_have_children(&self->sorted_chain); |
172 | self->init_have_children = true; | 180 | self->init_have_children = true; |
173 | } | 181 | } |
@@ -195,43 +203,115 @@ static bool hist_browser__toggle_fold(struct hist_browser *self) | |||
195 | return false; | 203 | return false; |
196 | } | 204 | } |
197 | 205 | ||
198 | static int hist_browser__run(struct hist_browser *self, const char *title, | 206 | static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold) |
199 | struct newtExitStruct *es) | ||
200 | { | 207 | { |
201 | char str[256], unit; | 208 | int n = 0; |
202 | unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE]; | 209 | struct rb_node *nd; |
210 | |||
211 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | ||
212 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | ||
213 | struct callchain_list *chain; | ||
214 | bool has_children = false; | ||
215 | |||
216 | list_for_each_entry(chain, &child->val, list) { | ||
217 | ++n; | ||
218 | map_symbol__set_folding(&chain->ms, unfold); | ||
219 | has_children = chain->ms.has_children; | ||
220 | } | ||
221 | |||
222 | if (has_children) | ||
223 | n += callchain_node__set_folding_rb_tree(child, unfold); | ||
224 | } | ||
225 | |||
226 | return n; | ||
227 | } | ||
228 | |||
229 | static int callchain_node__set_folding(struct callchain_node *node, bool unfold) | ||
230 | { | ||
231 | struct callchain_list *chain; | ||
232 | bool has_children = false; | ||
233 | int n = 0; | ||
234 | |||
235 | list_for_each_entry(chain, &node->val, list) { | ||
236 | ++n; | ||
237 | map_symbol__set_folding(&chain->ms, unfold); | ||
238 | has_children = chain->ms.has_children; | ||
239 | } | ||
240 | |||
241 | if (has_children) | ||
242 | n += callchain_node__set_folding_rb_tree(node, unfold); | ||
243 | |||
244 | return n; | ||
245 | } | ||
246 | |||
247 | static int callchain__set_folding(struct rb_root *chain, bool unfold) | ||
248 | { | ||
249 | struct rb_node *nd; | ||
250 | int n = 0; | ||
251 | |||
252 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | ||
253 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
254 | n += callchain_node__set_folding(node, unfold); | ||
255 | } | ||
256 | |||
257 | return n; | ||
258 | } | ||
259 | |||
260 | static void hist_entry__set_folding(struct hist_entry *self, bool unfold) | ||
261 | { | ||
262 | hist_entry__init_have_children(self); | ||
263 | map_symbol__set_folding(&self->ms, unfold); | ||
264 | |||
265 | if (self->ms.has_children) { | ||
266 | int n = callchain__set_folding(&self->sorted_chain, unfold); | ||
267 | self->nr_rows = unfold ? n : 0; | ||
268 | } else | ||
269 | self->nr_rows = 0; | ||
270 | } | ||
271 | |||
272 | static void hists__set_folding(struct hists *self, bool unfold) | ||
273 | { | ||
274 | struct rb_node *nd; | ||
275 | |||
276 | self->nr_entries = 0; | ||
277 | |||
278 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { | ||
279 | struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); | ||
280 | hist_entry__set_folding(he, unfold); | ||
281 | self->nr_entries += 1 + he->nr_rows; | ||
282 | } | ||
283 | } | ||
284 | |||
285 | static void hist_browser__set_folding(struct hist_browser *self, bool unfold) | ||
286 | { | ||
287 | hists__set_folding(self->hists, unfold); | ||
288 | self->b.nr_entries = self->hists->nr_entries; | ||
289 | /* Go to the start, we may be way after valid entries after a collapse */ | ||
290 | ui_browser__reset_index(&self->b); | ||
291 | } | ||
292 | |||
293 | static int hist_browser__run(struct hist_browser *self, const char *title) | ||
294 | { | ||
295 | int key; | ||
296 | int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't', | ||
297 | NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT, | ||
298 | NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, }; | ||
203 | 299 | ||
204 | self->b.entries = &self->hists->entries; | 300 | self->b.entries = &self->hists->entries; |
205 | self->b.nr_entries = self->hists->nr_entries; | 301 | self->b.nr_entries = self->hists->nr_entries; |
206 | 302 | ||
207 | hist_browser__refresh_dimensions(self); | 303 | hist_browser__refresh_dimensions(self); |
208 | 304 | ||
209 | nr_events = convert_unit(nr_events, &unit); | ||
210 | snprintf(str, sizeof(str), "Events: %lu%c ", | ||
211 | nr_events, unit); | ||
212 | newtDrawRootText(0, 0, str); | ||
213 | |||
214 | if (ui_browser__show(&self->b, title, | 305 | if (ui_browser__show(&self->b, title, |
215 | "Press '?' for help on key bindings") < 0) | 306 | "Press '?' for help on key bindings") < 0) |
216 | return -1; | 307 | return -1; |
217 | 308 | ||
218 | newtFormAddHotKey(self->b.form, 'a'); | 309 | ui_browser__add_exit_keys(&self->b, exit_keys); |
219 | newtFormAddHotKey(self->b.form, '?'); | ||
220 | newtFormAddHotKey(self->b.form, 'h'); | ||
221 | newtFormAddHotKey(self->b.form, 'd'); | ||
222 | newtFormAddHotKey(self->b.form, 'D'); | ||
223 | newtFormAddHotKey(self->b.form, 't'); | ||
224 | |||
225 | newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); | ||
226 | newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT); | ||
227 | newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); | ||
228 | 310 | ||
229 | while (1) { | 311 | while (1) { |
230 | ui_browser__run(&self->b, es); | 312 | key = ui_browser__run(&self->b); |
231 | 313 | ||
232 | if (es->reason != NEWT_EXIT_HOTKEY) | 314 | switch (key) { |
233 | break; | ||
234 | switch (es->u.key) { | ||
235 | case 'D': { /* Debug */ | 315 | case 'D': { /* Debug */ |
236 | static int seq; | 316 | static int seq; |
237 | struct hist_entry *h = rb_entry(self->b.top, | 317 | struct hist_entry *h = rb_entry(self->b.top, |
@@ -245,18 +325,26 @@ static int hist_browser__run(struct hist_browser *self, const char *title, | |||
245 | self->b.top_idx, | 325 | self->b.top_idx, |
246 | h->row_offset, h->nr_rows); | 326 | h->row_offset, h->nr_rows); |
247 | } | 327 | } |
248 | continue; | 328 | break; |
329 | case 'C': | ||
330 | /* Collapse the whole world. */ | ||
331 | hist_browser__set_folding(self, false); | ||
332 | break; | ||
333 | case 'E': | ||
334 | /* Expand the whole world. */ | ||
335 | hist_browser__set_folding(self, true); | ||
336 | break; | ||
249 | case NEWT_KEY_ENTER: | 337 | case NEWT_KEY_ENTER: |
250 | if (hist_browser__toggle_fold(self)) | 338 | if (hist_browser__toggle_fold(self)) |
251 | break; | 339 | break; |
252 | /* fall thru */ | 340 | /* fall thru */ |
253 | default: | 341 | default: |
254 | return 0; | 342 | goto out; |
255 | } | 343 | } |
256 | } | 344 | } |
257 | 345 | out: | |
258 | ui_browser__hide(&self->b); | 346 | ui_browser__hide(&self->b); |
259 | return 0; | 347 | return key; |
260 | } | 348 | } |
261 | 349 | ||
262 | static char *callchain_list__sym_name(struct callchain_list *self, | 350 | static char *callchain_list__sym_name(struct callchain_list *self, |
@@ -265,7 +353,7 @@ static char *callchain_list__sym_name(struct callchain_list *self, | |||
265 | if (self->ms.sym) | 353 | if (self->ms.sym) |
266 | return self->ms.sym->name; | 354 | return self->ms.sym->name; |
267 | 355 | ||
268 | snprintf(bf, bfsize, "%#Lx", self->ip); | 356 | snprintf(bf, bfsize, "%#" PRIx64, self->ip); |
269 | return bf; | 357 | return bf; |
270 | } | 358 | } |
271 | 359 | ||
@@ -292,7 +380,7 @@ static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, | |||
292 | while (node) { | 380 | while (node) { |
293 | struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); | 381 | struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); |
294 | struct rb_node *next = rb_next(node); | 382 | struct rb_node *next = rb_next(node); |
295 | u64 cumul = cumul_hits(child); | 383 | u64 cumul = callchain_cumul_hits(child); |
296 | struct callchain_list *chain; | 384 | struct callchain_list *chain; |
297 | char folded_sign = ' '; | 385 | char folded_sign = ' '; |
298 | int first = true; | 386 | int first = true; |
@@ -306,15 +394,10 @@ static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, | |||
306 | int color; | 394 | int color; |
307 | bool was_first = first; | 395 | bool was_first = first; |
308 | 396 | ||
309 | if (first) { | 397 | if (first) |
310 | first = false; | 398 | first = false; |
311 | chain->ms.has_children = chain->list.next != &child->val || | 399 | else |
312 | rb_first(&child->rb_root) != NULL; | ||
313 | } else { | ||
314 | extra_offset = LEVEL_OFFSET_STEP; | 400 | extra_offset = LEVEL_OFFSET_STEP; |
315 | chain->ms.has_children = chain->list.next == &child->val && | ||
316 | rb_first(&child->rb_root) != NULL; | ||
317 | } | ||
318 | 401 | ||
319 | folded_sign = callchain_list__folded(chain); | 402 | folded_sign = callchain_list__folded(chain); |
320 | if (*row_offset != 0) { | 403 | if (*row_offset != 0) { |
@@ -341,8 +424,8 @@ static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, | |||
341 | *is_current_entry = true; | 424 | *is_current_entry = true; |
342 | } | 425 | } |
343 | 426 | ||
344 | SLsmg_set_color(color); | 427 | ui_browser__set_color(&self->b, color); |
345 | SLsmg_gotorc(self->b.y + row, self->b.x); | 428 | ui_browser__gotorc(&self->b, row, 0); |
346 | slsmg_write_nstring(" ", offset + extra_offset); | 429 | slsmg_write_nstring(" ", offset + extra_offset); |
347 | slsmg_printf("%c ", folded_sign); | 430 | slsmg_printf("%c ", folded_sign); |
348 | slsmg_write_nstring(str, width); | 431 | slsmg_write_nstring(str, width); |
@@ -384,12 +467,7 @@ static int hist_browser__show_callchain_node(struct hist_browser *self, | |||
384 | list_for_each_entry(chain, &node->val, list) { | 467 | list_for_each_entry(chain, &node->val, list) { |
385 | char ipstr[BITS_PER_LONG / 4 + 1], *s; | 468 | char ipstr[BITS_PER_LONG / 4 + 1], *s; |
386 | int color; | 469 | int color; |
387 | /* | 470 | |
388 | * FIXME: This should be moved to somewhere else, | ||
389 | * probably when the callchain is created, so as not to | ||
390 | * traverse it all over again | ||
391 | */ | ||
392 | chain->ms.has_children = rb_first(&node->rb_root) != NULL; | ||
393 | folded_sign = callchain_list__folded(chain); | 471 | folded_sign = callchain_list__folded(chain); |
394 | 472 | ||
395 | if (*row_offset != 0) { | 473 | if (*row_offset != 0) { |
@@ -405,8 +483,8 @@ static int hist_browser__show_callchain_node(struct hist_browser *self, | |||
405 | } | 483 | } |
406 | 484 | ||
407 | s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | 485 | s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); |
408 | SLsmg_gotorc(self->b.y + row, self->b.x); | 486 | ui_browser__gotorc(&self->b, row, 0); |
409 | SLsmg_set_color(color); | 487 | ui_browser__set_color(&self->b, color); |
410 | slsmg_write_nstring(" ", offset); | 488 | slsmg_write_nstring(" ", offset); |
411 | slsmg_printf("%c ", folded_sign); | 489 | slsmg_printf("%c ", folded_sign); |
412 | slsmg_write_nstring(s, width - 2); | 490 | slsmg_write_nstring(s, width - 2); |
@@ -465,7 +543,7 @@ static int hist_browser__show_entry(struct hist_browser *self, | |||
465 | } | 543 | } |
466 | 544 | ||
467 | if (symbol_conf.use_callchain) { | 545 | if (symbol_conf.use_callchain) { |
468 | entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain); | 546 | hist_entry__init_have_children(entry); |
469 | folded_sign = hist_entry__folded(entry); | 547 | folded_sign = hist_entry__folded(entry); |
470 | } | 548 | } |
471 | 549 | ||
@@ -484,8 +562,8 @@ static int hist_browser__show_entry(struct hist_browser *self, | |||
484 | color = HE_COLORSET_NORMAL; | 562 | color = HE_COLORSET_NORMAL; |
485 | } | 563 | } |
486 | 564 | ||
487 | SLsmg_set_color(color); | 565 | ui_browser__set_color(&self->b, color); |
488 | SLsmg_gotorc(self->b.y + row, self->b.x); | 566 | ui_browser__gotorc(&self->b, row, 0); |
489 | if (symbol_conf.use_callchain) { | 567 | if (symbol_conf.use_callchain) { |
490 | slsmg_printf("%c ", folded_sign); | 568 | slsmg_printf("%c ", folded_sign); |
491 | width -= 2; | 569 | width -= 2; |
@@ -563,6 +641,9 @@ static void ui_browser__hists_seek(struct ui_browser *self, | |||
563 | struct rb_node *nd; | 641 | struct rb_node *nd; |
564 | bool first = true; | 642 | bool first = true; |
565 | 643 | ||
644 | if (self->nr_entries == 0) | ||
645 | return; | ||
646 | |||
566 | switch (whence) { | 647 | switch (whence) { |
567 | case SEEK_SET: | 648 | case SEEK_SET: |
568 | nd = hists__filter_entries(rb_first(self->entries)); | 649 | nd = hists__filter_entries(rb_first(self->entries)); |
@@ -687,8 +768,6 @@ static struct hist_browser *hist_browser__new(struct hists *hists) | |||
687 | 768 | ||
688 | static void hist_browser__delete(struct hist_browser *self) | 769 | static void hist_browser__delete(struct hist_browser *self) |
689 | { | 770 | { |
690 | newtFormDestroy(self->b.form); | ||
691 | newtPopWindow(); | ||
692 | free(self); | 771 | free(self); |
693 | } | 772 | } |
694 | 773 | ||
@@ -702,30 +781,37 @@ static struct thread *hist_browser__selected_thread(struct hist_browser *self) | |||
702 | return self->he_selection->thread; | 781 | return self->he_selection->thread; |
703 | } | 782 | } |
704 | 783 | ||
705 | static int hist_browser__title(char *bf, size_t size, const char *ev_name, | 784 | static int hists__browser_title(struct hists *self, char *bf, size_t size, |
706 | const struct dso *dso, const struct thread *thread) | 785 | const char *ev_name, const struct dso *dso, |
786 | const struct thread *thread) | ||
707 | { | 787 | { |
708 | int printed = 0; | 788 | char unit; |
789 | int printed; | ||
790 | unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE]; | ||
791 | |||
792 | nr_events = convert_unit(nr_events, &unit); | ||
793 | printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name); | ||
709 | 794 | ||
710 | if (thread) | 795 | if (thread) |
711 | printed += snprintf(bf + printed, size - printed, | 796 | printed += snprintf(bf + printed, size - printed, |
712 | "Thread: %s(%d)", | 797 | ", Thread: %s(%d)", |
713 | (thread->comm_set ? thread->comm : ""), | 798 | (thread->comm_set ? thread->comm : ""), |
714 | thread->pid); | 799 | thread->pid); |
715 | if (dso) | 800 | if (dso) |
716 | printed += snprintf(bf + printed, size - printed, | 801 | printed += snprintf(bf + printed, size - printed, |
717 | "%sDSO: %s", thread ? " " : "", | 802 | ", DSO: %s", dso->short_name); |
718 | dso->short_name); | 803 | return printed; |
719 | return printed ?: snprintf(bf, size, "Event: %s", ev_name); | ||
720 | } | 804 | } |
721 | 805 | ||
722 | int hists__browse(struct hists *self, const char *helpline, const char *ev_name) | 806 | static int perf_evsel__hists_browse(struct perf_evsel *evsel, |
807 | const char *helpline, const char *ev_name, | ||
808 | bool left_exits) | ||
723 | { | 809 | { |
810 | struct hists *self = &evsel->hists; | ||
724 | struct hist_browser *browser = hist_browser__new(self); | 811 | struct hist_browser *browser = hist_browser__new(self); |
725 | struct pstack *fstack; | 812 | struct pstack *fstack; |
726 | const struct thread *thread_filter = NULL; | 813 | const struct thread *thread_filter = NULL; |
727 | const struct dso *dso_filter = NULL; | 814 | const struct dso *dso_filter = NULL; |
728 | struct newtExitStruct es; | ||
729 | char msg[160]; | 815 | char msg[160]; |
730 | int key = -1; | 816 | int key = -1; |
731 | 817 | ||
@@ -738,84 +824,88 @@ int hists__browse(struct hists *self, const char *helpline, const char *ev_name) | |||
738 | 824 | ||
739 | ui_helpline__push(helpline); | 825 | ui_helpline__push(helpline); |
740 | 826 | ||
741 | hist_browser__title(msg, sizeof(msg), ev_name, | 827 | hists__browser_title(self, msg, sizeof(msg), ev_name, |
742 | dso_filter, thread_filter); | 828 | dso_filter, thread_filter); |
743 | |||
744 | while (1) { | 829 | while (1) { |
745 | const struct thread *thread; | 830 | const struct thread *thread = NULL; |
746 | const struct dso *dso; | 831 | const struct dso *dso = NULL; |
747 | char *options[16]; | 832 | char *options[16]; |
748 | int nr_options = 0, choice = 0, i, | 833 | int nr_options = 0, choice = 0, i, |
749 | annotate = -2, zoom_dso = -2, zoom_thread = -2, | 834 | annotate = -2, zoom_dso = -2, zoom_thread = -2, |
750 | browse_map = -2; | 835 | browse_map = -2; |
751 | 836 | ||
752 | if (hist_browser__run(browser, msg, &es)) | 837 | key = hist_browser__run(browser, msg); |
753 | break; | ||
754 | 838 | ||
755 | thread = hist_browser__selected_thread(browser); | 839 | if (browser->he_selection != NULL) { |
756 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | 840 | thread = hist_browser__selected_thread(browser); |
841 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | ||
842 | } | ||
757 | 843 | ||
758 | if (es.reason == NEWT_EXIT_HOTKEY) { | 844 | switch (key) { |
759 | key = es.u.key; | 845 | case NEWT_KEY_TAB: |
846 | case NEWT_KEY_UNTAB: | ||
847 | /* | ||
848 | * Exit the browser, let hists__browser_tree | ||
849 | * go to the next or previous | ||
850 | */ | ||
851 | goto out_free_stack; | ||
852 | case 'a': | ||
853 | if (browser->selection == NULL || | ||
854 | browser->selection->sym == NULL || | ||
855 | browser->selection->map->dso->annotate_warned) | ||
856 | continue; | ||
857 | goto do_annotate; | ||
858 | case 'd': | ||
859 | goto zoom_dso; | ||
860 | case 't': | ||
861 | goto zoom_thread; | ||
862 | case NEWT_KEY_F1: | ||
863 | case 'h': | ||
864 | case '?': | ||
865 | ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n" | ||
866 | "<- Zoom out\n" | ||
867 | "a Annotate current symbol\n" | ||
868 | "h/?/F1 Show this window\n" | ||
869 | "C Collapse all callchains\n" | ||
870 | "E Expand all callchains\n" | ||
871 | "d Zoom into current DSO\n" | ||
872 | "t Zoom into current Thread\n" | ||
873 | "TAB/UNTAB Switch events\n" | ||
874 | "q/CTRL+C Exit browser"); | ||
875 | continue; | ||
876 | case NEWT_KEY_ENTER: | ||
877 | case NEWT_KEY_RIGHT: | ||
878 | /* menu */ | ||
879 | break; | ||
880 | case NEWT_KEY_LEFT: { | ||
881 | const void *top; | ||
760 | 882 | ||
761 | switch (key) { | 883 | if (pstack__empty(fstack)) { |
762 | case NEWT_KEY_F1: | ||
763 | goto do_help; | ||
764 | case NEWT_KEY_TAB: | ||
765 | case NEWT_KEY_UNTAB: | ||
766 | /* | 884 | /* |
767 | * Exit the browser, let hists__browser_tree | 885 | * Go back to the perf_evsel_menu__run or other user |
768 | * go to the next or previous | ||
769 | */ | 886 | */ |
770 | goto out_free_stack; | 887 | if (left_exits) |
771 | default:; | 888 | goto out_free_stack; |
772 | } | ||
773 | |||
774 | switch (key) { | ||
775 | case 'a': | ||
776 | if (browser->selection->map == NULL || | ||
777 | browser->selection->map->dso->annotate_warned) | ||
778 | continue; | ||
779 | goto do_annotate; | ||
780 | case 'd': | ||
781 | goto zoom_dso; | ||
782 | case 't': | ||
783 | goto zoom_thread; | ||
784 | case 'h': | ||
785 | case '?': | ||
786 | do_help: | ||
787 | ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n" | ||
788 | "<- Zoom out\n" | ||
789 | "a Annotate current symbol\n" | ||
790 | "h/?/F1 Show this window\n" | ||
791 | "d Zoom into current DSO\n" | ||
792 | "t Zoom into current Thread\n" | ||
793 | "q/CTRL+C Exit browser"); | ||
794 | continue; | 889 | continue; |
795 | default:; | ||
796 | } | 890 | } |
797 | if (is_exit_key(key)) { | 891 | top = pstack__pop(fstack); |
798 | if (key == NEWT_KEY_ESCAPE && | 892 | if (top == &dso_filter) |
799 | !ui__dialog_yesno("Do you really want to exit?")) | 893 | goto zoom_out_dso; |
800 | continue; | 894 | if (top == &thread_filter) |
801 | break; | 895 | goto zoom_out_thread; |
802 | } | 896 | continue; |
803 | 897 | } | |
804 | if (es.u.key == NEWT_KEY_LEFT) { | 898 | case NEWT_KEY_ESCAPE: |
805 | const void *top; | 899 | if (!left_exits && |
806 | 900 | !ui__dialog_yesno("Do you really want to exit?")) | |
807 | if (pstack__empty(fstack)) | ||
808 | continue; | ||
809 | top = pstack__pop(fstack); | ||
810 | if (top == &dso_filter) | ||
811 | goto zoom_out_dso; | ||
812 | if (top == &thread_filter) | ||
813 | goto zoom_out_thread; | ||
814 | continue; | 901 | continue; |
815 | } | 902 | /* Fall thru */ |
903 | default: | ||
904 | goto out_free_stack; | ||
816 | } | 905 | } |
817 | 906 | ||
818 | if (browser->selection->sym != NULL && | 907 | if (browser->selection != NULL && |
908 | browser->selection->sym != NULL && | ||
819 | !browser->selection->map->dso->annotate_warned && | 909 | !browser->selection->map->dso->annotate_warned && |
820 | asprintf(&options[nr_options], "Annotate %s", | 910 | asprintf(&options[nr_options], "Annotate %s", |
821 | browser->selection->sym->name) > 0) | 911 | browser->selection->sym->name) > 0) |
@@ -834,7 +924,8 @@ do_help: | |||
834 | (dso->kernel ? "the Kernel" : dso->short_name)) > 0) | 924 | (dso->kernel ? "the Kernel" : dso->short_name)) > 0) |
835 | zoom_dso = nr_options++; | 925 | zoom_dso = nr_options++; |
836 | 926 | ||
837 | if (browser->selection->map != NULL && | 927 | if (browser->selection != NULL && |
928 | browser->selection->map != NULL && | ||
838 | asprintf(&options[nr_options], "Browse map details") > 0) | 929 | asprintf(&options[nr_options], "Browse map details") > 0) |
839 | browse_map = nr_options++; | 930 | browse_map = nr_options++; |
840 | 931 | ||
@@ -854,19 +945,11 @@ do_help: | |||
854 | if (choice == annotate) { | 945 | if (choice == annotate) { |
855 | struct hist_entry *he; | 946 | struct hist_entry *he; |
856 | do_annotate: | 947 | do_annotate: |
857 | if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) { | ||
858 | browser->selection->map->dso->annotate_warned = 1; | ||
859 | ui_helpline__puts("No vmlinux file found, can't " | ||
860 | "annotate with just a " | ||
861 | "kallsyms file"); | ||
862 | continue; | ||
863 | } | ||
864 | |||
865 | he = hist_browser__selected_entry(browser); | 948 | he = hist_browser__selected_entry(browser); |
866 | if (he == NULL) | 949 | if (he == NULL) |
867 | continue; | 950 | continue; |
868 | 951 | ||
869 | hist_entry__tui_annotate(he); | 952 | hist_entry__tui_annotate(he, evsel->idx); |
870 | } else if (choice == browse_map) | 953 | } else if (choice == browse_map) |
871 | map__browse(browser->selection->map); | 954 | map__browse(browser->selection->map); |
872 | else if (choice == zoom_dso) { | 955 | else if (choice == zoom_dso) { |
@@ -885,8 +968,8 @@ zoom_out_dso: | |||
885 | pstack__push(fstack, &dso_filter); | 968 | pstack__push(fstack, &dso_filter); |
886 | } | 969 | } |
887 | hists__filter_by_dso(self, dso_filter); | 970 | hists__filter_by_dso(self, dso_filter); |
888 | hist_browser__title(msg, sizeof(msg), ev_name, | 971 | hists__browser_title(self, msg, sizeof(msg), ev_name, |
889 | dso_filter, thread_filter); | 972 | dso_filter, thread_filter); |
890 | hist_browser__reset(browser); | 973 | hist_browser__reset(browser); |
891 | } else if (choice == zoom_thread) { | 974 | } else if (choice == zoom_thread) { |
892 | zoom_thread: | 975 | zoom_thread: |
@@ -903,8 +986,8 @@ zoom_out_thread: | |||
903 | pstack__push(fstack, &thread_filter); | 986 | pstack__push(fstack, &thread_filter); |
904 | } | 987 | } |
905 | hists__filter_by_thread(self, thread_filter); | 988 | hists__filter_by_thread(self, thread_filter); |
906 | hist_browser__title(msg, sizeof(msg), ev_name, | 989 | hists__browser_title(self, msg, sizeof(msg), ev_name, |
907 | dso_filter, thread_filter); | 990 | dso_filter, thread_filter); |
908 | hist_browser__reset(browser); | 991 | hist_browser__reset(browser); |
909 | } | 992 | } |
910 | } | 993 | } |
@@ -915,34 +998,141 @@ out: | |||
915 | return key; | 998 | return key; |
916 | } | 999 | } |
917 | 1000 | ||
918 | int hists__tui_browse_tree(struct rb_root *self, const char *help) | 1001 | struct perf_evsel_menu { |
1002 | struct ui_browser b; | ||
1003 | struct perf_evsel *selection; | ||
1004 | }; | ||
1005 | |||
1006 | static void perf_evsel_menu__write(struct ui_browser *browser, | ||
1007 | void *entry, int row) | ||
919 | { | 1008 | { |
920 | struct rb_node *first = rb_first(self), *nd = first, *next; | 1009 | struct perf_evsel_menu *menu = container_of(browser, |
921 | int key = 0; | 1010 | struct perf_evsel_menu, b); |
1011 | struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node); | ||
1012 | bool current_entry = ui_browser__is_current_entry(browser, row); | ||
1013 | unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE]; | ||
1014 | const char *ev_name = event_name(evsel); | ||
1015 | char bf[256], unit; | ||
1016 | |||
1017 | ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : | ||
1018 | HE_COLORSET_NORMAL); | ||
1019 | |||
1020 | nr_events = convert_unit(nr_events, &unit); | ||
1021 | snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, | ||
1022 | unit, unit == ' ' ? "" : " ", ev_name); | ||
1023 | slsmg_write_nstring(bf, browser->width); | ||
922 | 1024 | ||
923 | while (nd) { | 1025 | if (current_entry) |
924 | struct hists *hists = rb_entry(nd, struct hists, rb_node); | 1026 | menu->selection = evsel; |
925 | const char *ev_name = __event_name(hists->type, hists->config); | 1027 | } |
926 | 1028 | ||
927 | key = hists__browse(hists, help, ev_name); | 1029 | static int perf_evsel_menu__run(struct perf_evsel_menu *menu, const char *help) |
1030 | { | ||
1031 | int exit_keys[] = { NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, }; | ||
1032 | struct perf_evlist *evlist = menu->b.priv; | ||
1033 | struct perf_evsel *pos; | ||
1034 | const char *ev_name, *title = "Available samples"; | ||
1035 | int key; | ||
1036 | |||
1037 | if (ui_browser__show(&menu->b, title, | ||
1038 | "ESC: exit, ENTER|->: Browse histograms") < 0) | ||
1039 | return -1; | ||
928 | 1040 | ||
929 | if (is_exit_key(key)) | 1041 | ui_browser__add_exit_keys(&menu->b, exit_keys); |
1042 | |||
1043 | while (1) { | ||
1044 | key = ui_browser__run(&menu->b); | ||
1045 | |||
1046 | switch (key) { | ||
1047 | case NEWT_KEY_RIGHT: | ||
1048 | case NEWT_KEY_ENTER: | ||
1049 | if (!menu->selection) | ||
1050 | continue; | ||
1051 | pos = menu->selection; | ||
1052 | browse_hists: | ||
1053 | ev_name = event_name(pos); | ||
1054 | key = perf_evsel__hists_browse(pos, help, ev_name, true); | ||
1055 | ui_browser__show_title(&menu->b, title); | ||
930 | break; | 1056 | break; |
1057 | case NEWT_KEY_LEFT: | ||
1058 | continue; | ||
1059 | case NEWT_KEY_ESCAPE: | ||
1060 | if (!ui__dialog_yesno("Do you really want to exit?")) | ||
1061 | continue; | ||
1062 | /* Fall thru */ | ||
1063 | default: | ||
1064 | goto out; | ||
1065 | } | ||
931 | 1066 | ||
932 | switch (key) { | 1067 | switch (key) { |
933 | case NEWT_KEY_TAB: | 1068 | case NEWT_KEY_TAB: |
934 | next = rb_next(nd); | 1069 | if (pos->node.next == &evlist->entries) |
935 | if (next) | 1070 | pos = list_entry(evlist->entries.next, struct perf_evsel, node); |
936 | nd = next; | 1071 | else |
937 | break; | 1072 | pos = list_entry(pos->node.next, struct perf_evsel, node); |
1073 | goto browse_hists; | ||
938 | case NEWT_KEY_UNTAB: | 1074 | case NEWT_KEY_UNTAB: |
939 | if (nd == first) | 1075 | if (pos->node.prev == &evlist->entries) |
940 | continue; | 1076 | pos = list_entry(evlist->entries.prev, struct perf_evsel, node); |
941 | nd = rb_prev(nd); | 1077 | else |
1078 | pos = list_entry(pos->node.prev, struct perf_evsel, node); | ||
1079 | goto browse_hists; | ||
1080 | case 'q': | ||
1081 | case CTRL('c'): | ||
1082 | goto out; | ||
942 | default: | 1083 | default: |
943 | break; | 1084 | break; |
944 | } | 1085 | } |
945 | } | 1086 | } |
946 | 1087 | ||
1088 | out: | ||
1089 | ui_browser__hide(&menu->b); | ||
947 | return key; | 1090 | return key; |
948 | } | 1091 | } |
1092 | |||
1093 | static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, | ||
1094 | const char *help) | ||
1095 | { | ||
1096 | struct perf_evsel *pos; | ||
1097 | struct perf_evsel_menu menu = { | ||
1098 | .b = { | ||
1099 | .entries = &evlist->entries, | ||
1100 | .refresh = ui_browser__list_head_refresh, | ||
1101 | .seek = ui_browser__list_head_seek, | ||
1102 | .write = perf_evsel_menu__write, | ||
1103 | .nr_entries = evlist->nr_entries, | ||
1104 | .priv = evlist, | ||
1105 | }, | ||
1106 | }; | ||
1107 | |||
1108 | ui_helpline__push("Press ESC to exit"); | ||
1109 | |||
1110 | list_for_each_entry(pos, &evlist->entries, node) { | ||
1111 | const char *ev_name = event_name(pos); | ||
1112 | size_t line_len = strlen(ev_name) + 7; | ||
1113 | |||
1114 | if (menu.b.width < line_len) | ||
1115 | menu.b.width = line_len; | ||
1116 | /* | ||
1117 | * Cache the evsel name, tracepoints have a _high_ cost per | ||
1118 | * event_name() call. | ||
1119 | */ | ||
1120 | if (pos->name == NULL) | ||
1121 | pos->name = strdup(ev_name); | ||
1122 | } | ||
1123 | |||
1124 | return perf_evsel_menu__run(&menu, help); | ||
1125 | } | ||
1126 | |||
1127 | int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help) | ||
1128 | { | ||
1129 | |||
1130 | if (evlist->nr_entries == 1) { | ||
1131 | struct perf_evsel *first = list_entry(evlist->entries.next, | ||
1132 | struct perf_evsel, node); | ||
1133 | const char *ev_name = event_name(first); | ||
1134 | return perf_evsel__hists_browse(first, help, ev_name, false); | ||
1135 | } | ||
1136 | |||
1137 | return __perf_evlist__tui_browse_hists(evlist, help); | ||
1138 | } | ||