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