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