2 #include "../libslang.h"
5 #include <linux/rbtree.h>
7 #include "../../util/evsel.h"
8 #include "../../util/evlist.h"
9 #include "../../util/hist.h"
10 #include "../../util/pstack.h"
11 #include "../../util/sort.h"
12 #include "../../util/util.h"
13 #include "../../arch/common.h"
15 #include "../browser.h"
16 #include "../helpline.h"
24 struct hist_entry *he_selection;
25 struct map_symbol *selection;
29 u64 nr_non_filtered_entries;
32 extern void hist_browser__init_hpp(void);
34 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
36 static void hist_browser__update_nr_entries(struct hist_browser *hb);
38 static bool hist_browser__has_filter(struct hist_browser *hb)
40 return hists__has_filter(hb->hists) || hb->min_pcnt;
43 static void hist_browser__refresh_dimensions(struct hist_browser *browser)
45 /* 3 == +/- toggle symbol before actual hist_entry rendering */
46 browser->b.width = 3 + (hists__sort_list_width(browser->hists) +
50 static void hist_browser__reset(struct hist_browser *browser)
52 hist_browser__update_nr_entries(browser);
53 browser->b.nr_entries = browser->nr_non_filtered_entries;
54 hist_browser__refresh_dimensions(browser);
55 ui_browser__reset_index(&browser->b);
58 static char tree__folded_sign(bool unfolded)
60 return unfolded ? '-' : '+';
63 static char map_symbol__folded(const struct map_symbol *ms)
65 return ms->has_children ? tree__folded_sign(ms->unfolded) : ' ';
68 static char hist_entry__folded(const struct hist_entry *he)
70 return map_symbol__folded(&he->ms);
73 static char callchain_list__folded(const struct callchain_list *cl)
75 return map_symbol__folded(&cl->ms);
78 static void map_symbol__set_folding(struct map_symbol *ms, bool unfold)
80 ms->unfolded = unfold ? ms->has_children : false;
83 static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
88 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
89 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
90 struct callchain_list *chain;
91 char folded_sign = ' '; /* No children */
93 list_for_each_entry(chain, &child->val, list) {
95 /* We need this because we may not have children */
96 folded_sign = callchain_list__folded(chain);
97 if (folded_sign == '+')
101 if (folded_sign == '-') /* Have children and they're unfolded */
102 n += callchain_node__count_rows_rb_tree(child);
108 static int callchain_node__count_rows(struct callchain_node *node)
110 struct callchain_list *chain;
111 bool unfolded = false;
114 list_for_each_entry(chain, &node->val, list) {
116 unfolded = chain->ms.unfolded;
120 n += callchain_node__count_rows_rb_tree(node);
125 static int callchain__count_rows(struct rb_root *chain)
130 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
131 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
132 n += callchain_node__count_rows(node);
138 static bool map_symbol__toggle_fold(struct map_symbol *ms)
143 if (!ms->has_children)
146 ms->unfolded = !ms->unfolded;
150 static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
152 struct rb_node *nd = rb_first(&node->rb_root);
154 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
155 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
156 struct callchain_list *chain;
159 list_for_each_entry(chain, &child->val, list) {
162 chain->ms.has_children = chain->list.next != &child->val ||
163 !RB_EMPTY_ROOT(&child->rb_root);
165 chain->ms.has_children = chain->list.next == &child->val &&
166 !RB_EMPTY_ROOT(&child->rb_root);
169 callchain_node__init_have_children_rb_tree(child);
173 static void callchain_node__init_have_children(struct callchain_node *node)
175 struct callchain_list *chain;
177 list_for_each_entry(chain, &node->val, list)
178 chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root);
180 callchain_node__init_have_children_rb_tree(node);
183 static void callchain__init_have_children(struct rb_root *root)
187 for (nd = rb_first(root); nd; nd = rb_next(nd)) {
188 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
189 callchain_node__init_have_children(node);
193 static void hist_entry__init_have_children(struct hist_entry *he)
195 if (!he->init_have_children) {
196 he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
197 callchain__init_have_children(&he->sorted_chain);
198 he->init_have_children = true;
202 static bool hist_browser__toggle_fold(struct hist_browser *browser)
204 if (map_symbol__toggle_fold(browser->selection)) {
205 struct hist_entry *he = browser->he_selection;
207 hist_entry__init_have_children(he);
208 browser->hists->nr_entries -= he->nr_rows;
211 he->nr_rows = callchain__count_rows(&he->sorted_chain);
214 browser->hists->nr_entries += he->nr_rows;
215 browser->b.nr_entries = browser->hists->nr_entries;
220 /* If it doesn't have children, no toggling performed */
224 static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
229 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
230 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
231 struct callchain_list *chain;
232 bool has_children = false;
234 list_for_each_entry(chain, &child->val, list) {
236 map_symbol__set_folding(&chain->ms, unfold);
237 has_children = chain->ms.has_children;
241 n += callchain_node__set_folding_rb_tree(child, unfold);
247 static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
249 struct callchain_list *chain;
250 bool has_children = false;
253 list_for_each_entry(chain, &node->val, list) {
255 map_symbol__set_folding(&chain->ms, unfold);
256 has_children = chain->ms.has_children;
260 n += callchain_node__set_folding_rb_tree(node, unfold);
265 static int callchain__set_folding(struct rb_root *chain, bool unfold)
270 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
271 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
272 n += callchain_node__set_folding(node, unfold);
278 static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
280 hist_entry__init_have_children(he);
281 map_symbol__set_folding(&he->ms, unfold);
283 if (he->ms.has_children) {
284 int n = callchain__set_folding(&he->sorted_chain, unfold);
285 he->nr_rows = unfold ? n : 0;
290 static void hists__set_folding(struct hists *hists, bool unfold)
294 hists->nr_entries = 0;
296 for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
297 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
298 hist_entry__set_folding(he, unfold);
299 hists->nr_entries += 1 + he->nr_rows;
303 static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
305 hists__set_folding(browser->hists, unfold);
306 browser->b.nr_entries = browser->hists->nr_entries;
307 /* Go to the start, we may be way after valid entries after a collapse */
308 ui_browser__reset_index(&browser->b);
311 static void ui_browser__warn_lost_events(struct ui_browser *browser)
313 ui_browser__warning(browser, 4,
314 "Events are being lost, check IO/CPU overload!\n\n"
315 "You may want to run 'perf' using a RT scheduler policy:\n\n"
316 " perf top -r 80\n\n"
317 "Or reduce the sampling frequency.");
320 static int hist_browser__run(struct hist_browser *browser, const char *ev_name,
321 struct hist_browser_timer *hbt)
325 int delay_secs = hbt ? hbt->refresh : 0;
327 browser->b.entries = &browser->hists->entries;
328 if (hist_browser__has_filter(browser))
329 browser->b.nr_entries = browser->nr_non_filtered_entries;
331 browser->b.nr_entries = browser->hists->nr_entries;
333 hist_browser__refresh_dimensions(browser);
334 hists__browser_title(browser->hists, title, sizeof(title), ev_name);
336 if (ui_browser__show(&browser->b, title,
337 "Press '?' for help on key bindings") < 0)
341 key = ui_browser__run(&browser->b, delay_secs);
346 hbt->timer(hbt->arg);
348 if (hist_browser__has_filter(browser)) {
349 hist_browser__update_nr_entries(browser);
350 nr_entries = browser->nr_non_filtered_entries;
352 nr_entries = browser->hists->nr_entries;
355 ui_browser__update_nr_entries(&browser->b, nr_entries);
357 if (browser->hists->stats.nr_lost_warned !=
358 browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
359 browser->hists->stats.nr_lost_warned =
360 browser->hists->stats.nr_events[PERF_RECORD_LOST];
361 ui_browser__warn_lost_events(&browser->b);
364 hists__browser_title(browser->hists, title, sizeof(title), ev_name);
365 ui_browser__show_title(&browser->b, title);
368 case 'D': { /* Debug */
370 struct hist_entry *h = rb_entry(browser->b.top,
371 struct hist_entry, rb_node);
373 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
374 seq++, browser->b.nr_entries,
375 browser->hists->nr_entries,
379 h->row_offset, h->nr_rows);
383 /* Collapse the whole world. */
384 hist_browser__set_folding(browser, false);
387 /* Expand the whole world. */
388 hist_browser__set_folding(browser, true);
391 if (hist_browser__toggle_fold(browser))
399 ui_browser__hide(&browser->b);
403 static char *callchain_list__sym_name(struct callchain_list *cl,
404 char *bf, size_t bfsize, bool show_dso)
409 printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
411 printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
414 scnprintf(bf + printed, bfsize - printed, " %s",
415 cl->ms.map ? cl->ms.map->dso->short_name : "unknown");
420 #define LEVEL_OFFSET_STEP 3
422 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browser,
423 struct callchain_node *chain_node,
424 u64 total, int level,
427 bool *is_current_entry)
429 struct rb_node *node;
430 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
431 u64 new_total, remaining;
433 if (callchain_param.mode == CHAIN_GRAPH_REL)
434 new_total = chain_node->children_hit;
438 remaining = new_total;
439 node = rb_first(&chain_node->rb_root);
441 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
442 struct rb_node *next = rb_next(node);
443 u64 cumul = callchain_cumul_hits(child);
444 struct callchain_list *chain;
445 char folded_sign = ' ';
447 int extra_offset = 0;
451 list_for_each_entry(chain, &child->val, list) {
452 char bf[1024], *alloc_str;
455 bool was_first = first;
460 extra_offset = LEVEL_OFFSET_STEP;
462 folded_sign = callchain_list__folded(chain);
463 if (*row_offset != 0) {
469 str = callchain_list__sym_name(chain, bf, sizeof(bf),
472 double percent = cumul * 100.0 / new_total;
474 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
475 str = "Not enough memory!";
480 color = HE_COLORSET_NORMAL;
481 width = browser->b.width - (offset + extra_offset + 2);
482 if (ui_browser__is_current_entry(&browser->b, row)) {
483 browser->selection = &chain->ms;
484 color = HE_COLORSET_SELECTED;
485 *is_current_entry = true;
488 ui_browser__set_color(&browser->b, color);
489 ui_browser__gotorc(&browser->b, row, 0);
490 slsmg_write_nstring(" ", offset + extra_offset);
491 slsmg_printf("%c ", folded_sign);
492 slsmg_write_nstring(str, width);
495 if (++row == browser->b.height)
498 if (folded_sign == '+')
502 if (folded_sign == '-') {
503 const int new_level = level + (extra_offset ? 2 : 1);
504 row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total,
505 new_level, row, row_offset,
508 if (row == browser->b.height)
513 return row - first_row;
516 static int hist_browser__show_callchain_node(struct hist_browser *browser,
517 struct callchain_node *node,
518 int level, unsigned short row,
520 bool *is_current_entry)
522 struct callchain_list *chain;
524 offset = level * LEVEL_OFFSET_STEP,
525 width = browser->b.width - offset;
526 char folded_sign = ' ';
528 list_for_each_entry(chain, &node->val, list) {
532 folded_sign = callchain_list__folded(chain);
534 if (*row_offset != 0) {
539 color = HE_COLORSET_NORMAL;
540 if (ui_browser__is_current_entry(&browser->b, row)) {
541 browser->selection = &chain->ms;
542 color = HE_COLORSET_SELECTED;
543 *is_current_entry = true;
546 s = callchain_list__sym_name(chain, bf, sizeof(bf),
548 ui_browser__gotorc(&browser->b, row, 0);
549 ui_browser__set_color(&browser->b, color);
550 slsmg_write_nstring(" ", offset);
551 slsmg_printf("%c ", folded_sign);
552 slsmg_write_nstring(s, width - 2);
554 if (++row == browser->b.height)
558 if (folded_sign == '-')
559 row += hist_browser__show_callchain_node_rb_tree(browser, node,
560 browser->hists->stats.total_period,
565 return row - first_row;
568 static int hist_browser__show_callchain(struct hist_browser *browser,
569 struct rb_root *chain,
570 int level, unsigned short row,
572 bool *is_current_entry)
577 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
578 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
580 row += hist_browser__show_callchain_node(browser, node, level,
583 if (row == browser->b.height)
587 return row - first_row;
591 struct ui_browser *b;
596 static int __hpp__overhead_callback(struct perf_hpp *hpp, bool front)
598 struct hpp_arg *arg = hpp->ptr;
600 if (arg->current_entry && arg->b->navkeypressed)
601 ui_browser__set_color(arg->b, HE_COLORSET_SELECTED);
603 ui_browser__set_color(arg->b, HE_COLORSET_NORMAL);
606 if (!symbol_conf.use_callchain)
609 slsmg_printf("%c ", arg->folded_sign);
616 static int __hpp__color_callback(struct perf_hpp *hpp, bool front __maybe_unused)
618 struct hpp_arg *arg = hpp->ptr;
620 if (!arg->current_entry || !arg->b->navkeypressed)
621 ui_browser__set_color(arg->b, HE_COLORSET_NORMAL);
625 static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
627 struct hpp_arg *arg = hpp->ptr;
633 percent = va_arg(args, double);
636 ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
638 ret = scnprintf(hpp->buf, hpp->size, fmt, percent);
639 slsmg_printf("%s", hpp->buf);
641 advance_hpp(hpp, ret);
645 #define __HPP_COLOR_PERCENT_FN(_type, _field, _cb) \
646 static u64 __hpp_get_##_field(struct hist_entry *he) \
648 return he->stat._field; \
652 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\
653 struct perf_hpp *hpp, \
654 struct hist_entry *he) \
656 return __hpp__fmt(hpp, he, __hpp_get_##_field, _cb, " %6.2f%%", \
657 __hpp__slsmg_color_printf, true); \
660 __HPP_COLOR_PERCENT_FN(overhead, period, __hpp__overhead_callback)
661 __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys, __hpp__color_callback)
662 __HPP_COLOR_PERCENT_FN(overhead_us, period_us, __hpp__color_callback)
663 __HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys, __hpp__color_callback)
664 __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us, __hpp__color_callback)
666 #undef __HPP_COLOR_PERCENT_FN
668 void hist_browser__init_hpp(void)
672 perf_hpp__format[PERF_HPP__OVERHEAD].color =
673 hist_browser__hpp_color_overhead;
674 perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
675 hist_browser__hpp_color_overhead_sys;
676 perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
677 hist_browser__hpp_color_overhead_us;
678 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
679 hist_browser__hpp_color_overhead_guest_sys;
680 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
681 hist_browser__hpp_color_overhead_guest_us;
684 static int hist_browser__show_entry(struct hist_browser *browser,
685 struct hist_entry *entry,
690 int width = browser->b.width;
691 char folded_sign = ' ';
692 bool current_entry = ui_browser__is_current_entry(&browser->b, row);
693 off_t row_offset = entry->row_offset;
695 struct perf_hpp_fmt *fmt;
698 browser->he_selection = entry;
699 browser->selection = &entry->ms;
702 if (symbol_conf.use_callchain) {
703 hist_entry__init_have_children(entry);
704 folded_sign = hist_entry__folded(entry);
707 if (row_offset == 0) {
708 struct hpp_arg arg = {
710 .folded_sign = folded_sign,
711 .current_entry = current_entry,
713 struct perf_hpp hpp = {
719 ui_browser__gotorc(&browser->b, row, 0);
721 perf_hpp__for_each_format(fmt) {
729 width -= fmt->color(fmt, &hpp, entry);
731 width -= fmt->entry(fmt, &hpp, entry);
732 slsmg_printf("%s", s);
736 /* The scroll bar isn't being used */
737 if (!browser->b.navkeypressed)
740 hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists);
741 slsmg_write_nstring(s, width);
747 if (folded_sign == '-' && row != browser->b.height) {
748 printed += hist_browser__show_callchain(browser, &entry->sorted_chain,
752 browser->he_selection = entry;
758 static void ui_browser__hists_init_top(struct ui_browser *browser)
760 if (browser->top == NULL) {
761 struct hist_browser *hb;
763 hb = container_of(browser, struct hist_browser, b);
764 browser->top = rb_first(&hb->hists->entries);
768 static unsigned int hist_browser__refresh(struct ui_browser *browser)
772 struct hist_browser *hb = container_of(browser, struct hist_browser, b);
774 ui_browser__hists_init_top(browser);
776 for (nd = browser->top; nd; nd = rb_next(nd)) {
777 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
778 u64 total = hists__total_period(h->hists);
785 percent = h->stat.period * 100.0 / total;
787 if (percent < hb->min_pcnt)
790 row += hist_browser__show_entry(hb, h, row);
791 if (row == browser->height)
798 static struct rb_node *hists__filter_entries(struct rb_node *nd,
803 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
804 u64 total = hists__total_period(hists);
808 percent = h->stat.period * 100.0 / total;
810 if (percent < min_pcnt)
822 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
827 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
828 u64 total = hists__total_period(hists);
832 percent = h->stat.period * 100.0 / total;
834 if (!h->filtered && percent >= min_pcnt)
843 static void ui_browser__hists_seek(struct ui_browser *browser,
844 off_t offset, int whence)
846 struct hist_entry *h;
849 struct hist_browser *hb;
851 hb = container_of(browser, struct hist_browser, b);
853 if (browser->nr_entries == 0)
856 ui_browser__hists_init_top(browser);
860 nd = hists__filter_entries(rb_first(browser->entries),
861 hb->hists, hb->min_pcnt);
867 nd = hists__filter_prev_entries(rb_last(browser->entries),
868 hb->hists, hb->min_pcnt);
876 * Moves not relative to the first visible entry invalidates its
879 h = rb_entry(browser->top, struct hist_entry, rb_node);
883 * Here we have to check if nd is expanded (+), if it is we can't go
884 * the next top level hist_entry, instead we must compute an offset of
885 * what _not_ to show and not change the first visible entry.
887 * This offset increments when we are going from top to bottom and
888 * decreases when we're going from bottom to top.
890 * As we don't have backpointers to the top level in the callchains
891 * structure, we need to always print the whole hist_entry callchain,
892 * skipping the first ones that are before the first visible entry
893 * and stop when we printed enough lines to fill the screen.
898 h = rb_entry(nd, struct hist_entry, rb_node);
899 if (h->ms.unfolded) {
900 u16 remaining = h->nr_rows - h->row_offset;
901 if (offset > remaining) {
905 h->row_offset += offset;
911 nd = hists__filter_entries(rb_next(nd), hb->hists,
917 } while (offset != 0);
918 } else if (offset < 0) {
920 h = rb_entry(nd, struct hist_entry, rb_node);
921 if (h->ms.unfolded) {
923 if (-offset > h->row_offset) {
924 offset += h->row_offset;
927 h->row_offset += offset;
933 if (-offset > h->nr_rows) {
934 offset += h->nr_rows;
937 h->row_offset = h->nr_rows + offset;
945 nd = hists__filter_prev_entries(rb_prev(nd), hb->hists,
953 * Last unfiltered hist_entry, check if it is
954 * unfolded, if it is then we should have
955 * row_offset at its last entry.
957 h = rb_entry(nd, struct hist_entry, rb_node);
959 h->row_offset = h->nr_rows;
966 h = rb_entry(nd, struct hist_entry, rb_node);
971 static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser,
972 struct callchain_node *chain_node,
973 u64 total, int level,
976 struct rb_node *node;
977 int offset = level * LEVEL_OFFSET_STEP;
978 u64 new_total, remaining;
981 if (callchain_param.mode == CHAIN_GRAPH_REL)
982 new_total = chain_node->children_hit;
986 remaining = new_total;
987 node = rb_first(&chain_node->rb_root);
989 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
990 struct rb_node *next = rb_next(node);
991 u64 cumul = callchain_cumul_hits(child);
992 struct callchain_list *chain;
993 char folded_sign = ' ';
995 int extra_offset = 0;
999 list_for_each_entry(chain, &child->val, list) {
1000 char bf[1024], *alloc_str;
1002 bool was_first = first;
1007 extra_offset = LEVEL_OFFSET_STEP;
1009 folded_sign = callchain_list__folded(chain);
1012 str = callchain_list__sym_name(chain, bf, sizeof(bf),
1015 double percent = cumul * 100.0 / new_total;
1017 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
1018 str = "Not enough memory!";
1023 printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str);
1025 if (folded_sign == '+')
1029 if (folded_sign == '-') {
1030 const int new_level = level + (extra_offset ? 2 : 1);
1031 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total,
1041 static int hist_browser__fprintf_callchain_node(struct hist_browser *browser,
1042 struct callchain_node *node,
1043 int level, FILE *fp)
1045 struct callchain_list *chain;
1046 int offset = level * LEVEL_OFFSET_STEP;
1047 char folded_sign = ' ';
1050 list_for_each_entry(chain, &node->val, list) {
1053 folded_sign = callchain_list__folded(chain);
1054 s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso);
1055 printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s);
1058 if (folded_sign == '-')
1059 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node,
1060 browser->hists->stats.total_period,
1065 static int hist_browser__fprintf_callchain(struct hist_browser *browser,
1066 struct rb_root *chain, int level, FILE *fp)
1071 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
1072 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
1074 printed += hist_browser__fprintf_callchain_node(browser, node, level, fp);
1080 static int hist_browser__fprintf_entry(struct hist_browser *browser,
1081 struct hist_entry *he, FILE *fp)
1086 char folded_sign = ' ';
1088 if (symbol_conf.use_callchain)
1089 folded_sign = hist_entry__folded(he);
1091 hist_entry__sort_snprintf(he, s, sizeof(s), browser->hists);
1092 percent = (he->stat.period * 100.0) / browser->hists->stats.total_period;
1094 if (symbol_conf.use_callchain)
1095 printed += fprintf(fp, "%c ", folded_sign);
1097 printed += fprintf(fp, " %5.2f%%", percent);
1099 if (symbol_conf.show_nr_samples)
1100 printed += fprintf(fp, " %11u", he->stat.nr_events);
1102 if (symbol_conf.show_total_period)
1103 printed += fprintf(fp, " %12" PRIu64, he->stat.period);
1105 printed += fprintf(fp, "%s\n", rtrim(s));
1107 if (folded_sign == '-')
1108 printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp);
1113 static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
1115 struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
1121 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1123 printed += hist_browser__fprintf_entry(browser, h, fp);
1124 nd = hists__filter_entries(rb_next(nd), browser->hists,
1131 static int hist_browser__dump(struct hist_browser *browser)
1137 scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
1138 if (access(filename, F_OK))
1141 * XXX: Just an arbitrary lazy upper limit
1143 if (++browser->print_seq == 8192) {
1144 ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
1149 fp = fopen(filename, "w");
1152 const char *err = strerror_r(errno, bf, sizeof(bf));
1153 ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
1157 ++browser->print_seq;
1158 hist_browser__fprintf(browser, fp);
1160 ui_helpline__fpush("%s written!", filename);
1165 static struct hist_browser *hist_browser__new(struct hists *hists)
1167 struct hist_browser *browser = zalloc(sizeof(*browser));
1170 browser->hists = hists;
1171 browser->b.refresh = hist_browser__refresh;
1172 browser->b.seek = ui_browser__hists_seek;
1173 browser->b.use_navkeypressed = true;
1179 static void hist_browser__delete(struct hist_browser *browser)
1184 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
1186 return browser->he_selection;
1189 static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
1191 return browser->he_selection->thread;
1194 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
1195 const char *ev_name)
1199 const struct dso *dso = hists->dso_filter;
1200 const struct thread *thread = hists->thread_filter;
1201 unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
1202 u64 nr_events = hists->stats.total_period;
1203 struct perf_evsel *evsel = hists_to_evsel(hists);
1205 size_t buflen = sizeof(buf);
1207 if (symbol_conf.filter_relative) {
1208 nr_samples = hists->stats.nr_non_filtered_samples;
1209 nr_events = hists->stats.total_non_filtered_period;
1212 if (perf_evsel__is_group_event(evsel)) {
1213 struct perf_evsel *pos;
1215 perf_evsel__group_desc(evsel, buf, buflen);
1218 for_each_group_member(pos, evsel) {
1219 if (symbol_conf.filter_relative) {
1220 nr_samples += pos->hists.stats.nr_non_filtered_samples;
1221 nr_events += pos->hists.stats.total_non_filtered_period;
1223 nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1224 nr_events += pos->hists.stats.total_period;
1229 nr_samples = convert_unit(nr_samples, &unit);
1230 printed = scnprintf(bf, size,
1231 "Samples: %lu%c of event '%s', Event count (approx.): %lu",
1232 nr_samples, unit, ev_name, nr_events);
1235 if (hists->uid_filter_str)
1236 printed += snprintf(bf + printed, size - printed,
1237 ", UID: %s", hists->uid_filter_str);
1239 printed += scnprintf(bf + printed, size - printed,
1241 (thread->comm_set ? thread__comm_str(thread) : ""),
1244 printed += scnprintf(bf + printed, size - printed,
1245 ", DSO: %s", dso->short_name);
1249 static inline void free_popup_options(char **options, int n)
1253 for (i = 0; i < n; ++i)
1257 /* Check whether the browser is for 'top' or 'report' */
1258 static inline bool is_report_browser(void *timer)
1260 return timer == NULL;
1264 * Only runtime switching of perf data file will make "input_name" point
1265 * to a malloced buffer. So add "is_input_name_malloced" flag to decide
1266 * whether we need to call free() for current "input_name" during the switch.
1268 static bool is_input_name_malloced = false;
1270 static int switch_data_file(void)
1272 char *pwd, *options[32], *abs_path[32], *tmp;
1274 int nr_options = 0, choice = -1, ret = -1;
1275 struct dirent *dent;
1277 pwd = getenv("PWD");
1281 pwd_dir = opendir(pwd);
1285 memset(options, 0, sizeof(options));
1286 memset(options, 0, sizeof(abs_path));
1288 while ((dent = readdir(pwd_dir))) {
1289 char path[PATH_MAX];
1291 char *name = dent->d_name;
1294 if (!(dent->d_type == DT_REG))
1297 snprintf(path, sizeof(path), "%s/%s", pwd, name);
1299 file = fopen(path, "r");
1303 if (fread(&magic, 1, 8, file) < 8)
1304 goto close_file_and_continue;
1306 if (is_perf_magic(magic)) {
1307 options[nr_options] = strdup(name);
1308 if (!options[nr_options])
1309 goto close_file_and_continue;
1311 abs_path[nr_options] = strdup(path);
1312 if (!abs_path[nr_options]) {
1313 zfree(&options[nr_options]);
1314 ui__warning("Can't search all data files due to memory shortage.\n");
1322 close_file_and_continue:
1324 if (nr_options >= 32) {
1325 ui__warning("Too many perf data files in PWD!\n"
1326 "Only the first 32 files will be listed.\n");
1333 choice = ui__popup_menu(nr_options, options);
1334 if (choice < nr_options && choice >= 0) {
1335 tmp = strdup(abs_path[choice]);
1337 if (is_input_name_malloced)
1338 free((void *)input_name);
1340 is_input_name_malloced = true;
1343 ui__warning("Data switch failed due to memory shortage!\n");
1347 free_popup_options(options, nr_options);
1348 free_popup_options(abs_path, nr_options);
1352 static void hist_browser__update_nr_entries(struct hist_browser *hb)
1355 struct rb_node *nd = rb_first(&hb->hists->entries);
1357 if (hb->min_pcnt == 0) {
1358 hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
1362 while ((nd = hists__filter_entries(nd, hb->hists,
1363 hb->min_pcnt)) != NULL) {
1368 hb->nr_non_filtered_entries = nr_entries;
1371 static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
1372 const char *helpline, const char *ev_name,
1374 struct hist_browser_timer *hbt,
1376 struct perf_session_env *env)
1378 struct hists *hists = &evsel->hists;
1379 struct hist_browser *browser = hist_browser__new(hists);
1380 struct branch_info *bi;
1381 struct pstack *fstack;
1386 char script_opt[64];
1387 int delay_secs = hbt ? hbt->refresh : 0;
1389 #define HIST_BROWSER_HELP_COMMON \
1390 "h/?/F1 Show this window\n" \
1392 "PGDN/SPACE Navigate\n" \
1393 "q/ESC/CTRL+C Exit browser\n\n" \
1394 "For multiple event sessions:\n\n" \
1395 "TAB/UNTAB Switch events\n\n" \
1396 "For symbolic views (--sort has sym):\n\n" \
1397 "-> Zoom into DSO/Threads & Annotate current symbol\n" \
1399 "a Annotate current symbol\n" \
1400 "C Collapse all callchains\n" \
1401 "d Zoom into current DSO\n" \
1402 "E Expand all callchains\n" \
1403 "F Toggle percentage of filtered entries\n" \
1405 /* help messages are sorted by lexical order of the hotkey */
1406 const char report_help[] = HIST_BROWSER_HELP_COMMON
1407 "i Show header information\n"
1408 "P Print histograms to perf.hist.N\n"
1409 "r Run available scripts\n"
1410 "s Switch to another data file in PWD\n"
1411 "t Zoom into current Thread\n"
1412 "V Verbose (DSO names in callchains, etc)\n"
1413 "/ Filter symbol by name";
1414 const char top_help[] = HIST_BROWSER_HELP_COMMON
1415 "P Print histograms to perf.hist.N\n"
1416 "t Zoom into current Thread\n"
1417 "V Verbose (DSO names in callchains, etc)\n"
1418 "/ Filter symbol by name";
1420 if (browser == NULL)
1424 browser->min_pcnt = min_pcnt;
1425 hist_browser__update_nr_entries(browser);
1428 fstack = pstack__new(2);
1432 ui_helpline__push(helpline);
1434 memset(options, 0, sizeof(options));
1437 const struct thread *thread = NULL;
1438 const struct dso *dso = NULL;
1440 annotate = -2, zoom_dso = -2, zoom_thread = -2,
1441 annotate_f = -2, annotate_t = -2, browse_map = -2;
1442 int scripts_comm = -2, scripts_symbol = -2,
1443 scripts_all = -2, switch_data = -2;
1447 key = hist_browser__run(browser, ev_name, hbt);
1449 if (browser->he_selection != NULL) {
1450 thread = hist_browser__selected_thread(browser);
1451 dso = browser->selection->map ? browser->selection->map->dso : NULL;
1459 * Exit the browser, let hists__browser_tree
1460 * go to the next or previous
1462 goto out_free_stack;
1464 if (!sort__has_sym) {
1465 ui_browser__warning(&browser->b, delay_secs * 2,
1466 "Annotation is only available for symbolic views, "
1467 "include \"sym*\" in --sort to use it.");
1471 if (browser->selection == NULL ||
1472 browser->selection->sym == NULL ||
1473 browser->selection->map->dso->annotate_warned)
1477 hist_browser__dump(browser);
1482 browser->show_dso = !browser->show_dso;
1487 if (ui_browser__input_window("Symbol to show",
1488 "Please enter the name of symbol you want to see",
1489 buf, "ENTER: OK, ESC: Cancel",
1490 delay_secs * 2) == K_ENTER) {
1491 hists->symbol_filter_str = *buf ? buf : NULL;
1492 hists__filter_by_symbol(hists);
1493 hist_browser__reset(browser);
1497 if (is_report_browser(hbt))
1501 if (is_report_browser(hbt))
1502 goto do_data_switch;
1505 /* env->arch is NULL for live-mode (i.e. perf top) */
1507 tui__header_window(env);
1510 symbol_conf.filter_relative ^= 1;
1515 ui_browser__help_window(&browser->b,
1516 is_report_browser(hbt) ? report_help : top_help);
1525 if (pstack__empty(fstack)) {
1527 * Go back to the perf_evsel_menu__run or other user
1530 goto out_free_stack;
1533 top = pstack__pop(fstack);
1534 if (top == &browser->hists->dso_filter)
1536 if (top == &browser->hists->thread_filter)
1537 goto zoom_out_thread;
1542 !ui_browser__dialog_yesno(&browser->b,
1543 "Do you really want to exit?"))
1548 goto out_free_stack;
1554 goto add_exit_option;
1556 if (sort__mode == SORT_MODE__BRANCH) {
1557 bi = browser->he_selection->branch_info;
1558 if (browser->selection != NULL &&
1560 bi->from.sym != NULL &&
1561 !bi->from.map->dso->annotate_warned &&
1562 asprintf(&options[nr_options], "Annotate %s",
1563 bi->from.sym->name) > 0)
1564 annotate_f = nr_options++;
1566 if (browser->selection != NULL &&
1568 bi->to.sym != NULL &&
1569 !bi->to.map->dso->annotate_warned &&
1570 (bi->to.sym != bi->from.sym ||
1571 bi->to.map->dso != bi->from.map->dso) &&
1572 asprintf(&options[nr_options], "Annotate %s",
1573 bi->to.sym->name) > 0)
1574 annotate_t = nr_options++;
1577 if (browser->selection != NULL &&
1578 browser->selection->sym != NULL &&
1579 !browser->selection->map->dso->annotate_warned &&
1580 asprintf(&options[nr_options], "Annotate %s",
1581 browser->selection->sym->name) > 0)
1582 annotate = nr_options++;
1585 if (thread != NULL &&
1586 asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
1587 (browser->hists->thread_filter ? "out of" : "into"),
1588 (thread->comm_set ? thread__comm_str(thread) : ""),
1590 zoom_thread = nr_options++;
1593 asprintf(&options[nr_options], "Zoom %s %s DSO",
1594 (browser->hists->dso_filter ? "out of" : "into"),
1595 (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
1596 zoom_dso = nr_options++;
1598 if (browser->selection != NULL &&
1599 browser->selection->map != NULL &&
1600 asprintf(&options[nr_options], "Browse map details") > 0)
1601 browse_map = nr_options++;
1603 /* perf script support */
1604 if (browser->he_selection) {
1607 if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]",
1608 thread__comm_str(browser->he_selection->thread)) > 0)
1609 scripts_comm = nr_options++;
1611 sym = browser->he_selection->ms.sym;
1612 if (sym && sym->namelen &&
1613 asprintf(&options[nr_options], "Run scripts for samples of symbol [%s]",
1615 scripts_symbol = nr_options++;
1618 if (asprintf(&options[nr_options], "Run scripts for all samples") > 0)
1619 scripts_all = nr_options++;
1621 if (is_report_browser(hbt) && asprintf(&options[nr_options],
1622 "Switch to another data file in PWD") > 0)
1623 switch_data = nr_options++;
1625 options[nr_options++] = (char *)"Exit";
1627 choice = ui__popup_menu(nr_options, options);
1629 if (choice == nr_options - 1)
1633 free_popup_options(options, nr_options - 1);
1637 if (choice == annotate || choice == annotate_t || choice == annotate_f) {
1638 struct hist_entry *he;
1641 if (!objdump_path && perf_session_env__lookup_objdump(env))
1644 he = hist_browser__selected_entry(browser);
1649 * we stash the branch_info symbol + map into the
1650 * the ms so we don't have to rewrite all the annotation
1651 * code to use branch_info.
1652 * in branch mode, the ms struct is not used
1654 if (choice == annotate_f) {
1655 he->ms.sym = he->branch_info->from.sym;
1656 he->ms.map = he->branch_info->from.map;
1657 } else if (choice == annotate_t) {
1658 he->ms.sym = he->branch_info->to.sym;
1659 he->ms.map = he->branch_info->to.map;
1663 * Don't let this be freed, say, by hists__decay_entry.
1666 err = hist_entry__tui_annotate(he, evsel, hbt);
1669 * offer option to annotate the other branch source or target
1670 * (if they exists) when returning from annotate
1672 if ((err == 'q' || err == CTRL('c'))
1673 && annotate_t != -2 && annotate_f != -2)
1674 goto retry_popup_menu;
1676 ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
1678 ui_browser__handle_resize(&browser->b);
1680 } else if (choice == browse_map)
1681 map__browse(browser->selection->map);
1682 else if (choice == zoom_dso) {
1684 if (browser->hists->dso_filter) {
1685 pstack__remove(fstack, &browser->hists->dso_filter);
1688 browser->hists->dso_filter = NULL;
1689 sort_dso.elide = false;
1693 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
1694 dso->kernel ? "the Kernel" : dso->short_name);
1695 browser->hists->dso_filter = dso;
1696 sort_dso.elide = true;
1697 pstack__push(fstack, &browser->hists->dso_filter);
1699 hists__filter_by_dso(hists);
1700 hist_browser__reset(browser);
1701 } else if (choice == zoom_thread) {
1703 if (browser->hists->thread_filter) {
1704 pstack__remove(fstack, &browser->hists->thread_filter);
1707 browser->hists->thread_filter = NULL;
1708 sort_thread.elide = false;
1710 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
1711 thread->comm_set ? thread__comm_str(thread) : "",
1713 browser->hists->thread_filter = thread;
1714 sort_thread.elide = true;
1715 pstack__push(fstack, &browser->hists->thread_filter);
1717 hists__filter_by_thread(hists);
1718 hist_browser__reset(browser);
1720 /* perf scripts support */
1721 else if (choice == scripts_all || choice == scripts_comm ||
1722 choice == scripts_symbol) {
1724 memset(script_opt, 0, 64);
1726 if (choice == scripts_comm)
1727 sprintf(script_opt, " -c %s ", thread__comm_str(browser->he_selection->thread));
1729 if (choice == scripts_symbol)
1730 sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name);
1732 script_browse(script_opt);
1734 /* Switch to another data file */
1735 else if (choice == switch_data) {
1737 if (!switch_data_file()) {
1738 key = K_SWITCH_INPUT_DATA;
1741 ui__warning("Won't switch the data files due to\n"
1742 "no valid data file get selected!\n");
1746 pstack__delete(fstack);
1748 hist_browser__delete(browser);
1749 free_popup_options(options, nr_options - 1);
1753 struct perf_evsel_menu {
1754 struct ui_browser b;
1755 struct perf_evsel *selection;
1756 bool lost_events, lost_events_warned;
1758 struct perf_session_env *env;
1761 static void perf_evsel_menu__write(struct ui_browser *browser,
1762 void *entry, int row)
1764 struct perf_evsel_menu *menu = container_of(browser,
1765 struct perf_evsel_menu, b);
1766 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1767 bool current_entry = ui_browser__is_current_entry(browser, row);
1768 unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1769 const char *ev_name = perf_evsel__name(evsel);
1771 const char *warn = " ";
1774 ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
1775 HE_COLORSET_NORMAL);
1777 if (perf_evsel__is_group_event(evsel)) {
1778 struct perf_evsel *pos;
1780 ev_name = perf_evsel__group_name(evsel);
1782 for_each_group_member(pos, evsel) {
1783 nr_events += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1787 nr_events = convert_unit(nr_events, &unit);
1788 printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
1789 unit, unit == ' ' ? "" : " ", ev_name);
1790 slsmg_printf("%s", bf);
1792 nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST];
1793 if (nr_events != 0) {
1794 menu->lost_events = true;
1796 ui_browser__set_color(browser, HE_COLORSET_TOP);
1797 nr_events = convert_unit(nr_events, &unit);
1798 printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
1799 nr_events, unit, unit == ' ' ? "" : " ");
1803 slsmg_write_nstring(warn, browser->width - printed);
1806 menu->selection = evsel;
1809 static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
1810 int nr_events, const char *help,
1811 struct hist_browser_timer *hbt)
1813 struct perf_evlist *evlist = menu->b.priv;
1814 struct perf_evsel *pos;
1815 const char *ev_name, *title = "Available samples";
1816 int delay_secs = hbt ? hbt->refresh : 0;
1819 if (ui_browser__show(&menu->b, title,
1820 "ESC: exit, ENTER|->: Browse histograms") < 0)
1824 key = ui_browser__run(&menu->b, delay_secs);
1828 hbt->timer(hbt->arg);
1830 if (!menu->lost_events_warned && menu->lost_events) {
1831 ui_browser__warn_lost_events(&menu->b);
1832 menu->lost_events_warned = true;
1837 if (!menu->selection)
1839 pos = menu->selection;
1841 perf_evlist__set_selected(evlist, pos);
1843 * Give the calling tool a chance to populate the non
1844 * default evsel resorted hists tree.
1847 hbt->timer(hbt->arg);
1848 ev_name = perf_evsel__name(pos);
1849 key = perf_evsel__hists_browse(pos, nr_events, help,
1853 ui_browser__show_title(&menu->b, title);
1856 if (pos->node.next == &evlist->entries)
1857 pos = perf_evlist__first(evlist);
1859 pos = perf_evsel__next(pos);
1862 if (pos->node.prev == &evlist->entries)
1863 pos = perf_evlist__last(evlist);
1865 pos = perf_evsel__prev(pos);
1868 if (!ui_browser__dialog_yesno(&menu->b,
1869 "Do you really want to exit?"))
1872 case K_SWITCH_INPUT_DATA:
1882 if (!ui_browser__dialog_yesno(&menu->b,
1883 "Do you really want to exit?"))
1895 ui_browser__hide(&menu->b);
1899 static bool filter_group_entries(struct ui_browser *browser __maybe_unused,
1902 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1904 if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel))
1910 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1911 int nr_entries, const char *help,
1912 struct hist_browser_timer *hbt,
1914 struct perf_session_env *env)
1916 struct perf_evsel *pos;
1917 struct perf_evsel_menu menu = {
1919 .entries = &evlist->entries,
1920 .refresh = ui_browser__list_head_refresh,
1921 .seek = ui_browser__list_head_seek,
1922 .write = perf_evsel_menu__write,
1923 .filter = filter_group_entries,
1924 .nr_entries = nr_entries,
1927 .min_pcnt = min_pcnt,
1931 ui_helpline__push("Press ESC to exit");
1933 evlist__for_each(evlist, pos) {
1934 const char *ev_name = perf_evsel__name(pos);
1935 size_t line_len = strlen(ev_name) + 7;
1937 if (menu.b.width < line_len)
1938 menu.b.width = line_len;
1941 return perf_evsel_menu__run(&menu, nr_entries, help, hbt);
1944 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
1945 struct hist_browser_timer *hbt,
1947 struct perf_session_env *env)
1949 int nr_entries = evlist->nr_entries;
1952 if (nr_entries == 1) {
1953 struct perf_evsel *first = perf_evlist__first(evlist);
1954 const char *ev_name = perf_evsel__name(first);
1956 return perf_evsel__hists_browse(first, nr_entries, help,
1957 ev_name, false, hbt, min_pcnt,
1961 if (symbol_conf.event_group) {
1962 struct perf_evsel *pos;
1965 evlist__for_each(evlist, pos) {
1966 if (perf_evsel__is_group_leader(pos))
1970 if (nr_entries == 1)
1974 return __perf_evlist__tui_browse_hists(evlist, nr_entries, help,
1975 hbt, min_pcnt, env);