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