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