xref: /openbmc/linux/tools/perf/ui/browsers/hists.c (revision cf028200)
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 	bool first = true;
614 
615 	if (current_entry) {
616 		browser->he_selection = entry;
617 		browser->selection = &entry->ms;
618 	}
619 
620 	if (symbol_conf.use_callchain) {
621 		hist_entry__init_have_children(entry);
622 		folded_sign = hist_entry__folded(entry);
623 	}
624 
625 	if (row_offset == 0) {
626 		struct perf_hpp hpp = {
627 			.buf		= s,
628 			.size		= sizeof(s),
629 		};
630 
631 		ui_browser__gotorc(&browser->b, row, 0);
632 
633 		for (i = 0; i < PERF_HPP__MAX_INDEX; i++) {
634 			if (!perf_hpp__format[i].cond)
635 				continue;
636 
637 			if (!first) {
638 				slsmg_printf("  ");
639 				width -= 2;
640 			}
641 			first = false;
642 
643 			if (perf_hpp__format[i].color) {
644 				hpp.ptr = &percent;
645 				/* It will set percent for us. See HPP__COLOR_FN above. */
646 				width -= perf_hpp__format[i].color(&hpp, entry);
647 
648 				ui_browser__set_percent_color(&browser->b, percent, current_entry);
649 
650 				if (i == PERF_HPP__OVERHEAD && symbol_conf.use_callchain) {
651 					slsmg_printf("%c ", folded_sign);
652 					width -= 2;
653 				}
654 
655 				slsmg_printf("%s", s);
656 
657 				if (!current_entry || !browser->b.navkeypressed)
658 					ui_browser__set_color(&browser->b, HE_COLORSET_NORMAL);
659 			} else {
660 				width -= perf_hpp__format[i].entry(&hpp, entry);
661 				slsmg_printf("%s", s);
662 			}
663 		}
664 
665 		/* The scroll bar isn't being used */
666 		if (!browser->b.navkeypressed)
667 			width += 1;
668 
669 		hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists);
670 		slsmg_write_nstring(s, width);
671 		++row;
672 		++printed;
673 	} else
674 		--row_offset;
675 
676 	if (folded_sign == '-' && row != browser->b.height) {
677 		printed += hist_browser__show_callchain(browser, &entry->sorted_chain,
678 							1, row, &row_offset,
679 							&current_entry);
680 		if (current_entry)
681 			browser->he_selection = entry;
682 	}
683 
684 	return printed;
685 }
686 
687 static void ui_browser__hists_init_top(struct ui_browser *browser)
688 {
689 	if (browser->top == NULL) {
690 		struct hist_browser *hb;
691 
692 		hb = container_of(browser, struct hist_browser, b);
693 		browser->top = rb_first(&hb->hists->entries);
694 	}
695 }
696 
697 static unsigned int hist_browser__refresh(struct ui_browser *browser)
698 {
699 	unsigned row = 0;
700 	struct rb_node *nd;
701 	struct hist_browser *hb = container_of(browser, struct hist_browser, b);
702 
703 	ui_browser__hists_init_top(browser);
704 
705 	for (nd = browser->top; nd; nd = rb_next(nd)) {
706 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
707 
708 		if (h->filtered)
709 			continue;
710 
711 		row += hist_browser__show_entry(hb, h, row);
712 		if (row == browser->height)
713 			break;
714 	}
715 
716 	return row;
717 }
718 
719 static struct rb_node *hists__filter_entries(struct rb_node *nd)
720 {
721 	while (nd != NULL) {
722 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
723 		if (!h->filtered)
724 			return nd;
725 
726 		nd = rb_next(nd);
727 	}
728 
729 	return NULL;
730 }
731 
732 static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
733 {
734 	while (nd != NULL) {
735 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
736 		if (!h->filtered)
737 			return nd;
738 
739 		nd = rb_prev(nd);
740 	}
741 
742 	return NULL;
743 }
744 
745 static void ui_browser__hists_seek(struct ui_browser *browser,
746 				   off_t offset, int whence)
747 {
748 	struct hist_entry *h;
749 	struct rb_node *nd;
750 	bool first = true;
751 
752 	if (browser->nr_entries == 0)
753 		return;
754 
755 	ui_browser__hists_init_top(browser);
756 
757 	switch (whence) {
758 	case SEEK_SET:
759 		nd = hists__filter_entries(rb_first(browser->entries));
760 		break;
761 	case SEEK_CUR:
762 		nd = browser->top;
763 		goto do_offset;
764 	case SEEK_END:
765 		nd = hists__filter_prev_entries(rb_last(browser->entries));
766 		first = false;
767 		break;
768 	default:
769 		return;
770 	}
771 
772 	/*
773 	 * Moves not relative to the first visible entry invalidates its
774 	 * row_offset:
775 	 */
776 	h = rb_entry(browser->top, struct hist_entry, rb_node);
777 	h->row_offset = 0;
778 
779 	/*
780 	 * Here we have to check if nd is expanded (+), if it is we can't go
781 	 * the next top level hist_entry, instead we must compute an offset of
782 	 * what _not_ to show and not change the first visible entry.
783 	 *
784 	 * This offset increments when we are going from top to bottom and
785 	 * decreases when we're going from bottom to top.
786 	 *
787 	 * As we don't have backpointers to the top level in the callchains
788 	 * structure, we need to always print the whole hist_entry callchain,
789 	 * skipping the first ones that are before the first visible entry
790 	 * and stop when we printed enough lines to fill the screen.
791 	 */
792 do_offset:
793 	if (offset > 0) {
794 		do {
795 			h = rb_entry(nd, struct hist_entry, rb_node);
796 			if (h->ms.unfolded) {
797 				u16 remaining = h->nr_rows - h->row_offset;
798 				if (offset > remaining) {
799 					offset -= remaining;
800 					h->row_offset = 0;
801 				} else {
802 					h->row_offset += offset;
803 					offset = 0;
804 					browser->top = nd;
805 					break;
806 				}
807 			}
808 			nd = hists__filter_entries(rb_next(nd));
809 			if (nd == NULL)
810 				break;
811 			--offset;
812 			browser->top = nd;
813 		} while (offset != 0);
814 	} else if (offset < 0) {
815 		while (1) {
816 			h = rb_entry(nd, struct hist_entry, rb_node);
817 			if (h->ms.unfolded) {
818 				if (first) {
819 					if (-offset > h->row_offset) {
820 						offset += h->row_offset;
821 						h->row_offset = 0;
822 					} else {
823 						h->row_offset += offset;
824 						offset = 0;
825 						browser->top = nd;
826 						break;
827 					}
828 				} else {
829 					if (-offset > h->nr_rows) {
830 						offset += h->nr_rows;
831 						h->row_offset = 0;
832 					} else {
833 						h->row_offset = h->nr_rows + offset;
834 						offset = 0;
835 						browser->top = nd;
836 						break;
837 					}
838 				}
839 			}
840 
841 			nd = hists__filter_prev_entries(rb_prev(nd));
842 			if (nd == NULL)
843 				break;
844 			++offset;
845 			browser->top = nd;
846 			if (offset == 0) {
847 				/*
848 				 * Last unfiltered hist_entry, check if it is
849 				 * unfolded, if it is then we should have
850 				 * row_offset at its last entry.
851 				 */
852 				h = rb_entry(nd, struct hist_entry, rb_node);
853 				if (h->ms.unfolded)
854 					h->row_offset = h->nr_rows;
855 				break;
856 			}
857 			first = false;
858 		}
859 	} else {
860 		browser->top = nd;
861 		h = rb_entry(nd, struct hist_entry, rb_node);
862 		h->row_offset = 0;
863 	}
864 }
865 
866 static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser,
867 							struct callchain_node *chain_node,
868 							u64 total, int level,
869 							FILE *fp)
870 {
871 	struct rb_node *node;
872 	int offset = level * LEVEL_OFFSET_STEP;
873 	u64 new_total, remaining;
874 	int printed = 0;
875 
876 	if (callchain_param.mode == CHAIN_GRAPH_REL)
877 		new_total = chain_node->children_hit;
878 	else
879 		new_total = total;
880 
881 	remaining = new_total;
882 	node = rb_first(&chain_node->rb_root);
883 	while (node) {
884 		struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
885 		struct rb_node *next = rb_next(node);
886 		u64 cumul = callchain_cumul_hits(child);
887 		struct callchain_list *chain;
888 		char folded_sign = ' ';
889 		int first = true;
890 		int extra_offset = 0;
891 
892 		remaining -= cumul;
893 
894 		list_for_each_entry(chain, &child->val, list) {
895 			char bf[1024], *alloc_str;
896 			const char *str;
897 			bool was_first = first;
898 
899 			if (first)
900 				first = false;
901 			else
902 				extra_offset = LEVEL_OFFSET_STEP;
903 
904 			folded_sign = callchain_list__folded(chain);
905 
906 			alloc_str = NULL;
907 			str = callchain_list__sym_name(chain, bf, sizeof(bf),
908 						       browser->show_dso);
909 			if (was_first) {
910 				double percent = cumul * 100.0 / new_total;
911 
912 				if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
913 					str = "Not enough memory!";
914 				else
915 					str = alloc_str;
916 			}
917 
918 			printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str);
919 			free(alloc_str);
920 			if (folded_sign == '+')
921 				break;
922 		}
923 
924 		if (folded_sign == '-') {
925 			const int new_level = level + (extra_offset ? 2 : 1);
926 			printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total,
927 										new_level, fp);
928 		}
929 
930 		node = next;
931 	}
932 
933 	return printed;
934 }
935 
936 static int hist_browser__fprintf_callchain_node(struct hist_browser *browser,
937 						struct callchain_node *node,
938 						int level, FILE *fp)
939 {
940 	struct callchain_list *chain;
941 	int offset = level * LEVEL_OFFSET_STEP;
942 	char folded_sign = ' ';
943 	int printed = 0;
944 
945 	list_for_each_entry(chain, &node->val, list) {
946 		char bf[1024], *s;
947 
948 		folded_sign = callchain_list__folded(chain);
949 		s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso);
950 		printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s);
951 	}
952 
953 	if (folded_sign == '-')
954 		printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node,
955 									browser->hists->stats.total_period,
956 									level + 1,  fp);
957 	return printed;
958 }
959 
960 static int hist_browser__fprintf_callchain(struct hist_browser *browser,
961 					   struct rb_root *chain, int level, FILE *fp)
962 {
963 	struct rb_node *nd;
964 	int printed = 0;
965 
966 	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
967 		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
968 
969 		printed += hist_browser__fprintf_callchain_node(browser, node, level, fp);
970 	}
971 
972 	return printed;
973 }
974 
975 static int hist_browser__fprintf_entry(struct hist_browser *browser,
976 				       struct hist_entry *he, FILE *fp)
977 {
978 	char s[8192];
979 	double percent;
980 	int printed = 0;
981 	char folded_sign = ' ';
982 
983 	if (symbol_conf.use_callchain)
984 		folded_sign = hist_entry__folded(he);
985 
986 	hist_entry__sort_snprintf(he, s, sizeof(s), browser->hists);
987 	percent = (he->stat.period * 100.0) / browser->hists->stats.total_period;
988 
989 	if (symbol_conf.use_callchain)
990 		printed += fprintf(fp, "%c ", folded_sign);
991 
992 	printed += fprintf(fp, " %5.2f%%", percent);
993 
994 	if (symbol_conf.show_nr_samples)
995 		printed += fprintf(fp, " %11u", he->stat.nr_events);
996 
997 	if (symbol_conf.show_total_period)
998 		printed += fprintf(fp, " %12" PRIu64, he->stat.period);
999 
1000 	printed += fprintf(fp, "%s\n", rtrim(s));
1001 
1002 	if (folded_sign == '-')
1003 		printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp);
1004 
1005 	return printed;
1006 }
1007 
1008 static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
1009 {
1010 	struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries));
1011 	int printed = 0;
1012 
1013 	while (nd) {
1014 		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
1015 
1016 		printed += hist_browser__fprintf_entry(browser, h, fp);
1017 		nd = hists__filter_entries(rb_next(nd));
1018 	}
1019 
1020 	return printed;
1021 }
1022 
1023 static int hist_browser__dump(struct hist_browser *browser)
1024 {
1025 	char filename[64];
1026 	FILE *fp;
1027 
1028 	while (1) {
1029 		scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
1030 		if (access(filename, F_OK))
1031 			break;
1032 		/*
1033  		 * XXX: Just an arbitrary lazy upper limit
1034  		 */
1035 		if (++browser->print_seq == 8192) {
1036 			ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
1037 			return -1;
1038 		}
1039 	}
1040 
1041 	fp = fopen(filename, "w");
1042 	if (fp == NULL) {
1043 		char bf[64];
1044 		const char *err = strerror_r(errno, bf, sizeof(bf));
1045 		ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
1046 		return -1;
1047 	}
1048 
1049 	++browser->print_seq;
1050 	hist_browser__fprintf(browser, fp);
1051 	fclose(fp);
1052 	ui_helpline__fpush("%s written!", filename);
1053 
1054 	return 0;
1055 }
1056 
1057 static struct hist_browser *hist_browser__new(struct hists *hists)
1058 {
1059 	struct hist_browser *browser = zalloc(sizeof(*browser));
1060 
1061 	if (browser) {
1062 		browser->hists = hists;
1063 		browser->b.refresh = hist_browser__refresh;
1064 		browser->b.seek = ui_browser__hists_seek;
1065 		browser->b.use_navkeypressed = true;
1066 		if (sort__branch_mode == 1)
1067 			browser->has_symbols = sort_sym_from.list.next != NULL;
1068 		else
1069 			browser->has_symbols = sort_sym.list.next != NULL;
1070 	}
1071 
1072 	return browser;
1073 }
1074 
1075 static void hist_browser__delete(struct hist_browser *browser)
1076 {
1077 	free(browser);
1078 }
1079 
1080 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
1081 {
1082 	return browser->he_selection;
1083 }
1084 
1085 static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
1086 {
1087 	return browser->he_selection->thread;
1088 }
1089 
1090 static int hists__browser_title(struct hists *hists, char *bf, size_t size,
1091 				const char *ev_name)
1092 {
1093 	char unit;
1094 	int printed;
1095 	const struct dso *dso = hists->dso_filter;
1096 	const struct thread *thread = hists->thread_filter;
1097 	unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
1098 	u64 nr_events = hists->stats.total_period;
1099 
1100 	nr_samples = convert_unit(nr_samples, &unit);
1101 	printed = scnprintf(bf, size,
1102 			   "Samples: %lu%c of event '%s', Event count (approx.): %lu",
1103 			   nr_samples, unit, ev_name, nr_events);
1104 
1105 
1106 	if (hists->uid_filter_str)
1107 		printed += snprintf(bf + printed, size - printed,
1108 				    ", UID: %s", hists->uid_filter_str);
1109 	if (thread)
1110 		printed += scnprintf(bf + printed, size - printed,
1111 				    ", Thread: %s(%d)",
1112 				    (thread->comm_set ? thread->comm : ""),
1113 				    thread->pid);
1114 	if (dso)
1115 		printed += scnprintf(bf + printed, size - printed,
1116 				    ", DSO: %s", dso->short_name);
1117 	return printed;
1118 }
1119 
1120 static inline void free_popup_options(char **options, int n)
1121 {
1122 	int i;
1123 
1124 	for (i = 0; i < n; ++i) {
1125 		free(options[i]);
1126 		options[i] = NULL;
1127 	}
1128 }
1129 
1130 static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
1131 				    const char *helpline, const char *ev_name,
1132 				    bool left_exits,
1133 				    void(*timer)(void *arg), void *arg,
1134 				    int delay_secs)
1135 {
1136 	struct hists *hists = &evsel->hists;
1137 	struct hist_browser *browser = hist_browser__new(hists);
1138 	struct branch_info *bi;
1139 	struct pstack *fstack;
1140 	char *options[16];
1141 	int nr_options = 0;
1142 	int key = -1;
1143 	char buf[64];
1144 
1145 	if (browser == NULL)
1146 		return -1;
1147 
1148 	fstack = pstack__new(2);
1149 	if (fstack == NULL)
1150 		goto out;
1151 
1152 	ui_helpline__push(helpline);
1153 
1154 	memset(options, 0, sizeof(options));
1155 
1156 	while (1) {
1157 		const struct thread *thread = NULL;
1158 		const struct dso *dso = NULL;
1159 		int choice = 0,
1160 		    annotate = -2, zoom_dso = -2, zoom_thread = -2,
1161 		    annotate_f = -2, annotate_t = -2, browse_map = -2;
1162 
1163 		nr_options = 0;
1164 
1165 		key = hist_browser__run(browser, ev_name, timer, arg, delay_secs);
1166 
1167 		if (browser->he_selection != NULL) {
1168 			thread = hist_browser__selected_thread(browser);
1169 			dso = browser->selection->map ? browser->selection->map->dso : NULL;
1170 		}
1171 		switch (key) {
1172 		case K_TAB:
1173 		case K_UNTAB:
1174 			if (nr_events == 1)
1175 				continue;
1176 			/*
1177 			 * Exit the browser, let hists__browser_tree
1178 			 * go to the next or previous
1179 			 */
1180 			goto out_free_stack;
1181 		case 'a':
1182 			if (!browser->has_symbols) {
1183 				ui_browser__warning(&browser->b, delay_secs * 2,
1184 			"Annotation is only available for symbolic views, "
1185 			"include \"sym*\" in --sort to use it.");
1186 				continue;
1187 			}
1188 
1189 			if (browser->selection == NULL ||
1190 			    browser->selection->sym == NULL ||
1191 			    browser->selection->map->dso->annotate_warned)
1192 				continue;
1193 			goto do_annotate;
1194 		case 'P':
1195 			hist_browser__dump(browser);
1196 			continue;
1197 		case 'd':
1198 			goto zoom_dso;
1199 		case 'V':
1200 			browser->show_dso = !browser->show_dso;
1201 			continue;
1202 		case 't':
1203 			goto zoom_thread;
1204 		case '/':
1205 			if (ui_browser__input_window("Symbol to show",
1206 					"Please enter the name of symbol you want to see",
1207 					buf, "ENTER: OK, ESC: Cancel",
1208 					delay_secs * 2) == K_ENTER) {
1209 				hists->symbol_filter_str = *buf ? buf : NULL;
1210 				hists__filter_by_symbol(hists);
1211 				hist_browser__reset(browser);
1212 			}
1213 			continue;
1214 		case K_F1:
1215 		case 'h':
1216 		case '?':
1217 			ui_browser__help_window(&browser->b,
1218 					"h/?/F1        Show this window\n"
1219 					"UP/DOWN/PGUP\n"
1220 					"PGDN/SPACE    Navigate\n"
1221 					"q/ESC/CTRL+C  Exit browser\n\n"
1222 					"For multiple event sessions:\n\n"
1223 					"TAB/UNTAB Switch events\n\n"
1224 					"For symbolic views (--sort has sym):\n\n"
1225 					"->            Zoom into DSO/Threads & Annotate current symbol\n"
1226 					"<-            Zoom out\n"
1227 					"a             Annotate current symbol\n"
1228 					"C             Collapse all callchains\n"
1229 					"E             Expand all callchains\n"
1230 					"d             Zoom into current DSO\n"
1231 					"t             Zoom into current Thread\n"
1232 					"P             Print histograms to perf.hist.N\n"
1233 					"V             Verbose (DSO names in callchains, etc)\n"
1234 					"/             Filter symbol by name");
1235 			continue;
1236 		case K_ENTER:
1237 		case K_RIGHT:
1238 			/* menu */
1239 			break;
1240 		case K_LEFT: {
1241 			const void *top;
1242 
1243 			if (pstack__empty(fstack)) {
1244 				/*
1245 				 * Go back to the perf_evsel_menu__run or other user
1246 				 */
1247 				if (left_exits)
1248 					goto out_free_stack;
1249 				continue;
1250 			}
1251 			top = pstack__pop(fstack);
1252 			if (top == &browser->hists->dso_filter)
1253 				goto zoom_out_dso;
1254 			if (top == &browser->hists->thread_filter)
1255 				goto zoom_out_thread;
1256 			continue;
1257 		}
1258 		case K_ESC:
1259 			if (!left_exits &&
1260 			    !ui_browser__dialog_yesno(&browser->b,
1261 					       "Do you really want to exit?"))
1262 				continue;
1263 			/* Fall thru */
1264 		case 'q':
1265 		case CTRL('c'):
1266 			goto out_free_stack;
1267 		default:
1268 			continue;
1269 		}
1270 
1271 		if (!browser->has_symbols)
1272 			goto add_exit_option;
1273 
1274 		if (sort__branch_mode == 1) {
1275 			bi = browser->he_selection->branch_info;
1276 			if (browser->selection != NULL &&
1277 			    bi &&
1278 			    bi->from.sym != NULL &&
1279 			    !bi->from.map->dso->annotate_warned &&
1280 				asprintf(&options[nr_options], "Annotate %s",
1281 					 bi->from.sym->name) > 0)
1282 				annotate_f = nr_options++;
1283 
1284 			if (browser->selection != NULL &&
1285 			    bi &&
1286 			    bi->to.sym != NULL &&
1287 			    !bi->to.map->dso->annotate_warned &&
1288 			    (bi->to.sym != bi->from.sym ||
1289 			     bi->to.map->dso != bi->from.map->dso) &&
1290 				asprintf(&options[nr_options], "Annotate %s",
1291 					 bi->to.sym->name) > 0)
1292 				annotate_t = nr_options++;
1293 		} else {
1294 
1295 			if (browser->selection != NULL &&
1296 			    browser->selection->sym != NULL &&
1297 			    !browser->selection->map->dso->annotate_warned &&
1298 				asprintf(&options[nr_options], "Annotate %s",
1299 					 browser->selection->sym->name) > 0)
1300 				annotate = nr_options++;
1301 		}
1302 
1303 		if (thread != NULL &&
1304 		    asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
1305 			     (browser->hists->thread_filter ? "out of" : "into"),
1306 			     (thread->comm_set ? thread->comm : ""),
1307 			     thread->pid) > 0)
1308 			zoom_thread = nr_options++;
1309 
1310 		if (dso != NULL &&
1311 		    asprintf(&options[nr_options], "Zoom %s %s DSO",
1312 			     (browser->hists->dso_filter ? "out of" : "into"),
1313 			     (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
1314 			zoom_dso = nr_options++;
1315 
1316 		if (browser->selection != NULL &&
1317 		    browser->selection->map != NULL &&
1318 		    asprintf(&options[nr_options], "Browse map details") > 0)
1319 			browse_map = nr_options++;
1320 add_exit_option:
1321 		options[nr_options++] = (char *)"Exit";
1322 retry_popup_menu:
1323 		choice = ui__popup_menu(nr_options, options);
1324 
1325 		if (choice == nr_options - 1)
1326 			break;
1327 
1328 		if (choice == -1) {
1329 			free_popup_options(options, nr_options - 1);
1330 			continue;
1331 		}
1332 
1333 		if (choice == annotate || choice == annotate_t || choice == annotate_f) {
1334 			struct hist_entry *he;
1335 			int err;
1336 do_annotate:
1337 			he = hist_browser__selected_entry(browser);
1338 			if (he == NULL)
1339 				continue;
1340 
1341 			/*
1342 			 * we stash the branch_info symbol + map into the
1343 			 * the ms so we don't have to rewrite all the annotation
1344 			 * code to use branch_info.
1345 			 * in branch mode, the ms struct is not used
1346 			 */
1347 			if (choice == annotate_f) {
1348 				he->ms.sym = he->branch_info->from.sym;
1349 				he->ms.map = he->branch_info->from.map;
1350 			}  else if (choice == annotate_t) {
1351 				he->ms.sym = he->branch_info->to.sym;
1352 				he->ms.map = he->branch_info->to.map;
1353 			}
1354 
1355 			/*
1356 			 * Don't let this be freed, say, by hists__decay_entry.
1357 			 */
1358 			he->used = true;
1359 			err = hist_entry__tui_annotate(he, evsel->idx,
1360 						       timer, arg, delay_secs);
1361 			he->used = false;
1362 			/*
1363 			 * offer option to annotate the other branch source or target
1364 			 * (if they exists) when returning from annotate
1365 			 */
1366 			if ((err == 'q' || err == CTRL('c'))
1367 			    && annotate_t != -2 && annotate_f != -2)
1368 				goto retry_popup_menu;
1369 
1370 			ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
1371 			if (err)
1372 				ui_browser__handle_resize(&browser->b);
1373 
1374 		} else if (choice == browse_map)
1375 			map__browse(browser->selection->map);
1376 		else if (choice == zoom_dso) {
1377 zoom_dso:
1378 			if (browser->hists->dso_filter) {
1379 				pstack__remove(fstack, &browser->hists->dso_filter);
1380 zoom_out_dso:
1381 				ui_helpline__pop();
1382 				browser->hists->dso_filter = NULL;
1383 				sort_dso.elide = false;
1384 			} else {
1385 				if (dso == NULL)
1386 					continue;
1387 				ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
1388 						   dso->kernel ? "the Kernel" : dso->short_name);
1389 				browser->hists->dso_filter = dso;
1390 				sort_dso.elide = true;
1391 				pstack__push(fstack, &browser->hists->dso_filter);
1392 			}
1393 			hists__filter_by_dso(hists);
1394 			hist_browser__reset(browser);
1395 		} else if (choice == zoom_thread) {
1396 zoom_thread:
1397 			if (browser->hists->thread_filter) {
1398 				pstack__remove(fstack, &browser->hists->thread_filter);
1399 zoom_out_thread:
1400 				ui_helpline__pop();
1401 				browser->hists->thread_filter = NULL;
1402 				sort_thread.elide = false;
1403 			} else {
1404 				ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
1405 						   thread->comm_set ? thread->comm : "",
1406 						   thread->pid);
1407 				browser->hists->thread_filter = thread;
1408 				sort_thread.elide = true;
1409 				pstack__push(fstack, &browser->hists->thread_filter);
1410 			}
1411 			hists__filter_by_thread(hists);
1412 			hist_browser__reset(browser);
1413 		}
1414 	}
1415 out_free_stack:
1416 	pstack__delete(fstack);
1417 out:
1418 	hist_browser__delete(browser);
1419 	free_popup_options(options, nr_options - 1);
1420 	return key;
1421 }
1422 
1423 struct perf_evsel_menu {
1424 	struct ui_browser b;
1425 	struct perf_evsel *selection;
1426 	bool lost_events, lost_events_warned;
1427 };
1428 
1429 static void perf_evsel_menu__write(struct ui_browser *browser,
1430 				   void *entry, int row)
1431 {
1432 	struct perf_evsel_menu *menu = container_of(browser,
1433 						    struct perf_evsel_menu, b);
1434 	struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1435 	bool current_entry = ui_browser__is_current_entry(browser, row);
1436 	unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1437 	const char *ev_name = perf_evsel__name(evsel);
1438 	char bf[256], unit;
1439 	const char *warn = " ";
1440 	size_t printed;
1441 
1442 	ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
1443 						       HE_COLORSET_NORMAL);
1444 
1445 	nr_events = convert_unit(nr_events, &unit);
1446 	printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
1447 			   unit, unit == ' ' ? "" : " ", ev_name);
1448 	slsmg_printf("%s", bf);
1449 
1450 	nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST];
1451 	if (nr_events != 0) {
1452 		menu->lost_events = true;
1453 		if (!current_entry)
1454 			ui_browser__set_color(browser, HE_COLORSET_TOP);
1455 		nr_events = convert_unit(nr_events, &unit);
1456 		printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
1457 				     nr_events, unit, unit == ' ' ? "" : " ");
1458 		warn = bf;
1459 	}
1460 
1461 	slsmg_write_nstring(warn, browser->width - printed);
1462 
1463 	if (current_entry)
1464 		menu->selection = evsel;
1465 }
1466 
1467 static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
1468 				int nr_events, const char *help,
1469 				void(*timer)(void *arg), void *arg, int delay_secs)
1470 {
1471 	struct perf_evlist *evlist = menu->b.priv;
1472 	struct perf_evsel *pos;
1473 	const char *ev_name, *title = "Available samples";
1474 	int key;
1475 
1476 	if (ui_browser__show(&menu->b, title,
1477 			     "ESC: exit, ENTER|->: Browse histograms") < 0)
1478 		return -1;
1479 
1480 	while (1) {
1481 		key = ui_browser__run(&menu->b, delay_secs);
1482 
1483 		switch (key) {
1484 		case K_TIMER:
1485 			timer(arg);
1486 
1487 			if (!menu->lost_events_warned && menu->lost_events) {
1488 				ui_browser__warn_lost_events(&menu->b);
1489 				menu->lost_events_warned = true;
1490 			}
1491 			continue;
1492 		case K_RIGHT:
1493 		case K_ENTER:
1494 			if (!menu->selection)
1495 				continue;
1496 			pos = menu->selection;
1497 browse_hists:
1498 			perf_evlist__set_selected(evlist, pos);
1499 			/*
1500 			 * Give the calling tool a chance to populate the non
1501 			 * default evsel resorted hists tree.
1502 			 */
1503 			if (timer)
1504 				timer(arg);
1505 			ev_name = perf_evsel__name(pos);
1506 			key = perf_evsel__hists_browse(pos, nr_events, help,
1507 						       ev_name, true, timer,
1508 						       arg, delay_secs);
1509 			ui_browser__show_title(&menu->b, title);
1510 			switch (key) {
1511 			case K_TAB:
1512 				if (pos->node.next == &evlist->entries)
1513 					pos = list_entry(evlist->entries.next, struct perf_evsel, node);
1514 				else
1515 					pos = list_entry(pos->node.next, struct perf_evsel, node);
1516 				goto browse_hists;
1517 			case K_UNTAB:
1518 				if (pos->node.prev == &evlist->entries)
1519 					pos = list_entry(evlist->entries.prev, struct perf_evsel, node);
1520 				else
1521 					pos = list_entry(pos->node.prev, struct perf_evsel, node);
1522 				goto browse_hists;
1523 			case K_ESC:
1524 				if (!ui_browser__dialog_yesno(&menu->b,
1525 						"Do you really want to exit?"))
1526 					continue;
1527 				/* Fall thru */
1528 			case 'q':
1529 			case CTRL('c'):
1530 				goto out;
1531 			default:
1532 				continue;
1533 			}
1534 		case K_LEFT:
1535 			continue;
1536 		case K_ESC:
1537 			if (!ui_browser__dialog_yesno(&menu->b,
1538 					       "Do you really want to exit?"))
1539 				continue;
1540 			/* Fall thru */
1541 		case 'q':
1542 		case CTRL('c'):
1543 			goto out;
1544 		default:
1545 			continue;
1546 		}
1547 	}
1548 
1549 out:
1550 	ui_browser__hide(&menu->b);
1551 	return key;
1552 }
1553 
1554 static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1555 					   const char *help,
1556 					   void(*timer)(void *arg), void *arg,
1557 					   int delay_secs)
1558 {
1559 	struct perf_evsel *pos;
1560 	struct perf_evsel_menu menu = {
1561 		.b = {
1562 			.entries    = &evlist->entries,
1563 			.refresh    = ui_browser__list_head_refresh,
1564 			.seek	    = ui_browser__list_head_seek,
1565 			.write	    = perf_evsel_menu__write,
1566 			.nr_entries = evlist->nr_entries,
1567 			.priv	    = evlist,
1568 		},
1569 	};
1570 
1571 	ui_helpline__push("Press ESC to exit");
1572 
1573 	list_for_each_entry(pos, &evlist->entries, node) {
1574 		const char *ev_name = perf_evsel__name(pos);
1575 		size_t line_len = strlen(ev_name) + 7;
1576 
1577 		if (menu.b.width < line_len)
1578 			menu.b.width = line_len;
1579 	}
1580 
1581 	return perf_evsel_menu__run(&menu, evlist->nr_entries, help, timer,
1582 				    arg, delay_secs);
1583 }
1584 
1585 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
1586 				  void(*timer)(void *arg), void *arg,
1587 				  int delay_secs)
1588 {
1589 	if (evlist->nr_entries == 1) {
1590 		struct perf_evsel *first = list_entry(evlist->entries.next,
1591 						      struct perf_evsel, node);
1592 		const char *ev_name = perf_evsel__name(first);
1593 		return perf_evsel__hists_browse(first, evlist->nr_entries, help,
1594 						ev_name, false, timer, arg,
1595 						delay_secs);
1596 	}
1597 
1598 	return __perf_evlist__tui_browse_hists(evlist, help,
1599 					       timer, arg, delay_secs);
1600 }
1601