Merge branch 'master' into next
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / tools / perf / util / ui / browsers / hists.c
CommitLineData
d1b4f249
ACM
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
e248de33
ACM
10#include "../../evsel.h"
11#include "../../evlist.h"
d1b4f249
ACM
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
d1b4f249
ACM
22struct hist_browser {
23 struct ui_browser b;
24 struct hists *hists;
25 struct hist_entry *he_selection;
26 struct map_symbol *selection;
27};
28
29static void hist_browser__refresh_dimensions(struct hist_browser *self)
30{
31 /* 3 == +/- toggle symbol before actual hist_entry rendering */
32 self->b.width = 3 + (hists__sort_list_width(self->hists) +
33 sizeof("[k]"));
34}
35
36static void hist_browser__reset(struct hist_browser *self)
37{
38 self->b.nr_entries = self->hists->nr_entries;
39 hist_browser__refresh_dimensions(self);
40 ui_browser__reset_index(&self->b);
41}
42
43static char tree__folded_sign(bool unfolded)
44{
45 return unfolded ? '-' : '+';
46}
47
48static char map_symbol__folded(const struct map_symbol *self)
49{
50 return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
51}
52
53static char hist_entry__folded(const struct hist_entry *self)
54{
55 return map_symbol__folded(&self->ms);
56}
57
58static char callchain_list__folded(const struct callchain_list *self)
59{
60 return map_symbol__folded(&self->ms);
61}
62
3c916cc2
ACM
63static void map_symbol__set_folding(struct map_symbol *self, bool unfold)
64{
65 self->unfolded = unfold ? self->has_children : false;
66}
67
d1b4f249
ACM
68static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
69{
70 int n = 0;
71 struct rb_node *nd;
72
73 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
74 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
75 struct callchain_list *chain;
76 char folded_sign = ' '; /* No children */
77
78 list_for_each_entry(chain, &child->val, list) {
79 ++n;
80 /* We need this because we may not have children */
81 folded_sign = callchain_list__folded(chain);
82 if (folded_sign == '+')
83 break;
84 }
85
86 if (folded_sign == '-') /* Have children and they're unfolded */
87 n += callchain_node__count_rows_rb_tree(child);
88 }
89
90 return n;
91}
92
93static int callchain_node__count_rows(struct callchain_node *node)
94{
95 struct callchain_list *chain;
96 bool unfolded = false;
97 int n = 0;
98
99 list_for_each_entry(chain, &node->val, list) {
100 ++n;
101 unfolded = chain->ms.unfolded;
102 }
103
104 if (unfolded)
105 n += callchain_node__count_rows_rb_tree(node);
106
107 return n;
108}
109
110static int callchain__count_rows(struct rb_root *chain)
111{
112 struct rb_node *nd;
113 int n = 0;
114
115 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
116 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
117 n += callchain_node__count_rows(node);
118 }
119
120 return n;
121}
122
123static bool map_symbol__toggle_fold(struct map_symbol *self)
124{
125 if (!self->has_children)
126 return false;
127
128 self->unfolded = !self->unfolded;
129 return true;
130}
131
132static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
133{
134 struct rb_node *nd = rb_first(&self->rb_root);
135
136 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
137 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
138 struct callchain_list *chain;
293db47f 139 bool first = true;
d1b4f249
ACM
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 ||
293db47f 145 !RB_EMPTY_ROOT(&child->rb_root);
d1b4f249
ACM
146 } else
147 chain->ms.has_children = chain->list.next == &child->val &&
293db47f 148 !RB_EMPTY_ROOT(&child->rb_root);
d1b4f249
ACM
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)
293db47f 160 chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root);
d1b4f249
ACM
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) {
18b308d7 178 self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain);
d1b4f249
ACM
179 callchain__init_have_children(&self->sorted_chain);
180 self->init_have_children = true;
181 }
182}
183
184static bool hist_browser__toggle_fold(struct hist_browser *self)
185{
186 if (map_symbol__toggle_fold(self->selection)) {
187 struct hist_entry *he = self->he_selection;
188
189 hist_entry__init_have_children(he);
190 self->hists->nr_entries -= he->nr_rows;
191
192 if (he->ms.unfolded)
193 he->nr_rows = callchain__count_rows(&he->sorted_chain);
194 else
195 he->nr_rows = 0;
196 self->hists->nr_entries += he->nr_rows;
197 self->b.nr_entries = self->hists->nr_entries;
198
199 return true;
200 }
201
202 /* If it doesn't have children, no toggling performed */
203 return false;
204}
205
3c916cc2
ACM
206static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold)
207{
208 int n = 0;
209 struct rb_node *nd;
210
211 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
212 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
213 struct callchain_list *chain;
214 bool has_children = false;
215
216 list_for_each_entry(chain, &child->val, list) {
217 ++n;
218 map_symbol__set_folding(&chain->ms, unfold);
219 has_children = chain->ms.has_children;
220 }
221
222 if (has_children)
223 n += callchain_node__set_folding_rb_tree(child, unfold);
224 }
225
226 return n;
227}
228
229static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
230{
231 struct callchain_list *chain;
232 bool has_children = false;
233 int n = 0;
234
235 list_for_each_entry(chain, &node->val, list) {
236 ++n;
237 map_symbol__set_folding(&chain->ms, unfold);
238 has_children = chain->ms.has_children;
239 }
240
241 if (has_children)
242 n += callchain_node__set_folding_rb_tree(node, unfold);
243
244 return n;
245}
246
247static int callchain__set_folding(struct rb_root *chain, bool unfold)
248{
249 struct rb_node *nd;
250 int n = 0;
251
252 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
253 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
254 n += callchain_node__set_folding(node, unfold);
255 }
256
257 return n;
258}
259
260static void hist_entry__set_folding(struct hist_entry *self, bool unfold)
261{
262 hist_entry__init_have_children(self);
263 map_symbol__set_folding(&self->ms, unfold);
264
265 if (self->ms.has_children) {
266 int n = callchain__set_folding(&self->sorted_chain, unfold);
267 self->nr_rows = unfold ? n : 0;
268 } else
269 self->nr_rows = 0;
270}
271
272static void hists__set_folding(struct hists *self, bool unfold)
273{
274 struct rb_node *nd;
275
276 self->nr_entries = 0;
277
278 for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) {
279 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
280 hist_entry__set_folding(he, unfold);
281 self->nr_entries += 1 + he->nr_rows;
282 }
283}
284
285static void hist_browser__set_folding(struct hist_browser *self, bool unfold)
286{
287 hists__set_folding(self->hists, unfold);
288 self->b.nr_entries = self->hists->nr_entries;
289 /* Go to the start, we may be way after valid entries after a collapse */
290 ui_browser__reset_index(&self->b);
291}
292
b50e003d 293static int hist_browser__run(struct hist_browser *self, const char *title)
d1b4f249 294{
b50e003d 295 int key;
3c916cc2 296 int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't',
a03f35ce
ACM
297 NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT,
298 NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, };
d1b4f249
ACM
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
59e8fe32
ACM
305 if (ui_browser__show(&self->b, title,
306 "Press '?' for help on key bindings") < 0)
d1b4f249
ACM
307 return -1;
308
4c1c952e 309 ui_browser__add_exit_keys(&self->b, exit_keys);
d1b4f249
ACM
310
311 while (1) {
b50e003d 312 key = ui_browser__run(&self->b);
d1b4f249 313
b50e003d 314 switch (key) {
4694153c 315 case 'D': { /* Debug */
d1b4f249
ACM
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 }
3c916cc2
ACM
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;
d1b4f249
ACM
337 case NEWT_KEY_ENTER:
338 if (hist_browser__toggle_fold(self))
339 break;
340 /* fall thru */
341 default:
b50e003d 342 goto out;
d1b4f249
ACM
343 }
344 }
b50e003d 345out:
59e8fe32 346 ui_browser__hide(&self->b);
b50e003d 347 return key;
d1b4f249
ACM
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
9486aa38 356 snprintf(bf, bfsize, "%#" PRIx64, self->ip);
d1b4f249
ACM
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);
f08c3154 383 u64 cumul = callchain_cumul_hits(child);
d1b4f249
ACM
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
163caed9 397 if (first)
d1b4f249 398 first = false;
163caed9 399 else
d1b4f249 400 extra_offset = LEVEL_OFFSET_STEP;
d1b4f249
ACM
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
8f9bbc40
ACM
427 ui_browser__set_color(&self->b, color);
428 ui_browser__gotorc(&self->b, row, 0);
d1b4f249
ACM
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;
163caed9 470
d1b4f249
ACM
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));
8f9bbc40
ACM
486 ui_browser__gotorc(&self->b, row, 0);
487 ui_browser__set_color(&self->b, color);
d1b4f249
ACM
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) {
163caed9 546 hist_entry__init_have_children(entry);
d1b4f249
ACM
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
8f9bbc40
ACM
565 ui_browser__set_color(&self->b, color);
566 ui_browser__gotorc(&self->b, row, 0);
d1b4f249
ACM
567 if (symbol_conf.use_callchain) {
568 slsmg_printf("%c ", folded_sign);
569 width -= 2;
570 }
571 slsmg_write_nstring(s, width);
572 ++row;
573 ++printed;
574 } else
575 --row_offset;
576
577 if (folded_sign == '-' && row != self->b.height) {
578 printed += hist_browser__show_callchain(self, &entry->sorted_chain,
579 1, row, &row_offset,
580 &current_entry);
581 if (current_entry)
582 self->he_selection = entry;
583 }
584
585 return printed;
586}
587
588static unsigned int hist_browser__refresh(struct ui_browser *self)
589{
590 unsigned row = 0;
591 struct rb_node *nd;
592 struct hist_browser *hb = container_of(self, struct hist_browser, b);
593
594 if (self->top == NULL)
595 self->top = rb_first(&hb->hists->entries);
596
597 for (nd = self->top; nd; nd = rb_next(nd)) {
598 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
599
600 if (h->filtered)
601 continue;
602
603 row += hist_browser__show_entry(hb, h, row);
604 if (row == self->height)
605 break;
606 }
607
608 return row;
609}
610
611static struct rb_node *hists__filter_entries(struct rb_node *nd)
612{
613 while (nd != NULL) {
614 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
615 if (!h->filtered)
616 return nd;
617
618 nd = rb_next(nd);
619 }
620
621 return NULL;
622}
623
624static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
625{
626 while (nd != NULL) {
627 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
628 if (!h->filtered)
629 return nd;
630
631 nd = rb_prev(nd);
632 }
633
634 return NULL;
635}
636
637static void ui_browser__hists_seek(struct ui_browser *self,
638 off_t offset, int whence)
639{
640 struct hist_entry *h;
641 struct rb_node *nd;
642 bool first = true;
643
60098917
ACM
644 if (self->nr_entries == 0)
645 return;
646
d1b4f249
ACM
647 switch (whence) {
648 case SEEK_SET:
649 nd = hists__filter_entries(rb_first(self->entries));
650 break;
651 case SEEK_CUR:
652 nd = self->top;
653 goto do_offset;
654 case SEEK_END:
655 nd = hists__filter_prev_entries(rb_last(self->entries));
656 first = false;
657 break;
658 default:
659 return;
660 }
661
662 /*
663 * Moves not relative to the first visible entry invalidates its
664 * row_offset:
665 */
666 h = rb_entry(self->top, struct hist_entry, rb_node);
667 h->row_offset = 0;
668
669 /*
670 * Here we have to check if nd is expanded (+), if it is we can't go
671 * the next top level hist_entry, instead we must compute an offset of
672 * what _not_ to show and not change the first visible entry.
673 *
674 * This offset increments when we are going from top to bottom and
675 * decreases when we're going from bottom to top.
676 *
677 * As we don't have backpointers to the top level in the callchains
678 * structure, we need to always print the whole hist_entry callchain,
679 * skipping the first ones that are before the first visible entry
680 * and stop when we printed enough lines to fill the screen.
681 */
682do_offset:
683 if (offset > 0) {
684 do {
685 h = rb_entry(nd, struct hist_entry, rb_node);
686 if (h->ms.unfolded) {
687 u16 remaining = h->nr_rows - h->row_offset;
688 if (offset > remaining) {
689 offset -= remaining;
690 h->row_offset = 0;
691 } else {
692 h->row_offset += offset;
693 offset = 0;
694 self->top = nd;
695 break;
696 }
697 }
698 nd = hists__filter_entries(rb_next(nd));
699 if (nd == NULL)
700 break;
701 --offset;
702 self->top = nd;
703 } while (offset != 0);
704 } else if (offset < 0) {
705 while (1) {
706 h = rb_entry(nd, struct hist_entry, rb_node);
707 if (h->ms.unfolded) {
708 if (first) {
709 if (-offset > h->row_offset) {
710 offset += h->row_offset;
711 h->row_offset = 0;
712 } else {
713 h->row_offset += offset;
714 offset = 0;
715 self->top = nd;
716 break;
717 }
718 } else {
719 if (-offset > h->nr_rows) {
720 offset += h->nr_rows;
721 h->row_offset = 0;
722 } else {
723 h->row_offset = h->nr_rows + offset;
724 offset = 0;
725 self->top = nd;
726 break;
727 }
728 }
729 }
730
731 nd = hists__filter_prev_entries(rb_prev(nd));
732 if (nd == NULL)
733 break;
734 ++offset;
735 self->top = nd;
736 if (offset == 0) {
737 /*
738 * Last unfiltered hist_entry, check if it is
739 * unfolded, if it is then we should have
740 * row_offset at its last entry.
741 */
742 h = rb_entry(nd, struct hist_entry, rb_node);
743 if (h->ms.unfolded)
744 h->row_offset = h->nr_rows;
745 break;
746 }
747 first = false;
748 }
749 } else {
750 self->top = nd;
751 h = rb_entry(nd, struct hist_entry, rb_node);
752 h->row_offset = 0;
753 }
754}
755
756static struct hist_browser *hist_browser__new(struct hists *hists)
757{
758 struct hist_browser *self = zalloc(sizeof(*self));
759
760 if (self) {
761 self->hists = hists;
762 self->b.refresh = hist_browser__refresh;
763 self->b.seek = ui_browser__hists_seek;
764 }
765
766 return self;
767}
768
769static void hist_browser__delete(struct hist_browser *self)
770{
d1b4f249
ACM
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
469917ce
ACM
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)
d1b4f249 787{
469917ce
ACM
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);
d1b4f249
ACM
794
795 if (thread)
796 printed += snprintf(bf + printed, size - printed,
469917ce
ACM
797 ", Thread: %s(%d)",
798 (thread->comm_set ? thread->comm : ""),
d1b4f249
ACM
799 thread->pid);
800 if (dso)
801 printed += snprintf(bf + printed, size - printed,
469917ce
ACM
802 ", DSO: %s", dso->short_name);
803 return printed;
d1b4f249
ACM
804}
805
7f0030b2
ACM
806static int perf_evsel__hists_browse(struct perf_evsel *evsel,
807 const char *helpline, const char *ev_name,
808 bool left_exits)
d1b4f249 809{
7f0030b2 810 struct hists *self = &evsel->hists;
d1b4f249
ACM
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;
d1b4f249
ACM
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
469917ce
ACM
827 hists__browser_title(self, msg, sizeof(msg), ev_name,
828 dso_filter, thread_filter);
d1b4f249 829 while (1) {
60098917
ACM
830 const struct thread *thread = NULL;
831 const struct dso *dso = NULL;
d1b4f249
ACM
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
b50e003d 837 key = hist_browser__run(browser, msg);
d1b4f249 838
60098917
ACM
839 if (browser->he_selection != NULL) {
840 thread = hist_browser__selected_thread(browser);
841 dso = browser->selection->map ? browser->selection->map->dso : NULL;
842 }
d1b4f249 843
b50e003d 844 switch (key) {
b50e003d
ACM
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':
60098917 853 if (browser->selection == NULL ||
db9a9cbc 854 browser->selection->sym == NULL ||
b50e003d 855 browser->selection->map->dso->annotate_warned)
d1b4f249 856 continue;
b50e003d
ACM
857 goto do_annotate;
858 case 'd':
859 goto zoom_dso;
860 case 't':
861 goto zoom_thread;
3c916cc2 862 case NEWT_KEY_F1:
b50e003d
ACM
863 case 'h':
864 case '?':
b50e003d
ACM
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"
3c916cc2
ACM
869 "C Collapse all callchains\n"
870 "E Expand all callchains\n"
b50e003d
ACM
871 "d Zoom into current DSO\n"
872 "t Zoom into current Thread\n"
a03f35ce 873 "TAB/UNTAB Switch events\n"
b50e003d
ACM
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;
d1b4f249 882
7f0030b2
ACM
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;
d1b4f249 889 continue;
7f0030b2 890 }
b50e003d
ACM
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:
7f0030b2
ACM
899 if (!left_exits &&
900 !ui__dialog_yesno("Do you really want to exit?"))
b50e003d
ACM
901 continue;
902 /* Fall thru */
903 default:
904 goto out_free_stack;
d1b4f249
ACM
905 }
906
60098917
ACM
907 if (browser->selection != NULL &&
908 browser->selection->sym != NULL &&
d1b4f249
ACM
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
60098917
ACM
927 if (browser->selection != NULL &&
928 browser->selection->map != NULL &&
d1b4f249
ACM
929 asprintf(&options[nr_options], "Browse map details") > 0)
930 browse_map = nr_options++;
931
932 options[nr_options++] = (char *)"Exit";
933
1e6dd077 934 choice = ui__popup_menu(nr_options, options);
d1b4f249
ACM
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:
d1b4f249
ACM
948 he = hist_browser__selected_entry(browser);
949 if (he == NULL)
950 continue;
951
7f0030b2 952 hist_entry__tui_annotate(he, evsel->idx);
d1b4f249
ACM
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);
469917ce
ACM
971 hists__browser_title(self, msg, sizeof(msg), ev_name,
972 dso_filter, thread_filter);
d1b4f249
ACM
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);
469917ce
ACM
989 hists__browser_title(self, msg, sizeof(msg), ev_name,
990 dso_filter, thread_filter);
d1b4f249
ACM
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
7f0030b2
ACM
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)
d1b4f249 1030{
7f0030b2
ACM
1031 int exit_keys[] = { NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, };
1032 struct perf_evlist *evlist = menu->b.priv;
e248de33 1033 struct perf_evsel *pos;
7f0030b2
ACM
1034 const char *ev_name, *title = "Available samples";
1035 int key;
d1b4f249 1036
7f0030b2
ACM
1037 if (ui_browser__show(&menu->b, title,
1038 "ESC: exit, ENTER|->: Browse histograms") < 0)
1039 return -1;
1040
1041 ui_browser__add_exit_keys(&menu->b, exit_keys);
1042
1043 while (1) {
1044 key = ui_browser__run(&menu->b);
1045
1046 switch (key) {
1047 case NEWT_KEY_RIGHT:
1048 case NEWT_KEY_ENTER:
1049 if (!menu->selection)
1050 continue;
1051 pos = menu->selection;
1052browse_hists:
1053 ev_name = event_name(pos);
1054 key = perf_evsel__hists_browse(pos, help, ev_name, true);
1055 ui_browser__show_title(&menu->b, title);
1056 break;
1057 case NEWT_KEY_LEFT:
1058 continue;
1059 case NEWT_KEY_ESCAPE:
1060 if (!ui__dialog_yesno("Do you really want to exit?"))
1061 continue;
1062 /* Fall thru */
1063 default:
1064 goto out;
1065 }
d1b4f249 1066
d1b4f249
ACM
1067 switch (key) {
1068 case NEWT_KEY_TAB:
e248de33
ACM
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);
7f0030b2 1073 goto browse_hists;
d1b4f249 1074 case NEWT_KEY_UNTAB:
e248de33
ACM
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);
7f0030b2
ACM
1079 goto browse_hists;
1080 case 'q':
1081 case CTRL('c'):
1082 goto out;
d1b4f249 1083 default:
7f0030b2 1084 break;
d1b4f249
ACM
1085 }
1086 }
1087
7f0030b2
ACM
1088out:
1089 ui_browser__hide(&menu->b);
1090 return key;
1091}
1092
1093static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1094 const char *help)
1095{
1096 struct perf_evsel *pos;
1097 struct perf_evsel_menu menu = {
1098 .b = {
1099 .entries = &evlist->entries,
1100 .refresh = ui_browser__list_head_refresh,
1101 .seek = ui_browser__list_head_seek,
1102 .write = perf_evsel_menu__write,
1103 .nr_entries = evlist->nr_entries,
1104 .priv = evlist,
1105 },
1106 };
1107
1108 ui_helpline__push("Press ESC to exit");
1109
1110 list_for_each_entry(pos, &evlist->entries, node) {
1111 const char *ev_name = event_name(pos);
1112 size_t line_len = strlen(ev_name) + 7;
1113
1114 if (menu.b.width < line_len)
1115 menu.b.width = line_len;
1116 /*
1117 * Cache the evsel name, tracepoints have a _high_ cost per
1118 * event_name() call.
1119 */
1120 if (pos->name == NULL)
1121 pos->name = strdup(ev_name);
1122 }
1123
1124 return perf_evsel_menu__run(&menu, help);
1125}
1126
1127int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help)
1128{
1129
1130 if (evlist->nr_entries == 1) {
1131 struct perf_evsel *first = list_entry(evlist->entries.next,
1132 struct perf_evsel, node);
1133 const char *ev_name = event_name(first);
1134 return perf_evsel__hists_browse(first, help, ev_name, false);
1135 }
1136
1137 return __perf_evlist__tui_browse_hists(evlist, help);
d1b4f249 1138}