perf tui: Remove annotate from popup menu after failure
[linux-2.6.git] / tools / perf / util / newt.c
index edd628f..c65838c 100644 (file)
@@ -1,17 +1,38 @@
 #define _GNU_SOURCE
 #include <stdio.h>
 #undef _GNU_SOURCE
-
+/*
+ * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks
+ * the build if it isn't defined. Use the equivalent one that glibc
+ * has on features.h.
+ */
+#include <features.h>
+#ifndef HAVE_LONG_LONG
+#define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG
+#endif
+#include <slang.h>
 #include <stdlib.h>
 #include <newt.h>
 #include <sys/ttydefaults.h>
 
 #include "cache.h"
 #include "hist.h"
+#include "pstack.h"
 #include "session.h"
 #include "sort.h"
 #include "symbol.h"
 
+#if SLANG_VERSION < 20104
+#define slsmg_printf(msg, args...) SLsmg_printf((char *)msg, ##args)
+#define slsmg_write_nstring(msg, len) SLsmg_write_nstring((char *)msg, len)
+#define sltt_set_color(obj, name, fg, bg) SLtt_set_color(obj,(char *)name,\
+                                                        (char *)fg, (char *)bg)
+#else
+#define slsmg_printf SLsmg_printf
+#define slsmg_write_nstring SLsmg_write_nstring
+#define sltt_set_color SLtt_set_color
+#endif
+
 struct ui_progress {
        newtComponent form, scale;
 };
@@ -31,7 +52,7 @@ struct ui_progress *ui_progress__new(const char *title, u64 total)
                self->scale = newtScale(0, 0, cols, total);
                if (self->scale == NULL)
                        goto out_free_form;
-               newtFormAddComponents(self->form, self->scale, NULL);
+               newtFormAddComponent(self->form, self->scale);
                newtRefresh();
        }
 
@@ -57,6 +78,43 @@ void ui_progress__delete(struct ui_progress *self)
        free(self);
 }
 
+static void ui_helpline__pop(void)
+{
+       newtPopHelpLine();
+}
+
+static void ui_helpline__push(const char *msg)
+{
+       newtPushHelpLine(msg);
+}
+
+static void ui_helpline__vpush(const char *fmt, va_list ap)
+{
+       char *s;
+
+       if (vasprintf(&s, fmt, ap) < 0)
+               vfprintf(stderr, fmt, ap);
+       else {
+               ui_helpline__push(s);
+               free(s);
+       }
+}
+
+static void ui_helpline__fpush(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       ui_helpline__vpush(fmt, ap);
+       va_end(ap);
+}
+
+static void ui_helpline__puts(const char *msg)
+{
+       ui_helpline__pop();
+       ui_helpline__push(msg);
+}
+
 static char browser__last_msg[1024];
 
 int browser__show_help(const char *format, va_list ap)
@@ -69,8 +127,7 @@ int browser__show_help(const char *format, va_list ap)
        backlog += ret;
 
        if (browser__last_msg[backlog - 1] == '\n') {
-               newtPopHelpLine();
-               newtPushHelpLine(browser__last_msg);
+               ui_helpline__puts(browser__last_msg);
                newtRefresh();
                backlog = 0;
        }
@@ -80,6 +137,7 @@ int browser__show_help(const char *format, va_list ap)
 
 static void newt_form__set_exit_keys(newtComponent self)
 {
+       newtFormAddHotKey(self, NEWT_KEY_LEFT);
        newtFormAddHotKey(self, NEWT_KEY_ESCAPE);
        newtFormAddHotKey(self, 'Q');
        newtFormAddHotKey(self, 'q');
@@ -94,7 +152,7 @@ static newtComponent newt_form__new(void)
        return self;
 }
 
-static int popup_menu(int argc, const char *argv[])
+static int popup_menu(int argc, char * const argv[])
 {
        struct newtExitStruct es;
        int i, rc = -1, max_len = 5;
@@ -107,7 +165,7 @@ static int popup_menu(int argc, const char *argv[])
        if (listbox == NULL)
                goto out_destroy_form;
 
-       newtFormAddComponents(form, listbox, NULL);
+       newtFormAddComponent(form, listbox);
 
        for (i = 0; i < argc; ++i) {
                int len = strlen(argv[i]);
@@ -128,11 +186,304 @@ out_destroy_form:
        return rc;
 }
 
+static int ui__help_window(const char *text)
+{
+       struct newtExitStruct es;
+       newtComponent tb, form = newt_form__new();
+       int rc = -1;
+       int max_len = 0, nr_lines = 0;
+       const char *t;
+
+       if (form == NULL)
+               return -1;
+
+       t = text;
+       while (1) {
+               const char *sep = strchr(t, '\n');
+               int len;
+
+               if (sep == NULL)
+                       sep = strchr(t, '\0');
+               len = sep - t;
+               if (max_len < len)
+                       max_len = len;
+               ++nr_lines;
+               if (*sep == '\0')
+                       break;
+               t = sep + 1;
+       }
+
+       tb = newtTextbox(0, 0, max_len, nr_lines, 0);
+       if (tb == NULL)
+               goto out_destroy_form;
+
+       newtTextboxSetText(tb, text);
+       newtFormAddComponent(form, tb);
+       newtCenteredWindow(max_len, nr_lines, NULL);
+       newtFormRun(form, &es);
+       newtPopWindow();
+       rc = 0;
+out_destroy_form:
+       newtFormDestroy(form);
+       return rc;
+}
+
 static bool dialog_yesno(const char *msg)
 {
        /* newtWinChoice should really be accepting const char pointers... */
        char yes[] = "Yes", no[] = "No";
-       return newtWinChoice(NULL, no, yes, (char *)msg) == 2;
+       return newtWinChoice(NULL, yes, no, (char *)msg) == 1;
+}
+
+#define HE_COLORSET_TOP                50
+#define HE_COLORSET_MEDIUM     51
+#define HE_COLORSET_NORMAL     52
+#define HE_COLORSET_SELECTED   53
+#define HE_COLORSET_CODE       54
+
+static int ui_browser__percent_color(double percent, bool current)
+{
+       if (current)
+               return HE_COLORSET_SELECTED;
+       if (percent >= MIN_RED)
+               return HE_COLORSET_TOP;
+       if (percent >= MIN_GREEN)
+               return HE_COLORSET_MEDIUM;
+       return HE_COLORSET_NORMAL;
+}
+
+struct ui_browser {
+       newtComponent   form, sb;
+       u64             index, first_visible_entry_idx;
+       void            *first_visible_entry, *entries;
+       u16             top, left, width, height;
+       void            *priv;
+       u32             nr_entries;
+};
+
+static void ui_browser__refresh_dimensions(struct ui_browser *self)
+{
+       int cols, rows;
+       newtGetScreenSize(&cols, &rows);
+
+       if (self->width > cols - 4)
+               self->width = cols - 4;
+       self->height = rows - 5;
+       if (self->height > self->nr_entries)
+               self->height = self->nr_entries;
+       self->top  = (rows - self->height) / 2;
+       self->left = (cols - self->width) / 2;
+}
+
+static void ui_browser__reset_index(struct ui_browser *self)
+{
+        self->index = self->first_visible_entry_idx = 0;
+        self->first_visible_entry = NULL;
+}
+
+static int objdump_line__show(struct objdump_line *self, struct list_head *head,
+                             int width, struct hist_entry *he, int len,
+                             bool current_entry)
+{
+       if (self->offset != -1) {
+               struct symbol *sym = he->ms.sym;
+               unsigned int hits = 0;
+               double percent = 0.0;
+               int color;
+               struct sym_priv *priv = symbol__priv(sym);
+               struct sym_ext *sym_ext = priv->ext;
+               struct sym_hist *h = priv->hist;
+               s64 offset = self->offset;
+               struct objdump_line *next = objdump__get_next_ip_line(head, self);
+
+               while (offset < (s64)len &&
+                      (next == NULL || offset < next->offset)) {
+                       if (sym_ext) {
+                               percent += sym_ext[offset].percent;
+                       } else
+                               hits += h->ip[offset];
+
+                       ++offset;
+               }
+
+               if (sym_ext == NULL && h->sum)
+                       percent = 100.0 * hits / h->sum;
+
+               color = ui_browser__percent_color(percent, current_entry);
+               SLsmg_set_color(color);
+               slsmg_printf(" %7.2f ", percent);
+               if (!current_entry)
+                       SLsmg_set_color(HE_COLORSET_CODE);
+       } else {
+               int color = ui_browser__percent_color(0, current_entry);
+               SLsmg_set_color(color);
+               slsmg_write_nstring(" ", 9);
+       }
+
+       SLsmg_write_char(':');
+       slsmg_write_nstring(" ", 8);
+       if (!*self->line)
+               slsmg_write_nstring(" ", width - 18);
+       else
+               slsmg_write_nstring(self->line, width - 18);
+
+       return 0;
+}
+
+static int ui_browser__refresh_entries(struct ui_browser *self)
+{
+       struct objdump_line *pos;
+       struct list_head *head = self->entries;
+       struct hist_entry *he = self->priv;
+       int row = 0;
+       int len = he->ms.sym->end - he->ms.sym->start;
+
+       if (self->first_visible_entry == NULL || self->first_visible_entry == self->entries)
+                self->first_visible_entry = head->next;
+
+       pos = list_entry(self->first_visible_entry, struct objdump_line, node);
+
+       list_for_each_entry_from(pos, head, node) {
+               bool current_entry = (self->first_visible_entry_idx + row) == self->index;
+               SLsmg_gotorc(self->top + row, self->left);
+               objdump_line__show(pos, head, self->width,
+                                  he, len, current_entry);
+               if (++row == self->height)
+                       break;
+       }
+
+       SLsmg_set_color(HE_COLORSET_NORMAL);
+       SLsmg_fill_region(self->top + row, self->left,
+                         self->height - row, self->width, ' ');
+
+       return 0;
+}
+
+static int ui_browser__run(struct ui_browser *self, const char *title,
+                          struct newtExitStruct *es)
+{
+       if (self->form) {
+               newtFormDestroy(self->form);
+               newtPopWindow();
+       }
+
+       ui_browser__refresh_dimensions(self);
+       newtCenteredWindow(self->width + 2, self->height, title);
+       self->form = newt_form__new();
+       if (self->form == NULL)
+               return -1;
+
+       self->sb = newtVerticalScrollbar(self->width + 1, 0, self->height,
+                                        HE_COLORSET_NORMAL,
+                                        HE_COLORSET_SELECTED);
+       if (self->sb == NULL)
+               return -1;
+
+       newtFormAddHotKey(self->form, NEWT_KEY_UP);
+       newtFormAddHotKey(self->form, NEWT_KEY_DOWN);
+       newtFormAddHotKey(self->form, NEWT_KEY_PGUP);
+       newtFormAddHotKey(self->form, NEWT_KEY_PGDN);
+       newtFormAddHotKey(self->form, ' ');
+       newtFormAddHotKey(self->form, NEWT_KEY_HOME);
+       newtFormAddHotKey(self->form, NEWT_KEY_END);
+
+       if (ui_browser__refresh_entries(self) < 0)
+               return -1;
+       newtFormAddComponent(self->form, self->sb);
+
+       while (1) {
+               unsigned int offset;
+
+               newtFormRun(self->form, es);
+
+               if (es->reason != NEWT_EXIT_HOTKEY)
+                       break;
+               switch (es->u.key) {
+               case NEWT_KEY_DOWN:
+                       if (self->index == self->nr_entries - 1)
+                               break;
+                       ++self->index;
+                       if (self->index == self->first_visible_entry_idx + self->height) {
+                               struct list_head *pos = self->first_visible_entry;
+                               ++self->first_visible_entry_idx;
+                               self->first_visible_entry = pos->next;
+                       }
+                       break;
+               case NEWT_KEY_UP:
+                       if (self->index == 0)
+                               break;
+                       --self->index;
+                       if (self->index < self->first_visible_entry_idx) {
+                               struct list_head *pos = self->first_visible_entry;
+                               --self->first_visible_entry_idx;
+                               self->first_visible_entry = pos->prev;
+                       }
+                       break;
+               case NEWT_KEY_PGDN:
+               case ' ':
+                       if (self->first_visible_entry_idx + self->height > self->nr_entries - 1)
+                               break;
+
+                       offset = self->height;
+                       if (self->index + offset > self->nr_entries - 1)
+                               offset = self->nr_entries - 1 - self->index;
+                       self->index += offset;
+                       self->first_visible_entry_idx += offset;
+
+                       while (offset--) {
+                               struct list_head *pos = self->first_visible_entry;
+                               self->first_visible_entry = pos->next;
+                       }
+
+                       break;
+               case NEWT_KEY_PGUP:
+                       if (self->first_visible_entry_idx == 0)
+                               break;
+
+                       if (self->first_visible_entry_idx < self->height)
+                               offset = self->first_visible_entry_idx;
+                       else
+                               offset = self->height;
+
+                       self->index -= offset;
+                       self->first_visible_entry_idx -= offset;
+
+                       while (offset--) {
+                               struct list_head *pos = self->first_visible_entry;
+                               self->first_visible_entry = pos->prev;
+                       }
+                       break;
+               case NEWT_KEY_HOME:
+                       ui_browser__reset_index(self);
+                       break;
+               case NEWT_KEY_END: {
+                       struct list_head *head = self->entries;
+                       offset = self->height - 1;
+
+                       if (offset > self->nr_entries)
+                               offset = self->nr_entries;
+
+                       self->index = self->first_visible_entry_idx = self->nr_entries - 1 - offset;
+                       self->first_visible_entry = head->prev;
+                       while (offset-- != 0) {
+                               struct list_head *pos = self->first_visible_entry;
+                               self->first_visible_entry = pos->prev;
+                       }
+               }
+                       break;
+               case NEWT_KEY_ESCAPE:
+               case NEWT_KEY_LEFT:
+               case CTRL('c'):
+               case 'Q':
+               case 'q':
+                       return 0;
+               default:
+                       continue;
+               }
+               if (ui_browser__refresh_entries(self) < 0)
+                       return -1;
+       }
+       return 0;
 }
 
 /*
@@ -317,61 +668,40 @@ static size_t hist_entry__append_browser(struct hist_entry *self,
        return ret;
 }
 
-static void map_symbol__annotate_browser(const struct map_symbol *self)
+static void hist_entry__annotate_browser(struct hist_entry *self)
 {
-       FILE *fp;
-       int cols, rows;
-       newtComponent form, tree;
+       struct ui_browser browser;
        struct newtExitStruct es;
-       char *str;
-       size_t line_len, max_line_len = 0;
-       size_t max_usable_width;
-       char *line = NULL;
+       struct objdump_line *pos, *n;
+       LIST_HEAD(head);
 
-       if (self->sym == NULL)
+       if (self->ms.sym == NULL)
                return;
 
-       if (asprintf(&str, "perf annotate -d \"%s\" %s 2>&1 | expand",
-                    self->map->dso->name, self->sym->name) < 0)
+       if (hist_entry__annotate(self, &head) < 0)
                return;
 
-       fp = popen(str, "r");
-       if (fp == NULL)
-               goto out_free_str;
-
-       newtPushHelpLine("Press ESC to exit");
-       newtGetScreenSize(&cols, &rows);
-       tree = newtListbox(0, 0, rows - 5, NEWT_FLAG_SCROLL);
-
-       while (!feof(fp)) {
-               if (getline(&line, &line_len, fp) < 0 || !line_len)
-                       break;
-               while (line_len != 0 && isspace(line[line_len - 1]))
-                       line[--line_len] = '\0';
+       ui_helpline__push("Press <- or ESC to exit");
 
-               if (line_len > max_line_len)
-                       max_line_len = line_len;
-               newtListboxAppendEntry(tree, line, NULL);
+       memset(&browser, 0, sizeof(browser));
+       browser.entries = &head;
+       browser.priv = self;
+       list_for_each_entry(pos, &head, node) {
+               size_t line_len = strlen(pos->line);
+               if (browser.width < line_len)
+                       browser.width = line_len;
+               ++browser.nr_entries;
        }
-       fclose(fp);
-       free(line);
-
-       max_usable_width = cols - 22;
-       if (max_line_len > max_usable_width)
-               max_line_len = max_usable_width;
-
-       newtListboxSetWidth(tree, max_line_len);
 
-       newtCenteredWindow(max_line_len + 2, rows - 5, self->sym->name);
-       form = newt_form__new();
-       newtFormAddComponents(form, tree, NULL);
-
-       newtFormRun(form, &es);
-       newtFormDestroy(form);
+       browser.width += 18; /* Percentage */
+       ui_browser__run(&browser, self->ms.sym->name, &es);
+       newtFormDestroy(browser.form);
        newtPopWindow();
-       newtPopHelpLine();
-out_free_str:
-       free(str);
+       list_for_each_entry_safe(pos, n, &head, node) {
+               list_del(&pos->node);
+               objdump_line__free(pos);
+       }
+       ui_helpline__pop();
 }
 
 static const void *newt__symbol_tree_get_current(newtComponent self)
@@ -381,73 +711,87 @@ static const void *newt__symbol_tree_get_current(newtComponent self)
        return newtListboxGetCurrent(self);
 }
 
-static void perf_session__selection(newtComponent self, void *data)
+static void hist_browser__selection(newtComponent self, void *data)
 {
        const struct map_symbol **symbol_ptr = data;
        *symbol_ptr = newt__symbol_tree_get_current(self);
 }
 
-int perf_session__browse_hists(struct rb_root *hists, u64 nr_hists,
-                              u64 session_total, const char *helpline)
+struct hist_browser {
+       newtComponent           form, tree;
+       const struct map_symbol *selection;
+};
+
+static struct hist_browser *hist_browser__new(void)
+{
+       struct hist_browser *self = malloc(sizeof(*self));
+
+       if (self != NULL)
+               self->form = NULL;
+
+       return self;
+}
+
+static void hist_browser__delete(struct hist_browser *self)
+{
+       newtFormDestroy(self->form);
+       newtPopWindow();
+       free(self);
+}
+
+static int hist_browser__populate(struct hist_browser *self, struct hists *hists,
+                                 const char *title)
 {
-       struct sort_entry *se;
+       int max_len = 0, idx, cols, rows;
+       struct ui_progress *progress;
        struct rb_node *nd;
-       char seq[] = ".";
-       unsigned int width;
-       char *col_width = symbol_conf.col_width_list_str;
-       int rows, cols, idx;
-       int max_len = 0;
-       char str[1024];
-       newtComponent form, tree;
-       struct newtExitStruct es;
-       const struct map_symbol *selection;
        u64 curr_hist = 0;
-       struct ui_progress *progress;
+       char seq[] = ".", unit;
+       char str[256];
+       unsigned long nr_events = hists->stats.nr_events[PERF_RECORD_SAMPLE];
 
-       progress = ui_progress__new("Adding entries to the browser...", nr_hists);
-       if (progress == NULL)
-               return -1;
+       if (self->form) {
+               newtFormDestroy(self->form);
+               newtPopWindow();
+       }
 
-       snprintf(str, sizeof(str), "Samples: %Ld", session_total);
+       nr_events = convert_unit(nr_events, &unit);
+       snprintf(str, sizeof(str), "Events: %lu%c                            ",
+                nr_events, unit);
        newtDrawRootText(0, 0, str);
-       newtPushHelpLine(helpline);
 
-       newtGetScreenSize(&cols, &rows);
+       newtGetScreenSize(NULL, &rows);
 
        if (symbol_conf.use_callchain)
-               tree = newtCheckboxTreeMulti(0, 0, rows - 5, seq,
-                                               NEWT_FLAG_SCROLL);
+               self->tree = newtCheckboxTreeMulti(0, 0, rows - 5, seq,
+                                                  NEWT_FLAG_SCROLL);
        else
-               tree = newtListbox(0, 0, rows - 5, (NEWT_FLAG_SCROLL |
-                                                      NEWT_FLAG_RETURNEXIT));
+               self->tree = newtListbox(0, 0, rows - 5,
+                                       (NEWT_FLAG_SCROLL |
+                                        NEWT_FLAG_RETURNEXIT));
 
-       newtComponentAddCallback(tree, perf_session__selection, &selection);
+       newtComponentAddCallback(self->tree, hist_browser__selection,
+                                &self->selection);
 
-       list_for_each_entry(se, &hist_entry__sort_list, list) {
-               if (se->elide)
-                       continue;
-               width = strlen(se->header);
-               if (se->width) {
-                       if (symbol_conf.col_width_list_str) {
-                               if (col_width) {
-                                       *se->width = atoi(col_width);
-                                       col_width = strchr(col_width, ',');
-                                       if (col_width)
-                                               ++col_width;
-                               }
-                       }
-                       *se->width = max(*se->width, width);
-               }
-       }
+       progress = ui_progress__new("Adding entries to the browser...",
+                                   hists->nr_entries);
+       if (progress == NULL)
+               return -1;
 
        idx = 0;
-       for (nd = rb_first(hists); nd; nd = rb_next(nd)) {
+       for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
                struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
-               int len = hist_entry__append_browser(h, tree, session_total);
+               int len;
+
+               if (h->filtered)
+                       continue;
+
+               len = hist_entry__append_browser(h, self->tree, hists->stats.total_period);
                if (len > max_len)
                        max_len = len;
                if (symbol_conf.use_callchain)
-                       hist_entry__append_callchain_browser(h, tree, session_total, idx++);
+                       hist_entry__append_callchain_browser(h, self->tree,
+                                                            hists->stats.total_period, idx++);
                ++curr_hist;
                if (curr_hist % 5)
                        ui_progress__update(progress, curr_hist);
@@ -455,82 +799,300 @@ int perf_session__browse_hists(struct rb_root *hists, u64 nr_hists,
 
        ui_progress__delete(progress);
 
+       newtGetScreenSize(&cols, &rows);
+
        if (max_len > cols)
                max_len = cols - 3;
 
        if (!symbol_conf.use_callchain)
-               newtListboxSetWidth(tree, max_len);
+               newtListboxSetWidth(self->tree, max_len);
 
        newtCenteredWindow(max_len + (symbol_conf.use_callchain ? 5 : 0),
-                          rows - 5, "Report");
-       form = newt_form__new();
-       newtFormAddHotKey(form, 'A');
-       newtFormAddHotKey(form, 'a');
-       newtFormAddHotKey(form, NEWT_KEY_RIGHT);
-       newtFormAddComponents(form, tree, NULL);
-       selection = newt__symbol_tree_get_current(tree);
+                          rows - 5, title);
+       self->form = newt_form__new();
+       if (self->form == NULL)
+               return -1;
+
+       newtFormAddHotKey(self->form, 'A');
+       newtFormAddHotKey(self->form, 'a');
+       newtFormAddHotKey(self->form, 'D');
+       newtFormAddHotKey(self->form, 'd');
+       newtFormAddHotKey(self->form, 'T');
+       newtFormAddHotKey(self->form, 't');
+       newtFormAddHotKey(self->form, '?');
+       newtFormAddHotKey(self->form, 'H');
+       newtFormAddHotKey(self->form, 'h');
+       newtFormAddHotKey(self->form, NEWT_KEY_F1);
+       newtFormAddHotKey(self->form, NEWT_KEY_RIGHT);
+       newtFormAddComponents(self->form, self->tree, NULL);
+       self->selection = newt__symbol_tree_get_current(self->tree);
+
+       return 0;
+}
+
+static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
+{
+       int *indexes;
+
+       if (!symbol_conf.use_callchain)
+               goto out;
+
+       indexes = newtCheckboxTreeFindItem(self->tree, (void *)self->selection);
+       if (indexes) {
+               bool is_hist_entry = indexes[1] == NEWT_ARG_LAST;
+               free(indexes);
+               if (is_hist_entry)
+                       goto out;
+       }
+       return NULL;
+out:
+       return container_of(self->selection, struct hist_entry, ms);
+}
+
+static struct thread *hist_browser__selected_thread(struct hist_browser *self)
+{
+       struct hist_entry *he = hist_browser__selected_entry(self);
+       return he ? he->thread : NULL;
+}
+
+static int hist_browser__title(char *bf, size_t size, const char *input_name,
+                              const struct dso *dso, const struct thread *thread)
+{
+       int printed = 0;
+
+       if (thread)
+               printed += snprintf(bf + printed, size - printed,
+                                   "Thread: %s(%d)",
+                                   (thread->comm_set ?  thread->comm : ""),
+                                   thread->pid);
+       if (dso)
+               printed += snprintf(bf + printed, size - printed,
+                                   "%sDSO: %s", thread ? " " : "",
+                                   dso->short_name);
+       return printed ?: snprintf(bf, size, "Report: %s", input_name);
+}
+
+int hists__browse(struct hists *self, const char *helpline, const char *input_name)
+{
+       struct hist_browser *browser = hist_browser__new();
+       struct pstack *fstack = pstack__new(2);
+       const struct thread *thread_filter = NULL;
+       const struct dso *dso_filter = NULL;
+       struct newtExitStruct es;
+       char msg[160];
+       int err = -1;
+
+       if (browser == NULL)
+               return -1;
+
+       fstack = pstack__new(2);
+       if (fstack == NULL)
+               goto out;
+
+       ui_helpline__push(helpline);
+
+       hist_browser__title(msg, sizeof(msg), input_name,
+                           dso_filter, thread_filter);
+       if (hist_browser__populate(browser, self, msg) < 0)
+               goto out_free_stack;
 
        while (1) {
-               char annotate[512];
-               const char *options[2];
-               int nr_options = 0, choice = 0;
+               const struct thread *thread;
+               const struct dso *dso;
+               char *options[16];
+               int nr_options = 0, choice = 0, i,
+                   annotate = -2, zoom_dso = -2, zoom_thread = -2;
+
+               newtFormRun(browser->form, &es);
+
+               thread = hist_browser__selected_thread(browser);
+               dso = browser->selection->map ? browser->selection->map->dso : NULL;
 
-               newtFormRun(form, &es);
                if (es.reason == NEWT_EXIT_HOTKEY) {
-                       if (toupper(es.u.key) == 'A')
+                       if (es.u.key == NEWT_KEY_F1)
+                               goto do_help;
+
+                       switch (toupper(es.u.key)) {
+                       case 'A':
+                               if (browser->selection->map == NULL &&
+                                   browser->selection->map->dso->annotate_warned)
+                                       continue;
                                goto do_annotate;
-                       if (es.u.key == NEWT_KEY_ESCAPE ||
-                           toupper(es.u.key) == 'Q' ||
-                           es.u.key == CTRL('c')) {
+                       case 'D':
+                               goto zoom_dso;
+                       case 'T':
+                               goto zoom_thread;
+                       case 'H':
+                       case '?':
+do_help:
+                               ui__help_window("->        Zoom into DSO/Threads & Annotate current symbol\n"
+                                               "<-        Zoom out\n"
+                                               "a         Annotate current symbol\n"
+                                               "h/?/F1    Show this window\n"
+                                               "d         Zoom into current DSO\n"
+                                               "t         Zoom into current Thread\n"
+                                               "q/CTRL+C  Exit browser");
+                               continue;
+                       default:;
+                       }
+                       if (toupper(es.u.key) == 'Q' ||
+                           es.u.key == CTRL('c'))
+                               break;
+                       if (es.u.key == NEWT_KEY_ESCAPE) {
                                if (dialog_yesno("Do you really want to exit?"))
                                        break;
                                else
                                        continue;
                        }
-               }
 
-               if (selection->sym != NULL) {
-                       snprintf(annotate, sizeof(annotate),
-                                "Annotate %s", selection->sym->name);
-                       options[nr_options++] = annotate;
+                       if (es.u.key == NEWT_KEY_LEFT) {
+                               const void *top;
+
+                               if (pstack__empty(fstack))
+                                       continue;
+                               top = pstack__pop(fstack);
+                               if (top == &dso_filter)
+                                       goto zoom_out_dso;
+                               if (top == &thread_filter)
+                                       goto zoom_out_thread;
+                               continue;
+                       }
                }
 
-               options[nr_options++] = "Exit";
+               if (browser->selection->sym != NULL &&
+                   !browser->selection->map->dso->annotate_warned &&
+                   asprintf(&options[nr_options], "Annotate %s",
+                            browser->selection->sym->name) > 0)
+                       annotate = nr_options++;
+
+               if (thread != NULL &&
+                   asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
+                            (thread_filter ? "out of" : "into"),
+                            (thread->comm_set ? thread->comm : ""),
+                            thread->pid) > 0)
+                       zoom_thread = nr_options++;
+
+               if (dso != NULL &&
+                   asprintf(&options[nr_options], "Zoom %s %s DSO",
+                            (dso_filter ? "out of" : "into"),
+                            (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
+                       zoom_dso = nr_options++;
+
+               options[nr_options++] = (char *)"Exit";
+
                choice = popup_menu(nr_options, options);
+
+               for (i = 0; i < nr_options - 1; ++i)
+                       free(options[i]);
+
                if (choice == nr_options - 1)
                        break;
+
+               if (choice == -1)
+                       continue;
+
+               if (choice == annotate) {
+                       struct hist_entry *he;
 do_annotate:
-               if (selection->sym != NULL && choice >= 0) {
-                       if (selection->map->dso->origin == DSO__ORIG_KERNEL) {
-                               newtPopHelpLine();
-                               newtPushHelpLine("No vmlinux file found, can't "
+                       if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) {
+                               browser->selection->map->dso->annotate_warned = 1;
+                               ui_helpline__puts("No vmlinux file found, can't "
                                                 "annotate with just a "
                                                 "kallsyms file");
                                continue;
                        }
-                       map_symbol__annotate_browser(selection);
+
+                       he = hist_browser__selected_entry(browser);
+                       if (he == NULL)
+                               continue;
+
+                       hist_entry__annotate_browser(he);
+               } else if (choice == zoom_dso) {
+zoom_dso:
+                       if (dso_filter) {
+                               pstack__remove(fstack, &dso_filter);
+zoom_out_dso:
+                               ui_helpline__pop();
+                               dso_filter = NULL;
+                       } else {
+                               if (dso == NULL)
+                                       continue;
+                               ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
+                                                  dso->kernel ? "the Kernel" : dso->short_name);
+                               dso_filter = dso;
+                               pstack__push(fstack, &dso_filter);
+                       }
+                       hists__filter_by_dso(self, dso_filter);
+                       hist_browser__title(msg, sizeof(msg), input_name,
+                                           dso_filter, thread_filter);
+                       if (hist_browser__populate(browser, self, msg) < 0)
+                               goto out;
+               } else if (choice == zoom_thread) {
+zoom_thread:
+                       if (thread_filter) {
+                               pstack__remove(fstack, &thread_filter);
+zoom_out_thread:
+                               ui_helpline__pop();
+                               thread_filter = NULL;
+                       } else {
+                               ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
+                                                  thread->comm_set ? thread->comm : "",
+                                                  thread->pid);
+                               thread_filter = thread;
+                               pstack__push(fstack, &thread_filter);
+                       }
+                       hists__filter_by_thread(self, thread_filter);
+                       hist_browser__title(msg, sizeof(msg), input_name,
+                                           dso_filter, thread_filter);
+                       if (hist_browser__populate(browser, self, msg) < 0)
+                               goto out;
                }
        }
-
-       newtFormDestroy(form);
-       newtPopWindow();
-       return 0;
+       err = 0;
+out_free_stack:
+       pstack__delete(fstack);
+out:
+       hist_browser__delete(browser);
+       return err;
 }
 
+static struct newtPercentTreeColors {
+       const char *topColorFg, *topColorBg;
+       const char *mediumColorFg, *mediumColorBg;
+       const char *normalColorFg, *normalColorBg;
+       const char *selColorFg, *selColorBg;
+       const char *codeColorFg, *codeColorBg;
+} defaultPercentTreeColors = {
+       "red",       "lightgray",
+       "green",     "lightgray",
+       "black",     "lightgray",
+       "lightgray", "magenta",
+       "blue",      "lightgray",
+};
+
 void setup_browser(void)
 {
-       if (!isatty(1))
+       struct newtPercentTreeColors *c = &defaultPercentTreeColors;
+
+       if (!isatty(1) || !use_browser) {
+               setup_pager();
                return;
+       }
 
-       use_browser = true;
+       use_browser = 1;
        newtInit();
        newtCls();
-       newtPushHelpLine(" ");
+       ui_helpline__puts(" ");
+       sltt_set_color(HE_COLORSET_TOP, NULL, c->topColorFg, c->topColorBg);
+       sltt_set_color(HE_COLORSET_MEDIUM, NULL, c->mediumColorFg, c->mediumColorBg);
+       sltt_set_color(HE_COLORSET_NORMAL, NULL, c->normalColorFg, c->normalColorBg);
+       sltt_set_color(HE_COLORSET_SELECTED, NULL, c->selColorFg, c->selColorBg);
+       sltt_set_color(HE_COLORSET_CODE, NULL, c->codeColorFg, c->codeColorBg);
 }
 
 void exit_browser(bool wait_for_ok)
 {
-       if (use_browser) {
+       if (use_browser > 0) {
                if (wait_for_ok) {
                        char title[] = "Fatal Error", ok[] = "Ok";
                        newtWinMessage(title, ok, browser__last_msg);