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