1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <linux/rbtree.h>
5
6#include "../../util/evsel.h"
7#include "../../util/evlist.h"
8#include "../../util/hist.h"
9#include "../../util/pstack.h"
10#include "../../util/sort.h"
11#include "../../util/util.h"
12#include "../../util/top.h"
13#include "../../arch/common.h"
14
15#include "../browser.h"
16#include "../helpline.h"
17#include "../util.h"
18#include "../ui.h"
19#include "map.h"
20#include "annotate.h"
21
22struct hist_browser {
23	struct ui_browser   b;
24	struct hists	    *hists;
25	struct hist_entry   *he_selection;
26	struct map_symbol   *selection;
27	struct hist_browser_timer *hbt;
28	struct pstack	    *pstack;
29	struct perf_env *env;
30	int		     print_seq;
31	bool		     show_dso;
32	bool		     show_headers;
33	float		     min_pcnt;
34	u64		     nr_non_filtered_entries;
35	u64		     nr_callchain_rows;
36};
37
38extern void hist_browser__init_hpp(void);
39
40static int hists__browser_title(struct hists *hists,
41				struct hist_browser_timer *hbt,
42				char *bf, size_t size);
43static void hist_browser__update_nr_entries(struct hist_browser *hb);
44
45static struct rb_node *hists__filter_entries(struct rb_node *nd,
46					     float min_pcnt);
47
48static bool hist_browser__has_filter(struct hist_browser *hb)
49{
50	return hists__has_filter(hb->hists) || hb->min_pcnt || symbol_conf.has_filter;
51}
52
53static int hist_browser__get_folding(struct hist_browser *browser)
54{
55	struct rb_node *nd;
56	struct hists *hists = browser->hists;
57	int unfolded_rows = 0;
58
59	for (nd = rb_first(&hists->entries);
60	     (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
61	     nd = rb_next(nd)) {
62		struct hist_entry *he =
63			rb_entry(nd, struct hist_entry, rb_node);
64
65		if (he->unfolded)
66			unfolded_rows += he->nr_rows;
67	}
68	return unfolded_rows;
69}
70
71static u32 hist_browser__nr_entries(struct hist_browser *hb)
72{
73	u32 nr_entries;
74
75	if (hist_browser__has_filter(hb))
76		nr_entries = hb->nr_non_filtered_entries;
77	else
78		nr_entries = hb->hists->nr_entries;
79
80	hb->nr_callchain_rows = hist_browser__get_folding(hb);
81	return nr_entries + hb->nr_callchain_rows;
82}
83
84static void hist_browser__update_rows(struct hist_browser *hb)
85{
86	struct ui_browser *browser = &hb->b;
87	u16 header_offset = hb->show_headers ? 1 : 0, index_row;
88
89	browser->rows = browser->height - header_offset;
90	/*
91	 * Verify if we were at the last line and that line isn't
92	 * visibe because we now show the header line(s).
93	 */
94	index_row = browser->index - browser->top_idx;
95	if (index_row >= browser->rows)
96		browser->index -= index_row - browser->rows + 1;
97}
98
99static void hist_browser__refresh_dimensions(struct ui_browser *browser)
100{
101	struct hist_browser *hb = container_of(browser, struct hist_browser, b);
102
103	/* 3 == +/- toggle symbol before actual hist_entry rendering */
104	browser->width = 3 + (hists__sort_list_width(hb->hists) + sizeof("[k]"));
105	/*
106 	 * FIXME: Just keeping existing behaviour, but this really should be
107 	 *	  before updating browser->width, as it will invalidate the
108 	 *	  calculation above. Fix this and the fallout in another
109 	 *	  changeset.
110 	 */
111	ui_browser__refresh_dimensions(browser);
112	hist_browser__update_rows(hb);
113}
114
115static void hist_browser__gotorc(struct hist_browser *browser, int row, int column)
116{
117	u16 header_offset = browser->show_headers ? 1 : 0;
118
119	ui_browser__gotorc(&browser->b, row + header_offset, column);
120}
121
122static void hist_browser__reset(struct hist_browser *browser)
123{
124	/*
125	 * The hists__remove_entry_filter() already folds non-filtered
126	 * entries so we can assume it has 0 callchain rows.
127	 */
128	browser->nr_callchain_rows = 0;
129
130	hist_browser__update_nr_entries(browser);
131	browser->b.nr_entries = hist_browser__nr_entries(browser);
132	hist_browser__refresh_dimensions(&browser->b);
133	ui_browser__reset_index(&browser->b);
134}
135
136static char tree__folded_sign(bool unfolded)
137{
138	return unfolded ? '-' : '+';
139}
140
141static char hist_entry__folded(const struct hist_entry *he)
142{
143	return he->has_children ? tree__folded_sign(he->unfolded) : ' ';
144}
145
146static char callchain_list__folded(const struct callchain_list *cl)
147{
148	return cl->has_children ? tree__folded_sign(cl->unfolded) : ' ';
149}
150
151static void callchain_list__set_folding(struct callchain_list *cl, bool unfold)
152{
153	cl->unfolded = unfold ? cl->has_children : false;
154}
155
156static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
157{
158	int n = 0;
159	struct rb_node *nd;
160
161	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
162		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
163		struct callchain_list *chain;
164		char folded_sign = ' '; /* No children */
165
166		list_for_each_entry(chain, &child->val, list) {
167			++n;
168			/* We need this because we may not have children */
169			folded_sign = callchain_list__folded(chain);
170			if (folded_sign == '+')
171				break;
172		}
173
174		if (folded_sign == '-') /* Have children and they're unfolded */
175			n += callchain_node__count_rows_rb_tree(child);
176	}
177
178	return n;
179}
180
181static int callchain_node__count_rows(struct callchain_node *node)
182{
183	struct callchain_list *chain;
184	bool unfolded = false;
185	int n = 0;
186
187	list_for_each_entry(chain, &node->val, list) {
188		++n;
189		unfolded = chain->unfolded;
190	}
191
192	if (unfolded)
193		n += callchain_node__count_rows_rb_tree(node);
194
195	return n;
196}
197
198static int callchain__count_rows(struct rb_root *chain)
199{
200	struct rb_node *nd;
201	int n = 0;
202
203	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
204		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
205		n += callchain_node__count_rows(node);
206	}
207
208	return n;
209}
210
211static bool hist_entry__toggle_fold(struct hist_entry *he)
212{
213	if (!he)
214		return false;
215
216	if (!he->has_children)
217		return false;
218
219	he->unfolded = !he->unfolded;
220	return true;
221}
222
223static bool callchain_list__toggle_fold(struct callchain_list *cl)
224{
225	if (!cl)
226		return false;
227
228	if (!cl->has_children)
229		return false;
230
231	cl->unfolded = !cl->unfolded;
232	return true;
233}
234
235static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
236{
237	struct rb_node *nd = rb_first(&node->rb_root);
238
239	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
240		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
241		struct callchain_list *chain;
242		bool first = true;
243
244		list_for_each_entry(chain, &child->val, list) {
245			if (first) {
246				first = false;
247				chain->has_children = chain->list.next != &child->val ||
248							 !RB_EMPTY_ROOT(&child->rb_root);
249			} else
250				chain->has_children = chain->list.next == &child->val &&
251							 !RB_EMPTY_ROOT(&child->rb_root);
252		}
253
254		callchain_node__init_have_children_rb_tree(child);
255	}
256}
257
258static void callchain_node__init_have_children(struct callchain_node *node,
259					       bool has_sibling)
260{
261	struct callchain_list *chain;
262
263	chain = list_entry(node->val.next, struct callchain_list, list);
264	chain->has_children = has_sibling;
265
266	if (!list_empty(&node->val)) {
267		chain = list_entry(node->val.prev, struct callchain_list, list);
268		chain->has_children = !RB_EMPTY_ROOT(&node->rb_root);
269	}
270
271	callchain_node__init_have_children_rb_tree(node);
272}
273
274static void callchain__init_have_children(struct rb_root *root)
275{
276	struct rb_node *nd = rb_first(root);
277	bool has_sibling = nd && rb_next(nd);
278
279	for (nd = rb_first(root); nd; nd = rb_next(nd)) {
280		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
281		callchain_node__init_have_children(node, has_sibling);
282	}
283}
284
285static void hist_entry__init_have_children(struct hist_entry *he)
286{
287	if (!he->init_have_children) {
288		he->has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
289		callchain__init_have_children(&he->sorted_chain);
290		he->init_have_children = true;
291	}
292}
293
294static bool hist_browser__toggle_fold(struct hist_browser *browser)
295{
296	struct hist_entry *he = browser->he_selection;
297	struct map_symbol *ms = browser->selection;
298	struct callchain_list *cl = container_of(ms, struct callchain_list, ms);
299	bool has_children;
300
301	if (!he || !ms)
302		return false;
303
304	if (ms == &he->ms)
305		has_children = hist_entry__toggle_fold(he);
306	else
307		has_children = callchain_list__toggle_fold(cl);
308
309	if (has_children) {
310		hist_entry__init_have_children(he);
311		browser->b.nr_entries -= he->nr_rows;
312		browser->nr_callchain_rows -= he->nr_rows;
313
314		if (he->unfolded)
315			he->nr_rows = callchain__count_rows(&he->sorted_chain);
316		else
317			he->nr_rows = 0;
318
319		browser->b.nr_entries += he->nr_rows;
320		browser->nr_callchain_rows += he->nr_rows;
321
322		return true;
323	}
324
325	/* If it doesn't have children, no toggling performed */
326	return false;
327}
328
329static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
330{
331	int n = 0;
332	struct rb_node *nd;
333
334	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
335		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
336		struct callchain_list *chain;
337		bool has_children = false;
338
339		list_for_each_entry(chain, &child->val, list) {
340			++n;
341			callchain_list__set_folding(chain, unfold);
342			has_children = chain->has_children;
343		}
344
345		if (has_children)
346			n += callchain_node__set_folding_rb_tree(child, unfold);
347	}
348
349	return n;
350}
351
352static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
353{
354	struct callchain_list *chain;
355	bool has_children = false;
356	int n = 0;
357
358	list_for_each_entry(chain, &node->val, list) {
359		++n;
360		callchain_list__set_folding(chain, unfold);
361		has_children = chain->has_children;
362	}
363
364	if (has_children)
365		n += callchain_node__set_folding_rb_tree(node, unfold);
366
367	return n;
368}
369
370static int callchain__set_folding(struct rb_root *chain, bool unfold)
371{
372	struct rb_node *nd;
373	int n = 0;
374
375	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
376		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
377		n += callchain_node__set_folding(node, unfold);
378	}
379
380	return n;
381}
382
383static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
384{
385	hist_entry__init_have_children(he);
386	he->unfolded = unfold ? he->has_children : false;
387
388	if (he->has_children) {
389		int n = callchain__set_folding(&he->sorted_chain, unfold);
390		he->nr_rows = unfold ? n : 0;
391	} else
392		he->nr_rows = 0;
393}
394
395static void
396__hist_browser__set_folding(struct hist_browser *browser, bool unfold)
397{
398	struct rb_node *nd;
399	struct hists *hists = browser->hists;
400
401	for (nd = rb_first(&hists->entries);
402	     (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
403	     nd = rb_next(nd)) {
404		struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
405		hist_entry__set_folding(he, unfold);
406		browser->nr_callchain_rows += he->nr_rows;
407	}
408}
409
410static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
411{
412	browser->nr_callchain_rows = 0;
413	__hist_browser__set_folding(browser, unfold);
414
415	browser->b.nr_entries = hist_browser__nr_entries(browser);
416	/* Go to the start, we may be way after valid entries after a collapse */
417	ui_browser__reset_index(&browser->b);
418}
419
420static void ui_browser__warn_lost_events(struct ui_browser *browser)
421{
422	ui_browser__warning(browser, 4,
423		"Events are being lost, check IO/CPU overload!\n\n"
424		"You may want to run 'perf' using a RT scheduler policy:\n\n"
425		" perf top -r 80\n\n"
426		"Or reduce the sampling frequency.");
427}
428
429static int hist_browser__run(struct hist_browser *browser, const char *help)
430{
431	int key;
432	char title[160];
433	struct hist_browser_timer *hbt = browser->hbt;
434	int delay_secs = hbt ? hbt->refresh : 0;
435
436	browser->b.entries = &browser->hists->entries;
437	browser->b.nr_entries = hist_browser__nr_entries(browser);
438
439	hists__browser_title(browser->hists, hbt, title, sizeof(title));
440
441	if (ui_browser__show(&browser->b, title, help) < 0)
442		return -1;
443
444	while (1) {
445		key = ui_browser__run(&browser->b, delay_secs);
446
447		switch (key) {
448		case K_TIMER: {
449			u64 nr_entries;
450			hbt->timer(hbt->arg);
451
452			if (hist_browser__has_filter(browser))
453				hist_browser__update_nr_entries(browser);
454
455			nr_entries = hist_browser__nr_entries(browser);
456			ui_browser__update_nr_entries(&browser->b, nr_entries);
457
458			if (browser->hists->stats.nr_lost_warned !=
459			    browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
460				browser->hists->stats.nr_lost_warned =
461					browser->hists->stats.nr_events[PERF_RECORD_LOST];
462				ui_browser__warn_lost_events(&browser->b);
463			}
464
465			hists__browser_title(browser->hists,
466					     hbt, title, sizeof(title));
467			ui_browser__show_title(&browser->b, title);
468			continue;
469		}
470		case 'D': { /* Debug */
471			static int seq;
472			struct hist_entry *h = rb_entry(browser->b.top,
473							struct hist_entry, rb_node);
474			ui_helpline__pop();
475			ui_helpline__fpush("%d: nr_ent=(%d,%d), rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
476					   seq++, browser->b.nr_entries,
477					   browser->hists->nr_entries,
478					   browser->b.rows,
479					   browser->b.index,
480					   browser->b.top_idx,
481					   h->row_offset, h->nr_rows);
482		}
483			break;
484		case 'C':
485			/* Collapse the whole world. */
486			hist_browser__set_folding(browser, false);
487			break;
488		case 'E':
489			/* Expand the whole world. */
490			hist_browser__set_folding(browser, true);
491			break;
492		case 'H':
493			browser->show_headers = !browser->show_headers;
494			hist_browser__update_rows(browser);
495			break;
496		case K_ENTER:
497			if (hist_browser__toggle_fold(browser))
498				break;
499			/* fall thru */
500		default:
501			goto out;
502		}
503	}
504out:
505	ui_browser__hide(&browser->b);
506	return key;
507}
508
509struct callchain_print_arg {
510	/* for hists browser */
511	off_t	row_offset;
512	bool	is_current_entry;
513
514	/* for file dump */
515	FILE	*fp;
516	int	printed;
517};
518
519typedef void (*print_callchain_entry_fn)(struct hist_browser *browser,
520					 struct callchain_list *chain,
521					 const char *str, int offset,
522					 unsigned short row,
523					 struct callchain_print_arg *arg);
524
525static void hist_browser__show_callchain_entry(struct hist_browser *browser,
526					       struct callchain_list *chain,
527					       const char *str, int offset,
528					       unsigned short row,
529					       struct callchain_print_arg *arg)
530{
531	int color, width;
532	char folded_sign = callchain_list__folded(chain);
533	bool show_annotated = browser->show_dso && chain->ms.sym && symbol__annotation(chain->ms.sym)->src;
534
535	color = HE_COLORSET_NORMAL;
536	width = browser->b.width - (offset + 2);
537	if (ui_browser__is_current_entry(&browser->b, row)) {
538		browser->selection = &chain->ms;
539		color = HE_COLORSET_SELECTED;
540		arg->is_current_entry = true;
541	}
542
543	ui_browser__set_color(&browser->b, color);
544	hist_browser__gotorc(browser, row, 0);
545	ui_browser__write_nstring(&browser->b, " ", offset);
546	ui_browser__printf(&browser->b, "%c", folded_sign);
547	ui_browser__write_graph(&browser->b, show_annotated ? SLSMG_RARROW_CHAR : ' ');
548	ui_browser__write_nstring(&browser->b, str, width);
549}
550
551static void hist_browser__fprintf_callchain_entry(struct hist_browser *b __maybe_unused,
552						  struct callchain_list *chain,
553						  const char *str, int offset,
554						  unsigned short row __maybe_unused,
555						  struct callchain_print_arg *arg)
556{
557	char folded_sign = callchain_list__folded(chain);
558
559	arg->printed += fprintf(arg->fp, "%*s%c %s\n", offset, " ",
560				folded_sign, str);
561}
562
563typedef bool (*check_output_full_fn)(struct hist_browser *browser,
564				     unsigned short row);
565
566static bool hist_browser__check_output_full(struct hist_browser *browser,
567					    unsigned short row)
568{
569	return browser->b.rows == row;
570}
571
572static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_unused,
573					  unsigned short row __maybe_unused)
574{
575	return false;
576}
577
578#define LEVEL_OFFSET_STEP 3
579
580static int hist_browser__show_callchain(struct hist_browser *browser,
581					struct rb_root *root, int level,
582					unsigned short row, u64 total,
583					print_callchain_entry_fn print,
584					struct callchain_print_arg *arg,
585					check_output_full_fn is_output_full)
586{
587	struct rb_node *node;
588	int first_row = row, offset = level * LEVEL_OFFSET_STEP;
589	u64 new_total;
590	bool need_percent;
591
592	node = rb_first(root);
593	need_percent = node && rb_next(node);
594
595	while (node) {
596		struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
597		struct rb_node *next = rb_next(node);
598		u64 cumul = callchain_cumul_hits(child);
599		struct callchain_list *chain;
600		char folded_sign = ' ';
601		int first = true;
602		int extra_offset = 0;
603
604		list_for_each_entry(chain, &child->val, list) {
605			char bf[1024], *alloc_str;
606			const char *str;
607			bool was_first = first;
608
609			if (first)
610				first = false;
611			else if (need_percent)
612				extra_offset = LEVEL_OFFSET_STEP;
613
614			folded_sign = callchain_list__folded(chain);
615			if (arg->row_offset != 0) {
616				arg->row_offset--;
617				goto do_next;
618			}
619
620			alloc_str = NULL;
621			str = callchain_list__sym_name(chain, bf, sizeof(bf),
622						       browser->show_dso);
623
624			if (was_first && need_percent) {
625				double percent = cumul * 100.0 / total;
626
627				if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
628					str = "Not enough memory!";
629				else
630					str = alloc_str;
631			}
632
633			print(browser, chain, str, offset + extra_offset, row, arg);
634
635			free(alloc_str);
636
637			if (is_output_full(browser, ++row))
638				goto out;
639do_next:
640			if (folded_sign == '+')
641				break;
642		}
643
644		if (folded_sign == '-') {
645			const int new_level = level + (extra_offset ? 2 : 1);
646
647			if (callchain_param.mode == CHAIN_GRAPH_REL)
648				new_total = child->children_hit;
649			else
650				new_total = total;
651
652			row += hist_browser__show_callchain(browser, &child->rb_root,
653							    new_level, row, new_total,
654							    print, arg, is_output_full);
655		}
656		if (is_output_full(browser, row))
657			break;
658		node = next;
659	}
660out:
661	return row - first_row;
662}
663
664struct hpp_arg {
665	struct ui_browser *b;
666	char folded_sign;
667	bool current_entry;
668};
669
670static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
671{
672	struct hpp_arg *arg = hpp->ptr;
673	int ret, len;
674	va_list args;
675	double percent;
676
677	va_start(args, fmt);
678	len = va_arg(args, int);
679	percent = va_arg(args, double);
680	va_end(args);
681
682	ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
683
684	ret = scnprintf(hpp->buf, hpp->size, fmt, len, percent);
685	ui_browser__printf(arg->b, "%s", hpp->buf);
686
687	advance_hpp(hpp, ret);
688	return ret;
689}
690
691#define __HPP_COLOR_PERCENT_FN(_type, _field)				\
692static u64 __hpp_get_##_field(struct hist_entry *he)			\
693{									\
694	return he->stat._field;						\
695}									\
696									\
697static int								\
698hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt,		\
699				struct perf_hpp *hpp,			\
700				struct hist_entry *he)			\
701{									\
702	return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%",	\
703			__hpp__slsmg_color_printf, true);		\
704}
705
706#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field)			\
707static u64 __hpp_get_acc_##_field(struct hist_entry *he)		\
708{									\
709	return he->stat_acc->_field;					\
710}									\
711									\
712static int								\
713hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt,		\
714				struct perf_hpp *hpp,			\
715				struct hist_entry *he)			\
716{									\
717	if (!symbol_conf.cumulate_callchain) {				\
718		struct hpp_arg *arg = hpp->ptr;				\
719		int len = fmt->user_len ?: fmt->len;			\
720		int ret = scnprintf(hpp->buf, hpp->size,		\
721				    "%*s", len, "N/A");			\
722		ui_browser__printf(arg->b, "%s", hpp->buf);		\
723									\
724		return ret;						\
725	}								\
726	return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field,		\
727			" %*.2f%%", __hpp__slsmg_color_printf, true);	\
728}
729
730__HPP_COLOR_PERCENT_FN(overhead, period)
731__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
732__HPP_COLOR_PERCENT_FN(overhead_us, period_us)
733__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
734__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
735__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period)
736
737#undef __HPP_COLOR_PERCENT_FN
738#undef __HPP_COLOR_ACC_PERCENT_FN
739
740void hist_browser__init_hpp(void)
741{
742	perf_hpp__format[PERF_HPP__OVERHEAD].color =
743				hist_browser__hpp_color_overhead;
744	perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
745				hist_browser__hpp_color_overhead_sys;
746	perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
747				hist_browser__hpp_color_overhead_us;
748	perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
749				hist_browser__hpp_color_overhead_guest_sys;
750	perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
751				hist_browser__hpp_color_overhead_guest_us;
752	perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
753				hist_browser__hpp_color_overhead_acc;
754}
755
756static int hist_browser__show_entry(struct hist_browser *browser,
757				    struct hist_entry *entry,
758				    unsigned short row)
759{
760	char s[256];
761	int printed = 0;
762	int width = browser->b.width;
763	char folded_sign = ' ';
764	bool current_entry = ui_browser__is_current_entry(&browser->b, row);
765	off_t row_offset = entry->row_offset;
766	bool first = true;
767	struct perf_hpp_fmt *fmt;
768
769	if (current_entry) {
770		browser->he_selection = entry;
771		browser->selection = &entry->ms;
772	}
773
774	if (symbol_conf.use_callchain) {
775		hist_entry__init_have_children(entry);
776		folded_sign = hist_entry__folded(entry);
777	}
778
779	if (row_offset == 0) {
780		struct hpp_arg arg = {
781			.b		= &browser->b,
782			.folded_sign	= folded_sign,
783			.current_entry	= current_entry,
784		};
785		struct perf_hpp hpp = {
786			.buf		= s,
787			.size		= sizeof(s),
788			.ptr		= &arg,
789		};
790		int column = 0;
791
792		hist_browser__gotorc(browser, row, 0);
793
794		perf_hpp__for_each_format(fmt) {
795			if (perf_hpp__should_skip(fmt) || column++ < browser->b.horiz_scroll)
796				continue;
797
798			if (current_entry && browser->b.navkeypressed) {
799				ui_browser__set_color(&browser->b,
800						      HE_COLORSET_SELECTED);
801			} else {
802				ui_browser__set_color(&browser->b,
803						      HE_COLORSET_NORMAL);
804			}
805
806			if (first) {
807				if (symbol_conf.use_callchain) {
808					ui_browser__printf(&browser->b, "%c ", folded_sign);
809					width -= 2;
810				}
811				first = false;
812			} else {
813				ui_browser__printf(&browser->b, "  ");
814				width -= 2;
815			}
816
817			if (fmt->color) {
818				width -= fmt->color(fmt, &hpp, entry);
819			} else {
820				width -= fmt->entry(fmt, &hpp, entry);
821				ui_browser__printf(&browser->b, "%s", s);
822			}
823		}
824
825		/* The scroll bar isn't being used */
826		if (!browser->b.navkeypressed)
827			width += 1;
828
829		ui_browser__write_nstring(&browser->b, "", width);
830
831		++row;
832		++printed;
833	} else
834		--row_offset;
835
836	if (folded_sign == '-' && row != browser->b.rows) {
837		u64 total = hists__total_period(entry->hists);
838		struct callchain_print_arg arg = {
839			.row_offset = row_offset,
840			.is_current_entry = current_entry,
841		};
842
843		if (callchain_param.mode == CHAIN_GRAPH_REL) {
844			if (symbol_conf.cumulate_callchain)
845				total = entry->stat_acc->period;
846			else
847				total = entry->stat.period;
848		}
849
850		printed += hist_browser__show_callchain(browser,
851					&entry->sorted_chain, 1, row, total,
852					hist_browser__show_callchain_entry, &arg,
853					hist_browser__check_output_full);
854
855		if (arg.is_current_entry)
856			browser->he_selection = entry;
857	}
858
859	return printed;
860}
861
862static int advance_hpp_check(struct perf_hpp *hpp, int inc)
863{
864	advance_hpp(hpp, inc);
865	return hpp->size <= 0;
866}
867
868static int hists_browser__scnprintf_headers(struct hist_browser *browser, char *buf, size_t size)
869{
870	struct hists *hists = browser->hists;
871	struct perf_hpp dummy_hpp = {
872		.buf    = buf,
873		.size   = size,
874	};
875	struct perf_hpp_fmt *fmt;
876	size_t ret = 0;
877	int column = 0;
878
879	if (symbol_conf.use_callchain) {
880		ret = scnprintf(buf, size, "  ");
881		if (advance_hpp_check(&dummy_hpp, ret))
882			return ret;
883	}
884
885	perf_hpp__for_each_format(fmt) {
886		if (perf_hpp__should_skip(fmt)  || column++ < browser->b.horiz_scroll)
887			continue;
888
889		ret = fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists));
890		if (advance_hpp_check(&dummy_hpp, ret))
891			break;
892
893		ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "  ");
894		if (advance_hpp_check(&dummy_hpp, ret))
895			break;
896	}
897
898	return ret;
899}
900
901static void hist_browser__show_headers(struct hist_browser *browser)
902{
903	char headers[1024];
904
905	hists_browser__scnprintf_headers(browser, headers, sizeof(headers));
906	ui_browser__gotorc(&browser->b, 0, 0);
907	ui_browser__set_color(&browser->b, HE_COLORSET_ROOT);
908	ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1);
909}
910
911static void ui_browser__hists_init_top(struct ui_browser *browser)
912{
913	if (browser->top == NULL) {
914		struct hist_browser *hb;
915
916		hb = container_of(browser, struct hist_browser, b);
917		browser->top = rb_first(&hb->hists->entries);
918	}
919}
920
921static unsigned int hist_browser__refresh(struct ui_browser *browser)
922{
923	unsigned row = 0;
924	u16 header_offset = 0;
925	struct rb_node *nd;
926	struct hist_browser *hb = container_of(browser, struct hist_browser, b);
927
928	if (hb->show_headers) {
929		hist_browser__show_headers(hb);
930		header_offset = 1;
931	}
932
933	ui_browser__hists_init_top(browser);
934	hb->he_selection = NULL;
935	hb->selection = NULL;
936
937	for (nd = browser->top; nd; nd = rb_next(nd)) {
938		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
939		float percent;
940
941		if (h->filtered)
942			continue;
943
944		percent = hist_entry__get_percent_limit(h);
945		if (percent < hb->min_pcnt)
946			continue;
947
948		row += hist_browser__show_entry(hb, h, row);
949		if (row == browser->rows)
950			break;
951	}
952
953	return row + header_offset;
954}
955
956static struct rb_node *hists__filter_entries(struct rb_node *nd,
957					     float min_pcnt)
958{
959	while (nd != NULL) {
960		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
961		float percent = hist_entry__get_percent_limit(h);
962
963		if (!h->filtered && percent >= min_pcnt)
964			return nd;
965
966		nd = rb_next(nd);
967	}
968
969	return NULL;
970}
971
972static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
973						  float min_pcnt)
974{
975	while (nd != NULL) {
976		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
977		float percent = hist_entry__get_percent_limit(h);
978
979		if (!h->filtered && percent >= min_pcnt)
980			return nd;
981
982		nd = rb_prev(nd);
983	}
984
985	return NULL;
986}
987
988static void ui_browser__hists_seek(struct ui_browser *browser,
989				   off_t offset, int whence)
990{
991	struct hist_entry *h;
992	struct rb_node *nd;
993	bool first = true;
994	struct hist_browser *hb;
995
996	hb = container_of(browser, struct hist_browser, b);
997
998	if (browser->nr_entries == 0)
999		return;
1000
1001	ui_browser__hists_init_top(browser);
1002
1003	switch (whence) {
1004	case SEEK_SET:
1005		nd = hists__filter_entries(rb_first(browser->entries),
1006					   hb->min_pcnt);
1007		break;
1008	case SEEK_CUR:
1009		nd = browser->top;
1010		goto do_offset;
1011	case SEEK_END:
1012		nd = hists__filter_prev_entries(rb_last(browser->entries),
1013						hb->min_pcnt);
1014		first = false;
1015		break;
1016	default:
1017		return;
1018	}
1019
1020	/*
1021	 * Moves not relative to the first visible entry invalidates its
1022	 * row_offset:
1023	 */
1024	h = rb_entry(browser->top, struct hist_entry, rb_node);
1025	h->row_offset = 0;
1026
1027	/*
1028	 * Here we have to check if nd is expanded (+), if it is we can't go
1029	 * the next top level hist_entry, instead we must compute an offset of
1030	 * what _not_ to show and not change the first visible entry.
1031	 *
1032	 * This offset increments when we are going from top to bottom and
1033	 * decreases when we're going from bottom to top.
1034	 *
1035	 * As we don't have backpointers to the top level in the callchains
1036	 * structure, we need to always print the whole hist_entry callchain,
1037	 * skipping the first ones that are before the first visible entry
1038	 * and stop when we printed enough lines to fill the screen.
1039	 */
1040do_offset:
1041	if (!nd)
1042		return;
1043
1044	if (offset > 0) {
1045		do {
1046			h = rb_entry(nd, struct hist_entry, rb_node);
1047			if (h->unfolded) {
1048				u16 remaining = h->nr_rows - h->row_offset;
1049				if (offset > remaining) {
1050					offset -= remaining;
1051					h->row_offset = 0;
1052				} else {
1053					h->row_offset += offset;
1054					offset = 0;
1055					browser->top = nd;
1056					break;
1057				}
1058			}
1059			nd = hists__filter_entries(rb_next(nd), hb->min_pcnt);
1060			if (nd == NULL)
1061				break;
1062			--offset;
1063			browser->top = nd;
1064		} while (offset != 0);
1065	} else if (offset < 0) {
1066		while (1) {
1067			h = rb_entry(nd, struct hist_entry, rb_node);
1068			if (h->unfolded) {
1069				if (first) {
1070					if (-offset > h->row_offset) {
1071						offset += h->row_offset;
1072						h->row_offset = 0;
1073					} else {
1074						h->row_offset += offset;
1075						offset = 0;
1076						browser->top = nd;
1077						break;
1078					}
1079				} else {
1080					if (-offset > h->nr_rows) {
1081						offset += h->nr_rows;
1082						h->row_offset = 0;
1083					} else {
1084						h->row_offset = h->nr_rows + offset;
1085						offset = 0;
1086						browser->top = nd;
1087						break;
1088					}
1089				}
1090			}
1091
1092			nd = hists__filter_prev_entries(rb_prev(nd),
1093							hb->min_pcnt);
1094			if (nd == NULL)
1095				break;
1096			++offset;
1097			browser->top = nd;
1098			if (offset == 0) {
1099				/*
1100				 * Last unfiltered hist_entry, check if it is
1101				 * unfolded, if it is then we should have
1102				 * row_offset at its last entry.
1103				 */
1104				h = rb_entry(nd, struct hist_entry, rb_node);
1105				if (h->unfolded)
1106					h->row_offset = h->nr_rows;
1107				break;
1108			}
1109			first = false;
1110		}
1111	} else {
1112		browser->top = nd;
1113		h = rb_entry(nd, struct hist_entry, rb_node);
1114		h->row_offset = 0;
1115	}
1116}
1117
1118static int hist_browser__fprintf_callchain(struct hist_browser *browser,
1119					   struct hist_entry *he, FILE *fp)
1120{
1121	u64 total = hists__total_period(he->hists);
1122	struct callchain_print_arg arg  = {
1123		.fp = fp,
1124	};
1125
1126	if (symbol_conf.cumulate_callchain)
1127		total = he->stat_acc->period;
1128
1129	hist_browser__show_callchain(browser, &he->sorted_chain, 1, 0, total,
1130				     hist_browser__fprintf_callchain_entry, &arg,
1131				     hist_browser__check_dump_full);
1132	return arg.printed;
1133}
1134
1135static int hist_browser__fprintf_entry(struct hist_browser *browser,
1136				       struct hist_entry *he, FILE *fp)
1137{
1138	char s[8192];
1139	int printed = 0;
1140	char folded_sign = ' ';
1141	struct perf_hpp hpp = {
1142		.buf = s,
1143		.size = sizeof(s),
1144	};
1145	struct perf_hpp_fmt *fmt;
1146	bool first = true;
1147	int ret;
1148
1149	if (symbol_conf.use_callchain)
1150		folded_sign = hist_entry__folded(he);
1151
1152	if (symbol_conf.use_callchain)
1153		printed += fprintf(fp, "%c ", folded_sign);
1154
1155	perf_hpp__for_each_format(fmt) {
1156		if (perf_hpp__should_skip(fmt))
1157			continue;
1158
1159		if (!first) {
1160			ret = scnprintf(hpp.buf, hpp.size, "  ");
1161			advance_hpp(&hpp, ret);
1162		} else
1163			first = false;
1164
1165		ret = fmt->entry(fmt, &hpp, he);
1166		advance_hpp(&hpp, ret);
1167	}
1168	printed += fprintf(fp, "%s\n", rtrim(s));
1169
1170	if (folded_sign == '-')
1171		printed += hist_browser__fprintf_callchain(browser, he, fp);
1172
1173	return printed;
1174}
1175
1176static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
1177{
1178	struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
1179						   browser->min_pcnt);
1180	int printed = 0;
1181
1182	while (nd) {
1183		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1184
1185		printed += hist_browser__fprintf_entry(browser, h, fp);
1186		nd = hists__filter_entries(rb_next(nd), browser->min_pcnt);
1187	}
1188
1189	return printed;
1190}
1191
1192static int hist_browser__dump(struct hist_browser *browser)
1193{
1194	char filename[64];
1195	FILE *fp;
1196
1197	while (1) {
1198		scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
1199		if (access(filename, F_OK))
1200			break;
1201		/*
1202 		 * XXX: Just an arbitrary lazy upper limit
1203 		 */
1204		if (++browser->print_seq == 8192) {
1205			ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
1206			return -1;
1207		}
1208	}
1209
1210	fp = fopen(filename, "w");
1211	if (fp == NULL) {
1212		char bf[64];
1213		const char *err = strerror_r(errno, bf, sizeof(bf));
1214		ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
1215		return -1;
1216	}
1217
1218	++browser->print_seq;
1219	hist_browser__fprintf(browser, fp);
1220	fclose(fp);
1221	ui_helpline__fpush("%s written!", filename);
1222
1223	return 0;
1224}
1225
1226static struct hist_browser *hist_browser__new(struct hists *hists,
1227					      struct hist_browser_timer *hbt,
1228					      struct perf_env *env)
1229{
1230	struct hist_browser *browser = zalloc(sizeof(*browser));
1231
1232	if (browser) {
1233		browser->hists = hists;
1234		browser->b.refresh = hist_browser__refresh;
1235		browser->b.refresh_dimensions = hist_browser__refresh_dimensions;
1236		browser->b.seek = ui_browser__hists_seek;
1237		browser->b.use_navkeypressed = true;
1238		browser->show_headers = symbol_conf.show_hist_headers;
1239		browser->hbt = hbt;
1240		browser->env = env;
1241	}
1242
1243	return browser;
1244}
1245
1246static void hist_browser__delete(struct hist_browser *browser)
1247{
1248	free(browser);
1249}
1250
1251static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
1252{
1253	return browser->he_selection;
1254}
1255
1256static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
1257{
1258	return browser->he_selection->thread;
1259}
1260
1261/* Check whether the browser is for 'top' or 'report' */
1262static inline bool is_report_browser(void *timer)
1263{
1264	return timer == NULL;
1265}
1266
1267static int hists__browser_title(struct hists *hists,
1268				struct hist_browser_timer *hbt,
1269				char *bf, size_t size)
1270{
1271	char unit;
1272	int printed;
1273	const struct dso *dso = hists->dso_filter;
1274	const struct thread *thread = hists->thread_filter;
1275	int socket_id = hists->socket_filter;
1276	unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
1277	u64 nr_events = hists->stats.total_period;
1278	struct perf_evsel *evsel = hists_to_evsel(hists);
1279	const char *ev_name = perf_evsel__name(evsel);
1280	char buf[512];
1281	size_t buflen = sizeof(buf);
1282	char ref[30] = " show reference callgraph, ";
1283	bool enable_ref = false;
1284
1285	if (symbol_conf.filter_relative) {
1286		nr_samples = hists->stats.nr_non_filtered_samples;
1287		nr_events = hists->stats.total_non_filtered_period;
1288	}
1289
1290	if (perf_evsel__is_group_event(evsel)) {
1291		struct perf_evsel *pos;
1292
1293		perf_evsel__group_desc(evsel, buf, buflen);
1294		ev_name = buf;
1295
1296		for_each_group_member(pos, evsel) {
1297			struct hists *pos_hists = evsel__hists(pos);
1298
1299			if (symbol_conf.filter_relative) {
1300				nr_samples += pos_hists->stats.nr_non_filtered_samples;
1301				nr_events += pos_hists->stats.total_non_filtered_period;
1302			} else {
1303				nr_samples += pos_hists->stats.nr_events[PERF_RECORD_SAMPLE];
1304				nr_events += pos_hists->stats.total_period;
1305			}
1306		}
1307	}
1308
1309	if (symbol_conf.show_ref_callgraph &&
1310	    strstr(ev_name, "call-graph=no"))
1311		enable_ref = true;
1312	nr_samples = convert_unit(nr_samples, &unit);
1313	printed = scnprintf(bf, size,
1314			   "Samples: %lu%c of event '%s',%sEvent count (approx.): %" PRIu64,
1315			   nr_samples, unit, ev_name, enable_ref ? ref : " ", nr_events);
1316
1317
1318	if (hists->uid_filter_str)
1319		printed += snprintf(bf + printed, size - printed,
1320				    ", UID: %s", hists->uid_filter_str);
1321	if (thread)
1322		printed += scnprintf(bf + printed, size - printed,
1323				    ", Thread: %s(%d)",
1324				     (thread->comm_set ? thread__comm_str(thread) : ""),
1325				    thread->tid);
1326	if (dso)
1327		printed += scnprintf(bf + printed, size - printed,
1328				    ", DSO: %s", dso->short_name);
1329	if (socket_id > -1)
1330		printed += scnprintf(bf + printed, size - printed,
1331				    ", Processor Socket: %d", socket_id);
1332	if (!is_report_browser(hbt)) {
1333		struct perf_top *top = hbt->arg;
1334
1335		if (top->zero)
1336			printed += scnprintf(bf + printed, size - printed, " [z]");
1337	}
1338
1339	return printed;
1340}
1341
1342static inline void free_popup_options(char **options, int n)
1343{
1344	int i;
1345
1346	for (i = 0; i < n; ++i)
1347		zfree(&options[i]);
1348}
1349
1350/*
1351 * Only runtime switching of perf data file will make "input_name" point
1352 * to a malloced buffer. So add "is_input_name_malloced" flag to decide
1353 * whether we need to call free() for current "input_name" during the switch.
1354 */
1355static bool is_input_name_malloced = false;
1356
1357static int switch_data_file(void)
1358{
1359	char *pwd, *options[32], *abs_path[32], *tmp;
1360	DIR *pwd_dir;
1361	int nr_options = 0, choice = -1, ret = -1;
1362	struct dirent *dent;
1363
1364	pwd = getenv("PWD");
1365	if (!pwd)
1366		return ret;
1367
1368	pwd_dir = opendir(pwd);
1369	if (!pwd_dir)
1370		return ret;
1371
1372	memset(options, 0, sizeof(options));
1373	memset(options, 0, sizeof(abs_path));
1374
1375	while ((dent = readdir(pwd_dir))) {
1376		char path[PATH_MAX];
1377		u64 magic;
1378		char *name = dent->d_name;
1379		FILE *file;
1380
1381		if (!(dent->d_type == DT_REG))
1382			continue;
1383
1384		snprintf(path, sizeof(path), "%s/%s", pwd, name);
1385
1386		file = fopen(path, "r");
1387		if (!file)
1388			continue;
1389
1390		if (fread(&magic, 1, 8, file) < 8)
1391			goto close_file_and_continue;
1392
1393		if (is_perf_magic(magic)) {
1394			options[nr_options] = strdup(name);
1395			if (!options[nr_options])
1396				goto close_file_and_continue;
1397
1398			abs_path[nr_options] = strdup(path);
1399			if (!abs_path[nr_options]) {
1400				zfree(&options[nr_options]);
1401				ui__warning("Can't search all data files due to memory shortage.\n");
1402				fclose(file);
1403				break;
1404			}
1405
1406			nr_options++;
1407		}
1408
1409close_file_and_continue:
1410		fclose(file);
1411		if (nr_options >= 32) {
1412			ui__warning("Too many perf data files in PWD!\n"
1413				    "Only the first 32 files will be listed.\n");
1414			break;
1415		}
1416	}
1417	closedir(pwd_dir);
1418
1419	if (nr_options) {
1420		choice = ui__popup_menu(nr_options, options);
1421		if (choice < nr_options && choice >= 0) {
1422			tmp = strdup(abs_path[choice]);
1423			if (tmp) {
1424				if (is_input_name_malloced)
1425					free((void *)input_name);
1426				input_name = tmp;
1427				is_input_name_malloced = true;
1428				ret = 0;
1429			} else
1430				ui__warning("Data switch failed due to memory shortage!\n");
1431		}
1432	}
1433
1434	free_popup_options(options, nr_options);
1435	free_popup_options(abs_path, nr_options);
1436	return ret;
1437}
1438
1439struct popup_action {
1440	struct thread 		*thread;
1441	struct map_symbol 	ms;
1442	int			socket;
1443
1444	int (*fn)(struct hist_browser *browser, struct popup_action *act);
1445};
1446
1447static int
1448do_annotate(struct hist_browser *browser, struct popup_action *act)
1449{
1450	struct perf_evsel *evsel;
1451	struct annotation *notes;
1452	struct hist_entry *he;
1453	int err;
1454
1455	if (!objdump_path && perf_env__lookup_objdump(browser->env))
1456		return 0;
1457
1458	notes = symbol__annotation(act->ms.sym);
1459	if (!notes->src)
1460		return 0;
1461
1462	evsel = hists_to_evsel(browser->hists);
1463	err = map_symbol__tui_annotate(&act->ms, evsel, browser->hbt);
1464	he = hist_browser__selected_entry(browser);
1465	/*
1466	 * offer option to annotate the other branch source or target
1467	 * (if they exists) when returning from annotate
1468	 */
1469	if ((err == 'q' || err == CTRL('c')) && he->branch_info)
1470		return 1;
1471
1472	ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
1473	if (err)
1474		ui_browser__handle_resize(&browser->b);
1475	return 0;
1476}
1477
1478static int
1479add_annotate_opt(struct hist_browser *browser __maybe_unused,
1480		 struct popup_action *act, char **optstr,
1481		 struct map *map, struct symbol *sym)
1482{
1483	if (sym == NULL || map->dso->annotate_warned)
1484		return 0;
1485
1486	if (asprintf(optstr, "Annotate %s", sym->name) < 0)
1487		return 0;
1488
1489	act->ms.map = map;
1490	act->ms.sym = sym;
1491	act->fn = do_annotate;
1492	return 1;
1493}
1494
1495static int
1496do_zoom_thread(struct hist_browser *browser, struct popup_action *act)
1497{
1498	struct thread *thread = act->thread;
1499
1500	if (browser->hists->thread_filter) {
1501		pstack__remove(browser->pstack, &browser->hists->thread_filter);
1502		perf_hpp__set_elide(HISTC_THREAD, false);
1503		thread__zput(browser->hists->thread_filter);
1504		ui_helpline__pop();
1505	} else {
1506		ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s(%d) thread\"",
1507				   thread->comm_set ? thread__comm_str(thread) : "",
1508				   thread->tid);
1509		browser->hists->thread_filter = thread__get(thread);
1510		perf_hpp__set_elide(HISTC_THREAD, false);
1511		pstack__push(browser->pstack, &browser->hists->thread_filter);
1512	}
1513
1514	hists__filter_by_thread(browser->hists);
1515	hist_browser__reset(browser);
1516	return 0;
1517}
1518
1519static int
1520add_thread_opt(struct hist_browser *browser, struct popup_action *act,
1521	       char **optstr, struct thread *thread)
1522{
1523	if (thread == NULL)
1524		return 0;
1525
1526	if (asprintf(optstr, "Zoom %s %s(%d) thread",
1527		     browser->hists->thread_filter ? "out of" : "into",
1528		     thread->comm_set ? thread__comm_str(thread) : "",
1529		     thread->tid) < 0)
1530		return 0;
1531
1532	act->thread = thread;
1533	act->fn = do_zoom_thread;
1534	return 1;
1535}
1536
1537static int
1538do_zoom_dso(struct hist_browser *browser, struct popup_action *act)
1539{
1540	struct map *map = act->ms.map;
1541
1542	if (browser->hists->dso_filter) {
1543		pstack__remove(browser->pstack, &browser->hists->dso_filter);
1544		perf_hpp__set_elide(HISTC_DSO, false);
1545		browser->hists->dso_filter = NULL;
1546		ui_helpline__pop();
1547	} else {
1548		if (map == NULL)
1549			return 0;
1550		ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s DSO\"",
1551				   __map__is_kernel(map) ? "the Kernel" : map->dso->short_name);
1552		browser->hists->dso_filter = map->dso;
1553		perf_hpp__set_elide(HISTC_DSO, true);
1554		pstack__push(browser->pstack, &browser->hists->dso_filter);
1555	}
1556
1557	hists__filter_by_dso(browser->hists);
1558	hist_browser__reset(browser);
1559	return 0;
1560}
1561
1562static int
1563add_dso_opt(struct hist_browser *browser, struct popup_action *act,
1564	    char **optstr, struct map *map)
1565{
1566	if (map == NULL)
1567		return 0;
1568
1569	if (asprintf(optstr, "Zoom %s %s DSO",
1570		     browser->hists->dso_filter ? "out of" : "into",
1571		     __map__is_kernel(map) ? "the Kernel" : map->dso->short_name) < 0)
1572		return 0;
1573
1574	act->ms.map = map;
1575	act->fn = do_zoom_dso;
1576	return 1;
1577}
1578
1579static int
1580do_browse_map(struct hist_browser *browser __maybe_unused,
1581	      struct popup_action *act)
1582{
1583	map__browse(act->ms.map);
1584	return 0;
1585}
1586
1587static int
1588add_map_opt(struct hist_browser *browser __maybe_unused,
1589	    struct popup_action *act, char **optstr, struct map *map)
1590{
1591	if (map == NULL)
1592		return 0;
1593
1594	if (asprintf(optstr, "Browse map details") < 0)
1595		return 0;
1596
1597	act->ms.map = map;
1598	act->fn = do_browse_map;
1599	return 1;
1600}
1601
1602static int
1603do_run_script(struct hist_browser *browser __maybe_unused,
1604	      struct popup_action *act)
1605{
1606	char script_opt[64];
1607	memset(script_opt, 0, sizeof(script_opt));
1608
1609	if (act->thread) {
1610		scnprintf(script_opt, sizeof(script_opt), " -c %s ",
1611			  thread__comm_str(act->thread));
1612	} else if (act->ms.sym) {
1613		scnprintf(script_opt, sizeof(script_opt), " -S %s ",
1614			  act->ms.sym->name);
1615	}
1616
1617	script_browse(script_opt);
1618	return 0;
1619}
1620
1621static int
1622add_script_opt(struct hist_browser *browser __maybe_unused,
1623	       struct popup_action *act, char **optstr,
1624	       struct thread *thread, struct symbol *sym)
1625{
1626	if (thread) {
1627		if (asprintf(optstr, "Run scripts for samples of thread [%s]",
1628			     thread__comm_str(thread)) < 0)
1629			return 0;
1630	} else if (sym) {
1631		if (asprintf(optstr, "Run scripts for samples of symbol [%s]",
1632			     sym->name) < 0)
1633			return 0;
1634	} else {
1635		if (asprintf(optstr, "Run scripts for all samples") < 0)
1636			return 0;
1637	}
1638
1639	act->thread = thread;
1640	act->ms.sym = sym;
1641	act->fn = do_run_script;
1642	return 1;
1643}
1644
1645static int
1646do_switch_data(struct hist_browser *browser __maybe_unused,
1647	       struct popup_action *act __maybe_unused)
1648{
1649	if (switch_data_file()) {
1650		ui__warning("Won't switch the data files due to\n"
1651			    "no valid data file get selected!\n");
1652		return 0;
1653	}
1654
1655	return K_SWITCH_INPUT_DATA;
1656}
1657
1658static int
1659add_switch_opt(struct hist_browser *browser,
1660	       struct popup_action *act, char **optstr)
1661{
1662	if (!is_report_browser(browser->hbt))
1663		return 0;
1664
1665	if (asprintf(optstr, "Switch to another data file in PWD") < 0)
1666		return 0;
1667
1668	act->fn = do_switch_data;
1669	return 1;
1670}
1671
1672static int
1673do_exit_browser(struct hist_browser *browser __maybe_unused,
1674		struct popup_action *act __maybe_unused)
1675{
1676	return 0;
1677}
1678
1679static int
1680add_exit_opt(struct hist_browser *browser __maybe_unused,
1681	     struct popup_action *act, char **optstr)
1682{
1683	if (asprintf(optstr, "Exit") < 0)
1684		return 0;
1685
1686	act->fn = do_exit_browser;
1687	return 1;
1688}
1689
1690static int
1691do_zoom_socket(struct hist_browser *browser, struct popup_action *act)
1692{
1693	if (browser->hists->socket_filter > -1) {
1694		pstack__remove(browser->pstack, &browser->hists->socket_filter);
1695		browser->hists->socket_filter = -1;
1696		perf_hpp__set_elide(HISTC_SOCKET, false);
1697	} else {
1698		browser->hists->socket_filter = act->socket;
1699		perf_hpp__set_elide(HISTC_SOCKET, true);
1700		pstack__push(browser->pstack, &browser->hists->socket_filter);
1701	}
1702
1703	hists__filter_by_socket(browser->hists);
1704	hist_browser__reset(browser);
1705	return 0;
1706}
1707
1708static int
1709add_socket_opt(struct hist_browser *browser, struct popup_action *act,
1710	       char **optstr, int socket_id)
1711{
1712	if (socket_id < 0)
1713		return 0;
1714
1715	if (asprintf(optstr, "Zoom %s Processor Socket %d",
1716		     (browser->hists->socket_filter > -1) ? "out of" : "into",
1717		     socket_id) < 0)
1718		return 0;
1719
1720	act->socket = socket_id;
1721	act->fn = do_zoom_socket;
1722	return 1;
1723}
1724
1725static void hist_browser__update_nr_entries(struct hist_browser *hb)
1726{
1727	u64 nr_entries = 0;
1728	struct rb_node *nd = rb_first(&hb->hists->entries);
1729
1730	if (hb->min_pcnt == 0) {
1731		hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
1732		return;
1733	}
1734
1735	while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
1736		nr_entries++;
1737		nd = rb_next(nd);
1738	}
1739
1740	hb->nr_non_filtered_entries = nr_entries;
1741}
1742
1743static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
1744				    const char *helpline,
1745				    bool left_exits,
1746				    struct hist_browser_timer *hbt,
1747				    float min_pcnt,
1748				    struct perf_env *env)
1749{
1750	struct hists *hists = evsel__hists(evsel);
1751	struct hist_browser *browser = hist_browser__new(hists, hbt, env);
1752	struct branch_info *bi;
1753#define MAX_OPTIONS  16
1754	char *options[MAX_OPTIONS];
1755	struct popup_action actions[MAX_OPTIONS];
1756	int nr_options = 0;
1757	int key = -1;
1758	char buf[64];
1759	int delay_secs = hbt ? hbt->refresh : 0;
1760	struct perf_hpp_fmt *fmt;
1761
1762#define HIST_BROWSER_HELP_COMMON					\
1763	"h/?/F1        Show this window\n"				\
1764	"UP/DOWN/PGUP\n"						\
1765	"PGDN/SPACE    Navigate\n"					\
1766	"q/ESC/CTRL+C  Exit browser\n\n"				\
1767	"For multiple event sessions:\n\n"				\
1768	"TAB/UNTAB     Switch events\n\n"				\
1769	"For symbolic views (--sort has sym):\n\n"			\
1770	"ENTER         Zoom into DSO/Threads & Annotate current symbol\n" \
1771	"ESC           Zoom out\n"					\
1772	"a             Annotate current symbol\n"			\
1773	"C             Collapse all callchains\n"			\
1774	"d             Zoom into current DSO\n"				\
1775	"E             Expand all callchains\n"				\
1776	"F             Toggle percentage of filtered entries\n"		\
1777	"H             Display column headers\n"			\
1778	"m             Display context menu\n"				\
1779	"S             Zoom into current Processor Socket\n"		\
1780
1781	/* help messages are sorted by lexical order of the hotkey */
1782	const char report_help[] = HIST_BROWSER_HELP_COMMON
1783	"i             Show header information\n"
1784	"P             Print histograms to perf.hist.N\n"
1785	"r             Run available scripts\n"
1786	"s             Switch to another data file in PWD\n"
1787	"t             Zoom into current Thread\n"
1788	"V             Verbose (DSO names in callchains, etc)\n"
1789	"/             Filter symbol by name";
1790	const char top_help[] = HIST_BROWSER_HELP_COMMON
1791	"P             Print histograms to perf.hist.N\n"
1792	"t             Zoom into current Thread\n"
1793	"V             Verbose (DSO names in callchains, etc)\n"
1794	"z             Toggle zeroing of samples\n"
1795	"f             Enable/Disable events\n"
1796	"/             Filter symbol by name";
1797
1798	if (browser == NULL)
1799		return -1;
1800
1801	/* reset abort key so that it can get Ctrl-C as a key */
1802	SLang_reset_tty();
1803	SLang_init_tty(0, 0, 0);
1804
1805	if (min_pcnt) {
1806		browser->min_pcnt = min_pcnt;
1807		hist_browser__update_nr_entries(browser);
1808	}
1809
1810	browser->pstack = pstack__new(3);
1811	if (browser->pstack == NULL)
1812		goto out;
1813
1814	ui_helpline__push(helpline);
1815
1816	memset(options, 0, sizeof(options));
1817	memset(actions, 0, sizeof(actions));
1818
1819	perf_hpp__for_each_format(fmt) {
1820		perf_hpp__reset_width(fmt, hists);
1821		/*
1822		 * This is done just once, and activates the horizontal scrolling
1823		 * code in the ui_browser code, it would be better to have a the
1824		 * counter in the perf_hpp code, but I couldn't find doing it here
1825		 * works, FIXME by setting this in hist_browser__new, for now, be
1826		 * clever 8-)
1827		 */
1828		++browser->b.columns;
1829	}
1830
1831	if (symbol_conf.col_width_list_str)
1832		perf_hpp__set_user_width(symbol_conf.col_width_list_str);
1833
1834	while (1) {
1835		struct thread *thread = NULL;
1836		struct map *map = NULL;
1837		int choice = 0;
1838		int socked_id = -1;
1839
1840		nr_options = 0;
1841
1842		key = hist_browser__run(browser, helpline);
1843
1844		if (browser->he_selection != NULL) {
1845			thread = hist_browser__selected_thread(browser);
1846			map = browser->selection->map;
1847			socked_id = browser->he_selection->socket;
1848		}
1849		switch (key) {
1850		case K_TAB:
1851		case K_UNTAB:
1852			if (nr_events == 1)
1853				continue;
1854			/*
1855			 * Exit the browser, let hists__browser_tree
1856			 * go to the next or previous
1857			 */
1858			goto out_free_stack;
1859		case 'a':
1860			if (!sort__has_sym) {
1861				ui_browser__warning(&browser->b, delay_secs * 2,
1862			"Annotation is only available for symbolic views, "
1863			"include \"sym*\" in --sort to use it.");
1864				continue;
1865			}
1866
1867			if (browser->selection == NULL ||
1868			    browser->selection->sym == NULL ||
1869			    browser->selection->map->dso->annotate_warned)
1870				continue;
1871
1872			actions->ms.map = browser->selection->map;
1873			actions->ms.sym = browser->selection->sym;
1874			do_annotate(browser, actions);
1875			continue;
1876		case 'P':
1877			hist_browser__dump(browser);
1878			continue;
1879		case 'd':
1880			actions->ms.map = map;
1881			do_zoom_dso(browser, actions);
1882			continue;
1883		case 'V':
1884			browser->show_dso = !browser->show_dso;
1885			continue;
1886		case 't':
1887			actions->thread = thread;
1888			do_zoom_thread(browser, actions);
1889			continue;
1890		case 'S':
1891			actions->socket = socked_id;
1892			do_zoom_socket(browser, actions);
1893			continue;
1894		case '/':
1895			if (ui_browser__input_window("Symbol to show",
1896					"Please enter the name of symbol you want to see.\n"
1897					"To remove the filter later, press / + ENTER.",
1898					buf, "ENTER: OK, ESC: Cancel",
1899					delay_secs * 2) == K_ENTER) {
1900				hists->symbol_filter_str = *buf ? buf : NULL;
1901				hists__filter_by_symbol(hists);
1902				hist_browser__reset(browser);
1903			}
1904			continue;
1905		case 'r':
1906			if (is_report_browser(hbt)) {
1907				actions->thread = NULL;
1908				actions->ms.sym = NULL;
1909				do_run_script(browser, actions);
1910			}
1911			continue;
1912		case 's':
1913			if (is_report_browser(hbt)) {
1914				key = do_switch_data(browser, actions);
1915				if (key == K_SWITCH_INPUT_DATA)
1916					goto out_free_stack;
1917			}
1918			continue;
1919		case 'i':
1920			/* env->arch is NULL for live-mode (i.e. perf top) */
1921			if (env->arch)
1922				tui__header_window(env);
1923			continue;
1924		case 'F':
1925			symbol_conf.filter_relative ^= 1;
1926			continue;
1927		case 'z':
1928			if (!is_report_browser(hbt)) {
1929				struct perf_top *top = hbt->arg;
1930
1931				top->zero = !top->zero;
1932			}
1933			continue;
1934		case K_F1:
1935		case 'h':
1936		case '?':
1937			ui_browser__help_window(&browser->b,
1938				is_report_browser(hbt) ? report_help : top_help);
1939			continue;
1940		case K_ENTER:
1941		case K_RIGHT:
1942		case 'm':
1943			/* menu */
1944			break;
1945		case K_ESC:
1946		case K_LEFT: {
1947			const void *top;
1948
1949			if (pstack__empty(browser->pstack)) {
1950				/*
1951				 * Go back to the perf_evsel_menu__run or other user
1952				 */
1953				if (left_exits)
1954					goto out_free_stack;
1955
1956				if (key == K_ESC &&
1957				    ui_browser__dialog_yesno(&browser->b,
1958							     "Do you really want to exit?"))
1959					goto out_free_stack;
1960
1961				continue;
1962			}
1963			top = pstack__peek(browser->pstack);
1964			if (top == &browser->hists->dso_filter) {
1965				/*
1966				 * No need to set actions->dso here since
1967				 * it's just to remove the current filter.
1968				 * Ditto for thread below.
1969				 */
1970				do_zoom_dso(browser, actions);
1971			} else if (top == &browser->hists->thread_filter) {
1972				do_zoom_thread(browser, actions);
1973			} else if (top == &browser->hists->socket_filter) {
1974				do_zoom_socket(browser, actions);
1975			}
1976			continue;
1977		}
1978		case 'q':
1979		case CTRL('c'):
1980			goto out_free_stack;
1981		case 'f':
1982			if (!is_report_browser(hbt)) {
1983				struct perf_top *top = hbt->arg;
1984
1985				perf_evlist__toggle_enable(top->evlist);
1986				/*
1987				 * No need to refresh, resort/decay histogram
1988				 * entries if we are not collecting samples:
1989				 */
1990				if (top->evlist->enabled) {
1991					helpline = "Press 'f' to disable the events or 'h' to see other hotkeys";
1992					hbt->refresh = delay_secs;
1993				} else {
1994					helpline = "Press 'f' again to re-enable the events";
1995					hbt->refresh = 0;
1996				}
1997				continue;
1998			}
1999			/* Fall thru */
2000		default:
2001			helpline = "Press '?' for help on key bindings";
2002			continue;
2003		}
2004
2005		if (!sort__has_sym)
2006			goto add_exit_option;
2007
2008		if (browser->selection == NULL)
2009			goto skip_annotation;
2010
2011		if (sort__mode == SORT_MODE__BRANCH) {
2012			bi = browser->he_selection->branch_info;
2013
2014			if (bi == NULL)
2015				goto skip_annotation;
2016
2017			nr_options += add_annotate_opt(browser,
2018						       &actions[nr_options],
2019						       &options[nr_options],
2020						       bi->from.map,
2021						       bi->from.sym);
2022			if (bi->to.sym != bi->from.sym)
2023				nr_options += add_annotate_opt(browser,
2024							&actions[nr_options],
2025							&options[nr_options],
2026							bi->to.map,
2027							bi->to.sym);
2028		} else {
2029			nr_options += add_annotate_opt(browser,
2030						       &actions[nr_options],
2031						       &options[nr_options],
2032						       browser->selection->map,
2033						       browser->selection->sym);
2034		}
2035skip_annotation:
2036		nr_options += add_thread_opt(browser, &actions[nr_options],
2037					     &options[nr_options], thread);
2038		nr_options += add_dso_opt(browser, &actions[nr_options],
2039					  &options[nr_options], map);
2040		nr_options += add_map_opt(browser, &actions[nr_options],
2041					  &options[nr_options],
2042					  browser->selection ?
2043						browser->selection->map : NULL);
2044		nr_options += add_socket_opt(browser, &actions[nr_options],
2045					     &options[nr_options],
2046					     socked_id);
2047		/* perf script support */
2048		if (browser->he_selection) {
2049			nr_options += add_script_opt(browser,
2050						     &actions[nr_options],
2051						     &options[nr_options],
2052						     thread, NULL);
2053			/*
2054			 * Note that browser->selection != NULL
2055			 * when browser->he_selection is not NULL,
2056			 * so we don't need to check browser->selection
2057			 * before fetching browser->selection->sym like what
2058			 * we do before fetching browser->selection->map.
2059			 *
2060			 * See hist_browser__show_entry.
2061			 */
2062			if (sort__has_sym && browser->selection->sym) {
2063				nr_options += add_script_opt(browser,
2064							     &actions[nr_options],
2065							     &options[nr_options],
2066							     NULL, browser->selection->sym);
2067			}
2068		}
2069		nr_options += add_script_opt(browser, &actions[nr_options],
2070					     &options[nr_options], NULL, NULL);
2071		nr_options += add_switch_opt(browser, &actions[nr_options],
2072					     &options[nr_options]);
2073add_exit_option:
2074		nr_options += add_exit_opt(browser, &actions[nr_options],
2075					   &options[nr_options]);
2076
2077		do {
2078			struct popup_action *act;
2079
2080			choice = ui__popup_menu(nr_options, options);
2081			if (choice == -1 || choice >= nr_options)
2082				break;
2083
2084			act = &actions[choice];
2085			key = act->fn(browser, act);
2086		} while (key == 1);
2087
2088		if (key == K_SWITCH_INPUT_DATA)
2089			break;
2090	}
2091out_free_stack:
2092	pstack__delete(browser->pstack);
2093out:
2094	hist_browser__delete(browser);
2095	free_popup_options(options, MAX_OPTIONS);
2096	return key;
2097}
2098
2099struct perf_evsel_menu {
2100	struct ui_browser b;
2101	struct perf_evsel *selection;
2102	bool lost_events, lost_events_warned;
2103	float min_pcnt;
2104	struct perf_env *env;
2105};
2106
2107static void perf_evsel_menu__write(struct ui_browser *browser,
2108				   void *entry, int row)
2109{
2110	struct perf_evsel_menu *menu = container_of(browser,
2111						    struct perf_evsel_menu, b);
2112	struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
2113	struct hists *hists = evsel__hists(evsel);
2114	bool current_entry = ui_browser__is_current_entry(browser, row);
2115	unsigned long nr_events = hists->stats.nr_events[PERF_RECORD_SAMPLE];
2116	const char *ev_name = perf_evsel__name(evsel);
2117	char bf[256], unit;
2118	const char *warn = " ";
2119	size_t printed;
2120
2121	ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
2122						       HE_COLORSET_NORMAL);
2123
2124	if (perf_evsel__is_group_event(evsel)) {
2125		struct perf_evsel *pos;
2126
2127		ev_name = perf_evsel__group_name(evsel);
2128
2129		for_each_group_member(pos, evsel) {
2130			struct hists *pos_hists = evsel__hists(pos);
2131			nr_events += pos_hists->stats.nr_events[PERF_RECORD_SAMPLE];
2132		}
2133	}
2134
2135	nr_events = convert_unit(nr_events, &unit);
2136	printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
2137			   unit, unit == ' ' ? "" : " ", ev_name);
2138	ui_browser__printf(browser, "%s", bf);
2139
2140	nr_events = hists->stats.nr_events[PERF_RECORD_LOST];
2141	if (nr_events != 0) {
2142		menu->lost_events = true;
2143		if (!current_entry)
2144			ui_browser__set_color(browser, HE_COLORSET_TOP);
2145		nr_events = convert_unit(nr_events, &unit);
2146		printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
2147				     nr_events, unit, unit == ' ' ? "" : " ");
2148		warn = bf;
2149	}
2150
2151	ui_browser__write_nstring(browser, warn, browser->width - printed);
2152
2153	if (current_entry)
2154		menu->selection = evsel;
2155}
2156
2157static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
2158				int nr_events, const char *help,
2159				struct hist_browser_timer *hbt)
2160{
2161	struct perf_evlist *evlist = menu->b.priv;
2162	struct perf_evsel *pos;
2163	const char *title = "Available samples";
2164	int delay_secs = hbt ? hbt->refresh : 0;
2165	int key;
2166
2167	if (ui_browser__show(&menu->b, title,
2168			     "ESC: exit, ENTER|->: Browse histograms") < 0)
2169		return -1;
2170
2171	while (1) {
2172		key = ui_browser__run(&menu->b, delay_secs);
2173
2174		switch (key) {
2175		case K_TIMER:
2176			hbt->timer(hbt->arg);
2177
2178			if (!menu->lost_events_warned && menu->lost_events) {
2179				ui_browser__warn_lost_events(&menu->b);
2180				menu->lost_events_warned = true;
2181			}
2182			continue;
2183		case K_RIGHT:
2184		case K_ENTER:
2185			if (!menu->selection)
2186				continue;
2187			pos = menu->selection;
2188browse_hists:
2189			perf_evlist__set_selected(evlist, pos);
2190			/*
2191			 * Give the calling tool a chance to populate the non
2192			 * default evsel resorted hists tree.
2193			 */
2194			if (hbt)
2195				hbt->timer(hbt->arg);
2196			key = perf_evsel__hists_browse(pos, nr_events, help,
2197						       true, hbt,
2198						       menu->min_pcnt,
2199						       menu->env);
2200			ui_browser__show_title(&menu->b, title);
2201			switch (key) {
2202			case K_TAB:
2203				if (pos->node.next == &evlist->entries)
2204					pos = perf_evlist__first(evlist);
2205				else
2206					pos = perf_evsel__next(pos);
2207				goto browse_hists;
2208			case K_UNTAB:
2209				if (pos->node.prev == &evlist->entries)
2210					pos = perf_evlist__last(evlist);
2211				else
2212					pos = perf_evsel__prev(pos);
2213				goto browse_hists;
2214			case K_SWITCH_INPUT_DATA:
2215			case 'q':
2216			case CTRL('c'):
2217				goto out;
2218			case K_ESC:
2219			default:
2220				continue;
2221			}
2222		case K_LEFT:
2223			continue;
2224		case K_ESC:
2225			if (!ui_browser__dialog_yesno(&menu->b,
2226					       "Do you really want to exit?"))
2227				continue;
2228			/* Fall thru */
2229		case 'q':
2230		case CTRL('c'):
2231			goto out;
2232		default:
2233			continue;
2234		}
2235	}
2236
2237out:
2238	ui_browser__hide(&menu->b);
2239	return key;
2240}
2241
2242static bool filter_group_entries(struct ui_browser *browser __maybe_unused,
2243				 void *entry)
2244{
2245	struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
2246
2247	if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel))
2248		return true;
2249
2250	return false;
2251}
2252
2253static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
2254					   int nr_entries, const char *help,
2255					   struct hist_browser_timer *hbt,
2256					   float min_pcnt,
2257					   struct perf_env *env)
2258{
2259	struct perf_evsel *pos;
2260	struct perf_evsel_menu menu = {
2261		.b = {
2262			.entries    = &evlist->entries,
2263			.refresh    = ui_browser__list_head_refresh,
2264			.seek	    = ui_browser__list_head_seek,
2265			.write	    = perf_evsel_menu__write,
2266			.filter	    = filter_group_entries,
2267			.nr_entries = nr_entries,
2268			.priv	    = evlist,
2269		},
2270		.min_pcnt = min_pcnt,
2271		.env = env,
2272	};
2273
2274	ui_helpline__push("Press ESC to exit");
2275
2276	evlist__for_each(evlist, pos) {
2277		const char *ev_name = perf_evsel__name(pos);
2278		size_t line_len = strlen(ev_name) + 7;
2279
2280		if (menu.b.width < line_len)
2281			menu.b.width = line_len;
2282	}
2283
2284	return perf_evsel_menu__run(&menu, nr_entries, help, hbt);
2285}
2286
2287int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
2288				  struct hist_browser_timer *hbt,
2289				  float min_pcnt,
2290				  struct perf_env *env)
2291{
2292	int nr_entries = evlist->nr_entries;
2293
2294single_entry:
2295	if (nr_entries == 1) {
2296		struct perf_evsel *first = perf_evlist__first(evlist);
2297
2298		return perf_evsel__hists_browse(first, nr_entries, help,
2299						false, hbt, min_pcnt,
2300						env);
2301	}
2302
2303	if (symbol_conf.event_group) {
2304		struct perf_evsel *pos;
2305
2306		nr_entries = 0;
2307		evlist__for_each(evlist, pos) {
2308			if (perf_evsel__is_group_leader(pos))
2309				nr_entries++;
2310		}
2311
2312		if (nr_entries == 1)
2313			goto single_entry;
2314	}
2315
2316	return __perf_evlist__tui_browse_hists(evlist, nr_entries, help,
2317					       hbt, min_pcnt, env);
2318}
2319