]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blame - tools/perf/util/ui/browsers/hists.c
perf evlist: Split perf_evlist__id_hash
[mirror_ubuntu-hirsute-kernel.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
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 "map.h"
19
d1b4f249
ACM
20struct hist_browser {
21 struct ui_browser b;
22 struct hists *hists;
23 struct hist_entry *he_selection;
24 struct map_symbol *selection;
25};
26
27static void hist_browser__refresh_dimensions(struct hist_browser *self)
28{
29 /* 3 == +/- toggle symbol before actual hist_entry rendering */
30 self->b.width = 3 + (hists__sort_list_width(self->hists) +
31 sizeof("[k]"));
32}
33
34static void hist_browser__reset(struct hist_browser *self)
35{
36 self->b.nr_entries = self->hists->nr_entries;
37 hist_browser__refresh_dimensions(self);
38 ui_browser__reset_index(&self->b);
39}
40
41static char tree__folded_sign(bool unfolded)
42{
43 return unfolded ? '-' : '+';
44}
45
46static char map_symbol__folded(const struct map_symbol *self)
47{
48 return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
49}
50
51static char hist_entry__folded(const struct hist_entry *self)
52{
53 return map_symbol__folded(&self->ms);
54}
55
56static char callchain_list__folded(const struct callchain_list *self)
57{
58 return map_symbol__folded(&self->ms);
59}
60
3c916cc2
ACM
61static void map_symbol__set_folding(struct map_symbol *self, bool unfold)
62{
63 self->unfolded = unfold ? self->has_children : false;
64}
65
d1b4f249
ACM
66static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
67{
68 int n = 0;
69 struct rb_node *nd;
70
71 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
72 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
73 struct callchain_list *chain;
74 char folded_sign = ' '; /* No children */
75
76 list_for_each_entry(chain, &child->val, list) {
77 ++n;
78 /* We need this because we may not have children */
79 folded_sign = callchain_list__folded(chain);
80 if (folded_sign == '+')
81 break;
82 }
83
84 if (folded_sign == '-') /* Have children and they're unfolded */
85 n += callchain_node__count_rows_rb_tree(child);
86 }
87
88 return n;
89}
90
91static int callchain_node__count_rows(struct callchain_node *node)
92{
93 struct callchain_list *chain;
94 bool unfolded = false;
95 int n = 0;
96
97 list_for_each_entry(chain, &node->val, list) {
98 ++n;
99 unfolded = chain->ms.unfolded;
100 }
101
102 if (unfolded)
103 n += callchain_node__count_rows_rb_tree(node);
104
105 return n;
106}
107
108static int callchain__count_rows(struct rb_root *chain)
109{
110 struct rb_node *nd;
111 int n = 0;
112
113 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
114 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
115 n += callchain_node__count_rows(node);
116 }
117
118 return n;
119}
120
121static bool map_symbol__toggle_fold(struct map_symbol *self)
122{
123 if (!self->has_children)
124 return false;
125
126 self->unfolded = !self->unfolded;
127 return true;
128}
129
130static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
131{
132 struct rb_node *nd = rb_first(&self->rb_root);
133
134 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
135 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
136 struct callchain_list *chain;
293db47f 137 bool first = true;
d1b4f249
ACM
138
139 list_for_each_entry(chain, &child->val, list) {
140 if (first) {
141 first = false;
142 chain->ms.has_children = chain->list.next != &child->val ||
293db47f 143 !RB_EMPTY_ROOT(&child->rb_root);
d1b4f249
ACM
144 } else
145 chain->ms.has_children = chain->list.next == &child->val &&
293db47f 146 !RB_EMPTY_ROOT(&child->rb_root);
d1b4f249
ACM
147 }
148
149 callchain_node__init_have_children_rb_tree(child);
150 }
151}
152
153static void callchain_node__init_have_children(struct callchain_node *self)
154{
155 struct callchain_list *chain;
156
157 list_for_each_entry(chain, &self->val, list)
293db47f 158 chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root);
d1b4f249
ACM
159
160 callchain_node__init_have_children_rb_tree(self);
161}
162
163static void callchain__init_have_children(struct rb_root *self)
164{
165 struct rb_node *nd;
166
167 for (nd = rb_first(self); nd; nd = rb_next(nd)) {
168 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
169 callchain_node__init_have_children(node);
170 }
171}
172
173static void hist_entry__init_have_children(struct hist_entry *self)
174{
175 if (!self->init_have_children) {
18b308d7 176 self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain);
d1b4f249
ACM
177 callchain__init_have_children(&self->sorted_chain);
178 self->init_have_children = true;
179 }
180}
181
182static bool hist_browser__toggle_fold(struct hist_browser *self)
183{
184 if (map_symbol__toggle_fold(self->selection)) {
185 struct hist_entry *he = self->he_selection;
186
187 hist_entry__init_have_children(he);
188 self->hists->nr_entries -= he->nr_rows;
189
190 if (he->ms.unfolded)
191 he->nr_rows = callchain__count_rows(&he->sorted_chain);
192 else
193 he->nr_rows = 0;
194 self->hists->nr_entries += he->nr_rows;
195 self->b.nr_entries = self->hists->nr_entries;
196
197 return true;
198 }
199
200 /* If it doesn't have children, no toggling performed */
201 return false;
202}
203
3c916cc2
ACM
204static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold)
205{
206 int n = 0;
207 struct rb_node *nd;
208
209 for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
210 struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
211 struct callchain_list *chain;
212 bool has_children = false;
213
214 list_for_each_entry(chain, &child->val, list) {
215 ++n;
216 map_symbol__set_folding(&chain->ms, unfold);
217 has_children = chain->ms.has_children;
218 }
219
220 if (has_children)
221 n += callchain_node__set_folding_rb_tree(child, unfold);
222 }
223
224 return n;
225}
226
227static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
228{
229 struct callchain_list *chain;
230 bool has_children = false;
231 int n = 0;
232
233 list_for_each_entry(chain, &node->val, list) {
234 ++n;
235 map_symbol__set_folding(&chain->ms, unfold);
236 has_children = chain->ms.has_children;
237 }
238
239 if (has_children)
240 n += callchain_node__set_folding_rb_tree(node, unfold);
241
242 return n;
243}
244
245static int callchain__set_folding(struct rb_root *chain, bool unfold)
246{
247 struct rb_node *nd;
248 int n = 0;
249
250 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
251 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
252 n += callchain_node__set_folding(node, unfold);
253 }
254
255 return n;
256}
257
258static void hist_entry__set_folding(struct hist_entry *self, bool unfold)
259{
260 hist_entry__init_have_children(self);
261 map_symbol__set_folding(&self->ms, unfold);
262
263 if (self->ms.has_children) {
264 int n = callchain__set_folding(&self->sorted_chain, unfold);
265 self->nr_rows = unfold ? n : 0;
266 } else
267 self->nr_rows = 0;
268}
269
270static void hists__set_folding(struct hists *self, bool unfold)
271{
272 struct rb_node *nd;
273
274 self->nr_entries = 0;
275
276 for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) {
277 struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
278 hist_entry__set_folding(he, unfold);
279 self->nr_entries += 1 + he->nr_rows;
280 }
281}
282
283static void hist_browser__set_folding(struct hist_browser *self, bool unfold)
284{
285 hists__set_folding(self->hists, unfold);
286 self->b.nr_entries = self->hists->nr_entries;
287 /* Go to the start, we may be way after valid entries after a collapse */
288 ui_browser__reset_index(&self->b);
289}
290
b50e003d 291static int hist_browser__run(struct hist_browser *self, const char *title)
d1b4f249 292{
b50e003d 293 int key;
3c916cc2 294 int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't',
a03f35ce
ACM
295 NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT,
296 NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, };
d1b4f249
ACM
297
298 self->b.entries = &self->hists->entries;
299 self->b.nr_entries = self->hists->nr_entries;
300
301 hist_browser__refresh_dimensions(self);
302
59e8fe32
ACM
303 if (ui_browser__show(&self->b, title,
304 "Press '?' for help on key bindings") < 0)
d1b4f249
ACM
305 return -1;
306
4c1c952e 307 ui_browser__add_exit_keys(&self->b, exit_keys);
d1b4f249
ACM
308
309 while (1) {
b50e003d 310 key = ui_browser__run(&self->b);
d1b4f249 311
b50e003d 312 switch (key) {
4694153c 313 case 'D': { /* Debug */
d1b4f249
ACM
314 static int seq;
315 struct hist_entry *h = rb_entry(self->b.top,
316 struct hist_entry, rb_node);
317 ui_helpline__pop();
318 ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
319 seq++, self->b.nr_entries,
320 self->hists->nr_entries,
321 self->b.height,
322 self->b.index,
323 self->b.top_idx,
324 h->row_offset, h->nr_rows);
325 }
3c916cc2
ACM
326 break;
327 case 'C':
328 /* Collapse the whole world. */
329 hist_browser__set_folding(self, false);
330 break;
331 case 'E':
332 /* Expand the whole world. */
333 hist_browser__set_folding(self, true);
334 break;
d1b4f249
ACM
335 case NEWT_KEY_ENTER:
336 if (hist_browser__toggle_fold(self))
337 break;
338 /* fall thru */
339 default:
b50e003d 340 goto out;
d1b4f249
ACM
341 }
342 }
b50e003d 343out:
59e8fe32 344 ui_browser__hide(&self->b);
b50e003d 345 return key;
d1b4f249
ACM
346}
347
348static char *callchain_list__sym_name(struct callchain_list *self,
349 char *bf, size_t bfsize)
350{
351 if (self->ms.sym)
352 return self->ms.sym->name;
353
9486aa38 354 snprintf(bf, bfsize, "%#" PRIx64, self->ip);
d1b4f249
ACM
355 return bf;
356}
357
358#define LEVEL_OFFSET_STEP 3
359
360static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self,
361 struct callchain_node *chain_node,
362 u64 total, int level,
363 unsigned short row,
364 off_t *row_offset,
365 bool *is_current_entry)
366{
367 struct rb_node *node;
368 int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
369 u64 new_total, remaining;
370
371 if (callchain_param.mode == CHAIN_GRAPH_REL)
372 new_total = chain_node->children_hit;
373 else
374 new_total = total;
375
376 remaining = new_total;
377 node = rb_first(&chain_node->rb_root);
378 while (node) {
379 struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
380 struct rb_node *next = rb_next(node);
f08c3154 381 u64 cumul = callchain_cumul_hits(child);
d1b4f249
ACM
382 struct callchain_list *chain;
383 char folded_sign = ' ';
384 int first = true;
385 int extra_offset = 0;
386
387 remaining -= cumul;
388
389 list_for_each_entry(chain, &child->val, list) {
390 char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
391 const char *str;
392 int color;
393 bool was_first = first;
394
163caed9 395 if (first)
d1b4f249 396 first = false;
163caed9 397 else
d1b4f249 398 extra_offset = LEVEL_OFFSET_STEP;
d1b4f249
ACM
399
400 folded_sign = callchain_list__folded(chain);
401 if (*row_offset != 0) {
402 --*row_offset;
403 goto do_next;
404 }
405
406 alloc_str = NULL;
407 str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
408 if (was_first) {
409 double percent = cumul * 100.0 / new_total;
410
411 if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
412 str = "Not enough memory!";
413 else
414 str = alloc_str;
415 }
416
417 color = HE_COLORSET_NORMAL;
418 width = self->b.width - (offset + extra_offset + 2);
419 if (ui_browser__is_current_entry(&self->b, row)) {
420 self->selection = &chain->ms;
421 color = HE_COLORSET_SELECTED;
422 *is_current_entry = true;
423 }
424
8f9bbc40
ACM
425 ui_browser__set_color(&self->b, color);
426 ui_browser__gotorc(&self->b, row, 0);
d1b4f249
ACM
427 slsmg_write_nstring(" ", offset + extra_offset);
428 slsmg_printf("%c ", folded_sign);
429 slsmg_write_nstring(str, width);
430 free(alloc_str);
431
432 if (++row == self->b.height)
433 goto out;
434do_next:
435 if (folded_sign == '+')
436 break;
437 }
438
439 if (folded_sign == '-') {
440 const int new_level = level + (extra_offset ? 2 : 1);
441 row += hist_browser__show_callchain_node_rb_tree(self, child, new_total,
442 new_level, row, row_offset,
443 is_current_entry);
444 }
445 if (row == self->b.height)
446 goto out;
447 node = next;
448 }
449out:
450 return row - first_row;
451}
452
453static int hist_browser__show_callchain_node(struct hist_browser *self,
454 struct callchain_node *node,
455 int level, unsigned short row,
456 off_t *row_offset,
457 bool *is_current_entry)
458{
459 struct callchain_list *chain;
460 int first_row = row,
461 offset = level * LEVEL_OFFSET_STEP,
462 width = self->b.width - offset;
463 char folded_sign = ' ';
464
465 list_for_each_entry(chain, &node->val, list) {
466 char ipstr[BITS_PER_LONG / 4 + 1], *s;
467 int color;
163caed9 468
d1b4f249
ACM
469 folded_sign = callchain_list__folded(chain);
470
471 if (*row_offset != 0) {
472 --*row_offset;
473 continue;
474 }
475
476 color = HE_COLORSET_NORMAL;
477 if (ui_browser__is_current_entry(&self->b, row)) {
478 self->selection = &chain->ms;
479 color = HE_COLORSET_SELECTED;
480 *is_current_entry = true;
481 }
482
483 s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
8f9bbc40
ACM
484 ui_browser__gotorc(&self->b, row, 0);
485 ui_browser__set_color(&self->b, color);
d1b4f249
ACM
486 slsmg_write_nstring(" ", offset);
487 slsmg_printf("%c ", folded_sign);
488 slsmg_write_nstring(s, width - 2);
489
490 if (++row == self->b.height)
491 goto out;
492 }
493
494 if (folded_sign == '-')
495 row += hist_browser__show_callchain_node_rb_tree(self, node,
496 self->hists->stats.total_period,
497 level + 1, row,
498 row_offset,
499 is_current_entry);
500out:
501 return row - first_row;
502}
503
504static int hist_browser__show_callchain(struct hist_browser *self,
505 struct rb_root *chain,
506 int level, unsigned short row,
507 off_t *row_offset,
508 bool *is_current_entry)
509{
510 struct rb_node *nd;
511 int first_row = row;
512
513 for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
514 struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
515
516 row += hist_browser__show_callchain_node(self, node, level,
517 row, row_offset,
518 is_current_entry);
519 if (row == self->b.height)
520 break;
521 }
522
523 return row - first_row;
524}
525
526static int hist_browser__show_entry(struct hist_browser *self,
527 struct hist_entry *entry,
528 unsigned short row)
529{
530 char s[256];
531 double percent;
532 int printed = 0;
533 int color, width = self->b.width;
534 char folded_sign = ' ';
535 bool current_entry = ui_browser__is_current_entry(&self->b, row);
536 off_t row_offset = entry->row_offset;
537
538 if (current_entry) {
539 self->he_selection = entry;
540 self->selection = &entry->ms;
541 }
542
543 if (symbol_conf.use_callchain) {
163caed9 544 hist_entry__init_have_children(entry);
d1b4f249
ACM
545 folded_sign = hist_entry__folded(entry);
546 }
547
548 if (row_offset == 0) {
549 hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false,
550 0, false, self->hists->stats.total_period);
551 percent = (entry->period * 100.0) / self->hists->stats.total_period;
552
553 color = HE_COLORSET_SELECTED;
554 if (!current_entry) {
555 if (percent >= MIN_RED)
556 color = HE_COLORSET_TOP;
557 else if (percent >= MIN_GREEN)
558 color = HE_COLORSET_MEDIUM;
559 else
560 color = HE_COLORSET_NORMAL;
561 }
562
8f9bbc40
ACM
563 ui_browser__set_color(&self->b, color);
564 ui_browser__gotorc(&self->b, row, 0);
d1b4f249
ACM
565 if (symbol_conf.use_callchain) {
566 slsmg_printf("%c ", folded_sign);
567 width -= 2;
568 }
569 slsmg_write_nstring(s, width);
570 ++row;
571 ++printed;
572 } else
573 --row_offset;
574
575 if (folded_sign == '-' && row != self->b.height) {
576 printed += hist_browser__show_callchain(self, &entry->sorted_chain,
577 1, row, &row_offset,
578 &current_entry);
579 if (current_entry)
580 self->he_selection = entry;
581 }
582
583 return printed;
584}
585
586static unsigned int hist_browser__refresh(struct ui_browser *self)
587{
588 unsigned row = 0;
589 struct rb_node *nd;
590 struct hist_browser *hb = container_of(self, struct hist_browser, b);
591
592 if (self->top == NULL)
593 self->top = rb_first(&hb->hists->entries);
594
595 for (nd = self->top; nd; nd = rb_next(nd)) {
596 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
597
598 if (h->filtered)
599 continue;
600
601 row += hist_browser__show_entry(hb, h, row);
602 if (row == self->height)
603 break;
604 }
605
606 return row;
607}
608
609static struct rb_node *hists__filter_entries(struct rb_node *nd)
610{
611 while (nd != NULL) {
612 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
613 if (!h->filtered)
614 return nd;
615
616 nd = rb_next(nd);
617 }
618
619 return NULL;
620}
621
622static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
623{
624 while (nd != NULL) {
625 struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
626 if (!h->filtered)
627 return nd;
628
629 nd = rb_prev(nd);
630 }
631
632 return NULL;
633}
634
635static void ui_browser__hists_seek(struct ui_browser *self,
636 off_t offset, int whence)
637{
638 struct hist_entry *h;
639 struct rb_node *nd;
640 bool first = true;
641
60098917
ACM
642 if (self->nr_entries == 0)
643 return;
644
d1b4f249
ACM
645 switch (whence) {
646 case SEEK_SET:
647 nd = hists__filter_entries(rb_first(self->entries));
648 break;
649 case SEEK_CUR:
650 nd = self->top;
651 goto do_offset;
652 case SEEK_END:
653 nd = hists__filter_prev_entries(rb_last(self->entries));
654 first = false;
655 break;
656 default:
657 return;
658 }
659
660 /*
661 * Moves not relative to the first visible entry invalidates its
662 * row_offset:
663 */
664 h = rb_entry(self->top, struct hist_entry, rb_node);
665 h->row_offset = 0;
666
667 /*
668 * Here we have to check if nd is expanded (+), if it is we can't go
669 * the next top level hist_entry, instead we must compute an offset of
670 * what _not_ to show and not change the first visible entry.
671 *
672 * This offset increments when we are going from top to bottom and
673 * decreases when we're going from bottom to top.
674 *
675 * As we don't have backpointers to the top level in the callchains
676 * structure, we need to always print the whole hist_entry callchain,
677 * skipping the first ones that are before the first visible entry
678 * and stop when we printed enough lines to fill the screen.
679 */
680do_offset:
681 if (offset > 0) {
682 do {
683 h = rb_entry(nd, struct hist_entry, rb_node);
684 if (h->ms.unfolded) {
685 u16 remaining = h->nr_rows - h->row_offset;
686 if (offset > remaining) {
687 offset -= remaining;
688 h->row_offset = 0;
689 } else {
690 h->row_offset += offset;
691 offset = 0;
692 self->top = nd;
693 break;
694 }
695 }
696 nd = hists__filter_entries(rb_next(nd));
697 if (nd == NULL)
698 break;
699 --offset;
700 self->top = nd;
701 } while (offset != 0);
702 } else if (offset < 0) {
703 while (1) {
704 h = rb_entry(nd, struct hist_entry, rb_node);
705 if (h->ms.unfolded) {
706 if (first) {
707 if (-offset > h->row_offset) {
708 offset += h->row_offset;
709 h->row_offset = 0;
710 } else {
711 h->row_offset += offset;
712 offset = 0;
713 self->top = nd;
714 break;
715 }
716 } else {
717 if (-offset > h->nr_rows) {
718 offset += h->nr_rows;
719 h->row_offset = 0;
720 } else {
721 h->row_offset = h->nr_rows + offset;
722 offset = 0;
723 self->top = nd;
724 break;
725 }
726 }
727 }
728
729 nd = hists__filter_prev_entries(rb_prev(nd));
730 if (nd == NULL)
731 break;
732 ++offset;
733 self->top = nd;
734 if (offset == 0) {
735 /*
736 * Last unfiltered hist_entry, check if it is
737 * unfolded, if it is then we should have
738 * row_offset at its last entry.
739 */
740 h = rb_entry(nd, struct hist_entry, rb_node);
741 if (h->ms.unfolded)
742 h->row_offset = h->nr_rows;
743 break;
744 }
745 first = false;
746 }
747 } else {
748 self->top = nd;
749 h = rb_entry(nd, struct hist_entry, rb_node);
750 h->row_offset = 0;
751 }
752}
753
754static struct hist_browser *hist_browser__new(struct hists *hists)
755{
756 struct hist_browser *self = zalloc(sizeof(*self));
757
758 if (self) {
759 self->hists = hists;
760 self->b.refresh = hist_browser__refresh;
761 self->b.seek = ui_browser__hists_seek;
762 }
763
764 return self;
765}
766
767static void hist_browser__delete(struct hist_browser *self)
768{
d1b4f249
ACM
769 free(self);
770}
771
772static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
773{
774 return self->he_selection;
775}
776
777static struct thread *hist_browser__selected_thread(struct hist_browser *self)
778{
779 return self->he_selection->thread;
780}
781
469917ce
ACM
782static int hists__browser_title(struct hists *self, char *bf, size_t size,
783 const char *ev_name, const struct dso *dso,
784 const struct thread *thread)
d1b4f249 785{
469917ce
ACM
786 char unit;
787 int printed;
788 unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE];
789
790 nr_events = convert_unit(nr_events, &unit);
791 printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name);
d1b4f249
ACM
792
793 if (thread)
794 printed += snprintf(bf + printed, size - printed,
469917ce
ACM
795 ", Thread: %s(%d)",
796 (thread->comm_set ? thread->comm : ""),
d1b4f249
ACM
797 thread->pid);
798 if (dso)
799 printed += snprintf(bf + printed, size - printed,
469917ce
ACM
800 ", DSO: %s", dso->short_name);
801 return printed;
d1b4f249
ACM
802}
803
2f525d01
ACM
804int hists__browse(struct hists *self, const char *helpline,
805 const char *ev_name, int evidx)
d1b4f249
ACM
806{
807 struct hist_browser *browser = hist_browser__new(self);
808 struct pstack *fstack;
809 const struct thread *thread_filter = NULL;
810 const struct dso *dso_filter = NULL;
d1b4f249
ACM
811 char msg[160];
812 int key = -1;
813
814 if (browser == NULL)
815 return -1;
816
817 fstack = pstack__new(2);
818 if (fstack == NULL)
819 goto out;
820
821 ui_helpline__push(helpline);
822
469917ce
ACM
823 hists__browser_title(self, msg, sizeof(msg), ev_name,
824 dso_filter, thread_filter);
d1b4f249 825 while (1) {
60098917
ACM
826 const struct thread *thread = NULL;
827 const struct dso *dso = NULL;
d1b4f249
ACM
828 char *options[16];
829 int nr_options = 0, choice = 0, i,
830 annotate = -2, zoom_dso = -2, zoom_thread = -2,
831 browse_map = -2;
832
b50e003d 833 key = hist_browser__run(browser, msg);
d1b4f249 834
60098917
ACM
835 if (browser->he_selection != NULL) {
836 thread = hist_browser__selected_thread(browser);
837 dso = browser->selection->map ? browser->selection->map->dso : NULL;
838 }
d1b4f249 839
b50e003d 840 switch (key) {
b50e003d
ACM
841 case NEWT_KEY_TAB:
842 case NEWT_KEY_UNTAB:
843 /*
844 * Exit the browser, let hists__browser_tree
845 * go to the next or previous
846 */
847 goto out_free_stack;
848 case 'a':
60098917
ACM
849 if (browser->selection == NULL ||
850 browser->selection->map == NULL ||
b50e003d 851 browser->selection->map->dso->annotate_warned)
d1b4f249 852 continue;
b50e003d
ACM
853 goto do_annotate;
854 case 'd':
855 goto zoom_dso;
856 case 't':
857 goto zoom_thread;
3c916cc2 858 case NEWT_KEY_F1:
b50e003d
ACM
859 case 'h':
860 case '?':
b50e003d
ACM
861 ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n"
862 "<- Zoom out\n"
863 "a Annotate current symbol\n"
864 "h/?/F1 Show this window\n"
3c916cc2
ACM
865 "C Collapse all callchains\n"
866 "E Expand all callchains\n"
b50e003d
ACM
867 "d Zoom into current DSO\n"
868 "t Zoom into current Thread\n"
a03f35ce 869 "TAB/UNTAB Switch events\n"
b50e003d
ACM
870 "q/CTRL+C Exit browser");
871 continue;
872 case NEWT_KEY_ENTER:
873 case NEWT_KEY_RIGHT:
874 /* menu */
875 break;
876 case NEWT_KEY_LEFT: {
877 const void *top;
d1b4f249 878
b50e003d 879 if (pstack__empty(fstack))
d1b4f249 880 continue;
b50e003d
ACM
881 top = pstack__pop(fstack);
882 if (top == &dso_filter)
883 goto zoom_out_dso;
884 if (top == &thread_filter)
885 goto zoom_out_thread;
886 continue;
887 }
888 case NEWT_KEY_ESCAPE:
889 if (!ui__dialog_yesno("Do you really want to exit?"))
890 continue;
891 /* Fall thru */
892 default:
893 goto out_free_stack;
d1b4f249
ACM
894 }
895
60098917
ACM
896 if (browser->selection != NULL &&
897 browser->selection->sym != NULL &&
d1b4f249
ACM
898 !browser->selection->map->dso->annotate_warned &&
899 asprintf(&options[nr_options], "Annotate %s",
900 browser->selection->sym->name) > 0)
901 annotate = nr_options++;
902
903 if (thread != NULL &&
904 asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
905 (thread_filter ? "out of" : "into"),
906 (thread->comm_set ? thread->comm : ""),
907 thread->pid) > 0)
908 zoom_thread = nr_options++;
909
910 if (dso != NULL &&
911 asprintf(&options[nr_options], "Zoom %s %s DSO",
912 (dso_filter ? "out of" : "into"),
913 (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
914 zoom_dso = nr_options++;
915
60098917
ACM
916 if (browser->selection != NULL &&
917 browser->selection->map != NULL &&
d1b4f249
ACM
918 asprintf(&options[nr_options], "Browse map details") > 0)
919 browse_map = nr_options++;
920
921 options[nr_options++] = (char *)"Exit";
922
1e6dd077 923 choice = ui__popup_menu(nr_options, options);
d1b4f249
ACM
924
925 for (i = 0; i < nr_options - 1; ++i)
926 free(options[i]);
927
928 if (choice == nr_options - 1)
929 break;
930
931 if (choice == -1)
932 continue;
933
934 if (choice == annotate) {
935 struct hist_entry *he;
936do_annotate:
d1b4f249
ACM
937 he = hist_browser__selected_entry(browser);
938 if (he == NULL)
939 continue;
940
2f525d01 941 hist_entry__tui_annotate(he, evidx);
d1b4f249
ACM
942 } else if (choice == browse_map)
943 map__browse(browser->selection->map);
944 else if (choice == zoom_dso) {
945zoom_dso:
946 if (dso_filter) {
947 pstack__remove(fstack, &dso_filter);
948zoom_out_dso:
949 ui_helpline__pop();
950 dso_filter = NULL;
951 } else {
952 if (dso == NULL)
953 continue;
954 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
955 dso->kernel ? "the Kernel" : dso->short_name);
956 dso_filter = dso;
957 pstack__push(fstack, &dso_filter);
958 }
959 hists__filter_by_dso(self, dso_filter);
469917ce
ACM
960 hists__browser_title(self, msg, sizeof(msg), ev_name,
961 dso_filter, thread_filter);
d1b4f249
ACM
962 hist_browser__reset(browser);
963 } else if (choice == zoom_thread) {
964zoom_thread:
965 if (thread_filter) {
966 pstack__remove(fstack, &thread_filter);
967zoom_out_thread:
968 ui_helpline__pop();
969 thread_filter = NULL;
970 } else {
971 ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
972 thread->comm_set ? thread->comm : "",
973 thread->pid);
974 thread_filter = thread;
975 pstack__push(fstack, &thread_filter);
976 }
977 hists__filter_by_thread(self, thread_filter);
469917ce
ACM
978 hists__browser_title(self, msg, sizeof(msg), ev_name,
979 dso_filter, thread_filter);
d1b4f249
ACM
980 hist_browser__reset(browser);
981 }
982 }
983out_free_stack:
984 pstack__delete(fstack);
985out:
986 hist_browser__delete(browser);
987 return key;
988}
989
2f525d01 990int hists__tui_browse_tree(struct rb_root *self, const char *help, int evidx)
d1b4f249
ACM
991{
992 struct rb_node *first = rb_first(self), *nd = first, *next;
993 int key = 0;
994
995 while (nd) {
996 struct hists *hists = rb_entry(nd, struct hists, rb_node);
997 const char *ev_name = __event_name(hists->type, hists->config);
998
2f525d01 999 key = hists__browse(hists, help, ev_name, evidx);
d1b4f249
ACM
1000 switch (key) {
1001 case NEWT_KEY_TAB:
1002 next = rb_next(nd);
1003 if (next)
1004 nd = next;
1005 break;
1006 case NEWT_KEY_UNTAB:
1007 if (nd == first)
1008 continue;
1009 nd = rb_prev(nd);
a03f35ce 1010 break;
d1b4f249 1011 default:
b50e003d 1012 return key;
d1b4f249
ACM
1013 }
1014 }
1015
1016 return key;
1017}