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;
30 u64 nr_callchain_rows;
33 extern void hist_browser__init_hpp(void);
35 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
37 static void hist_browser__update_nr_entries(struct hist_browser *hb);
39 static struct rb_node *hists__filter_entries(struct rb_node *nd,
43 static bool hist_browser__has_filter(struct hist_browser *hb)
45 return hists__has_filter(hb->hists) || hb->min_pcnt;
48 static u32 hist_browser__nr_entries(struct hist_browser *hb)
52 if (hist_browser__has_filter(hb))
53 nr_entries = hb->nr_non_filtered_entries;
55 nr_entries = hb->hists->nr_entries;
57 return nr_entries + hb->nr_callchain_rows;
60 static void hist_browser__refresh_dimensions(struct hist_browser *browser)
62 /* 3 == +/- toggle symbol before actual hist_entry rendering */
63 browser->b.width = 3 + (hists__sort_list_width(browser->hists) +
67 static void hist_browser__reset(struct hist_browser *browser)
70 * The hists__remove_entry_filter() already folds non-filtered
71 * entries so we can assume it has 0 callchain rows.
73 browser->nr_callchain_rows = 0;
75 hist_browser__update_nr_entries(browser);
76 browser->b.nr_entries = hist_browser__nr_entries(browser);
77 hist_browser__refresh_dimensions(browser);
78 ui_browser__reset_index(&browser->b);
81 static char tree__folded_sign(bool unfolded)
83 return unfolded ? '-' : '+';
86 static char map_symbol__folded(const struct map_symbol *ms)
88 return ms->has_children ? tree__folded_sign(ms->unfolded) : ' ';
91 static char hist_entry__folded(const struct hist_entry *he)
93 return map_symbol__folded(&he->ms);
96 static char callchain_list__folded(const struct callchain_list *cl)
98 return map_symbol__folded(&cl->ms);
101 static void map_symbol__set_folding(struct map_symbol *ms, bool unfold)
103 ms->unfolded = unfold ? ms->has_children : false;
106 static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
111 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
112 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
113 struct callchain_list *chain;
114 char folded_sign = ' '; /* No children */
116 list_for_each_entry(chain, &child->val, list) {
118 /* We need this because we may not have children */
119 folded_sign = callchain_list__folded(chain);
120 if (folded_sign == '+')
124 if (folded_sign == '-') /* Have children and they're unfolded */
125 n += callchain_node__count_rows_rb_tree(child);
131 static int callchain_node__count_rows(struct callchain_node *node)
133 struct callchain_list *chain;
134 bool unfolded = false;
137 list_for_each_entry(chain, &node->val, list) {
139 unfolded = chain->ms.unfolded;
143 n += callchain_node__count_rows_rb_tree(node);
148 static int callchain__count_rows(struct rb_root *chain)
153 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
154 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
155 n += callchain_node__count_rows(node);
161 static bool map_symbol__toggle_fold(struct map_symbol *ms)
166 if (!ms->has_children)
169 ms->unfolded = !ms->unfolded;
173 static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
175 struct rb_node *nd = rb_first(&node->rb_root);
177 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
178 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
179 struct callchain_list *chain;
182 list_for_each_entry(chain, &child->val, list) {
185 chain->ms.has_children = chain->list.next != &child->val ||
186 !RB_EMPTY_ROOT(&child->rb_root);
188 chain->ms.has_children = chain->list.next == &child->val &&
189 !RB_EMPTY_ROOT(&child->rb_root);
192 callchain_node__init_have_children_rb_tree(child);
196 static void callchain_node__init_have_children(struct callchain_node *node)
198 struct callchain_list *chain;
200 list_for_each_entry(chain, &node->val, list)
201 chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root);
203 callchain_node__init_have_children_rb_tree(node);
206 static void callchain__init_have_children(struct rb_root *root)
210 for (nd = rb_first(root); nd; nd = rb_next(nd)) {
211 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
212 callchain_node__init_have_children(node);
216 static void hist_entry__init_have_children(struct hist_entry *he)
218 if (!he->init_have_children) {
219 he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
220 callchain__init_have_children(&he->sorted_chain);
221 he->init_have_children = true;
225 static bool hist_browser__toggle_fold(struct hist_browser *browser)
227 if (map_symbol__toggle_fold(browser->selection)) {
228 struct hist_entry *he = browser->he_selection;
230 hist_entry__init_have_children(he);
231 browser->b.nr_entries -= he->nr_rows;
232 browser->nr_callchain_rows -= he->nr_rows;
235 he->nr_rows = callchain__count_rows(&he->sorted_chain);
239 browser->b.nr_entries += he->nr_rows;
240 browser->nr_callchain_rows += he->nr_rows;
245 /* If it doesn't have children, no toggling performed */
249 static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
254 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
255 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
256 struct callchain_list *chain;
257 bool has_children = false;
259 list_for_each_entry(chain, &child->val, list) {
261 map_symbol__set_folding(&chain->ms, unfold);
262 has_children = chain->ms.has_children;
266 n += callchain_node__set_folding_rb_tree(child, unfold);
272 static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
274 struct callchain_list *chain;
275 bool has_children = false;
278 list_for_each_entry(chain, &node->val, list) {
280 map_symbol__set_folding(&chain->ms, unfold);
281 has_children = chain->ms.has_children;
285 n += callchain_node__set_folding_rb_tree(node, unfold);
290 static int callchain__set_folding(struct rb_root *chain, bool unfold)
295 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
296 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
297 n += callchain_node__set_folding(node, unfold);
303 static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
305 hist_entry__init_have_children(he);
306 map_symbol__set_folding(&he->ms, unfold);
308 if (he->ms.has_children) {
309 int n = callchain__set_folding(&he->sorted_chain, unfold);
310 he->nr_rows = unfold ? n : 0;
316 __hist_browser__set_folding(struct hist_browser *browser, bool unfold)
319 struct hists *hists = browser->hists;
321 for (nd = rb_first(&hists->entries);
322 (nd = hists__filter_entries(nd, hists, browser->min_pcnt)) != NULL;
324 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
325 hist_entry__set_folding(he, unfold);
326 browser->nr_callchain_rows += he->nr_rows;
330 static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
332 browser->nr_callchain_rows = 0;
333 __hist_browser__set_folding(browser, unfold);
335 browser->b.nr_entries = hist_browser__nr_entries(browser);
336 /* Go to the start, we may be way after valid entries after a collapse */
337 ui_browser__reset_index(&browser->b);
340 static void ui_browser__warn_lost_events(struct ui_browser *browser)
342 ui_browser__warning(browser, 4,
343 "Events are being lost, check IO/CPU overload!\n\n"
344 "You may want to run 'perf' using a RT scheduler policy:\n\n"
345 " perf top -r 80\n\n"
346 "Or reduce the sampling frequency.");
349 static int hist_browser__run(struct hist_browser *browser, const char *ev_name,
350 struct hist_browser_timer *hbt)
354 int delay_secs = hbt ? hbt->refresh : 0;
356 browser->b.entries = &browser->hists->entries;
357 browser->b.nr_entries = hist_browser__nr_entries(browser);
359 hist_browser__refresh_dimensions(browser);
360 hists__browser_title(browser->hists, title, sizeof(title), ev_name);
362 if (ui_browser__show(&browser->b, title,
363 "Press '?' for help on key bindings") < 0)
367 key = ui_browser__run(&browser->b, delay_secs);
372 hbt->timer(hbt->arg);
374 if (hist_browser__has_filter(browser))
375 hist_browser__update_nr_entries(browser);
377 nr_entries = hist_browser__nr_entries(browser);
378 ui_browser__update_nr_entries(&browser->b, nr_entries);
380 if (browser->hists->stats.nr_lost_warned !=
381 browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
382 browser->hists->stats.nr_lost_warned =
383 browser->hists->stats.nr_events[PERF_RECORD_LOST];
384 ui_browser__warn_lost_events(&browser->b);
387 hists__browser_title(browser->hists, title, sizeof(title), ev_name);
388 ui_browser__show_title(&browser->b, title);
391 case 'D': { /* Debug */
393 struct hist_entry *h = rb_entry(browser->b.top,
394 struct hist_entry, rb_node);
396 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
397 seq++, browser->b.nr_entries,
398 browser->hists->nr_entries,
402 h->row_offset, h->nr_rows);
406 /* Collapse the whole world. */
407 hist_browser__set_folding(browser, false);
410 /* Expand the whole world. */
411 hist_browser__set_folding(browser, true);
414 if (hist_browser__toggle_fold(browser))
422 ui_browser__hide(&browser->b);
426 static char *callchain_list__sym_name(struct callchain_list *cl,
427 char *bf, size_t bfsize, bool show_dso)
432 printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
434 printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
437 scnprintf(bf + printed, bfsize - printed, " %s",
438 cl->ms.map ? cl->ms.map->dso->short_name : "unknown");
443 #define LEVEL_OFFSET_STEP 3
445 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browser,
446 struct callchain_node *chain_node,
447 u64 total, int level,
450 bool *is_current_entry)
452 struct rb_node *node;
453 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
454 u64 new_total, remaining;
456 if (callchain_param.mode == CHAIN_GRAPH_REL)
457 new_total = chain_node->children_hit;
461 remaining = new_total;
462 node = rb_first(&chain_node->rb_root);
464 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
465 struct rb_node *next = rb_next(node);
466 u64 cumul = callchain_cumul_hits(child);
467 struct callchain_list *chain;
468 char folded_sign = ' ';
470 int extra_offset = 0;
474 list_for_each_entry(chain, &child->val, list) {
475 char bf[1024], *alloc_str;
478 bool was_first = first;
483 extra_offset = LEVEL_OFFSET_STEP;
485 folded_sign = callchain_list__folded(chain);
486 if (*row_offset != 0) {
492 str = callchain_list__sym_name(chain, bf, sizeof(bf),
495 double percent = cumul * 100.0 / new_total;
497 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
498 str = "Not enough memory!";
503 color = HE_COLORSET_NORMAL;
504 width = browser->b.width - (offset + extra_offset + 2);
505 if (ui_browser__is_current_entry(&browser->b, row)) {
506 browser->selection = &chain->ms;
507 color = HE_COLORSET_SELECTED;
508 *is_current_entry = true;
511 ui_browser__set_color(&browser->b, color);
512 ui_browser__gotorc(&browser->b, row, 0);
513 slsmg_write_nstring(" ", offset + extra_offset);
514 slsmg_printf("%c ", folded_sign);
515 slsmg_write_nstring(str, width);
518 if (++row == browser->b.height)
521 if (folded_sign == '+')
525 if (folded_sign == '-') {
526 const int new_level = level + (extra_offset ? 2 : 1);
527 row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total,
528 new_level, row, row_offset,
531 if (row == browser->b.height)
536 return row - first_row;
539 static int hist_browser__show_callchain_node(struct hist_browser *browser,
540 struct callchain_node *node,
541 int level, unsigned short row,
543 bool *is_current_entry)
545 struct callchain_list *chain;
547 offset = level * LEVEL_OFFSET_STEP,
548 width = browser->b.width - offset;
549 char folded_sign = ' ';
551 list_for_each_entry(chain, &node->val, list) {
555 folded_sign = callchain_list__folded(chain);
557 if (*row_offset != 0) {
562 color = HE_COLORSET_NORMAL;
563 if (ui_browser__is_current_entry(&browser->b, row)) {
564 browser->selection = &chain->ms;
565 color = HE_COLORSET_SELECTED;
566 *is_current_entry = true;
569 s = callchain_list__sym_name(chain, bf, sizeof(bf),
571 ui_browser__gotorc(&browser->b, row, 0);
572 ui_browser__set_color(&browser->b, color);
573 slsmg_write_nstring(" ", offset);
574 slsmg_printf("%c ", folded_sign);
575 slsmg_write_nstring(s, width - 2);
577 if (++row == browser->b.height)
581 if (folded_sign == '-')
582 row += hist_browser__show_callchain_node_rb_tree(browser, node,
583 browser->hists->stats.total_period,
588 return row - first_row;
591 static int hist_browser__show_callchain(struct hist_browser *browser,
592 struct rb_root *chain,
593 int level, unsigned short row,
595 bool *is_current_entry)
600 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
601 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
603 row += hist_browser__show_callchain_node(browser, node, level,
606 if (row == browser->b.height)
610 return row - first_row;
614 struct ui_browser *b;
619 static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
621 struct hpp_arg *arg = hpp->ptr;
627 percent = va_arg(args, double);
630 ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
632 ret = scnprintf(hpp->buf, hpp->size, fmt, percent);
633 slsmg_printf("%s", hpp->buf);
635 advance_hpp(hpp, ret);
639 #define __HPP_COLOR_PERCENT_FN(_type, _field) \
640 static u64 __hpp_get_##_field(struct hist_entry *he) \
642 return he->stat._field; \
646 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\
647 struct perf_hpp *hpp, \
648 struct hist_entry *he) \
650 return __hpp__fmt(hpp, he, __hpp_get_##_field, " %6.2f%%", \
651 __hpp__slsmg_color_printf, true); \
654 #define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
655 static u64 __hpp_get_acc_##_field(struct hist_entry *he) \
657 return he->stat_acc->_field; \
661 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\
662 struct perf_hpp *hpp, \
663 struct hist_entry *he) \
665 if (!symbol_conf.cumulate_callchain) { \
666 int ret = scnprintf(hpp->buf, hpp->size, "%8s", "N/A"); \
667 slsmg_printf("%s", hpp->buf); \
671 return __hpp__fmt(hpp, he, __hpp_get_acc_##_field, " %6.2f%%", \
672 __hpp__slsmg_color_printf, true); \
675 __HPP_COLOR_PERCENT_FN(overhead, period)
676 __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
677 __HPP_COLOR_PERCENT_FN(overhead_us, period_us)
678 __HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
679 __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
680 __HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period)
682 #undef __HPP_COLOR_PERCENT_FN
683 #undef __HPP_COLOR_ACC_PERCENT_FN
685 void hist_browser__init_hpp(void)
687 perf_hpp__format[PERF_HPP__OVERHEAD].color =
688 hist_browser__hpp_color_overhead;
689 perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
690 hist_browser__hpp_color_overhead_sys;
691 perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
692 hist_browser__hpp_color_overhead_us;
693 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
694 hist_browser__hpp_color_overhead_guest_sys;
695 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
696 hist_browser__hpp_color_overhead_guest_us;
697 perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
698 hist_browser__hpp_color_overhead_acc;
701 static int hist_browser__show_entry(struct hist_browser *browser,
702 struct hist_entry *entry,
707 int width = browser->b.width;
708 char folded_sign = ' ';
709 bool current_entry = ui_browser__is_current_entry(&browser->b, row);
710 off_t row_offset = entry->row_offset;
712 struct perf_hpp_fmt *fmt;
715 browser->he_selection = entry;
716 browser->selection = &entry->ms;
719 if (symbol_conf.use_callchain) {
720 hist_entry__init_have_children(entry);
721 folded_sign = hist_entry__folded(entry);
724 if (row_offset == 0) {
725 struct hpp_arg arg = {
727 .folded_sign = folded_sign,
728 .current_entry = current_entry,
730 struct perf_hpp hpp = {
736 ui_browser__gotorc(&browser->b, row, 0);
738 perf_hpp__for_each_format(fmt) {
739 if (perf_hpp__should_skip(fmt))
742 if (current_entry && browser->b.navkeypressed) {
743 ui_browser__set_color(&browser->b,
744 HE_COLORSET_SELECTED);
746 ui_browser__set_color(&browser->b,
751 if (symbol_conf.use_callchain) {
752 slsmg_printf("%c ", folded_sign);
762 width -= fmt->color(fmt, &hpp, entry);
764 width -= fmt->entry(fmt, &hpp, entry);
765 slsmg_printf("%s", s);
769 /* The scroll bar isn't being used */
770 if (!browser->b.navkeypressed)
773 slsmg_write_nstring("", width);
780 if (folded_sign == '-' && row != browser->b.height) {
781 printed += hist_browser__show_callchain(browser, &entry->sorted_chain,
785 browser->he_selection = entry;
791 static void ui_browser__hists_init_top(struct ui_browser *browser)
793 if (browser->top == NULL) {
794 struct hist_browser *hb;
796 hb = container_of(browser, struct hist_browser, b);
797 browser->top = rb_first(&hb->hists->entries);
801 static unsigned int hist_browser__refresh(struct ui_browser *browser)
805 struct hist_browser *hb = container_of(browser, struct hist_browser, b);
807 ui_browser__hists_init_top(browser);
809 for (nd = browser->top; nd; nd = rb_next(nd)) {
810 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
811 u64 total = hists__total_period(h->hists);
818 percent = h->stat.period * 100.0 / total;
820 if (percent < hb->min_pcnt)
823 row += hist_browser__show_entry(hb, h, row);
824 if (row == browser->height)
831 static struct rb_node *hists__filter_entries(struct rb_node *nd,
836 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
837 u64 total = hists__total_period(hists);
841 percent = h->stat.period * 100.0 / total;
843 if (!h->filtered && percent >= min_pcnt)
852 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
857 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
858 u64 total = hists__total_period(hists);
862 percent = h->stat.period * 100.0 / total;
864 if (!h->filtered && percent >= min_pcnt)
873 static void ui_browser__hists_seek(struct ui_browser *browser,
874 off_t offset, int whence)
876 struct hist_entry *h;
879 struct hist_browser *hb;
881 hb = container_of(browser, struct hist_browser, b);
883 if (browser->nr_entries == 0)
886 ui_browser__hists_init_top(browser);
890 nd = hists__filter_entries(rb_first(browser->entries),
891 hb->hists, hb->min_pcnt);
897 nd = hists__filter_prev_entries(rb_last(browser->entries),
898 hb->hists, hb->min_pcnt);
906 * Moves not relative to the first visible entry invalidates its
909 h = rb_entry(browser->top, struct hist_entry, rb_node);
913 * Here we have to check if nd is expanded (+), if it is we can't go
914 * the next top level hist_entry, instead we must compute an offset of
915 * what _not_ to show and not change the first visible entry.
917 * This offset increments when we are going from top to bottom and
918 * decreases when we're going from bottom to top.
920 * As we don't have backpointers to the top level in the callchains
921 * structure, we need to always print the whole hist_entry callchain,
922 * skipping the first ones that are before the first visible entry
923 * and stop when we printed enough lines to fill the screen.
928 h = rb_entry(nd, struct hist_entry, rb_node);
929 if (h->ms.unfolded) {
930 u16 remaining = h->nr_rows - h->row_offset;
931 if (offset > remaining) {
935 h->row_offset += offset;
941 nd = hists__filter_entries(rb_next(nd), hb->hists,
947 } while (offset != 0);
948 } else if (offset < 0) {
950 h = rb_entry(nd, struct hist_entry, rb_node);
951 if (h->ms.unfolded) {
953 if (-offset > h->row_offset) {
954 offset += h->row_offset;
957 h->row_offset += offset;
963 if (-offset > h->nr_rows) {
964 offset += h->nr_rows;
967 h->row_offset = h->nr_rows + offset;
975 nd = hists__filter_prev_entries(rb_prev(nd), hb->hists,
983 * Last unfiltered hist_entry, check if it is
984 * unfolded, if it is then we should have
985 * row_offset at its last entry.
987 h = rb_entry(nd, struct hist_entry, rb_node);
989 h->row_offset = h->nr_rows;
996 h = rb_entry(nd, struct hist_entry, rb_node);
1001 static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser,
1002 struct callchain_node *chain_node,
1003 u64 total, int level,
1006 struct rb_node *node;
1007 int offset = level * LEVEL_OFFSET_STEP;
1008 u64 new_total, remaining;
1011 if (callchain_param.mode == CHAIN_GRAPH_REL)
1012 new_total = chain_node->children_hit;
1016 remaining = new_total;
1017 node = rb_first(&chain_node->rb_root);
1019 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
1020 struct rb_node *next = rb_next(node);
1021 u64 cumul = callchain_cumul_hits(child);
1022 struct callchain_list *chain;
1023 char folded_sign = ' ';
1025 int extra_offset = 0;
1029 list_for_each_entry(chain, &child->val, list) {
1030 char bf[1024], *alloc_str;
1032 bool was_first = first;
1037 extra_offset = LEVEL_OFFSET_STEP;
1039 folded_sign = callchain_list__folded(chain);
1042 str = callchain_list__sym_name(chain, bf, sizeof(bf),
1045 double percent = cumul * 100.0 / new_total;
1047 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
1048 str = "Not enough memory!";
1053 printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str);
1055 if (folded_sign == '+')
1059 if (folded_sign == '-') {
1060 const int new_level = level + (extra_offset ? 2 : 1);
1061 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total,
1071 static int hist_browser__fprintf_callchain_node(struct hist_browser *browser,
1072 struct callchain_node *node,
1073 int level, FILE *fp)
1075 struct callchain_list *chain;
1076 int offset = level * LEVEL_OFFSET_STEP;
1077 char folded_sign = ' ';
1080 list_for_each_entry(chain, &node->val, list) {
1083 folded_sign = callchain_list__folded(chain);
1084 s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso);
1085 printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s);
1088 if (folded_sign == '-')
1089 printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node,
1090 browser->hists->stats.total_period,
1095 static int hist_browser__fprintf_callchain(struct hist_browser *browser,
1096 struct rb_root *chain, int level, FILE *fp)
1101 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
1102 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
1104 printed += hist_browser__fprintf_callchain_node(browser, node, level, fp);
1110 static int hist_browser__fprintf_entry(struct hist_browser *browser,
1111 struct hist_entry *he, FILE *fp)
1115 char folded_sign = ' ';
1116 struct perf_hpp hpp = {
1120 struct perf_hpp_fmt *fmt;
1124 if (symbol_conf.use_callchain)
1125 folded_sign = hist_entry__folded(he);
1127 if (symbol_conf.use_callchain)
1128 printed += fprintf(fp, "%c ", folded_sign);
1130 perf_hpp__for_each_format(fmt) {
1131 if (perf_hpp__should_skip(fmt))
1135 ret = scnprintf(hpp.buf, hpp.size, " ");
1136 advance_hpp(&hpp, ret);
1140 ret = fmt->entry(fmt, &hpp, he);
1141 advance_hpp(&hpp, ret);
1143 printed += fprintf(fp, "%s\n", rtrim(s));
1145 if (folded_sign == '-')
1146 printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp);
1151 static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
1153 struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
1159 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1161 printed += hist_browser__fprintf_entry(browser, h, fp);
1162 nd = hists__filter_entries(rb_next(nd), browser->hists,
1169 static int hist_browser__dump(struct hist_browser *browser)
1175 scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
1176 if (access(filename, F_OK))
1179 * XXX: Just an arbitrary lazy upper limit
1181 if (++browser->print_seq == 8192) {
1182 ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
1187 fp = fopen(filename, "w");
1190 const char *err = strerror_r(errno, bf, sizeof(bf));
1191 ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
1195 ++browser->print_seq;
1196 hist_browser__fprintf(browser, fp);
1198 ui_helpline__fpush("%s written!", filename);
1203 static struct hist_browser *hist_browser__new(struct hists *hists)
1205 struct hist_browser *browser = zalloc(sizeof(*browser));
1208 browser->hists = hists;
1209 browser->b.refresh = hist_browser__refresh;
1210 browser->b.seek = ui_browser__hists_seek;
1211 browser->b.use_navkeypressed = true;
1217 static void hist_browser__delete(struct hist_browser *browser)
1222 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
1224 return browser->he_selection;
1227 static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
1229 return browser->he_selection->thread;
1232 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
1233 const char *ev_name)
1237 const struct dso *dso = hists->dso_filter;
1238 const struct thread *thread = hists->thread_filter;
1239 unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
1240 u64 nr_events = hists->stats.total_period;
1241 struct perf_evsel *evsel = hists_to_evsel(hists);
1243 size_t buflen = sizeof(buf);
1245 if (symbol_conf.filter_relative) {
1246 nr_samples = hists->stats.nr_non_filtered_samples;
1247 nr_events = hists->stats.total_non_filtered_period;
1250 if (perf_evsel__is_group_event(evsel)) {
1251 struct perf_evsel *pos;
1253 perf_evsel__group_desc(evsel, buf, buflen);
1256 for_each_group_member(pos, evsel) {
1257 if (symbol_conf.filter_relative) {
1258 nr_samples += pos->hists.stats.nr_non_filtered_samples;
1259 nr_events += pos->hists.stats.total_non_filtered_period;
1261 nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1262 nr_events += pos->hists.stats.total_period;
1267 nr_samples = convert_unit(nr_samples, &unit);
1268 printed = scnprintf(bf, size,
1269 "Samples: %lu%c of event '%s', Event count (approx.): %lu",
1270 nr_samples, unit, ev_name, nr_events);
1273 if (hists->uid_filter_str)
1274 printed += snprintf(bf + printed, size - printed,
1275 ", UID: %s", hists->uid_filter_str);
1277 printed += scnprintf(bf + printed, size - printed,
1279 (thread->comm_set ? thread__comm_str(thread) : ""),
1282 printed += scnprintf(bf + printed, size - printed,
1283 ", DSO: %s", dso->short_name);
1287 static inline void free_popup_options(char **options, int n)
1291 for (i = 0; i < n; ++i)
1295 /* Check whether the browser is for 'top' or 'report' */
1296 static inline bool is_report_browser(void *timer)
1298 return timer == NULL;
1302 * Only runtime switching of perf data file will make "input_name" point
1303 * to a malloced buffer. So add "is_input_name_malloced" flag to decide
1304 * whether we need to call free() for current "input_name" during the switch.
1306 static bool is_input_name_malloced = false;
1308 static int switch_data_file(void)
1310 char *pwd, *options[32], *abs_path[32], *tmp;
1312 int nr_options = 0, choice = -1, ret = -1;
1313 struct dirent *dent;
1315 pwd = getenv("PWD");
1319 pwd_dir = opendir(pwd);
1323 memset(options, 0, sizeof(options));
1324 memset(options, 0, sizeof(abs_path));
1326 while ((dent = readdir(pwd_dir))) {
1327 char path[PATH_MAX];
1329 char *name = dent->d_name;
1332 if (!(dent->d_type == DT_REG))
1335 snprintf(path, sizeof(path), "%s/%s", pwd, name);
1337 file = fopen(path, "r");
1341 if (fread(&magic, 1, 8, file) < 8)
1342 goto close_file_and_continue;
1344 if (is_perf_magic(magic)) {
1345 options[nr_options] = strdup(name);
1346 if (!options[nr_options])
1347 goto close_file_and_continue;
1349 abs_path[nr_options] = strdup(path);
1350 if (!abs_path[nr_options]) {
1351 zfree(&options[nr_options]);
1352 ui__warning("Can't search all data files due to memory shortage.\n");
1360 close_file_and_continue:
1362 if (nr_options >= 32) {
1363 ui__warning("Too many perf data files in PWD!\n"
1364 "Only the first 32 files will be listed.\n");
1371 choice = ui__popup_menu(nr_options, options);
1372 if (choice < nr_options && choice >= 0) {
1373 tmp = strdup(abs_path[choice]);
1375 if (is_input_name_malloced)
1376 free((void *)input_name);
1378 is_input_name_malloced = true;
1381 ui__warning("Data switch failed due to memory shortage!\n");
1385 free_popup_options(options, nr_options);
1386 free_popup_options(abs_path, nr_options);
1390 static void hist_browser__update_nr_entries(struct hist_browser *hb)
1393 struct rb_node *nd = rb_first(&hb->hists->entries);
1395 if (hb->min_pcnt == 0) {
1396 hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
1400 while ((nd = hists__filter_entries(nd, hb->hists,
1401 hb->min_pcnt)) != NULL) {
1406 hb->nr_non_filtered_entries = nr_entries;
1409 static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
1410 const char *helpline, const char *ev_name,
1412 struct hist_browser_timer *hbt,
1414 struct perf_session_env *env)
1416 struct hists *hists = &evsel->hists;
1417 struct hist_browser *browser = hist_browser__new(hists);
1418 struct branch_info *bi;
1419 struct pstack *fstack;
1424 char script_opt[64];
1425 int delay_secs = hbt ? hbt->refresh : 0;
1427 #define HIST_BROWSER_HELP_COMMON \
1428 "h/?/F1 Show this window\n" \
1430 "PGDN/SPACE Navigate\n" \
1431 "q/ESC/CTRL+C Exit browser\n\n" \
1432 "For multiple event sessions:\n\n" \
1433 "TAB/UNTAB Switch events\n\n" \
1434 "For symbolic views (--sort has sym):\n\n" \
1435 "-> Zoom into DSO/Threads & Annotate current symbol\n" \
1437 "a Annotate current symbol\n" \
1438 "C Collapse all callchains\n" \
1439 "d Zoom into current DSO\n" \
1440 "E Expand all callchains\n" \
1441 "F Toggle percentage of filtered entries\n" \
1443 /* help messages are sorted by lexical order of the hotkey */
1444 const char report_help[] = HIST_BROWSER_HELP_COMMON
1445 "i Show header information\n"
1446 "P Print histograms to perf.hist.N\n"
1447 "r Run available scripts\n"
1448 "s Switch to another data file in PWD\n"
1449 "t Zoom into current Thread\n"
1450 "V Verbose (DSO names in callchains, etc)\n"
1451 "/ Filter symbol by name";
1452 const char top_help[] = HIST_BROWSER_HELP_COMMON
1453 "P Print histograms to perf.hist.N\n"
1454 "t Zoom into current Thread\n"
1455 "V Verbose (DSO names in callchains, etc)\n"
1456 "/ Filter symbol by name";
1458 if (browser == NULL)
1462 browser->min_pcnt = min_pcnt;
1463 hist_browser__update_nr_entries(browser);
1466 fstack = pstack__new(2);
1470 ui_helpline__push(helpline);
1472 memset(options, 0, sizeof(options));
1475 const struct thread *thread = NULL;
1476 const struct dso *dso = NULL;
1478 annotate = -2, zoom_dso = -2, zoom_thread = -2,
1479 annotate_f = -2, annotate_t = -2, browse_map = -2;
1480 int scripts_comm = -2, scripts_symbol = -2,
1481 scripts_all = -2, switch_data = -2;
1485 key = hist_browser__run(browser, ev_name, hbt);
1487 if (browser->he_selection != NULL) {
1488 thread = hist_browser__selected_thread(browser);
1489 dso = browser->selection->map ? browser->selection->map->dso : NULL;
1497 * Exit the browser, let hists__browser_tree
1498 * go to the next or previous
1500 goto out_free_stack;
1502 if (!sort__has_sym) {
1503 ui_browser__warning(&browser->b, delay_secs * 2,
1504 "Annotation is only available for symbolic views, "
1505 "include \"sym*\" in --sort to use it.");
1509 if (browser->selection == NULL ||
1510 browser->selection->sym == NULL ||
1511 browser->selection->map->dso->annotate_warned)
1515 hist_browser__dump(browser);
1520 browser->show_dso = !browser->show_dso;
1525 if (ui_browser__input_window("Symbol to show",
1526 "Please enter the name of symbol you want to see",
1527 buf, "ENTER: OK, ESC: Cancel",
1528 delay_secs * 2) == K_ENTER) {
1529 hists->symbol_filter_str = *buf ? buf : NULL;
1530 hists__filter_by_symbol(hists);
1531 hist_browser__reset(browser);
1535 if (is_report_browser(hbt))
1539 if (is_report_browser(hbt))
1540 goto do_data_switch;
1543 /* env->arch is NULL for live-mode (i.e. perf top) */
1545 tui__header_window(env);
1548 symbol_conf.filter_relative ^= 1;
1553 ui_browser__help_window(&browser->b,
1554 is_report_browser(hbt) ? report_help : top_help);
1563 if (pstack__empty(fstack)) {
1565 * Go back to the perf_evsel_menu__run or other user
1568 goto out_free_stack;
1571 top = pstack__pop(fstack);
1572 if (top == &browser->hists->dso_filter)
1574 if (top == &browser->hists->thread_filter)
1575 goto zoom_out_thread;
1580 !ui_browser__dialog_yesno(&browser->b,
1581 "Do you really want to exit?"))
1586 goto out_free_stack;
1592 goto add_exit_option;
1594 if (sort__mode == SORT_MODE__BRANCH) {
1595 bi = browser->he_selection->branch_info;
1596 if (browser->selection != NULL &&
1598 bi->from.sym != NULL &&
1599 !bi->from.map->dso->annotate_warned &&
1600 asprintf(&options[nr_options], "Annotate %s",
1601 bi->from.sym->name) > 0)
1602 annotate_f = nr_options++;
1604 if (browser->selection != NULL &&
1606 bi->to.sym != NULL &&
1607 !bi->to.map->dso->annotate_warned &&
1608 (bi->to.sym != bi->from.sym ||
1609 bi->to.map->dso != bi->from.map->dso) &&
1610 asprintf(&options[nr_options], "Annotate %s",
1611 bi->to.sym->name) > 0)
1612 annotate_t = nr_options++;
1615 if (browser->selection != NULL &&
1616 browser->selection->sym != NULL &&
1617 !browser->selection->map->dso->annotate_warned &&
1618 asprintf(&options[nr_options], "Annotate %s",
1619 browser->selection->sym->name) > 0)
1620 annotate = nr_options++;
1623 if (thread != NULL &&
1624 asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
1625 (browser->hists->thread_filter ? "out of" : "into"),
1626 (thread->comm_set ? thread__comm_str(thread) : ""),
1628 zoom_thread = nr_options++;
1631 asprintf(&options[nr_options], "Zoom %s %s DSO",
1632 (browser->hists->dso_filter ? "out of" : "into"),
1633 (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
1634 zoom_dso = nr_options++;
1636 if (browser->selection != NULL &&
1637 browser->selection->map != NULL &&
1638 asprintf(&options[nr_options], "Browse map details") > 0)
1639 browse_map = nr_options++;
1641 /* perf script support */
1642 if (browser->he_selection) {
1645 if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]",
1646 thread__comm_str(browser->he_selection->thread)) > 0)
1647 scripts_comm = nr_options++;
1649 sym = browser->he_selection->ms.sym;
1650 if (sym && sym->namelen &&
1651 asprintf(&options[nr_options], "Run scripts for samples of symbol [%s]",
1653 scripts_symbol = nr_options++;
1656 if (asprintf(&options[nr_options], "Run scripts for all samples") > 0)
1657 scripts_all = nr_options++;
1659 if (is_report_browser(hbt) && asprintf(&options[nr_options],
1660 "Switch to another data file in PWD") > 0)
1661 switch_data = nr_options++;
1663 options[nr_options++] = (char *)"Exit";
1665 choice = ui__popup_menu(nr_options, options);
1667 if (choice == nr_options - 1)
1671 free_popup_options(options, nr_options - 1);
1675 if (choice == annotate || choice == annotate_t || choice == annotate_f) {
1676 struct hist_entry *he;
1679 if (!objdump_path && perf_session_env__lookup_objdump(env))
1682 he = hist_browser__selected_entry(browser);
1687 * we stash the branch_info symbol + map into the
1688 * the ms so we don't have to rewrite all the annotation
1689 * code to use branch_info.
1690 * in branch mode, the ms struct is not used
1692 if (choice == annotate_f) {
1693 he->ms.sym = he->branch_info->from.sym;
1694 he->ms.map = he->branch_info->from.map;
1695 } else if (choice == annotate_t) {
1696 he->ms.sym = he->branch_info->to.sym;
1697 he->ms.map = he->branch_info->to.map;
1701 * Don't let this be freed, say, by hists__decay_entry.
1704 err = hist_entry__tui_annotate(he, evsel, hbt);
1707 * offer option to annotate the other branch source or target
1708 * (if they exists) when returning from annotate
1710 if ((err == 'q' || err == CTRL('c'))
1711 && annotate_t != -2 && annotate_f != -2)
1712 goto retry_popup_menu;
1714 ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
1716 ui_browser__handle_resize(&browser->b);
1718 } else if (choice == browse_map)
1719 map__browse(browser->selection->map);
1720 else if (choice == zoom_dso) {
1722 if (browser->hists->dso_filter) {
1723 pstack__remove(fstack, &browser->hists->dso_filter);
1726 browser->hists->dso_filter = NULL;
1727 sort_dso.elide = false;
1731 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
1732 dso->kernel ? "the Kernel" : dso->short_name);
1733 browser->hists->dso_filter = dso;
1734 sort_dso.elide = true;
1735 pstack__push(fstack, &browser->hists->dso_filter);
1737 hists__filter_by_dso(hists);
1738 hist_browser__reset(browser);
1739 } else if (choice == zoom_thread) {
1741 if (browser->hists->thread_filter) {
1742 pstack__remove(fstack, &browser->hists->thread_filter);
1745 browser->hists->thread_filter = NULL;
1746 sort_thread.elide = false;
1748 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
1749 thread->comm_set ? thread__comm_str(thread) : "",
1751 browser->hists->thread_filter = thread;
1752 sort_thread.elide = true;
1753 pstack__push(fstack, &browser->hists->thread_filter);
1755 hists__filter_by_thread(hists);
1756 hist_browser__reset(browser);
1758 /* perf scripts support */
1759 else if (choice == scripts_all || choice == scripts_comm ||
1760 choice == scripts_symbol) {
1762 memset(script_opt, 0, 64);
1764 if (choice == scripts_comm)
1765 sprintf(script_opt, " -c %s ", thread__comm_str(browser->he_selection->thread));
1767 if (choice == scripts_symbol)
1768 sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name);
1770 script_browse(script_opt);
1772 /* Switch to another data file */
1773 else if (choice == switch_data) {
1775 if (!switch_data_file()) {
1776 key = K_SWITCH_INPUT_DATA;
1779 ui__warning("Won't switch the data files due to\n"
1780 "no valid data file get selected!\n");
1784 pstack__delete(fstack);
1786 hist_browser__delete(browser);
1787 free_popup_options(options, nr_options - 1);
1791 struct perf_evsel_menu {
1792 struct ui_browser b;
1793 struct perf_evsel *selection;
1794 bool lost_events, lost_events_warned;
1796 struct perf_session_env *env;
1799 static void perf_evsel_menu__write(struct ui_browser *browser,
1800 void *entry, int row)
1802 struct perf_evsel_menu *menu = container_of(browser,
1803 struct perf_evsel_menu, b);
1804 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1805 bool current_entry = ui_browser__is_current_entry(browser, row);
1806 unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1807 const char *ev_name = perf_evsel__name(evsel);
1809 const char *warn = " ";
1812 ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
1813 HE_COLORSET_NORMAL);
1815 if (perf_evsel__is_group_event(evsel)) {
1816 struct perf_evsel *pos;
1818 ev_name = perf_evsel__group_name(evsel);
1820 for_each_group_member(pos, evsel) {
1821 nr_events += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1825 nr_events = convert_unit(nr_events, &unit);
1826 printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
1827 unit, unit == ' ' ? "" : " ", ev_name);
1828 slsmg_printf("%s", bf);
1830 nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST];
1831 if (nr_events != 0) {
1832 menu->lost_events = true;
1834 ui_browser__set_color(browser, HE_COLORSET_TOP);
1835 nr_events = convert_unit(nr_events, &unit);
1836 printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
1837 nr_events, unit, unit == ' ' ? "" : " ");
1841 slsmg_write_nstring(warn, browser->width - printed);
1844 menu->selection = evsel;
1847 static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
1848 int nr_events, const char *help,
1849 struct hist_browser_timer *hbt)
1851 struct perf_evlist *evlist = menu->b.priv;
1852 struct perf_evsel *pos;
1853 const char *ev_name, *title = "Available samples";
1854 int delay_secs = hbt ? hbt->refresh : 0;
1857 if (ui_browser__show(&menu->b, title,
1858 "ESC: exit, ENTER|->: Browse histograms") < 0)
1862 key = ui_browser__run(&menu->b, delay_secs);
1866 hbt->timer(hbt->arg);
1868 if (!menu->lost_events_warned && menu->lost_events) {
1869 ui_browser__warn_lost_events(&menu->b);
1870 menu->lost_events_warned = true;
1875 if (!menu->selection)
1877 pos = menu->selection;
1879 perf_evlist__set_selected(evlist, pos);
1881 * Give the calling tool a chance to populate the non
1882 * default evsel resorted hists tree.
1885 hbt->timer(hbt->arg);
1886 ev_name = perf_evsel__name(pos);
1887 key = perf_evsel__hists_browse(pos, nr_events, help,
1891 ui_browser__show_title(&menu->b, title);
1894 if (pos->node.next == &evlist->entries)
1895 pos = perf_evlist__first(evlist);
1897 pos = perf_evsel__next(pos);
1900 if (pos->node.prev == &evlist->entries)
1901 pos = perf_evlist__last(evlist);
1903 pos = perf_evsel__prev(pos);
1906 if (!ui_browser__dialog_yesno(&menu->b,
1907 "Do you really want to exit?"))
1910 case K_SWITCH_INPUT_DATA:
1920 if (!ui_browser__dialog_yesno(&menu->b,
1921 "Do you really want to exit?"))
1933 ui_browser__hide(&menu->b);
1937 static bool filter_group_entries(struct ui_browser *browser __maybe_unused,
1940 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1942 if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel))
1948 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1949 int nr_entries, const char *help,
1950 struct hist_browser_timer *hbt,
1952 struct perf_session_env *env)
1954 struct perf_evsel *pos;
1955 struct perf_evsel_menu menu = {
1957 .entries = &evlist->entries,
1958 .refresh = ui_browser__list_head_refresh,
1959 .seek = ui_browser__list_head_seek,
1960 .write = perf_evsel_menu__write,
1961 .filter = filter_group_entries,
1962 .nr_entries = nr_entries,
1965 .min_pcnt = min_pcnt,
1969 ui_helpline__push("Press ESC to exit");
1971 evlist__for_each(evlist, pos) {
1972 const char *ev_name = perf_evsel__name(pos);
1973 size_t line_len = strlen(ev_name) + 7;
1975 if (menu.b.width < line_len)
1976 menu.b.width = line_len;
1979 return perf_evsel_menu__run(&menu, nr_entries, help, hbt);
1982 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
1983 struct hist_browser_timer *hbt,
1985 struct perf_session_env *env)
1987 int nr_entries = evlist->nr_entries;
1990 if (nr_entries == 1) {
1991 struct perf_evsel *first = perf_evlist__first(evlist);
1992 const char *ev_name = perf_evsel__name(first);
1994 return perf_evsel__hists_browse(first, nr_entries, help,
1995 ev_name, false, hbt, min_pcnt,
1999 if (symbol_conf.event_group) {
2000 struct perf_evsel *pos;
2003 evlist__for_each(evlist, pos) {
2004 if (perf_evsel__is_group_leader(pos))
2008 if (nr_entries == 1)
2012 return __perf_evlist__tui_browse_hists(evlist, nr_entries, help,
2013 hbt, min_pcnt, env);