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 "../../util/top.h"
14 #include "../../arch/common.h"
16 #include "../browser.h"
17 #include "../helpline.h"
26 struct hist_entry *he_selection;
27 struct map_symbol *selection;
32 u64 nr_non_filtered_entries;
33 u64 nr_callchain_rows;
36 extern void hist_browser__init_hpp(void);
38 static int hists__browser_title(struct hists *hists, char *bf, size_t size);
39 static void hist_browser__update_nr_entries(struct hist_browser *hb);
41 static struct rb_node *hists__filter_entries(struct rb_node *nd,
44 static bool hist_browser__has_filter(struct hist_browser *hb)
46 return hists__has_filter(hb->hists) || hb->min_pcnt;
49 static u32 hist_browser__nr_entries(struct hist_browser *hb)
53 if (hist_browser__has_filter(hb))
54 nr_entries = hb->nr_non_filtered_entries;
56 nr_entries = hb->hists->nr_entries;
58 return nr_entries + hb->nr_callchain_rows;
61 static void hist_browser__update_rows(struct hist_browser *hb)
63 struct ui_browser *browser = &hb->b;
64 u16 header_offset = hb->show_headers ? 1 : 0, index_row;
66 browser->rows = browser->height - header_offset;
68 * Verify if we were at the last line and that line isn't
69 * visibe because we now show the header line(s).
71 index_row = browser->index - browser->top_idx;
72 if (index_row >= browser->rows)
73 browser->index -= index_row - browser->rows + 1;
76 static void hist_browser__refresh_dimensions(struct ui_browser *browser)
78 struct hist_browser *hb = container_of(browser, struct hist_browser, b);
80 /* 3 == +/- toggle symbol before actual hist_entry rendering */
81 browser->width = 3 + (hists__sort_list_width(hb->hists) + sizeof("[k]"));
83 * FIXME: Just keeping existing behaviour, but this really should be
84 * before updating browser->width, as it will invalidate the
85 * calculation above. Fix this and the fallout in another
88 ui_browser__refresh_dimensions(browser);
89 hist_browser__update_rows(hb);
92 static void hist_browser__gotorc(struct hist_browser *browser, int row, int column)
94 u16 header_offset = browser->show_headers ? 1 : 0;
96 ui_browser__gotorc(&browser->b, row + header_offset, column);
99 static void hist_browser__reset(struct hist_browser *browser)
102 * The hists__remove_entry_filter() already folds non-filtered
103 * entries so we can assume it has 0 callchain rows.
105 browser->nr_callchain_rows = 0;
107 hist_browser__update_nr_entries(browser);
108 browser->b.nr_entries = hist_browser__nr_entries(browser);
109 hist_browser__refresh_dimensions(&browser->b);
110 ui_browser__reset_index(&browser->b);
113 static char tree__folded_sign(bool unfolded)
115 return unfolded ? '-' : '+';
118 static char map_symbol__folded(const struct map_symbol *ms)
120 return ms->has_children ? tree__folded_sign(ms->unfolded) : ' ';
123 static char hist_entry__folded(const struct hist_entry *he)
125 return map_symbol__folded(&he->ms);
128 static char callchain_list__folded(const struct callchain_list *cl)
130 return map_symbol__folded(&cl->ms);
133 static void map_symbol__set_folding(struct map_symbol *ms, bool unfold)
135 ms->unfolded = unfold ? ms->has_children : false;
138 static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
143 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
144 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
145 struct callchain_list *chain;
146 char folded_sign = ' '; /* No children */
148 list_for_each_entry(chain, &child->val, list) {
150 /* We need this because we may not have children */
151 folded_sign = callchain_list__folded(chain);
152 if (folded_sign == '+')
156 if (folded_sign == '-') /* Have children and they're unfolded */
157 n += callchain_node__count_rows_rb_tree(child);
163 static int callchain_node__count_rows(struct callchain_node *node)
165 struct callchain_list *chain;
166 bool unfolded = false;
169 list_for_each_entry(chain, &node->val, list) {
171 unfolded = chain->ms.unfolded;
175 n += callchain_node__count_rows_rb_tree(node);
180 static int callchain__count_rows(struct rb_root *chain)
185 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
186 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
187 n += callchain_node__count_rows(node);
193 static bool map_symbol__toggle_fold(struct map_symbol *ms)
198 if (!ms->has_children)
201 ms->unfolded = !ms->unfolded;
205 static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
207 struct rb_node *nd = rb_first(&node->rb_root);
209 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
210 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
211 struct callchain_list *chain;
214 list_for_each_entry(chain, &child->val, list) {
217 chain->ms.has_children = chain->list.next != &child->val ||
218 !RB_EMPTY_ROOT(&child->rb_root);
220 chain->ms.has_children = chain->list.next == &child->val &&
221 !RB_EMPTY_ROOT(&child->rb_root);
224 callchain_node__init_have_children_rb_tree(child);
228 static void callchain_node__init_have_children(struct callchain_node *node)
230 struct callchain_list *chain;
232 if (!list_empty(&node->val)) {
233 chain = list_entry(node->val.prev, struct callchain_list, list);
234 chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root);
237 callchain_node__init_have_children_rb_tree(node);
240 static void callchain__init_have_children(struct rb_root *root)
244 for (nd = rb_first(root); nd; nd = rb_next(nd)) {
245 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
246 callchain_node__init_have_children(node);
250 static void hist_entry__init_have_children(struct hist_entry *he)
252 if (!he->init_have_children) {
253 he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
254 callchain__init_have_children(&he->sorted_chain);
255 he->init_have_children = true;
259 static bool hist_browser__toggle_fold(struct hist_browser *browser)
261 if (map_symbol__toggle_fold(browser->selection)) {
262 struct hist_entry *he = browser->he_selection;
264 hist_entry__init_have_children(he);
265 browser->b.nr_entries -= he->nr_rows;
266 browser->nr_callchain_rows -= he->nr_rows;
269 he->nr_rows = callchain__count_rows(&he->sorted_chain);
273 browser->b.nr_entries += he->nr_rows;
274 browser->nr_callchain_rows += he->nr_rows;
279 /* If it doesn't have children, no toggling performed */
283 static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
288 for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
289 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
290 struct callchain_list *chain;
291 bool has_children = false;
293 list_for_each_entry(chain, &child->val, list) {
295 map_symbol__set_folding(&chain->ms, unfold);
296 has_children = chain->ms.has_children;
300 n += callchain_node__set_folding_rb_tree(child, unfold);
306 static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
308 struct callchain_list *chain;
309 bool has_children = false;
312 list_for_each_entry(chain, &node->val, list) {
314 map_symbol__set_folding(&chain->ms, unfold);
315 has_children = chain->ms.has_children;
319 n += callchain_node__set_folding_rb_tree(node, unfold);
324 static int callchain__set_folding(struct rb_root *chain, bool unfold)
329 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
330 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
331 n += callchain_node__set_folding(node, unfold);
337 static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
339 hist_entry__init_have_children(he);
340 map_symbol__set_folding(&he->ms, unfold);
342 if (he->ms.has_children) {
343 int n = callchain__set_folding(&he->sorted_chain, unfold);
344 he->nr_rows = unfold ? n : 0;
350 __hist_browser__set_folding(struct hist_browser *browser, bool unfold)
353 struct hists *hists = browser->hists;
355 for (nd = rb_first(&hists->entries);
356 (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
358 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
359 hist_entry__set_folding(he, unfold);
360 browser->nr_callchain_rows += he->nr_rows;
364 static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
366 browser->nr_callchain_rows = 0;
367 __hist_browser__set_folding(browser, unfold);
369 browser->b.nr_entries = hist_browser__nr_entries(browser);
370 /* Go to the start, we may be way after valid entries after a collapse */
371 ui_browser__reset_index(&browser->b);
374 static void ui_browser__warn_lost_events(struct ui_browser *browser)
376 ui_browser__warning(browser, 4,
377 "Events are being lost, check IO/CPU overload!\n\n"
378 "You may want to run 'perf' using a RT scheduler policy:\n\n"
379 " perf top -r 80\n\n"
380 "Or reduce the sampling frequency.");
383 static int hist_browser__run(struct hist_browser *browser,
384 struct hist_browser_timer *hbt)
388 int delay_secs = hbt ? hbt->refresh : 0;
390 browser->b.entries = &browser->hists->entries;
391 browser->b.nr_entries = hist_browser__nr_entries(browser);
393 hists__browser_title(browser->hists, title, sizeof(title));
395 if (ui_browser__show(&browser->b, title,
396 "Press '?' for help on key bindings") < 0)
400 key = ui_browser__run(&browser->b, delay_secs);
405 hbt->timer(hbt->arg);
407 if (hist_browser__has_filter(browser))
408 hist_browser__update_nr_entries(browser);
410 nr_entries = hist_browser__nr_entries(browser);
411 ui_browser__update_nr_entries(&browser->b, nr_entries);
413 if (browser->hists->stats.nr_lost_warned !=
414 browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
415 browser->hists->stats.nr_lost_warned =
416 browser->hists->stats.nr_events[PERF_RECORD_LOST];
417 ui_browser__warn_lost_events(&browser->b);
420 hists__browser_title(browser->hists, title, sizeof(title));
421 ui_browser__show_title(&browser->b, title);
424 case 'D': { /* Debug */
426 struct hist_entry *h = rb_entry(browser->b.top,
427 struct hist_entry, rb_node);
429 ui_helpline__fpush("%d: nr_ent=(%d,%d), rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
430 seq++, browser->b.nr_entries,
431 browser->hists->nr_entries,
435 h->row_offset, h->nr_rows);
439 /* Collapse the whole world. */
440 hist_browser__set_folding(browser, false);
443 /* Expand the whole world. */
444 hist_browser__set_folding(browser, true);
447 browser->show_headers = !browser->show_headers;
448 hist_browser__update_rows(browser);
451 if (hist_browser__toggle_fold(browser))
459 ui_browser__hide(&browser->b);
463 static char *callchain_list__sym_name(struct callchain_list *cl,
464 char *bf, size_t bfsize, bool show_dso)
469 printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
471 printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
474 scnprintf(bf + printed, bfsize - printed, " %s",
475 cl->ms.map ? cl->ms.map->dso->short_name : "unknown");
480 struct callchain_print_arg {
481 /* for hists browser */
483 bool is_current_entry;
490 typedef void (*print_callchain_entry_fn)(struct hist_browser *browser,
491 struct callchain_list *chain,
492 const char *str, int offset,
494 struct callchain_print_arg *arg);
496 static void hist_browser__show_callchain_entry(struct hist_browser *browser,
497 struct callchain_list *chain,
498 const char *str, int offset,
500 struct callchain_print_arg *arg)
503 char folded_sign = callchain_list__folded(chain);
505 color = HE_COLORSET_NORMAL;
506 width = browser->b.width - (offset + 2);
507 if (ui_browser__is_current_entry(&browser->b, row)) {
508 browser->selection = &chain->ms;
509 color = HE_COLORSET_SELECTED;
510 arg->is_current_entry = true;
513 ui_browser__set_color(&browser->b, color);
514 hist_browser__gotorc(browser, row, 0);
515 slsmg_write_nstring(" ", offset);
516 slsmg_printf("%c ", folded_sign);
517 slsmg_write_nstring(str, width);
520 static void hist_browser__fprintf_callchain_entry(struct hist_browser *b __maybe_unused,
521 struct callchain_list *chain,
522 const char *str, int offset,
523 unsigned short row __maybe_unused,
524 struct callchain_print_arg *arg)
526 char folded_sign = callchain_list__folded(chain);
528 arg->printed += fprintf(arg->fp, "%*s%c %s\n", offset, " ",
532 typedef bool (*check_output_full_fn)(struct hist_browser *browser,
535 static bool hist_browser__check_output_full(struct hist_browser *browser,
538 return browser->b.rows == row;
541 static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_unused,
542 unsigned short row __maybe_unused)
547 #define LEVEL_OFFSET_STEP 3
549 static int hist_browser__show_callchain(struct hist_browser *browser,
550 struct rb_root *root, int level,
551 unsigned short row, u64 total,
552 print_callchain_entry_fn print,
553 struct callchain_print_arg *arg,
554 check_output_full_fn is_output_full)
556 struct rb_node *node;
557 int first_row = row, offset = level * LEVEL_OFFSET_STEP;
560 node = rb_first(root);
562 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
563 struct rb_node *next = rb_next(node);
564 u64 cumul = callchain_cumul_hits(child);
565 struct callchain_list *chain;
566 char folded_sign = ' ';
568 int extra_offset = 0;
570 list_for_each_entry(chain, &child->val, list) {
571 char bf[1024], *alloc_str;
573 bool was_first = first;
578 extra_offset = LEVEL_OFFSET_STEP;
580 folded_sign = callchain_list__folded(chain);
581 if (arg->row_offset != 0) {
587 str = callchain_list__sym_name(chain, bf, sizeof(bf),
590 if (was_first && level > 1) {
591 double percent = cumul * 100.0 / total;
593 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
594 str = "Not enough memory!";
599 print(browser, chain, str, offset + extra_offset, row, arg);
603 if (is_output_full(browser, ++row))
606 if (folded_sign == '+')
610 if (folded_sign == '-') {
611 const int new_level = level + (extra_offset ? 2 : 1);
613 if (callchain_param.mode == CHAIN_GRAPH_REL)
614 new_total = child->children_hit;
618 row += hist_browser__show_callchain(browser, &child->rb_root,
619 new_level, row, new_total,
620 print, arg, is_output_full);
622 if (is_output_full(browser, row))
627 return row - first_row;
631 struct ui_browser *b;
636 static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
638 struct hpp_arg *arg = hpp->ptr;
644 len = va_arg(args, int);
645 percent = va_arg(args, double);
648 ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
650 ret = scnprintf(hpp->buf, hpp->size, fmt, len, percent);
651 slsmg_printf("%s", hpp->buf);
653 advance_hpp(hpp, ret);
657 #define __HPP_COLOR_PERCENT_FN(_type, _field) \
658 static u64 __hpp_get_##_field(struct hist_entry *he) \
660 return he->stat._field; \
664 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
665 struct perf_hpp *hpp, \
666 struct hist_entry *he) \
668 return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%", \
669 __hpp__slsmg_color_printf, true); \
672 #define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
673 static u64 __hpp_get_acc_##_field(struct hist_entry *he) \
675 return he->stat_acc->_field; \
679 hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
680 struct perf_hpp *hpp, \
681 struct hist_entry *he) \
683 if (!symbol_conf.cumulate_callchain) { \
684 int len = fmt->user_len ?: fmt->len; \
685 int ret = scnprintf(hpp->buf, hpp->size, \
686 "%*s", len, "N/A"); \
687 slsmg_printf("%s", hpp->buf); \
691 return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \
692 " %*.2f%%", __hpp__slsmg_color_printf, true); \
695 __HPP_COLOR_PERCENT_FN(overhead, period)
696 __HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
697 __HPP_COLOR_PERCENT_FN(overhead_us, period_us)
698 __HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
699 __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
700 __HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period)
702 #undef __HPP_COLOR_PERCENT_FN
703 #undef __HPP_COLOR_ACC_PERCENT_FN
705 void hist_browser__init_hpp(void)
707 perf_hpp__format[PERF_HPP__OVERHEAD].color =
708 hist_browser__hpp_color_overhead;
709 perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
710 hist_browser__hpp_color_overhead_sys;
711 perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
712 hist_browser__hpp_color_overhead_us;
713 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
714 hist_browser__hpp_color_overhead_guest_sys;
715 perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
716 hist_browser__hpp_color_overhead_guest_us;
717 perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
718 hist_browser__hpp_color_overhead_acc;
721 static int hist_browser__show_entry(struct hist_browser *browser,
722 struct hist_entry *entry,
727 int width = browser->b.width;
728 char folded_sign = ' ';
729 bool current_entry = ui_browser__is_current_entry(&browser->b, row);
730 off_t row_offset = entry->row_offset;
732 struct perf_hpp_fmt *fmt;
735 browser->he_selection = entry;
736 browser->selection = &entry->ms;
739 if (symbol_conf.use_callchain) {
740 hist_entry__init_have_children(entry);
741 folded_sign = hist_entry__folded(entry);
744 if (row_offset == 0) {
745 struct hpp_arg arg = {
747 .folded_sign = folded_sign,
748 .current_entry = current_entry,
750 struct perf_hpp hpp = {
756 hist_browser__gotorc(browser, row, 0);
758 perf_hpp__for_each_format(fmt) {
759 if (perf_hpp__should_skip(fmt))
762 if (current_entry && browser->b.navkeypressed) {
763 ui_browser__set_color(&browser->b,
764 HE_COLORSET_SELECTED);
766 ui_browser__set_color(&browser->b,
771 if (symbol_conf.use_callchain) {
772 slsmg_printf("%c ", folded_sign);
782 width -= fmt->color(fmt, &hpp, entry);
784 width -= fmt->entry(fmt, &hpp, entry);
785 slsmg_printf("%s", s);
789 /* The scroll bar isn't being used */
790 if (!browser->b.navkeypressed)
793 slsmg_write_nstring("", width);
800 if (folded_sign == '-' && row != browser->b.rows) {
801 u64 total = hists__total_period(entry->hists);
802 struct callchain_print_arg arg = {
803 .row_offset = row_offset,
804 .is_current_entry = current_entry,
807 printed += hist_browser__show_callchain(browser,
808 &entry->sorted_chain, 1, row, total,
809 hist_browser__show_callchain_entry, &arg,
810 hist_browser__check_output_full);
812 if (arg.is_current_entry)
813 browser->he_selection = entry;
819 static int advance_hpp_check(struct perf_hpp *hpp, int inc)
821 advance_hpp(hpp, inc);
822 return hpp->size <= 0;
825 static int hists__scnprintf_headers(char *buf, size_t size, struct hists *hists)
827 struct perf_hpp dummy_hpp = {
831 struct perf_hpp_fmt *fmt;
834 if (symbol_conf.use_callchain) {
835 ret = scnprintf(buf, size, " ");
836 if (advance_hpp_check(&dummy_hpp, ret))
840 perf_hpp__for_each_format(fmt) {
841 if (perf_hpp__should_skip(fmt))
844 ret = fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists));
845 if (advance_hpp_check(&dummy_hpp, ret))
848 ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " ");
849 if (advance_hpp_check(&dummy_hpp, ret))
856 static void hist_browser__show_headers(struct hist_browser *browser)
860 hists__scnprintf_headers(headers, sizeof(headers), browser->hists);
861 ui_browser__gotorc(&browser->b, 0, 0);
862 ui_browser__set_color(&browser->b, HE_COLORSET_ROOT);
863 slsmg_write_nstring(headers, browser->b.width + 1);
866 static void ui_browser__hists_init_top(struct ui_browser *browser)
868 if (browser->top == NULL) {
869 struct hist_browser *hb;
871 hb = container_of(browser, struct hist_browser, b);
872 browser->top = rb_first(&hb->hists->entries);
876 static unsigned int hist_browser__refresh(struct ui_browser *browser)
879 u16 header_offset = 0;
881 struct hist_browser *hb = container_of(browser, struct hist_browser, b);
883 if (hb->show_headers) {
884 hist_browser__show_headers(hb);
888 ui_browser__hists_init_top(browser);
890 for (nd = browser->top; nd; nd = rb_next(nd)) {
891 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
897 percent = hist_entry__get_percent_limit(h);
898 if (percent < hb->min_pcnt)
901 row += hist_browser__show_entry(hb, h, row);
902 if (row == browser->rows)
906 return row + header_offset;
909 static struct rb_node *hists__filter_entries(struct rb_node *nd,
913 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
914 float percent = hist_entry__get_percent_limit(h);
916 if (!h->filtered && percent >= min_pcnt)
925 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
929 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
930 float percent = hist_entry__get_percent_limit(h);
932 if (!h->filtered && percent >= min_pcnt)
941 static void ui_browser__hists_seek(struct ui_browser *browser,
942 off_t offset, int whence)
944 struct hist_entry *h;
947 struct hist_browser *hb;
949 hb = container_of(browser, struct hist_browser, b);
951 if (browser->nr_entries == 0)
954 ui_browser__hists_init_top(browser);
958 nd = hists__filter_entries(rb_first(browser->entries),
965 nd = hists__filter_prev_entries(rb_last(browser->entries),
974 * Moves not relative to the first visible entry invalidates its
977 h = rb_entry(browser->top, struct hist_entry, rb_node);
981 * Here we have to check if nd is expanded (+), if it is we can't go
982 * the next top level hist_entry, instead we must compute an offset of
983 * what _not_ to show and not change the first visible entry.
985 * This offset increments when we are going from top to bottom and
986 * decreases when we're going from bottom to top.
988 * As we don't have backpointers to the top level in the callchains
989 * structure, we need to always print the whole hist_entry callchain,
990 * skipping the first ones that are before the first visible entry
991 * and stop when we printed enough lines to fill the screen.
996 h = rb_entry(nd, struct hist_entry, rb_node);
997 if (h->ms.unfolded) {
998 u16 remaining = h->nr_rows - h->row_offset;
999 if (offset > remaining) {
1000 offset -= remaining;
1003 h->row_offset += offset;
1009 nd = hists__filter_entries(rb_next(nd), hb->min_pcnt);
1014 } while (offset != 0);
1015 } else if (offset < 0) {
1017 h = rb_entry(nd, struct hist_entry, rb_node);
1018 if (h->ms.unfolded) {
1020 if (-offset > h->row_offset) {
1021 offset += h->row_offset;
1024 h->row_offset += offset;
1030 if (-offset > h->nr_rows) {
1031 offset += h->nr_rows;
1034 h->row_offset = h->nr_rows + offset;
1042 nd = hists__filter_prev_entries(rb_prev(nd),
1050 * Last unfiltered hist_entry, check if it is
1051 * unfolded, if it is then we should have
1052 * row_offset at its last entry.
1054 h = rb_entry(nd, struct hist_entry, rb_node);
1056 h->row_offset = h->nr_rows;
1063 h = rb_entry(nd, struct hist_entry, rb_node);
1068 static int hist_browser__fprintf_callchain(struct hist_browser *browser,
1069 struct hist_entry *he, FILE *fp)
1071 u64 total = hists__total_period(he->hists);
1072 struct callchain_print_arg arg = {
1076 if (symbol_conf.cumulate_callchain)
1077 total = he->stat_acc->period;
1079 hist_browser__show_callchain(browser, &he->sorted_chain, 1, 0, total,
1080 hist_browser__fprintf_callchain_entry, &arg,
1081 hist_browser__check_dump_full);
1085 static int hist_browser__fprintf_entry(struct hist_browser *browser,
1086 struct hist_entry *he, FILE *fp)
1090 char folded_sign = ' ';
1091 struct perf_hpp hpp = {
1095 struct perf_hpp_fmt *fmt;
1099 if (symbol_conf.use_callchain)
1100 folded_sign = hist_entry__folded(he);
1102 if (symbol_conf.use_callchain)
1103 printed += fprintf(fp, "%c ", folded_sign);
1105 perf_hpp__for_each_format(fmt) {
1106 if (perf_hpp__should_skip(fmt))
1110 ret = scnprintf(hpp.buf, hpp.size, " ");
1111 advance_hpp(&hpp, ret);
1115 ret = fmt->entry(fmt, &hpp, he);
1116 advance_hpp(&hpp, ret);
1118 printed += fprintf(fp, "%s\n", rtrim(s));
1120 if (folded_sign == '-')
1121 printed += hist_browser__fprintf_callchain(browser, he, fp);
1126 static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
1128 struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
1133 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1135 printed += hist_browser__fprintf_entry(browser, h, fp);
1136 nd = hists__filter_entries(rb_next(nd), browser->min_pcnt);
1142 static int hist_browser__dump(struct hist_browser *browser)
1148 scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
1149 if (access(filename, F_OK))
1152 * XXX: Just an arbitrary lazy upper limit
1154 if (++browser->print_seq == 8192) {
1155 ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
1160 fp = fopen(filename, "w");
1163 const char *err = strerror_r(errno, bf, sizeof(bf));
1164 ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
1168 ++browser->print_seq;
1169 hist_browser__fprintf(browser, fp);
1171 ui_helpline__fpush("%s written!", filename);
1176 static struct hist_browser *hist_browser__new(struct hists *hists)
1178 struct hist_browser *browser = zalloc(sizeof(*browser));
1181 browser->hists = hists;
1182 browser->b.refresh = hist_browser__refresh;
1183 browser->b.refresh_dimensions = hist_browser__refresh_dimensions;
1184 browser->b.seek = ui_browser__hists_seek;
1185 browser->b.use_navkeypressed = true;
1186 browser->show_headers = symbol_conf.show_hist_headers;
1192 static void hist_browser__delete(struct hist_browser *browser)
1197 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
1199 return browser->he_selection;
1202 static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
1204 return browser->he_selection->thread;
1207 static int hists__browser_title(struct hists *hists, char *bf, size_t size)
1211 const struct dso *dso = hists->dso_filter;
1212 const struct thread *thread = hists->thread_filter;
1213 unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
1214 u64 nr_events = hists->stats.total_period;
1215 struct perf_evsel *evsel = hists_to_evsel(hists);
1216 const char *ev_name = perf_evsel__name(evsel);
1218 size_t buflen = sizeof(buf);
1220 if (symbol_conf.filter_relative) {
1221 nr_samples = hists->stats.nr_non_filtered_samples;
1222 nr_events = hists->stats.total_non_filtered_period;
1225 if (perf_evsel__is_group_event(evsel)) {
1226 struct perf_evsel *pos;
1228 perf_evsel__group_desc(evsel, buf, buflen);
1231 for_each_group_member(pos, evsel) {
1232 if (symbol_conf.filter_relative) {
1233 nr_samples += pos->hists.stats.nr_non_filtered_samples;
1234 nr_events += pos->hists.stats.total_non_filtered_period;
1236 nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1237 nr_events += pos->hists.stats.total_period;
1242 nr_samples = convert_unit(nr_samples, &unit);
1243 printed = scnprintf(bf, size,
1244 "Samples: %lu%c of event '%s', Event count (approx.): %lu",
1245 nr_samples, unit, ev_name, nr_events);
1248 if (hists->uid_filter_str)
1249 printed += snprintf(bf + printed, size - printed,
1250 ", UID: %s", hists->uid_filter_str);
1252 printed += scnprintf(bf + printed, size - printed,
1254 (thread->comm_set ? thread__comm_str(thread) : ""),
1257 printed += scnprintf(bf + printed, size - printed,
1258 ", DSO: %s", dso->short_name);
1262 static inline void free_popup_options(char **options, int n)
1266 for (i = 0; i < n; ++i)
1270 /* Check whether the browser is for 'top' or 'report' */
1271 static inline bool is_report_browser(void *timer)
1273 return timer == NULL;
1277 * Only runtime switching of perf data file will make "input_name" point
1278 * to a malloced buffer. So add "is_input_name_malloced" flag to decide
1279 * whether we need to call free() for current "input_name" during the switch.
1281 static bool is_input_name_malloced = false;
1283 static int switch_data_file(void)
1285 char *pwd, *options[32], *abs_path[32], *tmp;
1287 int nr_options = 0, choice = -1, ret = -1;
1288 struct dirent *dent;
1290 pwd = getenv("PWD");
1294 pwd_dir = opendir(pwd);
1298 memset(options, 0, sizeof(options));
1299 memset(options, 0, sizeof(abs_path));
1301 while ((dent = readdir(pwd_dir))) {
1302 char path[PATH_MAX];
1304 char *name = dent->d_name;
1307 if (!(dent->d_type == DT_REG))
1310 snprintf(path, sizeof(path), "%s/%s", pwd, name);
1312 file = fopen(path, "r");
1316 if (fread(&magic, 1, 8, file) < 8)
1317 goto close_file_and_continue;
1319 if (is_perf_magic(magic)) {
1320 options[nr_options] = strdup(name);
1321 if (!options[nr_options])
1322 goto close_file_and_continue;
1324 abs_path[nr_options] = strdup(path);
1325 if (!abs_path[nr_options]) {
1326 zfree(&options[nr_options]);
1327 ui__warning("Can't search all data files due to memory shortage.\n");
1335 close_file_and_continue:
1337 if (nr_options >= 32) {
1338 ui__warning("Too many perf data files in PWD!\n"
1339 "Only the first 32 files will be listed.\n");
1346 choice = ui__popup_menu(nr_options, options);
1347 if (choice < nr_options && choice >= 0) {
1348 tmp = strdup(abs_path[choice]);
1350 if (is_input_name_malloced)
1351 free((void *)input_name);
1353 is_input_name_malloced = true;
1356 ui__warning("Data switch failed due to memory shortage!\n");
1360 free_popup_options(options, nr_options);
1361 free_popup_options(abs_path, nr_options);
1365 static void hist_browser__update_nr_entries(struct hist_browser *hb)
1368 struct rb_node *nd = rb_first(&hb->hists->entries);
1370 if (hb->min_pcnt == 0) {
1371 hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
1375 while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
1380 hb->nr_non_filtered_entries = nr_entries;
1383 static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
1384 const char *helpline,
1386 struct hist_browser_timer *hbt,
1388 struct perf_session_env *env)
1390 struct hists *hists = &evsel->hists;
1391 struct hist_browser *browser = hist_browser__new(hists);
1392 struct branch_info *bi;
1393 struct pstack *fstack;
1398 char script_opt[64];
1399 int delay_secs = hbt ? hbt->refresh : 0;
1400 struct perf_hpp_fmt *fmt;
1402 #define HIST_BROWSER_HELP_COMMON \
1403 "h/?/F1 Show this window\n" \
1405 "PGDN/SPACE Navigate\n" \
1406 "q/ESC/CTRL+C Exit browser\n\n" \
1407 "For multiple event sessions:\n\n" \
1408 "TAB/UNTAB Switch events\n\n" \
1409 "For symbolic views (--sort has sym):\n\n" \
1410 "-> Zoom into DSO/Threads & Annotate current symbol\n" \
1412 "a Annotate current symbol\n" \
1413 "C Collapse all callchains\n" \
1414 "d Zoom into current DSO\n" \
1415 "E Expand all callchains\n" \
1416 "F Toggle percentage of filtered entries\n" \
1417 "H Display column headers\n" \
1419 /* help messages are sorted by lexical order of the hotkey */
1420 const char report_help[] = HIST_BROWSER_HELP_COMMON
1421 "i Show header information\n"
1422 "P Print histograms to perf.hist.N\n"
1423 "r Run available scripts\n"
1424 "s Switch to another data file in PWD\n"
1425 "t Zoom into current Thread\n"
1426 "V Verbose (DSO names in callchains, etc)\n"
1427 "/ Filter symbol by name";
1428 const char top_help[] = HIST_BROWSER_HELP_COMMON
1429 "P Print histograms to perf.hist.N\n"
1430 "t Zoom into current Thread\n"
1431 "V Verbose (DSO names in callchains, etc)\n"
1432 "z Toggle zeroing of samples\n"
1433 "/ Filter symbol by name";
1435 if (browser == NULL)
1439 browser->min_pcnt = min_pcnt;
1440 hist_browser__update_nr_entries(browser);
1443 fstack = pstack__new(2);
1447 ui_helpline__push(helpline);
1449 memset(options, 0, sizeof(options));
1451 perf_hpp__for_each_format(fmt)
1452 perf_hpp__reset_width(fmt, hists);
1454 if (symbol_conf.col_width_list_str)
1455 perf_hpp__set_user_width(symbol_conf.col_width_list_str);
1458 const struct thread *thread = NULL;
1459 const struct dso *dso = NULL;
1461 annotate = -2, zoom_dso = -2, zoom_thread = -2,
1462 annotate_f = -2, annotate_t = -2, browse_map = -2;
1463 int scripts_comm = -2, scripts_symbol = -2,
1464 scripts_all = -2, switch_data = -2;
1468 key = hist_browser__run(browser, hbt);
1470 if (browser->he_selection != NULL) {
1471 thread = hist_browser__selected_thread(browser);
1472 dso = browser->selection->map ? browser->selection->map->dso : NULL;
1480 * Exit the browser, let hists__browser_tree
1481 * go to the next or previous
1483 goto out_free_stack;
1485 if (!sort__has_sym) {
1486 ui_browser__warning(&browser->b, delay_secs * 2,
1487 "Annotation is only available for symbolic views, "
1488 "include \"sym*\" in --sort to use it.");
1492 if (browser->selection == NULL ||
1493 browser->selection->sym == NULL ||
1494 browser->selection->map->dso->annotate_warned)
1498 hist_browser__dump(browser);
1503 browser->show_dso = !browser->show_dso;
1508 if (ui_browser__input_window("Symbol to show",
1509 "Please enter the name of symbol you want to see",
1510 buf, "ENTER: OK, ESC: Cancel",
1511 delay_secs * 2) == K_ENTER) {
1512 hists->symbol_filter_str = *buf ? buf : NULL;
1513 hists__filter_by_symbol(hists);
1514 hist_browser__reset(browser);
1518 if (is_report_browser(hbt))
1522 if (is_report_browser(hbt))
1523 goto do_data_switch;
1526 /* env->arch is NULL for live-mode (i.e. perf top) */
1528 tui__header_window(env);
1531 symbol_conf.filter_relative ^= 1;
1534 if (!is_report_browser(hbt)) {
1535 struct perf_top *top = hbt->arg;
1537 top->zero = !top->zero;
1543 ui_browser__help_window(&browser->b,
1544 is_report_browser(hbt) ? report_help : top_help);
1553 if (pstack__empty(fstack)) {
1555 * Go back to the perf_evsel_menu__run or other user
1558 goto out_free_stack;
1561 top = pstack__pop(fstack);
1562 if (top == &browser->hists->dso_filter)
1564 if (top == &browser->hists->thread_filter)
1565 goto zoom_out_thread;
1570 !ui_browser__dialog_yesno(&browser->b,
1571 "Do you really want to exit?"))
1576 goto out_free_stack;
1582 goto add_exit_option;
1584 if (sort__mode == SORT_MODE__BRANCH) {
1585 bi = browser->he_selection->branch_info;
1586 if (browser->selection != NULL &&
1588 bi->from.sym != NULL &&
1589 !bi->from.map->dso->annotate_warned &&
1590 asprintf(&options[nr_options], "Annotate %s",
1591 bi->from.sym->name) > 0)
1592 annotate_f = nr_options++;
1594 if (browser->selection != NULL &&
1596 bi->to.sym != NULL &&
1597 !bi->to.map->dso->annotate_warned &&
1598 (bi->to.sym != bi->from.sym ||
1599 bi->to.map->dso != bi->from.map->dso) &&
1600 asprintf(&options[nr_options], "Annotate %s",
1601 bi->to.sym->name) > 0)
1602 annotate_t = nr_options++;
1604 if (browser->selection != NULL &&
1605 browser->selection->sym != NULL &&
1606 !browser->selection->map->dso->annotate_warned) {
1607 struct annotation *notes;
1609 notes = symbol__annotation(browser->selection->sym);
1612 asprintf(&options[nr_options], "Annotate %s",
1613 browser->selection->sym->name) > 0)
1614 annotate = nr_options++;
1618 if (thread != NULL &&
1619 asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
1620 (browser->hists->thread_filter ? "out of" : "into"),
1621 (thread->comm_set ? thread__comm_str(thread) : ""),
1623 zoom_thread = nr_options++;
1626 asprintf(&options[nr_options], "Zoom %s %s DSO",
1627 (browser->hists->dso_filter ? "out of" : "into"),
1628 (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
1629 zoom_dso = nr_options++;
1631 if (browser->selection != NULL &&
1632 browser->selection->map != NULL &&
1633 asprintf(&options[nr_options], "Browse map details") > 0)
1634 browse_map = nr_options++;
1636 /* perf script support */
1637 if (browser->he_selection) {
1640 if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]",
1641 thread__comm_str(browser->he_selection->thread)) > 0)
1642 scripts_comm = nr_options++;
1644 sym = browser->he_selection->ms.sym;
1645 if (sym && sym->namelen &&
1646 asprintf(&options[nr_options], "Run scripts for samples of symbol [%s]",
1648 scripts_symbol = nr_options++;
1651 if (asprintf(&options[nr_options], "Run scripts for all samples") > 0)
1652 scripts_all = nr_options++;
1654 if (is_report_browser(hbt) && asprintf(&options[nr_options],
1655 "Switch to another data file in PWD") > 0)
1656 switch_data = nr_options++;
1658 options[nr_options++] = (char *)"Exit";
1660 choice = ui__popup_menu(nr_options, options);
1662 if (choice == nr_options - 1)
1666 free_popup_options(options, nr_options - 1);
1670 if (choice == annotate || choice == annotate_t || choice == annotate_f) {
1671 struct hist_entry *he;
1672 struct annotation *notes;
1675 if (!objdump_path && perf_session_env__lookup_objdump(env))
1678 he = hist_browser__selected_entry(browser);
1683 * we stash the branch_info symbol + map into the
1684 * the ms so we don't have to rewrite all the annotation
1685 * code to use branch_info.
1686 * in branch mode, the ms struct is not used
1688 if (choice == annotate_f) {
1689 he->ms.sym = he->branch_info->from.sym;
1690 he->ms.map = he->branch_info->from.map;
1691 } else if (choice == annotate_t) {
1692 he->ms.sym = he->branch_info->to.sym;
1693 he->ms.map = he->branch_info->to.map;
1696 notes = symbol__annotation(he->ms.sym);
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 perf_hpp__set_elide(HISTC_DSO, 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 perf_hpp__set_elide(HISTC_DSO, 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 perf_hpp__set_elide(HISTC_THREAD, 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 perf_hpp__set_elide(HISTC_THREAD, false);
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 *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 key = perf_evsel__hists_browse(pos, nr_events, help,
1890 ui_browser__show_title(&menu->b, title);
1893 if (pos->node.next == &evlist->entries)
1894 pos = perf_evlist__first(evlist);
1896 pos = perf_evsel__next(pos);
1899 if (pos->node.prev == &evlist->entries)
1900 pos = perf_evlist__last(evlist);
1902 pos = perf_evsel__prev(pos);
1905 if (!ui_browser__dialog_yesno(&menu->b,
1906 "Do you really want to exit?"))
1909 case K_SWITCH_INPUT_DATA:
1919 if (!ui_browser__dialog_yesno(&menu->b,
1920 "Do you really want to exit?"))
1932 ui_browser__hide(&menu->b);
1936 static bool filter_group_entries(struct ui_browser *browser __maybe_unused,
1939 struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1941 if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel))
1947 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1948 int nr_entries, const char *help,
1949 struct hist_browser_timer *hbt,
1951 struct perf_session_env *env)
1953 struct perf_evsel *pos;
1954 struct perf_evsel_menu menu = {
1956 .entries = &evlist->entries,
1957 .refresh = ui_browser__list_head_refresh,
1958 .seek = ui_browser__list_head_seek,
1959 .write = perf_evsel_menu__write,
1960 .filter = filter_group_entries,
1961 .nr_entries = nr_entries,
1964 .min_pcnt = min_pcnt,
1968 ui_helpline__push("Press ESC to exit");
1970 evlist__for_each(evlist, pos) {
1971 const char *ev_name = perf_evsel__name(pos);
1972 size_t line_len = strlen(ev_name) + 7;
1974 if (menu.b.width < line_len)
1975 menu.b.width = line_len;
1978 return perf_evsel_menu__run(&menu, nr_entries, help, hbt);
1981 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
1982 struct hist_browser_timer *hbt,
1984 struct perf_session_env *env)
1986 int nr_entries = evlist->nr_entries;
1989 if (nr_entries == 1) {
1990 struct perf_evsel *first = perf_evlist__first(evlist);
1992 return perf_evsel__hists_browse(first, nr_entries, help,
1993 false, hbt, min_pcnt,
1997 if (symbol_conf.event_group) {
1998 struct perf_evsel *pos;
2001 evlist__for_each(evlist, pos) {
2002 if (perf_evsel__is_group_leader(pos))
2006 if (nr_entries == 1)
2010 return __perf_evlist__tui_browse_hists(evlist, nr_entries, help,
2011 hbt, min_pcnt, env);