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 
hist_browser__refresh_dimensions(struct hist_browser * self)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 
hist_browser__reset(struct hist_browser * self)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 
tree__folded_sign(bool unfolded)43 static char tree__folded_sign(bool unfolded)
44 {
45 	return unfolded ? '-' : '+';
46 }
47 
map_symbol__folded(const struct map_symbol * self)48 static char map_symbol__folded(const struct map_symbol *self)
49 {
50 	return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
51 }
52 
hist_entry__folded(const struct hist_entry * self)53 static char hist_entry__folded(const struct hist_entry *self)
54 {
55 	return map_symbol__folded(&self->ms);
56 }
57 
callchain_list__folded(const struct callchain_list * self)58 static char callchain_list__folded(const struct callchain_list *self)
59 {
60 	return map_symbol__folded(&self->ms);
61 }
62 
map_symbol__set_folding(struct map_symbol * self,bool unfold)63 static void map_symbol__set_folding(struct map_symbol *self, bool unfold)
64 {
65 	self->unfolded = unfold ? self->has_children : false;
66 }
67 
callchain_node__count_rows_rb_tree(struct callchain_node * self)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 
callchain_node__count_rows(struct callchain_node * node)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 
callchain__count_rows(struct rb_root * chain)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 
map_symbol__toggle_fold(struct map_symbol * self)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 
callchain_node__init_have_children_rb_tree(struct callchain_node * self)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 
callchain_node__init_have_children(struct callchain_node * self)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 
callchain__init_have_children(struct rb_root * self)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 
hist_entry__init_have_children(struct hist_entry * self)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 
hist_browser__toggle_fold(struct hist_browser * self)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 
callchain_node__set_folding_rb_tree(struct callchain_node * self,bool unfold)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 
callchain_node__set_folding(struct callchain_node * node,bool unfold)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 
callchain__set_folding(struct rb_root * chain,bool unfold)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 
hist_entry__set_folding(struct hist_entry * self,bool unfold)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 
hists__set_folding(struct hists * self,bool unfold)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 
hist_browser__set_folding(struct hist_browser * self,bool unfold)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 
hist_browser__run(struct hist_browser * self,const char * title)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 
callchain_list__sym_name(struct callchain_list * self,char * bf,size_t bfsize)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 
hist_browser__show_callchain_node_rb_tree(struct hist_browser * self,struct callchain_node * chain_node,u64 total,int level,unsigned short row,off_t * row_offset,bool * is_current_entry)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 
hist_browser__show_callchain_node(struct hist_browser * self,struct callchain_node * node,int level,unsigned short row,off_t * row_offset,bool * is_current_entry)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 
hist_browser__show_callchain(struct hist_browser * self,struct rb_root * chain,int level,unsigned short row,off_t * row_offset,bool * is_current_entry)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 
hist_browser__show_entry(struct hist_browser * self,struct hist_entry * entry,unsigned short row)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 
hist_browser__refresh(struct ui_browser * self)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 
hists__filter_entries(struct rb_node * nd)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 
hists__filter_prev_entries(struct rb_node * nd)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 
ui_browser__hists_seek(struct ui_browser * self,off_t offset,int whence)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 
hist_browser__new(struct hists * hists)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 
hist_browser__delete(struct hist_browser * self)769 static void hist_browser__delete(struct hist_browser *self)
770 {
771 	free(self);
772 }
773 
hist_browser__selected_entry(struct hist_browser * self)774 static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
775 {
776 	return self->he_selection;
777 }
778 
hist_browser__selected_thread(struct hist_browser * self)779 static struct thread *hist_browser__selected_thread(struct hist_browser *self)
780 {
781 	return self->he_selection->thread;
782 }
783 
hists__browser_title(struct hists * self,char * bf,size_t size,const char * ev_name,const struct dso * dso,const struct thread * thread)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 
perf_evsel__hists_browse(struct perf_evsel * evsel,const char * helpline,const char * ev_name,bool left_exits)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 
perf_evsel_menu__write(struct ui_browser * browser,void * entry,int row)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 
perf_evsel_menu__run(struct perf_evsel_menu * menu,const char * help)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 
__perf_evlist__tui_browse_hists(struct perf_evlist * evlist,const char * help)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 
perf_evlist__tui_browse_hists(struct perf_evlist * evlist,const char * help)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