1#define _GNU_SOURCE
2#include <stdio.h>
3#undef _GNU_SOURCE
4#include "../libslang.h"
5#include <stdlib.h>
6#include <string.h>
7#include <newt.h>
8#include <linux/rbtree.h>
9
10#include "../../evsel.h"
11#include "../../evlist.h"
12#include "../../hist.h"
13#include "../../pstack.h"
14#include "../../sort.h"
15#include "../../util.h"
16
17#include "../browser.h"
18#include "../helpline.h"
19#include "../util.h"
20#include "map.h"
21
22struct hist_browser {
23	struct ui_browser   b;
24	struct hists	    *hists;
25	struct hist_entry   *he_selection;
26	struct map_symbol   *selection;
27};
28
29static void hist_browser__refresh_dimensions(struct hist_browser *self)
30{
31	/* 3 == +/- toggle symbol before actual hist_entry rendering */
32	self->b.width = 3 + (hists__sort_list_width(self->hists) +
33			     sizeof("[k]"));
34}
35
36static void hist_browser__reset(struct hist_browser *self)
37{
38	self->b.nr_entries = self->hists->nr_entries;
39	hist_browser__refresh_dimensions(self);
40	ui_browser__reset_index(&self->b);
41}
42
43static char tree__folded_sign(bool unfolded)
44{
45	return unfolded ? '-' : '+';
46}
47
48static char map_symbol__folded(const struct map_symbol *self)
49{
50	return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
51}
52
53static char hist_entry__folded(const struct hist_entry *self)
54{
55	return map_symbol__folded(&self->ms);
56}
57
58static char callchain_list__folded(const struct callchain_list *self)
59{
60	return map_symbol__folded(&self->ms);
61}
62
63static void map_symbol__set_folding(struct map_symbol *self, bool unfold)
64{
65	self->unfolded = unfold ? self->has_children : false;
66}
67
68static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
69{
70	int n = 0;
71	struct rb_node *nd;
72
73	for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
74		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
75		struct callchain_list *chain;
76		char folded_sign = ' '; /* No children */
77
78		list_for_each_entry(chain, &child->val, list) {
79			++n;
80			/* We need this because we may not have children */
81			folded_sign = callchain_list__folded(chain);
82			if (folded_sign == '+')
83				break;
84		}
85
86		if (folded_sign == '-') /* Have children and they're unfolded */
87			n += callchain_node__count_rows_rb_tree(child);
88	}
89
90	return n;
91}
92
93static int callchain_node__count_rows(struct callchain_node *node)
94{
95	struct callchain_list *chain;
96	bool unfolded = false;
97	int n = 0;
98
99	list_for_each_entry(chain, &node->val, list) {
100		++n;
101		unfolded = chain->ms.unfolded;
102	}
103
104	if (unfolded)
105		n += callchain_node__count_rows_rb_tree(node);
106
107	return n;
108}
109
110static int callchain__count_rows(struct rb_root *chain)
111{
112	struct rb_node *nd;
113	int n = 0;
114
115	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
116		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
117		n += callchain_node__count_rows(node);
118	}
119
120	return n;
121}
122
123static bool map_symbol__toggle_fold(struct map_symbol *self)
124{
125	if (!self->has_children)
126		return false;
127
128	self->unfolded = !self->unfolded;
129	return true;
130}
131
132static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
133{
134	struct rb_node *nd = rb_first(&self->rb_root);
135
136	for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
137		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
138		struct callchain_list *chain;
139		bool first = true;
140
141		list_for_each_entry(chain, &child->val, list) {
142			if (first) {
143				first = false;
144				chain->ms.has_children = chain->list.next != &child->val ||
145							 !RB_EMPTY_ROOT(&child->rb_root);
146			} else
147				chain->ms.has_children = chain->list.next == &child->val &&
148							 !RB_EMPTY_ROOT(&child->rb_root);
149		}
150
151		callchain_node__init_have_children_rb_tree(child);
152	}
153}
154
155static void callchain_node__init_have_children(struct callchain_node *self)
156{
157	struct callchain_list *chain;
158
159	list_for_each_entry(chain, &self->val, list)
160		chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root);
161
162	callchain_node__init_have_children_rb_tree(self);
163}
164
165static void callchain__init_have_children(struct rb_root *self)
166{
167	struct rb_node *nd;
168
169	for (nd = rb_first(self); nd; nd = rb_next(nd)) {
170		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
171		callchain_node__init_have_children(node);
172	}
173}
174
175static void hist_entry__init_have_children(struct hist_entry *self)
176{
177	if (!self->init_have_children) {
178		self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain);
179		callchain__init_have_children(&self->sorted_chain);
180		self->init_have_children = true;
181	}
182}
183
184static bool hist_browser__toggle_fold(struct hist_browser *self)
185{
186	if (map_symbol__toggle_fold(self->selection)) {
187		struct hist_entry *he = self->he_selection;
188
189		hist_entry__init_have_children(he);
190		self->hists->nr_entries -= he->nr_rows;
191
192		if (he->ms.unfolded)
193			he->nr_rows = callchain__count_rows(&he->sorted_chain);
194		else
195			he->nr_rows = 0;
196		self->hists->nr_entries += he->nr_rows;
197		self->b.nr_entries = self->hists->nr_entries;
198
199		return true;
200	}
201
202	/* If it doesn't have children, no toggling performed */
203	return false;
204}
205
206static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold)
207{
208	int n = 0;
209	struct rb_node *nd;
210
211	for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
212		struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
213		struct callchain_list *chain;
214		bool has_children = false;
215
216		list_for_each_entry(chain, &child->val, list) {
217			++n;
218			map_symbol__set_folding(&chain->ms, unfold);
219			has_children = chain->ms.has_children;
220		}
221
222		if (has_children)
223			n += callchain_node__set_folding_rb_tree(child, unfold);
224	}
225
226	return n;
227}
228
229static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
230{
231	struct callchain_list *chain;
232	bool has_children = false;
233	int n = 0;
234
235	list_for_each_entry(chain, &node->val, list) {
236		++n;
237		map_symbol__set_folding(&chain->ms, unfold);
238		has_children = chain->ms.has_children;
239	}
240
241	if (has_children)
242		n += callchain_node__set_folding_rb_tree(node, unfold);
243
244	return n;
245}
246
247static int callchain__set_folding(struct rb_root *chain, bool unfold)
248{
249	struct rb_node *nd;
250	int n = 0;
251
252	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
253		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
254		n += callchain_node__set_folding(node, unfold);
255	}
256
257	return n;
258}
259
260static void hist_entry__set_folding(struct hist_entry *self, bool unfold)
261{
262	hist_entry__init_have_children(self);
263	map_symbol__set_folding(&self->ms, unfold);
264
265	if (self->ms.has_children) {
266		int n = callchain__set_folding(&self->sorted_chain, unfold);
267		self->nr_rows = unfold ? n : 0;
268	} else
269		self->nr_rows = 0;
270}
271
272static void hists__set_folding(struct hists *self, bool unfold)
273{
274	struct rb_node *nd;
275
276	self->nr_entries = 0;
277
278	for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) {
279		struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
280		hist_entry__set_folding(he, unfold);
281		self->nr_entries += 1 + he->nr_rows;
282	}
283}
284
285static void hist_browser__set_folding(struct hist_browser *self, bool unfold)
286{
287	hists__set_folding(self->hists, unfold);
288	self->b.nr_entries = self->hists->nr_entries;
289	/* Go to the start, we may be way after valid entries after a collapse */
290	ui_browser__reset_index(&self->b);
291}
292
293static int hist_browser__run(struct hist_browser *self, const char *title)
294{
295	int key;
296	int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't',
297			    NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT,
298			    NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, };
299
300	self->b.entries = &self->hists->entries;
301	self->b.nr_entries = self->hists->nr_entries;
302
303	hist_browser__refresh_dimensions(self);
304
305	if (ui_browser__show(&self->b, title,
306			     "Press '?' for help on key bindings") < 0)
307		return -1;
308
309	ui_browser__add_exit_keys(&self->b, exit_keys);
310
311	while (1) {
312		key = ui_browser__run(&self->b);
313
314		switch (key) {
315		case 'D': { /* Debug */
316			static int seq;
317			struct hist_entry *h = rb_entry(self->b.top,
318							struct hist_entry, rb_node);
319			ui_helpline__pop();
320			ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
321					   seq++, self->b.nr_entries,
322					   self->hists->nr_entries,
323					   self->b.height,
324					   self->b.index,
325					   self->b.top_idx,
326					   h->row_offset, h->nr_rows);
327		}
328			break;
329		case 'C':
330			/* Collapse the whole world. */
331			hist_browser__set_folding(self, false);
332			break;
333		case 'E':
334			/* Expand the whole world. */
335			hist_browser__set_folding(self, true);
336			break;
337		case NEWT_KEY_ENTER:
338			if (hist_browser__toggle_fold(self))
339				break;
340			/* fall thru */
341		default:
342			goto out;
343		}
344	}
345out:
346	ui_browser__hide(&self->b);
347	return key;
348}
349
350static char *callchain_list__sym_name(struct callchain_list *self,
351				      char *bf, size_t bfsize)
352{
353	if (self->ms.sym)
354		return self->ms.sym->name;
355
356	snprintf(bf, bfsize, "%#" PRIx64, self->ip);
357	return bf;
358}
359
360#define LEVEL_OFFSET_STEP 3
361
362static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self,
363						     struct callchain_node *chain_node,
364						     u64 total, int level,
365						     unsigned short row,
366						     off_t *row_offset,
367						     bool *is_current_entry)
368{
369	struct rb_node *node;
370	int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
371	u64 new_total, remaining;
372
373	if (callchain_param.mode == CHAIN_GRAPH_REL)
374		new_total = chain_node->children_hit;
375	else
376		new_total = total;
377
378	remaining = new_total;
379	node = rb_first(&chain_node->rb_root);
380	while (node) {
381		struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
382		struct rb_node *next = rb_next(node);
383		u64 cumul = callchain_cumul_hits(child);
384		struct callchain_list *chain;
385		char folded_sign = ' ';
386		int first = true;
387		int extra_offset = 0;
388
389		remaining -= cumul;
390
391		list_for_each_entry(chain, &child->val, list) {
392			char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
393			const char *str;
394			int color;
395			bool was_first = first;
396
397			if (first)
398				first = false;
399			else
400				extra_offset = LEVEL_OFFSET_STEP;
401
402			folded_sign = callchain_list__folded(chain);
403			if (*row_offset != 0) {
404				--*row_offset;
405				goto do_next;
406			}
407
408			alloc_str = NULL;
409			str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
410			if (was_first) {
411				double percent = cumul * 100.0 / new_total;
412
413				if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
414					str = "Not enough memory!";
415				else
416					str = alloc_str;
417			}
418
419			color = HE_COLORSET_NORMAL;
420			width = self->b.width - (offset + extra_offset + 2);
421			if (ui_browser__is_current_entry(&self->b, row)) {
422				self->selection = &chain->ms;
423				color = HE_COLORSET_SELECTED;
424				*is_current_entry = true;
425			}
426
427			ui_browser__set_color(&self->b, color);
428			ui_browser__gotorc(&self->b, row, 0);
429			slsmg_write_nstring(" ", offset + extra_offset);
430			slsmg_printf("%c ", folded_sign);
431			slsmg_write_nstring(str, width);
432			free(alloc_str);
433
434			if (++row == self->b.height)
435				goto out;
436do_next:
437			if (folded_sign == '+')
438				break;
439		}
440
441		if (folded_sign == '-') {
442			const int new_level = level + (extra_offset ? 2 : 1);
443			row += hist_browser__show_callchain_node_rb_tree(self, child, new_total,
444									 new_level, row, row_offset,
445									 is_current_entry);
446		}
447		if (row == self->b.height)
448			goto out;
449		node = next;
450	}
451out:
452	return row - first_row;
453}
454
455static int hist_browser__show_callchain_node(struct hist_browser *self,
456					     struct callchain_node *node,
457					     int level, unsigned short row,
458					     off_t *row_offset,
459					     bool *is_current_entry)
460{
461	struct callchain_list *chain;
462	int first_row = row,
463	     offset = level * LEVEL_OFFSET_STEP,
464	     width = self->b.width - offset;
465	char folded_sign = ' ';
466
467	list_for_each_entry(chain, &node->val, list) {
468		char ipstr[BITS_PER_LONG / 4 + 1], *s;
469		int color;
470
471		folded_sign = callchain_list__folded(chain);
472
473		if (*row_offset != 0) {
474			--*row_offset;
475			continue;
476		}
477
478		color = HE_COLORSET_NORMAL;
479		if (ui_browser__is_current_entry(&self->b, row)) {
480			self->selection = &chain->ms;
481			color = HE_COLORSET_SELECTED;
482			*is_current_entry = true;
483		}
484
485		s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
486		ui_browser__gotorc(&self->b, row, 0);
487		ui_browser__set_color(&self->b, color);
488		slsmg_write_nstring(" ", offset);
489		slsmg_printf("%c ", folded_sign);
490		slsmg_write_nstring(s, width - 2);
491
492		if (++row == self->b.height)
493			goto out;
494	}
495
496	if (folded_sign == '-')
497		row += hist_browser__show_callchain_node_rb_tree(self, node,
498								 self->hists->stats.total_period,
499								 level + 1, row,
500								 row_offset,
501								 is_current_entry);
502out:
503	return row - first_row;
504}
505
506static int hist_browser__show_callchain(struct hist_browser *self,
507					struct rb_root *chain,
508					int level, unsigned short row,
509					off_t *row_offset,
510					bool *is_current_entry)
511{
512	struct rb_node *nd;
513	int first_row = row;
514
515	for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
516		struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
517
518		row += hist_browser__show_callchain_node(self, node, level,
519							 row, row_offset,
520							 is_current_entry);
521		if (row == self->b.height)
522			break;
523	}
524
525	return row - first_row;
526}
527
528static int hist_browser__show_entry(struct hist_browser *self,
529				    struct hist_entry *entry,
530				    unsigned short row)
531{
532	char s[256];
533	double percent;
534	int printed = 0;
535	int color, width = self->b.width;
536	char folded_sign = ' ';
537	bool current_entry = ui_browser__is_current_entry(&self->b, row);
538	off_t row_offset = entry->row_offset;
539
540	if (current_entry) {
541		self->he_selection = entry;
542		self->selection = &entry->ms;
543	}
544
545	if (symbol_conf.use_callchain) {
546		hist_entry__init_have_children(entry);
547		folded_sign = hist_entry__folded(entry);
548	}
549
550	if (row_offset == 0) {
551		hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false,
552				     0, false, self->hists->stats.total_period);
553		percent = (entry->period * 100.0) / self->hists->stats.total_period;
554
555		color = HE_COLORSET_SELECTED;
556		if (!current_entry) {
557			if (percent >= MIN_RED)
558				color = HE_COLORSET_TOP;
559			else if (percent >= MIN_GREEN)
560				color = HE_COLORSET_MEDIUM;
561			else
562				color = HE_COLORSET_NORMAL;
563		}
564
565		ui_browser__set_color(&self->b, color);
566		ui_browser__gotorc(&self->b, row, 0);
567		if (symbol_conf.use_callchain) {
568			slsmg_printf("%c ", folded_sign);
569			width -= 2;
570		}
571		slsmg_write_nstring(s, width);
572		++row;
573		++printed;
574	} else
575		--row_offset;
576
577	if (folded_sign == '-' && row != self->b.height) {
578		printed += hist_browser__show_callchain(self, &entry->sorted_chain,
579							1, row, &row_offset,
580							&current_entry);
581		if (current_entry)
582			self->he_selection = entry;
583	}
584
585	return printed;
586}
587
588static unsigned int hist_browser__refresh(struct ui_browser *self)
589{
590	unsigned row = 0;
591	struct rb_node *nd;
592	struct hist_browser *hb = container_of(self, struct hist_browser, b);
593
594	if (self->top == NULL)
595		self->top = rb_first(&hb->hists->entries);
596
597	for (nd = self->top; nd; nd = rb_next(nd)) {
598		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
599
600		if (h->filtered)
601			continue;
602
603		row += hist_browser__show_entry(hb, h, row);
604		if (row == self->height)
605			break;
606	}
607
608	return row;
609}
610
611static struct rb_node *hists__filter_entries(struct rb_node *nd)
612{
613	while (nd != NULL) {
614		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
615		if (!h->filtered)
616			return nd;
617
618		nd = rb_next(nd);
619	}
620
621	return NULL;
622}
623
624static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
625{
626	while (nd != NULL) {
627		struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
628		if (!h->filtered)
629			return nd;
630
631		nd = rb_prev(nd);
632	}
633
634	return NULL;
635}
636
637static void ui_browser__hists_seek(struct ui_browser *self,
638				   off_t offset, int whence)
639{
640	struct hist_entry *h;
641	struct rb_node *nd;
642	bool first = true;
643
644	if (self->nr_entries == 0)
645		return;
646
647	switch (whence) {
648	case SEEK_SET:
649		nd = hists__filter_entries(rb_first(self->entries));
650		break;
651	case SEEK_CUR:
652		nd = self->top;
653		goto do_offset;
654	case SEEK_END:
655		nd = hists__filter_prev_entries(rb_last(self->entries));
656		first = false;
657		break;
658	default:
659		return;
660	}
661
662	/*
663	 * Moves not relative to the first visible entry invalidates its
664	 * row_offset:
665	 */
666	h = rb_entry(self->top, struct hist_entry, rb_node);
667	h->row_offset = 0;
668
669	/*
670	 * Here we have to check if nd is expanded (+), if it is we can't go
671	 * the next top level hist_entry, instead we must compute an offset of
672	 * what _not_ to show and not change the first visible entry.
673	 *
674	 * This offset increments when we are going from top to bottom and
675	 * decreases when we're going from bottom to top.
676	 *
677	 * As we don't have backpointers to the top level in the callchains
678	 * structure, we need to always print the whole hist_entry callchain,
679	 * skipping the first ones that are before the first visible entry
680	 * and stop when we printed enough lines to fill the screen.
681	 */
682do_offset:
683	if (offset > 0) {
684		do {
685			h = rb_entry(nd, struct hist_entry, rb_node);
686			if (h->ms.unfolded) {
687				u16 remaining = h->nr_rows - h->row_offset;
688				if (offset > remaining) {
689					offset -= remaining;
690					h->row_offset = 0;
691				} else {
692					h->row_offset += offset;
693					offset = 0;
694					self->top = nd;
695					break;
696				}
697			}
698			nd = hists__filter_entries(rb_next(nd));
699			if (nd == NULL)
700				break;
701			--offset;
702			self->top = nd;
703		} while (offset != 0);
704	} else if (offset < 0) {
705		while (1) {
706			h = rb_entry(nd, struct hist_entry, rb_node);
707			if (h->ms.unfolded) {
708				if (first) {
709					if (-offset > h->row_offset) {
710						offset += h->row_offset;
711						h->row_offset = 0;
712					} else {
713						h->row_offset += offset;
714						offset = 0;
715						self->top = nd;
716						break;
717					}
718				} else {
719					if (-offset > h->nr_rows) {
720						offset += h->nr_rows;
721						h->row_offset = 0;
722					} else {
723						h->row_offset = h->nr_rows + offset;
724						offset = 0;
725						self->top = nd;
726						break;
727					}
728				}
729			}
730
731			nd = hists__filter_prev_entries(rb_prev(nd));
732			if (nd == NULL)
733				break;
734			++offset;
735			self->top = nd;
736			if (offset == 0) {
737				/*
738				 * Last unfiltered hist_entry, check if it is
739				 * unfolded, if it is then we should have
740				 * row_offset at its last entry.
741				 */
742				h = rb_entry(nd, struct hist_entry, rb_node);
743				if (h->ms.unfolded)
744					h->row_offset = h->nr_rows;
745				break;
746			}
747			first = false;
748		}
749	} else {
750		self->top = nd;
751		h = rb_entry(nd, struct hist_entry, rb_node);
752		h->row_offset = 0;
753	}
754}
755
756static struct hist_browser *hist_browser__new(struct hists *hists)
757{
758	struct hist_browser *self = zalloc(sizeof(*self));
759
760	if (self) {
761		self->hists = hists;
762		self->b.refresh = hist_browser__refresh;
763		self->b.seek = ui_browser__hists_seek;
764	}
765
766	return self;
767}
768
769static void hist_browser__delete(struct hist_browser *self)
770{
771	free(self);
772}
773
774static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
775{
776	return self->he_selection;
777}
778
779static struct thread *hist_browser__selected_thread(struct hist_browser *self)
780{
781	return self->he_selection->thread;
782}
783
784static int hists__browser_title(struct hists *self, char *bf, size_t size,
785				const char *ev_name, const struct dso *dso,
786				const struct thread *thread)
787{
788	char unit;
789	int printed;
790	unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE];
791
792	nr_events = convert_unit(nr_events, &unit);
793	printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name);
794
795	if (thread)
796		printed += snprintf(bf + printed, size - printed,
797				    ", Thread: %s(%d)",
798				    (thread->comm_set ? thread->comm : ""),
799				    thread->pid);
800	if (dso)
801		printed += snprintf(bf + printed, size - printed,
802				    ", DSO: %s", dso->short_name);
803	return printed;
804}
805
806static int perf_evsel__hists_browse(struct perf_evsel *evsel,
807				    const char *helpline, const char *ev_name,
808				    bool left_exits)
809{
810	struct hists *self = &evsel->hists;
811	struct hist_browser *browser = hist_browser__new(self);
812	struct pstack *fstack;
813	const struct thread *thread_filter = NULL;
814	const struct dso *dso_filter = NULL;
815	char msg[160];
816	int key = -1;
817
818	if (browser == NULL)
819		return -1;
820
821	fstack = pstack__new(2);
822	if (fstack == NULL)
823		goto out;
824
825	ui_helpline__push(helpline);
826
827	hists__browser_title(self, msg, sizeof(msg), ev_name,
828			     dso_filter, thread_filter);
829	while (1) {
830		const struct thread *thread = NULL;
831		const struct dso *dso = NULL;
832		char *options[16];
833		int nr_options = 0, choice = 0, i,
834		    annotate = -2, zoom_dso = -2, zoom_thread = -2,
835		    browse_map = -2;
836
837		key = hist_browser__run(browser, msg);
838
839		if (browser->he_selection != NULL) {
840			thread = hist_browser__selected_thread(browser);
841			dso = browser->selection->map ? browser->selection->map->dso : NULL;
842		}
843
844		switch (key) {
845		case NEWT_KEY_TAB:
846		case NEWT_KEY_UNTAB:
847			/*
848			 * Exit the browser, let hists__browser_tree
849			 * go to the next or previous
850			 */
851			goto out_free_stack;
852		case 'a':
853			if (browser->selection == NULL ||
854			    browser->selection->sym == NULL ||
855			    browser->selection->map->dso->annotate_warned)
856				continue;
857			goto do_annotate;
858		case 'd':
859			goto zoom_dso;
860		case 't':
861			goto zoom_thread;
862		case NEWT_KEY_F1:
863		case 'h':
864		case '?':
865			ui__help_window("->        Zoom into DSO/Threads & Annotate current symbol\n"
866					"<-        Zoom out\n"
867					"a         Annotate current symbol\n"
868					"h/?/F1    Show this window\n"
869					"C         Collapse all callchains\n"
870					"E         Expand all callchains\n"
871					"d         Zoom into current DSO\n"
872					"t         Zoom into current Thread\n"
873					"TAB/UNTAB Switch events\n"
874					"q/CTRL+C  Exit browser");
875			continue;
876		case NEWT_KEY_ENTER:
877		case NEWT_KEY_RIGHT:
878			/* menu */
879			break;
880		case NEWT_KEY_LEFT: {
881			const void *top;
882
883			if (pstack__empty(fstack)) {
884				/*
885				 * Go back to the perf_evsel_menu__run or other user
886				 */
887				if (left_exits)
888					goto out_free_stack;
889				continue;
890			}
891			top = pstack__pop(fstack);
892			if (top == &dso_filter)
893				goto zoom_out_dso;
894			if (top == &thread_filter)
895				goto zoom_out_thread;
896			continue;
897		}
898		case NEWT_KEY_ESCAPE:
899			if (!left_exits &&
900			    !ui__dialog_yesno("Do you really want to exit?"))
901				continue;
902			/* Fall thru */
903		default:
904			goto out_free_stack;
905		}
906
907		if (browser->selection != NULL &&
908		    browser->selection->sym != NULL &&
909		    !browser->selection->map->dso->annotate_warned &&
910		    asprintf(&options[nr_options], "Annotate %s",
911			     browser->selection->sym->name) > 0)
912			annotate = nr_options++;
913
914		if (thread != NULL &&
915		    asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
916			     (thread_filter ? "out of" : "into"),
917			     (thread->comm_set ? thread->comm : ""),
918			     thread->pid) > 0)
919			zoom_thread = nr_options++;
920
921		if (dso != NULL &&
922		    asprintf(&options[nr_options], "Zoom %s %s DSO",
923			     (dso_filter ? "out of" : "into"),
924			     (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
925			zoom_dso = nr_options++;
926
927		if (browser->selection != NULL &&
928		    browser->selection->map != NULL &&
929		    asprintf(&options[nr_options], "Browse map details") > 0)
930			browse_map = nr_options++;
931
932		options[nr_options++] = (char *)"Exit";
933
934		choice = ui__popup_menu(nr_options, options);
935
936		for (i = 0; i < nr_options - 1; ++i)
937			free(options[i]);
938
939		if (choice == nr_options - 1)
940			break;
941
942		if (choice == -1)
943			continue;
944
945		if (choice == annotate) {
946			struct hist_entry *he;
947do_annotate:
948			he = hist_browser__selected_entry(browser);
949			if (he == NULL)
950				continue;
951
952			hist_entry__tui_annotate(he, evsel->idx);
953		} else if (choice == browse_map)
954			map__browse(browser->selection->map);
955		else if (choice == zoom_dso) {
956zoom_dso:
957			if (dso_filter) {
958				pstack__remove(fstack, &dso_filter);
959zoom_out_dso:
960				ui_helpline__pop();
961				dso_filter = NULL;
962			} else {
963				if (dso == NULL)
964					continue;
965				ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
966						   dso->kernel ? "the Kernel" : dso->short_name);
967				dso_filter = dso;
968				pstack__push(fstack, &dso_filter);
969			}
970			hists__filter_by_dso(self, dso_filter);
971			hists__browser_title(self, msg, sizeof(msg), ev_name,
972					     dso_filter, thread_filter);
973			hist_browser__reset(browser);
974		} else if (choice == zoom_thread) {
975zoom_thread:
976			if (thread_filter) {
977				pstack__remove(fstack, &thread_filter);
978zoom_out_thread:
979				ui_helpline__pop();
980				thread_filter = NULL;
981			} else {
982				ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
983						   thread->comm_set ? thread->comm : "",
984						   thread->pid);
985				thread_filter = thread;
986				pstack__push(fstack, &thread_filter);
987			}
988			hists__filter_by_thread(self, thread_filter);
989			hists__browser_title(self, msg, sizeof(msg), ev_name,
990					     dso_filter, thread_filter);
991			hist_browser__reset(browser);
992		}
993	}
994out_free_stack:
995	pstack__delete(fstack);
996out:
997	hist_browser__delete(browser);
998	return key;
999}
1000
1001struct perf_evsel_menu {
1002	struct ui_browser b;
1003	struct perf_evsel *selection;
1004};
1005
1006static void perf_evsel_menu__write(struct ui_browser *browser,
1007				   void *entry, int row)
1008{
1009	struct perf_evsel_menu *menu = container_of(browser,
1010						    struct perf_evsel_menu, b);
1011	struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1012	bool current_entry = ui_browser__is_current_entry(browser, row);
1013	unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1014	const char *ev_name = event_name(evsel);
1015	char bf[256], unit;
1016
1017	ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
1018						       HE_COLORSET_NORMAL);
1019
1020	nr_events = convert_unit(nr_events, &unit);
1021	snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
1022		 unit, unit == ' ' ? "" : " ", ev_name);
1023	slsmg_write_nstring(bf, browser->width);
1024
1025	if (current_entry)
1026		menu->selection = evsel;
1027}
1028
1029static int perf_evsel_menu__run(struct perf_evsel_menu *menu, const char *help)
1030{
1031	int exit_keys[] = { NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, };
1032	struct perf_evlist *evlist = menu->b.priv;
1033	struct perf_evsel *pos;
1034	const char *ev_name, *title = "Available samples";
1035	int key;
1036
1037	if (ui_browser__show(&menu->b, title,
1038			     "ESC: exit, ENTER|->: Browse histograms") < 0)
1039		return -1;
1040
1041	ui_browser__add_exit_keys(&menu->b, exit_keys);
1042
1043	while (1) {
1044		key = ui_browser__run(&menu->b);
1045
1046		switch (key) {
1047		case NEWT_KEY_RIGHT:
1048		case NEWT_KEY_ENTER:
1049			if (!menu->selection)
1050				continue;
1051			pos = menu->selection;
1052browse_hists:
1053			ev_name = event_name(pos);
1054			key = perf_evsel__hists_browse(pos, help, ev_name, true);
1055			ui_browser__show_title(&menu->b, title);
1056			break;
1057		case NEWT_KEY_LEFT:
1058			continue;
1059		case NEWT_KEY_ESCAPE:
1060			if (!ui__dialog_yesno("Do you really want to exit?"))
1061				continue;
1062			/* Fall thru */
1063		default:
1064			goto out;
1065		}
1066
1067		switch (key) {
1068		case NEWT_KEY_TAB:
1069			if (pos->node.next == &evlist->entries)
1070				pos = list_entry(evlist->entries.next, struct perf_evsel, node);
1071			else
1072				pos = list_entry(pos->node.next, struct perf_evsel, node);
1073			goto browse_hists;
1074		case NEWT_KEY_UNTAB:
1075			if (pos->node.prev == &evlist->entries)
1076				pos = list_entry(evlist->entries.prev, struct perf_evsel, node);
1077			else
1078				pos = list_entry(pos->node.prev, struct perf_evsel, node);
1079			goto browse_hists;
1080		case 'q':
1081		case CTRL('c'):
1082			goto out;
1083		default:
1084			break;
1085		}
1086	}
1087
1088out:
1089	ui_browser__hide(&menu->b);
1090	return key;
1091}
1092
1093static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1094					   const char *help)
1095{
1096	struct perf_evsel *pos;
1097	struct perf_evsel_menu menu = {
1098		.b = {
1099			.entries    = &evlist->entries,
1100			.refresh    = ui_browser__list_head_refresh,
1101			.seek	    = ui_browser__list_head_seek,
1102			.write	    = perf_evsel_menu__write,
1103			.nr_entries = evlist->nr_entries,
1104			.priv	    = evlist,
1105		},
1106	};
1107
1108	ui_helpline__push("Press ESC to exit");
1109
1110	list_for_each_entry(pos, &evlist->entries, node) {
1111		const char *ev_name = event_name(pos);
1112		size_t line_len = strlen(ev_name) + 7;
1113
1114		if (menu.b.width < line_len)
1115			menu.b.width = line_len;
1116		/*
1117		 * Cache the evsel name, tracepoints have a _high_ cost per
1118		 * event_name() call.
1119		 */
1120		if (pos->name == NULL)
1121			pos->name = strdup(ev_name);
1122	}
1123
1124	return perf_evsel_menu__run(&menu, help);
1125}
1126
1127int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help)
1128{
1129
1130	if (evlist->nr_entries == 1) {
1131		struct perf_evsel *first = list_entry(evlist->entries.next,
1132						      struct perf_evsel, node);
1133		const char *ev_name = event_name(first);
1134		return perf_evsel__hists_browse(first, help, ev_name, false);
1135	}
1136
1137	return __perf_evlist__tui_browse_hists(evlist, help);
1138}
1139