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