xref: /openbmc/linux/tools/perf/ui/browsers/hists.c (revision b6bec26c)
1 #include <stdio.h>
2 #include "../libslang.h"
3 #include <stdlib.h>
4 #include <string.h>
5 #include <newt.h>
6 #include <linux/rbtree.h>
7 
8 #include "../../util/evsel.h"
9 #include "../../util/evlist.h"
10 #include "../../util/hist.h"
11 #include "../../util/pstack.h"
12 #include "../../util/sort.h"
13 #include "../../util/util.h"
14 #include "../../arch/common.h"
15 
16 #include "../browser.h"
17 #include "../helpline.h"
18 #include "../util.h"
19 #include "../ui.h"
20 #include "map.h"
21 
22 struct hist_browser {
23 	struct ui_browser   b;
24 	struct hists	    *hists;
25 	struct hist_entry   *he_selection;
26 	struct map_symbol   *selection;
27 	int		     print_seq;
28 	bool		     show_dso;
29 	bool		     has_symbols;
30 };
31 
32 extern void hist_browser__init_hpp(void);
33 
34 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
35 				const char *ev_name);
36 
37 static void hist_browser__refresh_dimensions(struct hist_browser *browser)
38 {
39 	/* 3 == +/- toggle symbol before actual hist_entry rendering */
40 	browser->b.width = 3 + (hists__sort_list_width(browser->hists) +
41 			     sizeof("[k]"));
42 }
43 
44 static void hist_browser__reset(struct hist_browser *browser)
45 {
46 	browser->b.nr_entries = browser->hists->nr_entries;
47 	hist_browser__refresh_dimensions(browser);
48 	ui_browser__reset_index(&browser->b);
49 }
50 
51 static char tree__folded_sign(bool unfolded)
52 {
53 	return unfolded ? '-' : '+';
54 }
55 
56 static char map_symbol__folded(const struct map_symbol *ms)
57 {
58 	return ms->has_children ? tree__folded_sign(ms->unfolded) : ' ';
59 }
60 
61 static char hist_entry__folded(const struct hist_entry *he)
62 {
63 	return map_symbol__folded(&he->ms);
64 }
65 
66 static char callchain_list__folded(const struct callchain_list *cl)
67 {
68 	return map_symbol__folded(&cl->ms);
69 }
70 
71 static void map_symbol__set_folding(struct map_symbol *ms, bool unfold)
72 {
73 	ms->unfolded = unfold ? ms->has_children : false;
74 }
75 
76 static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
77 {
78 	int n = 0;
79 	struct rb_node *nd;
80 
81 	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
82 		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
83 		struct callchain_list *chain;
84 		char folded_sign = ' '; /* No children */
85 
86 		list_for_each_entry(chain, &child->val, list) {
87 			++n;
88 			/* We need this because we may not have children */
89 			folded_sign = callchain_list__folded(chain);
90 			if (folded_sign == '+')
91 				break;
92 		}
93 
94 		if (folded_sign == '-') /* Have children and they're unfolded */
95 			n += callchain_node__count_rows_rb_tree(child);
96 	}
97 
98 	return n;
99 }
100 
101 static int callchain_node__count_rows(struct callchain_node *node)
102 {
103 	struct callchain_list *chain;
104 	bool unfolded = false;
105 	int n = 0;
106 
107 	list_for_each_entry(chain, &node->val, list) {
108 		++n;
109 		unfolded = chain->ms.unfolded;
110 	}
111 
112 	if (unfolded)
113 		n += callchain_node__count_rows_rb_tree(node);
114 
115 	return n;
116 }
117 
118 static int callchain__count_rows(struct rb_root *chain)
119 {
120 	struct rb_node *nd;
121 	int n = 0;
122 
123 	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
124 		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
125 		n += callchain_node__count_rows(node);
126 	}
127 
128 	return n;
129 }
130 
131 static bool map_symbol__toggle_fold(struct map_symbol *ms)
132 {
133 	if (!ms)
134 		return false;
135 
136 	if (!ms->has_children)
137 		return false;
138 
139 	ms->unfolded = !ms->unfolded;
140 	return true;
141 }
142 
143 static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
144 {
145 	struct rb_node *nd = rb_first(&node->rb_root);
146 
147 	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
148 		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
149 		struct callchain_list *chain;
150 		bool first = true;
151 
152 		list_for_each_entry(chain, &child->val, list) {
153 			if (first) {
154 				first = false;
155 				chain->ms.has_children = chain->list.next != &child->val ||
156 							 !RB_EMPTY_ROOT(&child->rb_root);
157 			} else
158 				chain->ms.has_children = chain->list.next == &child->val &&
159 							 !RB_EMPTY_ROOT(&child->rb_root);
160 		}
161 
162 		callchain_node__init_have_children_rb_tree(child);
163 	}
164 }
165 
166 static void callchain_node__init_have_children(struct callchain_node *node)
167 {
168 	struct callchain_list *chain;
169 
170 	list_for_each_entry(chain, &node->val, list)
171 		chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root);
172 
173 	callchain_node__init_have_children_rb_tree(node);
174 }
175 
176 static void callchain__init_have_children(struct rb_root *root)
177 {
178 	struct rb_node *nd;
179 
180 	for (nd = rb_first(root); nd; nd = rb_next(nd)) {
181 		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
182 		callchain_node__init_have_children(node);
183 	}
184 }
185 
186 static void hist_entry__init_have_children(struct hist_entry *he)
187 {
188 	if (!he->init_have_children) {
189 		he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
190 		callchain__init_have_children(&he->sorted_chain);
191 		he->init_have_children = true;
192 	}
193 }
194 
195 static bool hist_browser__toggle_fold(struct hist_browser *browser)
196 {
197 	if (map_symbol__toggle_fold(browser->selection)) {
198 		struct hist_entry *he = browser->he_selection;
199 
200 		hist_entry__init_have_children(he);
201 		browser->hists->nr_entries -= he->nr_rows;
202 
203 		if (he->ms.unfolded)
204 			he->nr_rows = callchain__count_rows(&he->sorted_chain);
205 		else
206 			he->nr_rows = 0;
207 		browser->hists->nr_entries += he->nr_rows;
208 		browser->b.nr_entries = browser->hists->nr_entries;
209 
210 		return true;
211 	}
212 
213 	/* If it doesn't have children, no toggling performed */
214 	return false;
215 }
216 
217 static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
218 {
219 	int n = 0;
220 	struct rb_node *nd;
221 
222 	for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
223 		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
224 		struct callchain_list *chain;
225 		bool has_children = false;
226 
227 		list_for_each_entry(chain, &child->val, list) {
228 			++n;
229 			map_symbol__set_folding(&chain->ms, unfold);
230 			has_children = chain->ms.has_children;
231 		}
232 
233 		if (has_children)
234 			n += callchain_node__set_folding_rb_tree(child, unfold);
235 	}
236 
237 	return n;
238 }
239 
240 static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
241 {
242 	struct callchain_list *chain;
243 	bool has_children = false;
244 	int n = 0;
245 
246 	list_for_each_entry(chain, &node->val, list) {
247 		++n;
248 		map_symbol__set_folding(&chain->ms, unfold);
249 		has_children = chain->ms.has_children;
250 	}
251 
252 	if (has_children)
253 		n += callchain_node__set_folding_rb_tree(node, unfold);
254 
255 	return n;
256 }
257 
258 static int callchain__set_folding(struct rb_root *chain, bool unfold)
259 {
260 	struct rb_node *nd;
261 	int n = 0;
262 
263 	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
264 		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
265 		n += callchain_node__set_folding(node, unfold);
266 	}
267 
268 	return n;
269 }
270 
271 static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
272 {
273 	hist_entry__init_have_children(he);
274 	map_symbol__set_folding(&he->ms, unfold);
275 
276 	if (he->ms.has_children) {
277 		int n = callchain__set_folding(&he->sorted_chain, unfold);
278 		he->nr_rows = unfold ? n : 0;
279 	} else
280 		he->nr_rows = 0;
281 }
282 
283 static void hists__set_folding(struct hists *hists, bool unfold)
284 {
285 	struct rb_node *nd;
286 
287 	hists->nr_entries = 0;
288 
289 	for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
290 		struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
291 		hist_entry__set_folding(he, unfold);
292 		hists->nr_entries += 1 + he->nr_rows;
293 	}
294 }
295 
296 static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
297 {
298 	hists__set_folding(browser->hists, unfold);
299 	browser->b.nr_entries = browser->hists->nr_entries;
300 	/* Go to the start, we may be way after valid entries after a collapse */
301 	ui_browser__reset_index(&browser->b);
302 }
303 
304 static void ui_browser__warn_lost_events(struct ui_browser *browser)
305 {
306 	ui_browser__warning(browser, 4,
307 		"Events are being lost, check IO/CPU overload!\n\n"
308 		"You may want to run 'perf' using a RT scheduler policy:\n\n"
309 		" perf top -r 80\n\n"
310 		"Or reduce the sampling frequency.");
311 }
312 
313 static int hist_browser__run(struct hist_browser *browser, const char *ev_name,
314 			     struct hist_browser_timer *hbt)
315 {
316 	int key;
317 	char title[160];
318 	int delay_secs = hbt ? hbt->refresh : 0;
319 
320 	browser->b.entries = &browser->hists->entries;
321 	browser->b.nr_entries = browser->hists->nr_entries;
322 
323 	hist_browser__refresh_dimensions(browser);
324 	hists__browser_title(browser->hists, title, sizeof(title), ev_name);
325 
326 	if (ui_browser__show(&browser->b, title,
327 			     "Press '?' for help on key bindings") < 0)
328 		return -1;
329 
330 	while (1) {
331 		key = ui_browser__run(&browser->b, delay_secs);
332 
333 		switch (key) {
334 		case K_TIMER:
335 			hbt->timer(hbt->arg);
336 			ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
337 
338 			if (browser->hists->stats.nr_lost_warned !=
339 			    browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
340 				browser->hists->stats.nr_lost_warned =
341 					browser->hists->stats.nr_events[PERF_RECORD_LOST];
342 				ui_browser__warn_lost_events(&browser->b);
343 			}
344 
345 			hists__browser_title(browser->hists, title, sizeof(title), ev_name);
346 			ui_browser__show_title(&browser->b, title);
347 			continue;
348 		case 'D': { /* Debug */
349 			static int seq;
350 			struct hist_entry *h = rb_entry(browser->b.top,
351 							struct hist_entry, rb_node);
352 			ui_helpline__pop();
353 			ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
354 					   seq++, browser->b.nr_entries,
355 					   browser->hists->nr_entries,
356 					   browser->b.height,
357 					   browser->b.index,
358 					   browser->b.top_idx,
359 					   h->row_offset, h->nr_rows);
360 		}
361 			break;
362 		case 'C':
363 			/* Collapse the whole world. */
364 			hist_browser__set_folding(browser, false);
365 			break;
366 		case 'E':
367 			/* Expand the whole world. */
368 			hist_browser__set_folding(browser, true);
369 			break;
370 		case K_ENTER:
371 			if (hist_browser__toggle_fold(browser))
372 				break;
373 			/* fall thru */
374 		default:
375 			goto out;
376 		}
377 	}
378 out:
379 	ui_browser__hide(&browser->b);
380 	return key;
381 }
382 
383 static char *callchain_list__sym_name(struct callchain_list *cl,
384 				      char *bf, size_t bfsize, bool show_dso)
385 {
386 	int printed;
387 
388 	if (cl->ms.sym)
389 		printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
390 	else
391 		printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
392 
393 	if (show_dso)
394 		scnprintf(bf + printed, bfsize - printed, " %s",
395 			  cl->ms.map ? cl->ms.map->dso->short_name : "unknown");
396 
397 	return bf;
398 }
399 
400 #define LEVEL_OFFSET_STEP 3
401 
402 static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browser,
403 						     struct callchain_node *chain_node,
404 						     u64 total, int level,
405 						     unsigned short row,
406 						     off_t *row_offset,
407 						     bool *is_current_entry)
408 {
409 	struct rb_node *node;
410 	int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
411 	u64 new_total, remaining;
412 
413 	if (callchain_param.mode == CHAIN_GRAPH_REL)
414 		new_total = chain_node->children_hit;
415 	else
416 		new_total = total;
417 
418 	remaining = new_total;
419 	node = rb_first(&chain_node->rb_root);
420 	while (node) {
421 		struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
422 		struct rb_node *next = rb_next(node);
423 		u64 cumul = callchain_cumul_hits(child);
424 		struct callchain_list *chain;
425 		char folded_sign = ' ';
426 		int first = true;
427 		int extra_offset = 0;
428 
429 		remaining -= cumul;
430 
431 		list_for_each_entry(chain, &child->val, list) {
432 			char bf[1024], *alloc_str;
433 			const char *str;
434 			int color;
435 			bool was_first = first;
436 
437 			if (first)
438 				first = false;
439 			else
440 				extra_offset = LEVEL_OFFSET_STEP;
441 
442 			folded_sign = callchain_list__folded(chain);
443 			if (*row_offset != 0) {
444 				--*row_offset;
445 				goto do_next;
446 			}
447 
448 			alloc_str = NULL;
449 			str = callchain_list__sym_name(chain, bf, sizeof(bf),
450 						       browser->show_dso);
451 			if (was_first) {
452 				double percent = cumul * 100.0 / new_total;
453 
454 				if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
455 					str = "Not enough memory!";
456 				else
457 					str = alloc_str;
458 			}
459 
460 			color = HE_COLORSET_NORMAL;
461 			width = browser->b.width - (offset + extra_offset + 2);
462 			if (ui_browser__is_current_entry(&browser->b, row)) {
463 				browser->selection = &chain->ms;
464 				color = HE_COLORSET_SELECTED;
465 				*is_current_entry = true;
466 			}
467 
468 			ui_browser__set_color(&browser->b, color);
469 			ui_browser__gotorc(&browser->b, row, 0);
470 			slsmg_write_nstring(" ", offset + extra_offset);
471 			slsmg_printf("%c ", folded_sign);
472 			slsmg_write_nstring(str, width);
473 			free(alloc_str);
474 
475 			if (++row == browser->b.height)
476 				goto out;
477 do_next:
478 			if (folded_sign == '+')
479 				break;
480 		}
481 
482 		if (folded_sign == '-') {
483 			const int new_level = level + (extra_offset ? 2 : 1);
484 			row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total,
485 									 new_level, row, row_offset,
486 									 is_current_entry);
487 		}
488 		if (row == browser->b.height)
489 			goto out;
490 		node = next;
491 	}
492 out:
493 	return row - first_row;
494 }
495 
496 static int hist_browser__show_callchain_node(struct hist_browser *browser,
497 					     struct callchain_node *node,
498 					     int level, unsigned short row,
499 					     off_t *row_offset,
500 					     bool *is_current_entry)
501 {
502 	struct callchain_list *chain;
503 	int first_row = row,
504 	     offset = level * LEVEL_OFFSET_STEP,
505 	     width = browser->b.width - offset;
506 	char folded_sign = ' ';
507 
508 	list_for_each_entry(chain, &node->val, list) {
509 		char bf[1024], *s;
510 		int color;
511 
512 		folded_sign = callchain_list__folded(chain);
513 
514 		if (*row_offset != 0) {
515 			--*row_offset;
516 			continue;
517 		}
518 
519 		color = HE_COLORSET_NORMAL;
520 		if (ui_browser__is_current_entry(&browser->b, row)) {
521 			browser->selection = &chain->ms;
522 			color = HE_COLORSET_SELECTED;
523 			*is_current_entry = true;
524 		}
525 
526 		s = callchain_list__sym_name(chain, bf, sizeof(bf),
527 					     browser->show_dso);
528 		ui_browser__gotorc(&browser->b, row, 0);
529 		ui_browser__set_color(&browser->b, color);
530 		slsmg_write_nstring(" ", offset);
531 		slsmg_printf("%c ", folded_sign);
532 		slsmg_write_nstring(s, width - 2);
533 
534 		if (++row == browser->b.height)
535 			goto out;
536 	}
537 
538 	if (folded_sign == '-')
539 		row += hist_browser__show_callchain_node_rb_tree(browser, node,
540 								 browser->hists->stats.total_period,
541 								 level + 1, row,
542 								 row_offset,
543 								 is_current_entry);
544 out:
545 	return row - first_row;
546 }
547 
548 static int hist_browser__show_callchain(struct hist_browser *browser,
549 					struct rb_root *chain,
550 					int level, unsigned short row,
551 					off_t *row_offset,
552 					bool *is_current_entry)
553 {
554 	struct rb_node *nd;
555 	int first_row = row;
556 
557 	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
558 		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
559 
560 		row += hist_browser__show_callchain_node(browser, node, level,
561 							 row, row_offset,
562 							 is_current_entry);
563 		if (row == browser->b.height)
564 			break;
565 	}
566 
567 	return row - first_row;
568 }
569 
570 #define HPP__COLOR_FN(_name, _field)					\
571 static int hist_browser__hpp_color_ ## _name(struct perf_hpp *hpp,	\
572 					     struct hist_entry *he)	\
573 {									\
574 	struct hists *hists = he->hists;				\
575 	double percent = 100.0 * he->stat._field / hists->stats.total_period; \
576 	*(double *)hpp->ptr = percent;					\
577 	return scnprintf(hpp->buf, hpp->size, "%6.2f%%", percent);	\
578 }
579 
580 HPP__COLOR_FN(overhead, period)
581 HPP__COLOR_FN(overhead_sys, period_sys)
582 HPP__COLOR_FN(overhead_us, period_us)
583 HPP__COLOR_FN(overhead_guest_sys, period_guest_sys)
584 HPP__COLOR_FN(overhead_guest_us, period_guest_us)
585 
586 #undef HPP__COLOR_FN
587 
588 void hist_browser__init_hpp(void)
589 {
590 	perf_hpp__init();
591 
592 	perf_hpp__format[PERF_HPP__OVERHEAD].color =
593 				hist_browser__hpp_color_overhead;
594 	perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
595 				hist_browser__hpp_color_overhead_sys;
596 	perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
597 				hist_browser__hpp_color_overhead_us;
598 	perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
599 				hist_browser__hpp_color_overhead_guest_sys;
600 	perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
601 				hist_browser__hpp_color_overhead_guest_us;
602 }
603 
604 static int hist_browser__show_entry(struct hist_browser *browser,
605 				    struct hist_entry *entry,
606 				    unsigned short row)
607 {
608 	char s[256];
609 	double percent;
610 	int i, printed = 0;
611 	int width = browser->b.width;
612 	char folded_sign = ' ';
613 	bool current_entry = ui_browser__is_current_entry(&browser->b, row);
614 	off_t row_offset = entry->row_offset;
615 	bool first = true;
616 
617 	if (current_entry) {
618 		browser->he_selection = entry;
619 		browser->selection = &entry->ms;
620 	}
621 
622 	if (symbol_conf.use_callchain) {
623 		hist_entry__init_have_children(entry);
624 		folded_sign = hist_entry__folded(entry);
625 	}
626 
627 	if (row_offset == 0) {
628 		struct perf_hpp hpp = {
629 			.buf		= s,
630 			.size		= sizeof(s),
631 		};
632 
633 		ui_browser__gotorc(&browser->b, row, 0);
634 
635 		for (i = 0; i < PERF_HPP__MAX_INDEX; i++) {
636 			if (!perf_hpp__format[i].cond)
637 				continue;
638 
639 			if (!first) {
640 				slsmg_printf("  ");
641 				width -= 2;
642 			}
643 			first = false;
644 
645 			if (perf_hpp__format[i].color) {
646 				hpp.ptr = &percent;
647 				/* It will set percent for us. See HPP__COLOR_FN above. */
648 				width -= perf_hpp__format[i].color(&hpp, entry);
649 
650 				ui_browser__set_percent_color(&browser->b, percent, current_entry);
651 
652 				if (i == PERF_HPP__OVERHEAD && symbol_conf.use_callchain) {
653 					slsmg_printf("%c ", folded_sign);
654 					width -= 2;
655 				}
656 
657 				slsmg_printf("%s", s);
658 
659 				if (!current_entry || !browser->b.navkeypressed)
660 					ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL);
661 			} else {
662 				width -= perf_hpp__format[i].entry(&hpp, entry);
663 				slsmg_printf("%s", s);
664 			}
665 		}
666 
667 		/* The scroll bar isn't being used */
668 		if (!browser->b.navkeypressed)
669 			width += 1;
670 
671 		hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists);
672 		slsmg_write_nstring(s, width);
673 		++row;
674 		++printed;
675 	} else
676 		--row_offset;
677 
678 	if (folded_sign == '-' && row != browser->b.height) {
679 		printed += hist_browser__show_callchain(browser, &entry->sorted_chain,
680 							1, row, &row_offset,
681 							&current_entry);
682 		if (current_entry)
683 			browser->he_selection = entry;
684 	}
685 
686 	return printed;
687 }
688 
689 static void ui_browser__hists_init_top(struct ui_browser *browser)
690 {
691 	if (browser->top == NULL) {
692 		struct hist_browser *hb;
693 
694 		hb = container_of(browser, struct hist_browser, b);
695 		browser->top = rb_first(&hb->hists->entries);
696 	}
697 }
698 
699 static unsigned int hist_browser__refresh(struct ui_browser *browser)
700 {
701 	unsigned row = 0;
702 	struct rb_node *nd;
703 	struct hist_browser *hb = container_of(browser, struct hist_browser, b);
704 
705 	ui_browser__hists_init_top(browser);
706 
707 	for (nd = browser->top; nd; nd = rb_next(nd)) {
708 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
709 
710 		if (h->filtered)
711 			continue;
712 
713 		row += hist_browser__show_entry(hb, h, row);
714 		if (row == browser->height)
715 			break;
716 	}
717 
718 	return row;
719 }
720 
721 static struct rb_node *hists__filter_entries(struct rb_node *nd)
722 {
723 	while (nd != NULL) {
724 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
725 		if (!h->filtered)
726 			return nd;
727 
728 		nd = rb_next(nd);
729 	}
730 
731 	return NULL;
732 }
733 
734 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
735 {
736 	while (nd != NULL) {
737 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
738 		if (!h->filtered)
739 			return nd;
740 
741 		nd = rb_prev(nd);
742 	}
743 
744 	return NULL;
745 }
746 
747 static void ui_browser__hists_seek(struct ui_browser *browser,
748 				   off_t offset, int whence)
749 {
750 	struct hist_entry *h;
751 	struct rb_node *nd;
752 	bool first = true;
753 
754 	if (browser->nr_entries == 0)
755 		return;
756 
757 	ui_browser__hists_init_top(browser);
758 
759 	switch (whence) {
760 	case SEEK_SET:
761 		nd = hists__filter_entries(rb_first(browser->entries));
762 		break;
763 	case SEEK_CUR:
764 		nd = browser->top;
765 		goto do_offset;
766 	case SEEK_END:
767 		nd = hists__filter_prev_entries(rb_last(browser->entries));
768 		first = false;
769 		break;
770 	default:
771 		return;
772 	}
773 
774 	/*
775 	 * Moves not relative to the first visible entry invalidates its
776 	 * row_offset:
777 	 */
778 	h = rb_entry(browser->top, struct hist_entry, rb_node);
779 	h->row_offset = 0;
780 
781 	/*
782 	 * Here we have to check if nd is expanded (+), if it is we can't go
783 	 * the next top level hist_entry, instead we must compute an offset of
784 	 * what _not_ to show and not change the first visible entry.
785 	 *
786 	 * This offset increments when we are going from top to bottom and
787 	 * decreases when we're going from bottom to top.
788 	 *
789 	 * As we don't have backpointers to the top level in the callchains
790 	 * structure, we need to always print the whole hist_entry callchain,
791 	 * skipping the first ones that are before the first visible entry
792 	 * and stop when we printed enough lines to fill the screen.
793 	 */
794 do_offset:
795 	if (offset > 0) {
796 		do {
797 			h = rb_entry(nd, struct hist_entry, rb_node);
798 			if (h->ms.unfolded) {
799 				u16 remaining = h->nr_rows - h->row_offset;
800 				if (offset > remaining) {
801 					offset -= remaining;
802 					h->row_offset = 0;
803 				} else {
804 					h->row_offset += offset;
805 					offset = 0;
806 					browser->top = nd;
807 					break;
808 				}
809 			}
810 			nd = hists__filter_entries(rb_next(nd));
811 			if (nd == NULL)
812 				break;
813 			--offset;
814 			browser->top = nd;
815 		} while (offset != 0);
816 	} else if (offset < 0) {
817 		while (1) {
818 			h = rb_entry(nd, struct hist_entry, rb_node);
819 			if (h->ms.unfolded) {
820 				if (first) {
821 					if (-offset > h->row_offset) {
822 						offset += h->row_offset;
823 						h->row_offset = 0;
824 					} else {
825 						h->row_offset += offset;
826 						offset = 0;
827 						browser->top = nd;
828 						break;
829 					}
830 				} else {
831 					if (-offset > h->nr_rows) {
832 						offset += h->nr_rows;
833 						h->row_offset = 0;
834 					} else {
835 						h->row_offset = h->nr_rows + offset;
836 						offset = 0;
837 						browser->top = nd;
838 						break;
839 					}
840 				}
841 			}
842 
843 			nd = hists__filter_prev_entries(rb_prev(nd));
844 			if (nd == NULL)
845 				break;
846 			++offset;
847 			browser->top = nd;
848 			if (offset == 0) {
849 				/*
850 				 * Last unfiltered hist_entry, check if it is
851 				 * unfolded, if it is then we should have
852 				 * row_offset at its last entry.
853 				 */
854 				h = rb_entry(nd, struct hist_entry, rb_node);
855 				if (h->ms.unfolded)
856 					h->row_offset = h->nr_rows;
857 				break;
858 			}
859 			first = false;
860 		}
861 	} else {
862 		browser->top = nd;
863 		h = rb_entry(nd, struct hist_entry, rb_node);
864 		h->row_offset = 0;
865 	}
866 }
867 
868 static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser,
869 							struct callchain_node *chain_node,
870 							u64 total, int level,
871 							FILE *fp)
872 {
873 	struct rb_node *node;
874 	int offset = level * LEVEL_OFFSET_STEP;
875 	u64 new_total, remaining;
876 	int printed = 0;
877 
878 	if (callchain_param.mode == CHAIN_GRAPH_REL)
879 		new_total = chain_node->children_hit;
880 	else
881 		new_total = total;
882 
883 	remaining = new_total;
884 	node = rb_first(&chain_node->rb_root);
885 	while (node) {
886 		struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
887 		struct rb_node *next = rb_next(node);
888 		u64 cumul = callchain_cumul_hits(child);
889 		struct callchain_list *chain;
890 		char folded_sign = ' ';
891 		int first = true;
892 		int extra_offset = 0;
893 
894 		remaining -= cumul;
895 
896 		list_for_each_entry(chain, &child->val, list) {
897 			char bf[1024], *alloc_str;
898 			const char *str;
899 			bool was_first = first;
900 
901 			if (first)
902 				first = false;
903 			else
904 				extra_offset = LEVEL_OFFSET_STEP;
905 
906 			folded_sign = callchain_list__folded(chain);
907 
908 			alloc_str = NULL;
909 			str = callchain_list__sym_name(chain, bf, sizeof(bf),
910 						       browser->show_dso);
911 			if (was_first) {
912 				double percent = cumul * 100.0 / new_total;
913 
914 				if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
915 					str = "Not enough memory!";
916 				else
917 					str = alloc_str;
918 			}
919 
920 			printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str);
921 			free(alloc_str);
922 			if (folded_sign == '+')
923 				break;
924 		}
925 
926 		if (folded_sign == '-') {
927 			const int new_level = level + (extra_offset ? 2 : 1);
928 			printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total,
929 										new_level, fp);
930 		}
931 
932 		node = next;
933 	}
934 
935 	return printed;
936 }
937 
938 static int hist_browser__fprintf_callchain_node(struct hist_browser *browser,
939 						struct callchain_node *node,
940 						int level, FILE *fp)
941 {
942 	struct callchain_list *chain;
943 	int offset = level * LEVEL_OFFSET_STEP;
944 	char folded_sign = ' ';
945 	int printed = 0;
946 
947 	list_for_each_entry(chain, &node->val, list) {
948 		char bf[1024], *s;
949 
950 		folded_sign = callchain_list__folded(chain);
951 		s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso);
952 		printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s);
953 	}
954 
955 	if (folded_sign == '-')
956 		printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node,
957 									browser->hists->stats.total_period,
958 									level + 1,  fp);
959 	return printed;
960 }
961 
962 static int hist_browser__fprintf_callchain(struct hist_browser *browser,
963 					   struct rb_root *chain, int level, FILE *fp)
964 {
965 	struct rb_node *nd;
966 	int printed = 0;
967 
968 	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
969 		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
970 
971 		printed += hist_browser__fprintf_callchain_node(browser, node, level, fp);
972 	}
973 
974 	return printed;
975 }
976 
977 static int hist_browser__fprintf_entry(struct hist_browser *browser,
978 				       struct hist_entry *he, FILE *fp)
979 {
980 	char s[8192];
981 	double percent;
982 	int printed = 0;
983 	char folded_sign = ' ';
984 
985 	if (symbol_conf.use_callchain)
986 		folded_sign = hist_entry__folded(he);
987 
988 	hist_entry__sort_snprintf(he, s, sizeof(s), browser->hists);
989 	percent = (he->stat.period * 100.0) / browser->hists->stats.total_period;
990 
991 	if (symbol_conf.use_callchain)
992 		printed += fprintf(fp, "%c ", folded_sign);
993 
994 	printed += fprintf(fp, " %5.2f%%", percent);
995 
996 	if (symbol_conf.show_nr_samples)
997 		printed += fprintf(fp, " %11u", he->stat.nr_events);
998 
999 	if (symbol_conf.show_total_period)
1000 		printed += fprintf(fp, " %12" PRIu64, he->stat.period);
1001 
1002 	printed += fprintf(fp, "%s\n", rtrim(s));
1003 
1004 	if (folded_sign == '-')
1005 		printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp);
1006 
1007 	return printed;
1008 }
1009 
1010 static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
1011 {
1012 	struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries));
1013 	int printed = 0;
1014 
1015 	while (nd) {
1016 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1017 
1018 		printed += hist_browser__fprintf_entry(browser, h, fp);
1019 		nd = hists__filter_entries(rb_next(nd));
1020 	}
1021 
1022 	return printed;
1023 }
1024 
1025 static int hist_browser__dump(struct hist_browser *browser)
1026 {
1027 	char filename[64];
1028 	FILE *fp;
1029 
1030 	while (1) {
1031 		scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
1032 		if (access(filename, F_OK))
1033 			break;
1034 		/*
1035  		 * XXX: Just an arbitrary lazy upper limit
1036  		 */
1037 		if (++browser->print_seq == 8192) {
1038 			ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
1039 			return -1;
1040 		}
1041 	}
1042 
1043 	fp = fopen(filename, "w");
1044 	if (fp == NULL) {
1045 		char bf[64];
1046 		const char *err = strerror_r(errno, bf, sizeof(bf));
1047 		ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
1048 		return -1;
1049 	}
1050 
1051 	++browser->print_seq;
1052 	hist_browser__fprintf(browser, fp);
1053 	fclose(fp);
1054 	ui_helpline__fpush("%s written!", filename);
1055 
1056 	return 0;
1057 }
1058 
1059 static struct hist_browser *hist_browser__new(struct hists *hists)
1060 {
1061 	struct hist_browser *browser = zalloc(sizeof(*browser));
1062 
1063 	if (browser) {
1064 		browser->hists = hists;
1065 		browser->b.refresh = hist_browser__refresh;
1066 		browser->b.seek = ui_browser__hists_seek;
1067 		browser->b.use_navkeypressed = true;
1068 		if (sort__branch_mode == 1)
1069 			browser->has_symbols = sort_sym_from.list.next != NULL;
1070 		else
1071 			browser->has_symbols = sort_sym.list.next != NULL;
1072 	}
1073 
1074 	return browser;
1075 }
1076 
1077 static void hist_browser__delete(struct hist_browser *browser)
1078 {
1079 	free(browser);
1080 }
1081 
1082 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
1083 {
1084 	return browser->he_selection;
1085 }
1086 
1087 static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
1088 {
1089 	return browser->he_selection->thread;
1090 }
1091 
1092 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
1093 				const char *ev_name)
1094 {
1095 	char unit;
1096 	int printed;
1097 	const struct dso *dso = hists->dso_filter;
1098 	const struct thread *thread = hists->thread_filter;
1099 	unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
1100 	u64 nr_events = hists->stats.total_period;
1101 
1102 	nr_samples = convert_unit(nr_samples, &unit);
1103 	printed = scnprintf(bf, size,
1104 			   "Samples: %lu%c of event '%s', Event count (approx.): %lu",
1105 			   nr_samples, unit, ev_name, nr_events);
1106 
1107 
1108 	if (hists->uid_filter_str)
1109 		printed += snprintf(bf + printed, size - printed,
1110 				    ", UID: %s", hists->uid_filter_str);
1111 	if (thread)
1112 		printed += scnprintf(bf + printed, size - printed,
1113 				    ", Thread: %s(%d)",
1114 				    (thread->comm_set ? thread->comm : ""),
1115 				    thread->pid);
1116 	if (dso)
1117 		printed += scnprintf(bf + printed, size - printed,
1118 				    ", DSO: %s", dso->short_name);
1119 	return printed;
1120 }
1121 
1122 static inline void free_popup_options(char **options, int n)
1123 {
1124 	int i;
1125 
1126 	for (i = 0; i < n; ++i) {
1127 		free(options[i]);
1128 		options[i] = NULL;
1129 	}
1130 }
1131 
1132 /* Check whether the browser is for 'top' or 'report' */
1133 static inline bool is_report_browser(void *timer)
1134 {
1135 	return timer == NULL;
1136 }
1137 
1138 static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
1139 				    const char *helpline, const char *ev_name,
1140 				    bool left_exits,
1141 				    struct hist_browser_timer *hbt,
1142 				    struct perf_session_env *env)
1143 {
1144 	struct hists *hists = &evsel->hists;
1145 	struct hist_browser *browser = hist_browser__new(hists);
1146 	struct branch_info *bi;
1147 	struct pstack *fstack;
1148 	char *options[16];
1149 	int nr_options = 0;
1150 	int key = -1;
1151 	char buf[64];
1152 	char script_opt[64];
1153 	int delay_secs = hbt ? hbt->refresh : 0;
1154 
1155 	if (browser == NULL)
1156 		return -1;
1157 
1158 	fstack = pstack__new(2);
1159 	if (fstack == NULL)
1160 		goto out;
1161 
1162 	ui_helpline__push(helpline);
1163 
1164 	memset(options, 0, sizeof(options));
1165 
1166 	while (1) {
1167 		const struct thread *thread = NULL;
1168 		const struct dso *dso = NULL;
1169 		int choice = 0,
1170 		    annotate = -2, zoom_dso = -2, zoom_thread = -2,
1171 		    annotate_f = -2, annotate_t = -2, browse_map = -2;
1172 		int scripts_comm = -2, scripts_symbol = -2, scripts_all = -2;
1173 
1174 		nr_options = 0;
1175 
1176 		key = hist_browser__run(browser, ev_name, hbt);
1177 
1178 		if (browser->he_selection != NULL) {
1179 			thread = hist_browser__selected_thread(browser);
1180 			dso = browser->selection->map ? browser->selection->map->dso : NULL;
1181 		}
1182 		switch (key) {
1183 		case K_TAB:
1184 		case K_UNTAB:
1185 			if (nr_events == 1)
1186 				continue;
1187 			/*
1188 			 * Exit the browser, let hists__browser_tree
1189 			 * go to the next or previous
1190 			 */
1191 			goto out_free_stack;
1192 		case 'a':
1193 			if (!browser->has_symbols) {
1194 				ui_browser__warning(&browser->b, delay_secs * 2,
1195 			"Annotation is only available for symbolic views, "
1196 			"include \"sym*\" in --sort to use it.");
1197 				continue;
1198 			}
1199 
1200 			if (browser->selection == NULL ||
1201 			    browser->selection->sym == NULL ||
1202 			    browser->selection->map->dso->annotate_warned)
1203 				continue;
1204 			goto do_annotate;
1205 		case 'P':
1206 			hist_browser__dump(browser);
1207 			continue;
1208 		case 'd':
1209 			goto zoom_dso;
1210 		case 'V':
1211 			browser->show_dso = !browser->show_dso;
1212 			continue;
1213 		case 't':
1214 			goto zoom_thread;
1215 		case '/':
1216 			if (ui_browser__input_window("Symbol to show",
1217 					"Please enter the name of symbol you want to see",
1218 					buf, "ENTER: OK, ESC: Cancel",
1219 					delay_secs * 2) == K_ENTER) {
1220 				hists->symbol_filter_str = *buf ? buf : NULL;
1221 				hists__filter_by_symbol(hists);
1222 				hist_browser__reset(browser);
1223 			}
1224 			continue;
1225 		case 'r':
1226 			if (is_report_browser(hbt))
1227 				goto do_scripts;
1228 			continue;
1229 		case K_F1:
1230 		case 'h':
1231 		case '?':
1232 			ui_browser__help_window(&browser->b,
1233 					"h/?/F1        Show this window\n"
1234 					"UP/DOWN/PGUP\n"
1235 					"PGDN/SPACE    Navigate\n"
1236 					"q/ESC/CTRL+C  Exit browser\n\n"
1237 					"For multiple event sessions:\n\n"
1238 					"TAB/UNTAB Switch events\n\n"
1239 					"For symbolic views (--sort has sym):\n\n"
1240 					"->            Zoom into DSO/Threads & Annotate current symbol\n"
1241 					"<-            Zoom out\n"
1242 					"a             Annotate current symbol\n"
1243 					"C             Collapse all callchains\n"
1244 					"E             Expand all callchains\n"
1245 					"d             Zoom into current DSO\n"
1246 					"t             Zoom into current Thread\n"
1247 					"r             Run available scripts('perf report' only)\n"
1248 					"P             Print histograms to perf.hist.N\n"
1249 					"V             Verbose (DSO names in callchains, etc)\n"
1250 					"/             Filter symbol by name");
1251 			continue;
1252 		case K_ENTER:
1253 		case K_RIGHT:
1254 			/* menu */
1255 			break;
1256 		case K_LEFT: {
1257 			const void *top;
1258 
1259 			if (pstack__empty(fstack)) {
1260 				/*
1261 				 * Go back to the perf_evsel_menu__run or other user
1262 				 */
1263 				if (left_exits)
1264 					goto out_free_stack;
1265 				continue;
1266 			}
1267 			top = pstack__pop(fstack);
1268 			if (top == &browser->hists->dso_filter)
1269 				goto zoom_out_dso;
1270 			if (top == &browser->hists->thread_filter)
1271 				goto zoom_out_thread;
1272 			continue;
1273 		}
1274 		case K_ESC:
1275 			if (!left_exits &&
1276 			    !ui_browser__dialog_yesno(&browser->b,
1277 					       "Do you really want to exit?"))
1278 				continue;
1279 			/* Fall thru */
1280 		case 'q':
1281 		case CTRL('c'):
1282 			goto out_free_stack;
1283 		default:
1284 			continue;
1285 		}
1286 
1287 		if (!browser->has_symbols)
1288 			goto add_exit_option;
1289 
1290 		if (sort__branch_mode == 1) {
1291 			bi = browser->he_selection->branch_info;
1292 			if (browser->selection != NULL &&
1293 			    bi &&
1294 			    bi->from.sym != NULL &&
1295 			    !bi->from.map->dso->annotate_warned &&
1296 				asprintf(&options[nr_options], "Annotate %s",
1297 					 bi->from.sym->name) > 0)
1298 				annotate_f = nr_options++;
1299 
1300 			if (browser->selection != NULL &&
1301 			    bi &&
1302 			    bi->to.sym != NULL &&
1303 			    !bi->to.map->dso->annotate_warned &&
1304 			    (bi->to.sym != bi->from.sym ||
1305 			     bi->to.map->dso != bi->from.map->dso) &&
1306 				asprintf(&options[nr_options], "Annotate %s",
1307 					 bi->to.sym->name) > 0)
1308 				annotate_t = nr_options++;
1309 		} else {
1310 
1311 			if (browser->selection != NULL &&
1312 			    browser->selection->sym != NULL &&
1313 			    !browser->selection->map->dso->annotate_warned &&
1314 				asprintf(&options[nr_options], "Annotate %s",
1315 					 browser->selection->sym->name) > 0)
1316 				annotate = nr_options++;
1317 		}
1318 
1319 		if (thread != NULL &&
1320 		    asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
1321 			     (browser->hists->thread_filter ? "out of" : "into"),
1322 			     (thread->comm_set ? thread->comm : ""),
1323 			     thread->pid) > 0)
1324 			zoom_thread = nr_options++;
1325 
1326 		if (dso != NULL &&
1327 		    asprintf(&options[nr_options], "Zoom %s %s DSO",
1328 			     (browser->hists->dso_filter ? "out of" : "into"),
1329 			     (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
1330 			zoom_dso = nr_options++;
1331 
1332 		if (browser->selection != NULL &&
1333 		    browser->selection->map != NULL &&
1334 		    asprintf(&options[nr_options], "Browse map details") > 0)
1335 			browse_map = nr_options++;
1336 
1337 		/* perf script support */
1338 		if (browser->he_selection) {
1339 			struct symbol *sym;
1340 
1341 			if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]",
1342 				browser->he_selection->thread->comm) > 0)
1343 				scripts_comm = nr_options++;
1344 
1345 			sym = browser->he_selection->ms.sym;
1346 			if (sym && sym->namelen &&
1347 				asprintf(&options[nr_options], "Run scripts for samples of symbol [%s]",
1348 						sym->name) > 0)
1349 				scripts_symbol = nr_options++;
1350 		}
1351 
1352 		if (asprintf(&options[nr_options], "Run scripts for all samples") > 0)
1353 			scripts_all = nr_options++;
1354 
1355 add_exit_option:
1356 		options[nr_options++] = (char *)"Exit";
1357 retry_popup_menu:
1358 		choice = ui__popup_menu(nr_options, options);
1359 
1360 		if (choice == nr_options - 1)
1361 			break;
1362 
1363 		if (choice == -1) {
1364 			free_popup_options(options, nr_options - 1);
1365 			continue;
1366 		}
1367 
1368 		if (choice == annotate || choice == annotate_t || choice == annotate_f) {
1369 			struct hist_entry *he;
1370 			int err;
1371 do_annotate:
1372 			if (!objdump_path && perf_session_env__lookup_objdump(env))
1373 				continue;
1374 
1375 			he = hist_browser__selected_entry(browser);
1376 			if (he == NULL)
1377 				continue;
1378 
1379 			/*
1380 			 * we stash the branch_info symbol + map into the
1381 			 * the ms so we don't have to rewrite all the annotation
1382 			 * code to use branch_info.
1383 			 * in branch mode, the ms struct is not used
1384 			 */
1385 			if (choice == annotate_f) {
1386 				he->ms.sym = he->branch_info->from.sym;
1387 				he->ms.map = he->branch_info->from.map;
1388 			}  else if (choice == annotate_t) {
1389 				he->ms.sym = he->branch_info->to.sym;
1390 				he->ms.map = he->branch_info->to.map;
1391 			}
1392 
1393 			/*
1394 			 * Don't let this be freed, say, by hists__decay_entry.
1395 			 */
1396 			he->used = true;
1397 			err = hist_entry__tui_annotate(he, evsel->idx, hbt);
1398 			he->used = false;
1399 			/*
1400 			 * offer option to annotate the other branch source or target
1401 			 * (if they exists) when returning from annotate
1402 			 */
1403 			if ((err == 'q' || err == CTRL('c'))
1404 			    && annotate_t != -2 && annotate_f != -2)
1405 				goto retry_popup_menu;
1406 
1407 			ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
1408 			if (err)
1409 				ui_browser__handle_resize(&browser->b);
1410 
1411 		} else if (choice == browse_map)
1412 			map__browse(browser->selection->map);
1413 		else if (choice == zoom_dso) {
1414 zoom_dso:
1415 			if (browser->hists->dso_filter) {
1416 				pstack__remove(fstack, &browser->hists->dso_filter);
1417 zoom_out_dso:
1418 				ui_helpline__pop();
1419 				browser->hists->dso_filter = NULL;
1420 				sort_dso.elide = false;
1421 			} else {
1422 				if (dso == NULL)
1423 					continue;
1424 				ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
1425 						   dso->kernel ? "the Kernel" : dso->short_name);
1426 				browser->hists->dso_filter = dso;
1427 				sort_dso.elide = true;
1428 				pstack__push(fstack, &browser->hists->dso_filter);
1429 			}
1430 			hists__filter_by_dso(hists);
1431 			hist_browser__reset(browser);
1432 		} else if (choice == zoom_thread) {
1433 zoom_thread:
1434 			if (browser->hists->thread_filter) {
1435 				pstack__remove(fstack, &browser->hists->thread_filter);
1436 zoom_out_thread:
1437 				ui_helpline__pop();
1438 				browser->hists->thread_filter = NULL;
1439 				sort_thread.elide = false;
1440 			} else {
1441 				ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
1442 						   thread->comm_set ? thread->comm : "",
1443 						   thread->pid);
1444 				browser->hists->thread_filter = thread;
1445 				sort_thread.elide = true;
1446 				pstack__push(fstack, &browser->hists->thread_filter);
1447 			}
1448 			hists__filter_by_thread(hists);
1449 			hist_browser__reset(browser);
1450 		}
1451 		/* perf scripts support */
1452 		else if (choice == scripts_all || choice == scripts_comm ||
1453 				choice == scripts_symbol) {
1454 do_scripts:
1455 			memset(script_opt, 0, 64);
1456 
1457 			if (choice == scripts_comm)
1458 				sprintf(script_opt, " -c %s ", browser->he_selection->thread->comm);
1459 
1460 			if (choice == scripts_symbol)
1461 				sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name);
1462 
1463 			script_browse(script_opt);
1464 		}
1465 	}
1466 out_free_stack:
1467 	pstack__delete(fstack);
1468 out:
1469 	hist_browser__delete(browser);
1470 	free_popup_options(options, nr_options - 1);
1471 	return key;
1472 }
1473 
1474 struct perf_evsel_menu {
1475 	struct ui_browser b;
1476 	struct perf_evsel *selection;
1477 	bool lost_events, lost_events_warned;
1478 	struct perf_session_env *env;
1479 };
1480 
1481 static void perf_evsel_menu__write(struct ui_browser *browser,
1482 				   void *entry, int row)
1483 {
1484 	struct perf_evsel_menu *menu = container_of(browser,
1485 						    struct perf_evsel_menu, b);
1486 	struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1487 	bool current_entry = ui_browser__is_current_entry(browser, row);
1488 	unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1489 	const char *ev_name = perf_evsel__name(evsel);
1490 	char bf[256], unit;
1491 	const char *warn = " ";
1492 	size_t printed;
1493 
1494 	ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
1495 						       HE_COLORSET_NORMAL);
1496 
1497 	nr_events = convert_unit(nr_events, &unit);
1498 	printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
1499 			   unit, unit == ' ' ? "" : " ", ev_name);
1500 	slsmg_printf("%s", bf);
1501 
1502 	nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST];
1503 	if (nr_events != 0) {
1504 		menu->lost_events = true;
1505 		if (!current_entry)
1506 			ui_browser__set_color(browser, HE_COLORSET_TOP);
1507 		nr_events = convert_unit(nr_events, &unit);
1508 		printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
1509 				     nr_events, unit, unit == ' ' ? "" : " ");
1510 		warn = bf;
1511 	}
1512 
1513 	slsmg_write_nstring(warn, browser->width - printed);
1514 
1515 	if (current_entry)
1516 		menu->selection = evsel;
1517 }
1518 
1519 static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
1520 				int nr_events, const char *help,
1521 				struct hist_browser_timer *hbt)
1522 {
1523 	struct perf_evlist *evlist = menu->b.priv;
1524 	struct perf_evsel *pos;
1525 	const char *ev_name, *title = "Available samples";
1526 	int delay_secs = hbt ? hbt->refresh : 0;
1527 	int key;
1528 
1529 	if (ui_browser__show(&menu->b, title,
1530 			     "ESC: exit, ENTER|->: Browse histograms") < 0)
1531 		return -1;
1532 
1533 	while (1) {
1534 		key = ui_browser__run(&menu->b, delay_secs);
1535 
1536 		switch (key) {
1537 		case K_TIMER:
1538 			hbt->timer(hbt->arg);
1539 
1540 			if (!menu->lost_events_warned && menu->lost_events) {
1541 				ui_browser__warn_lost_events(&menu->b);
1542 				menu->lost_events_warned = true;
1543 			}
1544 			continue;
1545 		case K_RIGHT:
1546 		case K_ENTER:
1547 			if (!menu->selection)
1548 				continue;
1549 			pos = menu->selection;
1550 browse_hists:
1551 			perf_evlist__set_selected(evlist, pos);
1552 			/*
1553 			 * Give the calling tool a chance to populate the non
1554 			 * default evsel resorted hists tree.
1555 			 */
1556 			if (hbt)
1557 				hbt->timer(hbt->arg);
1558 			ev_name = perf_evsel__name(pos);
1559 			key = perf_evsel__hists_browse(pos, nr_events, help,
1560 						       ev_name, true, hbt,
1561 						       menu->env);
1562 			ui_browser__show_title(&menu->b, title);
1563 			switch (key) {
1564 			case K_TAB:
1565 				if (pos->node.next == &evlist->entries)
1566 					pos = list_entry(evlist->entries.next, struct perf_evsel, node);
1567 				else
1568 					pos = list_entry(pos->node.next, struct perf_evsel, node);
1569 				goto browse_hists;
1570 			case K_UNTAB:
1571 				if (pos->node.prev == &evlist->entries)
1572 					pos = list_entry(evlist->entries.prev, struct perf_evsel, node);
1573 				else
1574 					pos = list_entry(pos->node.prev, struct perf_evsel, node);
1575 				goto browse_hists;
1576 			case K_ESC:
1577 				if (!ui_browser__dialog_yesno(&menu->b,
1578 						"Do you really want to exit?"))
1579 					continue;
1580 				/* Fall thru */
1581 			case 'q':
1582 			case CTRL('c'):
1583 				goto out;
1584 			default:
1585 				continue;
1586 			}
1587 		case K_LEFT:
1588 			continue;
1589 		case K_ESC:
1590 			if (!ui_browser__dialog_yesno(&menu->b,
1591 					       "Do you really want to exit?"))
1592 				continue;
1593 			/* Fall thru */
1594 		case 'q':
1595 		case CTRL('c'):
1596 			goto out;
1597 		default:
1598 			continue;
1599 		}
1600 	}
1601 
1602 out:
1603 	ui_browser__hide(&menu->b);
1604 	return key;
1605 }
1606 
1607 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1608 					   const char *help,
1609 					   struct hist_browser_timer *hbt,
1610 					   struct perf_session_env *env)
1611 {
1612 	struct perf_evsel *pos;
1613 	struct perf_evsel_menu menu = {
1614 		.b = {
1615 			.entries    = &evlist->entries,
1616 			.refresh    = ui_browser__list_head_refresh,
1617 			.seek	    = ui_browser__list_head_seek,
1618 			.write	    = perf_evsel_menu__write,
1619 			.nr_entries = evlist->nr_entries,
1620 			.priv	    = evlist,
1621 		},
1622 		.env = env,
1623 	};
1624 
1625 	ui_helpline__push("Press ESC to exit");
1626 
1627 	list_for_each_entry(pos, &evlist->entries, node) {
1628 		const char *ev_name = perf_evsel__name(pos);
1629 		size_t line_len = strlen(ev_name) + 7;
1630 
1631 		if (menu.b.width < line_len)
1632 			menu.b.width = line_len;
1633 	}
1634 
1635 	return perf_evsel_menu__run(&menu, evlist->nr_entries, help, hbt);
1636 }
1637 
1638 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
1639 				  struct hist_browser_timer *hbt,
1640 				  struct perf_session_env *env)
1641 {
1642 	if (evlist->nr_entries == 1) {
1643 		struct perf_evsel *first = list_entry(evlist->entries.next,
1644 						      struct perf_evsel, node);
1645 		const char *ev_name = perf_evsel__name(first);
1646 		return perf_evsel__hists_browse(first, evlist->nr_entries, help,
1647 						ev_name, false, hbt, env);
1648 	}
1649 
1650 	return __perf_evlist__tui_browse_hists(evlist, help, hbt, env);
1651 }
1652