Home | History | Annotate | Download | only in browsers
      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 
     22 struct hist_browser {
     23 	struct ui_browser   b;
     24 	struct hists	    *hists;
     25 	struct hist_entry   *he_selection;
     26 	struct map_symbol   *selection;
     27 };
     28 
     29 static 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 
     36 static 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 
     43 static char tree__folded_sign(bool unfolded)
     44 {
     45 	return unfolded ? '-' : '+';
     46 }
     47 
     48 static char map_symbol__folded(const struct map_symbol *self)
     49 {
     50 	return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
     51 }
     52 
     53 static char hist_entry__folded(const struct hist_entry *self)
     54 {
     55 	return map_symbol__folded(&self->ms);
     56 }
     57 
     58 static char callchain_list__folded(const struct callchain_list *self)
     59 {
     60 	return map_symbol__folded(&self->ms);
     61 }
     62 
     63 static void map_symbol__set_folding(struct map_symbol *self, bool unfold)
     64 {
     65 	self->unfolded = unfold ? self->has_children : false;
     66 }
     67 
     68 static 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 
     93 static 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 
    110 static 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 
    123 static 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 
    132 static 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 
    155 static 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 
    165 static 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 
    175 static 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 
    184 static 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 
    206 static 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 
    229 static 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 
    247 static 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 
    260 static 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 
    272 static 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 
    285 static 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 
    293 static 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 	}
    345 out:
    346 	ui_browser__hide(&self->b);
    347 	return key;
    348 }
    349 
    350 static 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 
    362 static 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;
    436 do_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 	}
    451 out:
    452 	return row - first_row;
    453 }
    454 
    455 static 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);
    502 out:
    503 	return row - first_row;
    504 }
    505 
    506 static 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 
    528 static 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 
    588 static 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 
    611 static 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 
    624 static 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 
    637 static 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 	 */
    682 do_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 
    756 static 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 
    769 static void hist_browser__delete(struct hist_browser *self)
    770 {
    771 	free(self);
    772 }
    773 
    774 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
    775 {
    776 	return self->he_selection;
    777 }
    778 
    779 static struct thread *hist_browser__selected_thread(struct hist_browser *self)
    780 {
    781 	return self->he_selection->thread;
    782 }
    783 
    784 static 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 
    806 static 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;
    947 do_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) {
    956 zoom_dso:
    957 			if (dso_filter) {
    958 				pstack__remove(fstack, &dso_filter);
    959 zoom_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) {
    975 zoom_thread:
    976 			if (thread_filter) {
    977 				pstack__remove(fstack, &thread_filter);
    978 zoom_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 	}
    994 out_free_stack:
    995 	pstack__delete(fstack);
    996 out:
    997 	hist_browser__delete(browser);
    998 	return key;
    999 }
   1000 
   1001 struct perf_evsel_menu {
   1002 	struct ui_browser b;
   1003 	struct perf_evsel *selection;
   1004 };
   1005 
   1006 static 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 
   1029 static 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;
   1052 browse_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 
   1088 out:
   1089 	ui_browser__hide(&menu->b);
   1090 	return key;
   1091 }
   1092 
   1093 static 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 
   1127 int 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