summaryrefslogtreecommitdiff
path: root/tools/perf/ui/browsers
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /tools/perf/ui/browsers
Initial import
Diffstat (limited to 'tools/perf/ui/browsers')
-rw-r--r--tools/perf/ui/browsers/Build10
-rw-r--r--tools/perf/ui/browsers/annotate.c1039
-rw-r--r--tools/perf/ui/browsers/header.c128
-rw-r--r--tools/perf/ui/browsers/hists.c2042
-rw-r--r--tools/perf/ui/browsers/map.c130
-rw-r--r--tools/perf/ui/browsers/map.h6
-rw-r--r--tools/perf/ui/browsers/scripts.c187
7 files changed, 3542 insertions, 0 deletions
diff --git a/tools/perf/ui/browsers/Build b/tools/perf/ui/browsers/Build
new file mode 100644
index 000000000..de223f5be
--- /dev/null
+++ b/tools/perf/ui/browsers/Build
@@ -0,0 +1,10 @@
+libperf-y += annotate.o
+libperf-y += hists.o
+libperf-y += map.o
+libperf-y += scripts.o
+libperf-y += header.o
+
+CFLAGS_annotate.o += -DENABLE_SLFUTURE_CONST
+CFLAGS_hists.o += -DENABLE_SLFUTURE_CONST
+CFLAGS_map.o += -DENABLE_SLFUTURE_CONST
+CFLAGS_scripts.o += -DENABLE_SLFUTURE_CONST
diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c
new file mode 100644
index 000000000..e5250eb2d
--- /dev/null
+++ b/tools/perf/ui/browsers/annotate.c
@@ -0,0 +1,1039 @@
+#include "../../util/util.h"
+#include "../browser.h"
+#include "../helpline.h"
+#include "../libslang.h"
+#include "../ui.h"
+#include "../util.h"
+#include "../../util/annotate.h"
+#include "../../util/hist.h"
+#include "../../util/sort.h"
+#include "../../util/symbol.h"
+#include "../../util/evsel.h"
+#include <pthread.h>
+
+struct browser_disasm_line {
+ struct rb_node rb_node;
+ u32 idx;
+ int idx_asm;
+ int jump_sources;
+ /*
+ * actual length of this array is saved on the nr_events field
+ * of the struct annotate_browser
+ */
+ double percent[1];
+};
+
+static struct annotate_browser_opt {
+ bool hide_src_code,
+ use_offset,
+ jump_arrows,
+ show_linenr,
+ show_nr_jumps;
+} annotate_browser__opts = {
+ .use_offset = true,
+ .jump_arrows = true,
+};
+
+struct annotate_browser {
+ struct ui_browser b;
+ struct rb_root entries;
+ struct rb_node *curr_hot;
+ struct disasm_line *selection;
+ struct disasm_line **offsets;
+ int nr_events;
+ u64 start;
+ int nr_asm_entries;
+ int nr_entries;
+ int max_jump_sources;
+ int nr_jumps;
+ bool searching_backwards;
+ u8 addr_width;
+ u8 jumps_width;
+ u8 target_width;
+ u8 min_addr_width;
+ u8 max_addr_width;
+ char search_bf[128];
+};
+
+static inline struct browser_disasm_line *disasm_line__browser(struct disasm_line *dl)
+{
+ return (struct browser_disasm_line *)(dl + 1);
+}
+
+static bool disasm_line__filter(struct ui_browser *browser __maybe_unused,
+ void *entry)
+{
+ if (annotate_browser__opts.hide_src_code) {
+ struct disasm_line *dl = list_entry(entry, struct disasm_line, node);
+ return dl->offset == -1;
+ }
+
+ return false;
+}
+
+static int annotate_browser__jumps_percent_color(struct annotate_browser *browser,
+ int nr, bool current)
+{
+ if (current && (!browser->b.use_navkeypressed || browser->b.navkeypressed))
+ return HE_COLORSET_SELECTED;
+ if (nr == browser->max_jump_sources)
+ return HE_COLORSET_TOP;
+ if (nr > 1)
+ return HE_COLORSET_MEDIUM;
+ return HE_COLORSET_NORMAL;
+}
+
+static int annotate_browser__set_jumps_percent_color(struct annotate_browser *browser,
+ int nr, bool current)
+{
+ int color = annotate_browser__jumps_percent_color(browser, nr, current);
+ return ui_browser__set_color(&browser->b, color);
+}
+
+static void annotate_browser__write(struct ui_browser *browser, void *entry, int row)
+{
+ struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
+ struct disasm_line *dl = list_entry(entry, struct disasm_line, node);
+ struct browser_disasm_line *bdl = disasm_line__browser(dl);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ bool change_color = (!annotate_browser__opts.hide_src_code &&
+ (!current_entry || (browser->use_navkeypressed &&
+ !browser->navkeypressed)));
+ int width = browser->width, printed;
+ int i, pcnt_width = 7 * ab->nr_events;
+ double percent_max = 0.0;
+ char bf[256];
+
+ for (i = 0; i < ab->nr_events; i++) {
+ if (bdl->percent[i] > percent_max)
+ percent_max = bdl->percent[i];
+ }
+
+ if (dl->offset != -1 && percent_max != 0.0) {
+ for (i = 0; i < ab->nr_events; i++) {
+ ui_browser__set_percent_color(browser, bdl->percent[i],
+ current_entry);
+ slsmg_printf("%6.2f ", bdl->percent[i]);
+ }
+ } else {
+ ui_browser__set_percent_color(browser, 0, current_entry);
+ slsmg_write_nstring(" ", pcnt_width);
+ }
+
+ SLsmg_write_char(' ');
+
+ /* The scroll bar isn't being used */
+ if (!browser->navkeypressed)
+ width += 1;
+
+ if (!*dl->line)
+ slsmg_write_nstring(" ", width - pcnt_width);
+ else if (dl->offset == -1) {
+ if (dl->line_nr && annotate_browser__opts.show_linenr)
+ printed = scnprintf(bf, sizeof(bf), "%-*d ",
+ ab->addr_width + 1, dl->line_nr);
+ else
+ printed = scnprintf(bf, sizeof(bf), "%*s ",
+ ab->addr_width, " ");
+ slsmg_write_nstring(bf, printed);
+ slsmg_write_nstring(dl->line, width - printed - pcnt_width + 1);
+ } else {
+ u64 addr = dl->offset;
+ int color = -1;
+
+ if (!annotate_browser__opts.use_offset)
+ addr += ab->start;
+
+ if (!annotate_browser__opts.use_offset) {
+ printed = scnprintf(bf, sizeof(bf), "%" PRIx64 ": ", addr);
+ } else {
+ if (bdl->jump_sources) {
+ if (annotate_browser__opts.show_nr_jumps) {
+ int prev;
+ printed = scnprintf(bf, sizeof(bf), "%*d ",
+ ab->jumps_width,
+ bdl->jump_sources);
+ prev = annotate_browser__set_jumps_percent_color(ab, bdl->jump_sources,
+ current_entry);
+ slsmg_write_nstring(bf, printed);
+ ui_browser__set_color(browser, prev);
+ }
+
+ printed = scnprintf(bf, sizeof(bf), "%*" PRIx64 ": ",
+ ab->target_width, addr);
+ } else {
+ printed = scnprintf(bf, sizeof(bf), "%*s ",
+ ab->addr_width, " ");
+ }
+ }
+
+ if (change_color)
+ color = ui_browser__set_color(browser, HE_COLORSET_ADDR);
+ slsmg_write_nstring(bf, printed);
+ if (change_color)
+ ui_browser__set_color(browser, color);
+ if (dl->ins && dl->ins->ops->scnprintf) {
+ if (ins__is_jump(dl->ins)) {
+ bool fwd = dl->ops.target.offset > (u64)dl->offset;
+
+ ui_browser__write_graph(browser, fwd ? SLSMG_DARROW_CHAR :
+ SLSMG_UARROW_CHAR);
+ SLsmg_write_char(' ');
+ } else if (ins__is_call(dl->ins)) {
+ ui_browser__write_graph(browser, SLSMG_RARROW_CHAR);
+ SLsmg_write_char(' ');
+ } else {
+ slsmg_write_nstring(" ", 2);
+ }
+ } else {
+ if (strcmp(dl->name, "retq")) {
+ slsmg_write_nstring(" ", 2);
+ } else {
+ ui_browser__write_graph(browser, SLSMG_LARROW_CHAR);
+ SLsmg_write_char(' ');
+ }
+ }
+
+ disasm_line__scnprintf(dl, bf, sizeof(bf), !annotate_browser__opts.use_offset);
+ slsmg_write_nstring(bf, width - pcnt_width - 3 - printed);
+ }
+
+ if (current_entry)
+ ab->selection = dl;
+}
+
+static bool disasm_line__is_valid_jump(struct disasm_line *dl, struct symbol *sym)
+{
+ if (!dl || !dl->ins || !ins__is_jump(dl->ins)
+ || !disasm_line__has_offset(dl)
+ || dl->ops.target.offset >= symbol__size(sym))
+ return false;
+
+ return true;
+}
+
+static void annotate_browser__draw_current_jump(struct ui_browser *browser)
+{
+ struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
+ struct disasm_line *cursor = ab->selection, *target;
+ struct browser_disasm_line *btarget, *bcursor;
+ unsigned int from, to;
+ struct map_symbol *ms = ab->b.priv;
+ struct symbol *sym = ms->sym;
+ u8 pcnt_width = 7;
+
+ /* PLT symbols contain external offsets */
+ if (strstr(sym->name, "@plt"))
+ return;
+
+ if (!disasm_line__is_valid_jump(cursor, sym))
+ return;
+
+ target = ab->offsets[cursor->ops.target.offset];
+ if (!target)
+ return;
+
+ bcursor = disasm_line__browser(cursor);
+ btarget = disasm_line__browser(target);
+
+ if (annotate_browser__opts.hide_src_code) {
+ from = bcursor->idx_asm;
+ to = btarget->idx_asm;
+ } else {
+ from = (u64)bcursor->idx;
+ to = (u64)btarget->idx;
+ }
+
+ pcnt_width *= ab->nr_events;
+
+ ui_browser__set_color(browser, HE_COLORSET_CODE);
+ __ui_browser__line_arrow(browser, pcnt_width + 2 + ab->addr_width,
+ from, to);
+}
+
+static unsigned int annotate_browser__refresh(struct ui_browser *browser)
+{
+ struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
+ int ret = ui_browser__list_head_refresh(browser);
+ int pcnt_width;
+
+ pcnt_width = 7 * ab->nr_events;
+
+ if (annotate_browser__opts.jump_arrows)
+ annotate_browser__draw_current_jump(browser);
+
+ ui_browser__set_color(browser, HE_COLORSET_NORMAL);
+ __ui_browser__vline(browser, pcnt_width, 0, browser->height - 1);
+ return ret;
+}
+
+static int disasm__cmp(struct browser_disasm_line *a,
+ struct browser_disasm_line *b, int nr_pcnt)
+{
+ int i;
+
+ for (i = 0; i < nr_pcnt; i++) {
+ if (a->percent[i] == b->percent[i])
+ continue;
+ return a->percent[i] < b->percent[i];
+ }
+ return 0;
+}
+
+static void disasm_rb_tree__insert(struct rb_root *root, struct browser_disasm_line *bdl,
+ int nr_events)
+{
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+ struct browser_disasm_line *l;
+
+ while (*p != NULL) {
+ parent = *p;
+ l = rb_entry(parent, struct browser_disasm_line, rb_node);
+
+ if (disasm__cmp(bdl, l, nr_events))
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+ rb_link_node(&bdl->rb_node, parent, p);
+ rb_insert_color(&bdl->rb_node, root);
+}
+
+static void annotate_browser__set_top(struct annotate_browser *browser,
+ struct disasm_line *pos, u32 idx)
+{
+ unsigned back;
+
+ ui_browser__refresh_dimensions(&browser->b);
+ back = browser->b.height / 2;
+ browser->b.top_idx = browser->b.index = idx;
+
+ while (browser->b.top_idx != 0 && back != 0) {
+ pos = list_entry(pos->node.prev, struct disasm_line, node);
+
+ if (disasm_line__filter(&browser->b, &pos->node))
+ continue;
+
+ --browser->b.top_idx;
+ --back;
+ }
+
+ browser->b.top = pos;
+ browser->b.navkeypressed = true;
+}
+
+static void annotate_browser__set_rb_top(struct annotate_browser *browser,
+ struct rb_node *nd)
+{
+ struct browser_disasm_line *bpos;
+ struct disasm_line *pos;
+ u32 idx;
+
+ bpos = rb_entry(nd, struct browser_disasm_line, rb_node);
+ pos = ((struct disasm_line *)bpos) - 1;
+ idx = bpos->idx;
+ if (annotate_browser__opts.hide_src_code)
+ idx = bpos->idx_asm;
+ annotate_browser__set_top(browser, pos, idx);
+ browser->curr_hot = nd;
+}
+
+static void annotate_browser__calc_percent(struct annotate_browser *browser,
+ struct perf_evsel *evsel)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos, *next;
+ s64 len = symbol__size(sym);
+
+ browser->entries = RB_ROOT;
+
+ pthread_mutex_lock(&notes->lock);
+
+ list_for_each_entry(pos, &notes->src->source, node) {
+ struct browser_disasm_line *bpos = disasm_line__browser(pos);
+ const char *path = NULL;
+ double max_percent = 0.0;
+ int i;
+
+ if (pos->offset == -1) {
+ RB_CLEAR_NODE(&bpos->rb_node);
+ continue;
+ }
+
+ next = disasm__get_next_ip_line(&notes->src->source, pos);
+
+ for (i = 0; i < browser->nr_events; i++) {
+ bpos->percent[i] = disasm__calc_percent(notes,
+ evsel->idx + i,
+ pos->offset,
+ next ? next->offset : len,
+ &path);
+
+ if (max_percent < bpos->percent[i])
+ max_percent = bpos->percent[i];
+ }
+
+ if (max_percent < 0.01) {
+ RB_CLEAR_NODE(&bpos->rb_node);
+ continue;
+ }
+ disasm_rb_tree__insert(&browser->entries, bpos,
+ browser->nr_events);
+ }
+ pthread_mutex_unlock(&notes->lock);
+
+ browser->curr_hot = rb_last(&browser->entries);
+}
+
+static bool annotate_browser__toggle_source(struct annotate_browser *browser)
+{
+ struct disasm_line *dl;
+ struct browser_disasm_line *bdl;
+ off_t offset = browser->b.index - browser->b.top_idx;
+
+ browser->b.seek(&browser->b, offset, SEEK_CUR);
+ dl = list_entry(browser->b.top, struct disasm_line, node);
+ bdl = disasm_line__browser(dl);
+
+ if (annotate_browser__opts.hide_src_code) {
+ if (bdl->idx_asm < offset)
+ offset = bdl->idx;
+
+ browser->b.nr_entries = browser->nr_entries;
+ annotate_browser__opts.hide_src_code = false;
+ browser->b.seek(&browser->b, -offset, SEEK_CUR);
+ browser->b.top_idx = bdl->idx - offset;
+ browser->b.index = bdl->idx;
+ } else {
+ if (bdl->idx_asm < 0) {
+ ui_helpline__puts("Only available for assembly lines.");
+ browser->b.seek(&browser->b, -offset, SEEK_CUR);
+ return false;
+ }
+
+ if (bdl->idx_asm < offset)
+ offset = bdl->idx_asm;
+
+ browser->b.nr_entries = browser->nr_asm_entries;
+ annotate_browser__opts.hide_src_code = true;
+ browser->b.seek(&browser->b, -offset, SEEK_CUR);
+ browser->b.top_idx = bdl->idx_asm - offset;
+ browser->b.index = bdl->idx_asm;
+ }
+
+ return true;
+}
+
+static void annotate_browser__init_asm_mode(struct annotate_browser *browser)
+{
+ ui_browser__reset_index(&browser->b);
+ browser->b.nr_entries = browser->nr_asm_entries;
+}
+
+#define SYM_TITLE_MAX_SIZE (PATH_MAX + 64)
+
+static int sym_title(struct symbol *sym, struct map *map, char *title,
+ size_t sz)
+{
+ return snprintf(title, sz, "%s %s", sym->name, map->dso->long_name);
+}
+
+static bool annotate_browser__callq(struct annotate_browser *browser,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct disasm_line *dl = browser->selection;
+ struct annotation *notes;
+ struct addr_map_symbol target = {
+ .map = ms->map,
+ .addr = map__objdump_2mem(ms->map, dl->ops.target.addr),
+ };
+ char title[SYM_TITLE_MAX_SIZE];
+
+ if (!ins__is_call(dl->ins))
+ return false;
+
+ if (map_groups__find_ams(&target, NULL) ||
+ map__rip_2objdump(target.map, target.map->map_ip(target.map,
+ target.addr)) !=
+ dl->ops.target.addr) {
+ ui_helpline__puts("The called function was not found.");
+ return true;
+ }
+
+ notes = symbol__annotation(target.sym);
+ pthread_mutex_lock(&notes->lock);
+
+ if (notes->src == NULL && symbol__alloc_hist(target.sym) < 0) {
+ pthread_mutex_unlock(&notes->lock);
+ ui__warning("Not enough memory for annotating '%s' symbol!\n",
+ target.sym->name);
+ return true;
+ }
+
+ pthread_mutex_unlock(&notes->lock);
+ symbol__tui_annotate(target.sym, target.map, evsel, hbt);
+ sym_title(ms->sym, ms->map, title, sizeof(title));
+ ui_browser__show_title(&browser->b, title);
+ return true;
+}
+
+static
+struct disasm_line *annotate_browser__find_offset(struct annotate_browser *browser,
+ s64 offset, s64 *idx)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos;
+
+ *idx = 0;
+ list_for_each_entry(pos, &notes->src->source, node) {
+ if (pos->offset == offset)
+ return pos;
+ if (!disasm_line__filter(&browser->b, &pos->node))
+ ++*idx;
+ }
+
+ return NULL;
+}
+
+static bool annotate_browser__jump(struct annotate_browser *browser)
+{
+ struct disasm_line *dl = browser->selection;
+ s64 idx;
+
+ if (!ins__is_jump(dl->ins))
+ return false;
+
+ dl = annotate_browser__find_offset(browser, dl->ops.target.offset, &idx);
+ if (dl == NULL) {
+ ui_helpline__puts("Invalid jump offset");
+ return true;
+ }
+
+ annotate_browser__set_top(browser, dl, idx);
+
+ return true;
+}
+
+static
+struct disasm_line *annotate_browser__find_string(struct annotate_browser *browser,
+ char *s, s64 *idx)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos = browser->selection;
+
+ *idx = browser->b.index;
+ list_for_each_entry_continue(pos, &notes->src->source, node) {
+ if (disasm_line__filter(&browser->b, &pos->node))
+ continue;
+
+ ++*idx;
+
+ if (pos->line && strstr(pos->line, s) != NULL)
+ return pos;
+ }
+
+ return NULL;
+}
+
+static bool __annotate_browser__search(struct annotate_browser *browser)
+{
+ struct disasm_line *dl;
+ s64 idx;
+
+ dl = annotate_browser__find_string(browser, browser->search_bf, &idx);
+ if (dl == NULL) {
+ ui_helpline__puts("String not found!");
+ return false;
+ }
+
+ annotate_browser__set_top(browser, dl, idx);
+ browser->searching_backwards = false;
+ return true;
+}
+
+static
+struct disasm_line *annotate_browser__find_string_reverse(struct annotate_browser *browser,
+ char *s, s64 *idx)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos = browser->selection;
+
+ *idx = browser->b.index;
+ list_for_each_entry_continue_reverse(pos, &notes->src->source, node) {
+ if (disasm_line__filter(&browser->b, &pos->node))
+ continue;
+
+ --*idx;
+
+ if (pos->line && strstr(pos->line, s) != NULL)
+ return pos;
+ }
+
+ return NULL;
+}
+
+static bool __annotate_browser__search_reverse(struct annotate_browser *browser)
+{
+ struct disasm_line *dl;
+ s64 idx;
+
+ dl = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx);
+ if (dl == NULL) {
+ ui_helpline__puts("String not found!");
+ return false;
+ }
+
+ annotate_browser__set_top(browser, dl, idx);
+ browser->searching_backwards = true;
+ return true;
+}
+
+static bool annotate_browser__search_window(struct annotate_browser *browser,
+ int delay_secs)
+{
+ if (ui_browser__input_window("Search", "String: ", browser->search_bf,
+ "ENTER: OK, ESC: Cancel",
+ delay_secs * 2) != K_ENTER ||
+ !*browser->search_bf)
+ return false;
+
+ return true;
+}
+
+static bool annotate_browser__search(struct annotate_browser *browser, int delay_secs)
+{
+ if (annotate_browser__search_window(browser, delay_secs))
+ return __annotate_browser__search(browser);
+
+ return false;
+}
+
+static bool annotate_browser__continue_search(struct annotate_browser *browser,
+ int delay_secs)
+{
+ if (!*browser->search_bf)
+ return annotate_browser__search(browser, delay_secs);
+
+ return __annotate_browser__search(browser);
+}
+
+static bool annotate_browser__search_reverse(struct annotate_browser *browser,
+ int delay_secs)
+{
+ if (annotate_browser__search_window(browser, delay_secs))
+ return __annotate_browser__search_reverse(browser);
+
+ return false;
+}
+
+static
+bool annotate_browser__continue_search_reverse(struct annotate_browser *browser,
+ int delay_secs)
+{
+ if (!*browser->search_bf)
+ return annotate_browser__search_reverse(browser, delay_secs);
+
+ return __annotate_browser__search_reverse(browser);
+}
+
+static void annotate_browser__update_addr_width(struct annotate_browser *browser)
+{
+ if (annotate_browser__opts.use_offset)
+ browser->target_width = browser->min_addr_width;
+ else
+ browser->target_width = browser->max_addr_width;
+
+ browser->addr_width = browser->target_width;
+
+ if (annotate_browser__opts.show_nr_jumps)
+ browser->addr_width += browser->jumps_width + 1;
+}
+
+static int annotate_browser__run(struct annotate_browser *browser,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ struct rb_node *nd = NULL;
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ const char *help = "Press 'h' for help on key bindings";
+ int delay_secs = hbt ? hbt->refresh : 0;
+ int key;
+ char title[SYM_TITLE_MAX_SIZE];
+
+ sym_title(sym, ms->map, title, sizeof(title));
+ if (ui_browser__show(&browser->b, title, help) < 0)
+ return -1;
+
+ annotate_browser__calc_percent(browser, evsel);
+
+ if (browser->curr_hot) {
+ annotate_browser__set_rb_top(browser, browser->curr_hot);
+ browser->b.navkeypressed = false;
+ }
+
+ nd = browser->curr_hot;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, delay_secs);
+
+ if (delay_secs != 0) {
+ annotate_browser__calc_percent(browser, evsel);
+ /*
+ * Current line focus got out of the list of most active
+ * lines, NULL it so that if TAB|UNTAB is pressed, we
+ * move to curr_hot (current hottest line).
+ */
+ if (nd != NULL && RB_EMPTY_NODE(nd))
+ nd = NULL;
+ }
+
+ switch (key) {
+ case K_TIMER:
+ if (hbt)
+ hbt->timer(hbt->arg);
+
+ if (delay_secs != 0)
+ symbol__annotate_decay_histogram(sym, evsel->idx);
+ continue;
+ case K_TAB:
+ if (nd != NULL) {
+ nd = rb_prev(nd);
+ if (nd == NULL)
+ nd = rb_last(&browser->entries);
+ } else
+ nd = browser->curr_hot;
+ break;
+ case K_UNTAB:
+ if (nd != NULL)
+ nd = rb_next(nd);
+ if (nd == NULL)
+ nd = rb_first(&browser->entries);
+ else
+ nd = browser->curr_hot;
+ break;
+ case K_F1:
+ case 'h':
+ ui_browser__help_window(&browser->b,
+ "UP/DOWN/PGUP\n"
+ "PGDN/SPACE Navigate\n"
+ "q/ESC/CTRL+C Exit\n\n"
+ "-> Go to target\n"
+ "<- Exit\n"
+ "H Cycle thru hottest instructions\n"
+ "j Toggle showing jump to target arrows\n"
+ "J Toggle showing number of jump sources on targets\n"
+ "n Search next string\n"
+ "o Toggle disassembler output/simplified view\n"
+ "s Toggle source code view\n"
+ "/ Search string\n"
+ "k Toggle line numbers\n"
+ "r Run available scripts\n"
+ "? Search string backwards\n");
+ continue;
+ case 'r':
+ {
+ script_browse(NULL);
+ continue;
+ }
+ case 'k':
+ annotate_browser__opts.show_linenr =
+ !annotate_browser__opts.show_linenr;
+ break;
+ case 'H':
+ nd = browser->curr_hot;
+ break;
+ case 's':
+ if (annotate_browser__toggle_source(browser))
+ ui_helpline__puts(help);
+ continue;
+ case 'o':
+ annotate_browser__opts.use_offset = !annotate_browser__opts.use_offset;
+ annotate_browser__update_addr_width(browser);
+ continue;
+ case 'j':
+ annotate_browser__opts.jump_arrows = !annotate_browser__opts.jump_arrows;
+ continue;
+ case 'J':
+ annotate_browser__opts.show_nr_jumps = !annotate_browser__opts.show_nr_jumps;
+ annotate_browser__update_addr_width(browser);
+ continue;
+ case '/':
+ if (annotate_browser__search(browser, delay_secs)) {
+show_help:
+ ui_helpline__puts(help);
+ }
+ continue;
+ case 'n':
+ if (browser->searching_backwards ?
+ annotate_browser__continue_search_reverse(browser, delay_secs) :
+ annotate_browser__continue_search(browser, delay_secs))
+ goto show_help;
+ continue;
+ case '?':
+ if (annotate_browser__search_reverse(browser, delay_secs))
+ goto show_help;
+ continue;
+ case 'D': {
+ static int seq;
+ ui_helpline__pop();
+ ui_helpline__fpush("%d: nr_ent=%d, height=%d, idx=%d, top_idx=%d, nr_asm_entries=%d",
+ seq++, browser->b.nr_entries,
+ browser->b.height,
+ browser->b.index,
+ browser->b.top_idx,
+ browser->nr_asm_entries);
+ }
+ continue;
+ case K_ENTER:
+ case K_RIGHT:
+ if (browser->selection == NULL)
+ ui_helpline__puts("Huh? No selection. Report to linux-kernel@vger.kernel.org");
+ else if (browser->selection->offset == -1)
+ ui_helpline__puts("Actions are only available for assembly lines.");
+ else if (!browser->selection->ins) {
+ if (strcmp(browser->selection->name, "retq"))
+ goto show_sup_ins;
+ goto out;
+ } else if (!(annotate_browser__jump(browser) ||
+ annotate_browser__callq(browser, evsel, hbt))) {
+show_sup_ins:
+ ui_helpline__puts("Actions are only available for 'callq', 'retq' & jump instructions.");
+ }
+ continue;
+ case K_LEFT:
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ default:
+ continue;
+ }
+
+ if (nd != NULL)
+ annotate_browser__set_rb_top(browser, nd);
+ }
+out:
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+int map_symbol__tui_annotate(struct map_symbol *ms, struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ return symbol__tui_annotate(ms->sym, ms->map, evsel, hbt);
+}
+
+int hist_entry__tui_annotate(struct hist_entry *he, struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ return map_symbol__tui_annotate(&he->ms, evsel, hbt);
+}
+
+static void annotate_browser__mark_jump_targets(struct annotate_browser *browser,
+ size_t size)
+{
+ u64 offset;
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+
+ /* PLT symbols contain external offsets */
+ if (strstr(sym->name, "@plt"))
+ return;
+
+ for (offset = 0; offset < size; ++offset) {
+ struct disasm_line *dl = browser->offsets[offset], *dlt;
+ struct browser_disasm_line *bdlt;
+
+ if (!disasm_line__is_valid_jump(dl, sym))
+ continue;
+
+ dlt = browser->offsets[dl->ops.target.offset];
+ /*
+ * FIXME: Oops, no jump target? Buggy disassembler? Or do we
+ * have to adjust to the previous offset?
+ */
+ if (dlt == NULL)
+ continue;
+
+ bdlt = disasm_line__browser(dlt);
+ if (++bdlt->jump_sources > browser->max_jump_sources)
+ browser->max_jump_sources = bdlt->jump_sources;
+
+ ++browser->nr_jumps;
+ }
+}
+
+static inline int width_jumps(int n)
+{
+ if (n >= 100)
+ return 5;
+ if (n / 10)
+ return 2;
+ return 1;
+}
+
+int symbol__tui_annotate(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ struct disasm_line *pos, *n;
+ struct annotation *notes;
+ size_t size;
+ struct map_symbol ms = {
+ .map = map,
+ .sym = sym,
+ };
+ struct annotate_browser browser = {
+ .b = {
+ .refresh = annotate_browser__refresh,
+ .seek = ui_browser__list_head_seek,
+ .write = annotate_browser__write,
+ .filter = disasm_line__filter,
+ .priv = &ms,
+ .use_navkeypressed = true,
+ },
+ };
+ int ret = -1;
+ int nr_pcnt = 1;
+ size_t sizeof_bdl = sizeof(struct browser_disasm_line);
+
+ if (sym == NULL)
+ return -1;
+
+ size = symbol__size(sym);
+
+ if (map->dso->annotate_warned)
+ return -1;
+
+ browser.offsets = zalloc(size * sizeof(struct disasm_line *));
+ if (browser.offsets == NULL) {
+ ui__error("Not enough memory!");
+ return -1;
+ }
+
+ if (perf_evsel__is_group_event(evsel)) {
+ nr_pcnt = evsel->nr_members;
+ sizeof_bdl += sizeof(double) * (nr_pcnt - 1);
+ }
+
+ if (symbol__annotate(sym, map, sizeof_bdl) < 0) {
+ ui__error("%s", ui_helpline__last_msg);
+ goto out_free_offsets;
+ }
+
+ ui_helpline__push("Press <- or ESC to exit");
+
+ notes = symbol__annotation(sym);
+ browser.start = map__rip_2objdump(map, sym->start);
+
+ list_for_each_entry(pos, &notes->src->source, node) {
+ struct browser_disasm_line *bpos;
+ size_t line_len = strlen(pos->line);
+
+ if (browser.b.width < line_len)
+ browser.b.width = line_len;
+ bpos = disasm_line__browser(pos);
+ bpos->idx = browser.nr_entries++;
+ if (pos->offset != -1) {
+ bpos->idx_asm = browser.nr_asm_entries++;
+ /*
+ * FIXME: short term bandaid to cope with assembly
+ * routines that comes with labels in the same column
+ * as the address in objdump, sigh.
+ *
+ * E.g. copy_user_generic_unrolled
+ */
+ if (pos->offset < (s64)size)
+ browser.offsets[pos->offset] = pos;
+ } else
+ bpos->idx_asm = -1;
+ }
+
+ annotate_browser__mark_jump_targets(&browser, size);
+
+ browser.addr_width = browser.target_width = browser.min_addr_width = hex_width(size);
+ browser.max_addr_width = hex_width(sym->end);
+ browser.jumps_width = width_jumps(browser.max_jump_sources);
+ browser.nr_events = nr_pcnt;
+ browser.b.nr_entries = browser.nr_entries;
+ browser.b.entries = &notes->src->source,
+ browser.b.width += 18; /* Percentage */
+
+ if (annotate_browser__opts.hide_src_code)
+ annotate_browser__init_asm_mode(&browser);
+
+ annotate_browser__update_addr_width(&browser);
+
+ ret = annotate_browser__run(&browser, evsel, hbt);
+ list_for_each_entry_safe(pos, n, &notes->src->source, node) {
+ list_del(&pos->node);
+ disasm_line__free(pos);
+ }
+
+out_free_offsets:
+ free(browser.offsets);
+ return ret;
+}
+
+#define ANNOTATE_CFG(n) \
+ { .name = #n, .value = &annotate_browser__opts.n, }
+
+/*
+ * Keep the entries sorted, they are bsearch'ed
+ */
+static struct annotate_config {
+ const char *name;
+ bool *value;
+} annotate__configs[] = {
+ ANNOTATE_CFG(hide_src_code),
+ ANNOTATE_CFG(jump_arrows),
+ ANNOTATE_CFG(show_linenr),
+ ANNOTATE_CFG(show_nr_jumps),
+ ANNOTATE_CFG(use_offset),
+};
+
+#undef ANNOTATE_CFG
+
+static int annotate_config__cmp(const void *name, const void *cfgp)
+{
+ const struct annotate_config *cfg = cfgp;
+
+ return strcmp(name, cfg->name);
+}
+
+static int annotate__config(const char *var, const char *value,
+ void *data __maybe_unused)
+{
+ struct annotate_config *cfg;
+ const char *name;
+
+ if (prefixcmp(var, "annotate.") != 0)
+ return 0;
+
+ name = var + 9;
+ cfg = bsearch(name, annotate__configs, ARRAY_SIZE(annotate__configs),
+ sizeof(struct annotate_config), annotate_config__cmp);
+
+ if (cfg == NULL)
+ return -1;
+
+ *cfg->value = perf_config_bool(name, value);
+ return 0;
+}
+
+void annotate_browser__init(void)
+{
+ perf_config(annotate__config, NULL);
+}
diff --git a/tools/perf/ui/browsers/header.c b/tools/perf/ui/browsers/header.c
new file mode 100644
index 000000000..e8278c558
--- /dev/null
+++ b/tools/perf/ui/browsers/header.c
@@ -0,0 +1,128 @@
+#include "util/cache.h"
+#include "util/debug.h"
+#include "ui/browser.h"
+#include "ui/keysyms.h"
+#include "ui/ui.h"
+#include "ui/util.h"
+#include "ui/libslang.h"
+#include "util/header.h"
+#include "util/session.h"
+
+static void ui_browser__argv_write(struct ui_browser *browser,
+ void *entry, int row)
+{
+ char **arg = entry;
+ char *str = *arg;
+ char empty[] = " ";
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ unsigned long offset = (unsigned long)browser->priv;
+
+ if (offset >= strlen(str))
+ str = empty;
+ else
+ str = str + offset;
+
+ ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
+ HE_COLORSET_NORMAL);
+
+ slsmg_write_nstring(str, browser->width);
+}
+
+static int list_menu__run(struct ui_browser *menu)
+{
+ int key;
+ unsigned long offset;
+ const char help[] =
+ "h/?/F1 Show this window\n"
+ "UP/DOWN/PGUP\n"
+ "PGDN/SPACE\n"
+ "LEFT/RIGHT Navigate\n"
+ "q/ESC/CTRL+C Exit browser";
+
+ if (ui_browser__show(menu, "Header information", "Press 'q' to exit") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(menu, 0);
+
+ switch (key) {
+ case K_RIGHT:
+ offset = (unsigned long)menu->priv;
+ offset += 10;
+ menu->priv = (void *)offset;
+ continue;
+ case K_LEFT:
+ offset = (unsigned long)menu->priv;
+ if (offset >= 10)
+ offset -= 10;
+ menu->priv = (void *)offset;
+ continue;
+ case K_F1:
+ case 'h':
+ case '?':
+ ui_browser__help_window(menu, help);
+ continue;
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ key = -1;
+ break;
+ default:
+ continue;
+ }
+
+ break;
+ }
+
+ ui_browser__hide(menu);
+ return key;
+}
+
+static int ui__list_menu(int argc, char * const argv[])
+{
+ struct ui_browser menu = {
+ .entries = (void *)argv,
+ .refresh = ui_browser__argv_refresh,
+ .seek = ui_browser__argv_seek,
+ .write = ui_browser__argv_write,
+ .nr_entries = argc,
+ };
+
+ return list_menu__run(&menu);
+}
+
+int tui__header_window(struct perf_session_env *env)
+{
+ int i, argc = 0;
+ char **argv;
+ struct perf_session *session;
+ char *ptr, *pos;
+ size_t size;
+ FILE *fp = open_memstream(&ptr, &size);
+
+ session = container_of(env, struct perf_session, header.env);
+ perf_header__fprintf_info(session, fp, true);
+ fclose(fp);
+
+ for (pos = ptr, argc = 0; (pos = strchr(pos, '\n')) != NULL; pos++)
+ argc++;
+
+ argv = calloc(argc + 1, sizeof(*argv));
+ if (argv == NULL)
+ goto out;
+
+ argv[0] = pos = ptr;
+ for (i = 1; (pos = strchr(pos, '\n')) != NULL; i++) {
+ *pos++ = '\0';
+ argv[i] = pos;
+ }
+
+ BUG_ON(i != argc + 1);
+
+ ui__list_menu(argc, argv);
+
+out:
+ free(argv);
+ free(ptr);
+ return 0;
+}
diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c
new file mode 100644
index 000000000..995b7a859
--- /dev/null
+++ b/tools/perf/ui/browsers/hists.c
@@ -0,0 +1,2042 @@
+#include <stdio.h>
+#include "../libslang.h"
+#include <stdlib.h>
+#include <string.h>
+#include <linux/rbtree.h>
+
+#include "../../util/evsel.h"
+#include "../../util/evlist.h"
+#include "../../util/hist.h"
+#include "../../util/pstack.h"
+#include "../../util/sort.h"
+#include "../../util/util.h"
+#include "../../util/top.h"
+#include "../../arch/common.h"
+
+#include "../browser.h"
+#include "../helpline.h"
+#include "../util.h"
+#include "../ui.h"
+#include "map.h"
+#include "annotate.h"
+
+struct hist_browser {
+ struct ui_browser b;
+ struct hists *hists;
+ struct hist_entry *he_selection;
+ struct map_symbol *selection;
+ int print_seq;
+ bool show_dso;
+ bool show_headers;
+ float min_pcnt;
+ u64 nr_non_filtered_entries;
+ u64 nr_callchain_rows;
+};
+
+extern void hist_browser__init_hpp(void);
+
+static int hists__browser_title(struct hists *hists,
+ struct hist_browser_timer *hbt,
+ char *bf, size_t size);
+static void hist_browser__update_nr_entries(struct hist_browser *hb);
+
+static struct rb_node *hists__filter_entries(struct rb_node *nd,
+ float min_pcnt);
+
+static bool hist_browser__has_filter(struct hist_browser *hb)
+{
+ return hists__has_filter(hb->hists) || hb->min_pcnt;
+}
+
+static int hist_browser__get_folding(struct hist_browser *browser)
+{
+ struct rb_node *nd;
+ struct hists *hists = browser->hists;
+ int unfolded_rows = 0;
+
+ for (nd = rb_first(&hists->entries);
+ (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
+ nd = rb_next(nd)) {
+ struct hist_entry *he =
+ rb_entry(nd, struct hist_entry, rb_node);
+
+ if (he->ms.unfolded)
+ unfolded_rows += he->nr_rows;
+ }
+ return unfolded_rows;
+}
+
+static u32 hist_browser__nr_entries(struct hist_browser *hb)
+{
+ u32 nr_entries;
+
+ if (hist_browser__has_filter(hb))
+ nr_entries = hb->nr_non_filtered_entries;
+ else
+ nr_entries = hb->hists->nr_entries;
+
+ hb->nr_callchain_rows = hist_browser__get_folding(hb);
+ return nr_entries + hb->nr_callchain_rows;
+}
+
+static void hist_browser__update_rows(struct hist_browser *hb)
+{
+ struct ui_browser *browser = &hb->b;
+ u16 header_offset = hb->show_headers ? 1 : 0, index_row;
+
+ browser->rows = browser->height - header_offset;
+ /*
+ * Verify if we were at the last line and that line isn't
+ * visibe because we now show the header line(s).
+ */
+ index_row = browser->index - browser->top_idx;
+ if (index_row >= browser->rows)
+ browser->index -= index_row - browser->rows + 1;
+}
+
+static void hist_browser__refresh_dimensions(struct ui_browser *browser)
+{
+ struct hist_browser *hb = container_of(browser, struct hist_browser, b);
+
+ /* 3 == +/- toggle symbol before actual hist_entry rendering */
+ browser->width = 3 + (hists__sort_list_width(hb->hists) + sizeof("[k]"));
+ /*
+ * FIXME: Just keeping existing behaviour, but this really should be
+ * before updating browser->width, as it will invalidate the
+ * calculation above. Fix this and the fallout in another
+ * changeset.
+ */
+ ui_browser__refresh_dimensions(browser);
+ hist_browser__update_rows(hb);
+}
+
+static void hist_browser__gotorc(struct hist_browser *browser, int row, int column)
+{
+ u16 header_offset = browser->show_headers ? 1 : 0;
+
+ ui_browser__gotorc(&browser->b, row + header_offset, column);
+}
+
+static void hist_browser__reset(struct hist_browser *browser)
+{
+ /*
+ * The hists__remove_entry_filter() already folds non-filtered
+ * entries so we can assume it has 0 callchain rows.
+ */
+ browser->nr_callchain_rows = 0;
+
+ hist_browser__update_nr_entries(browser);
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
+ hist_browser__refresh_dimensions(&browser->b);
+ ui_browser__reset_index(&browser->b);
+}
+
+static char tree__folded_sign(bool unfolded)
+{
+ return unfolded ? '-' : '+';
+}
+
+static char map_symbol__folded(const struct map_symbol *ms)
+{
+ return ms->has_children ? tree__folded_sign(ms->unfolded) : ' ';
+}
+
+static char hist_entry__folded(const struct hist_entry *he)
+{
+ return map_symbol__folded(&he->ms);
+}
+
+static char callchain_list__folded(const struct callchain_list *cl)
+{
+ return map_symbol__folded(&cl->ms);
+}
+
+static void map_symbol__set_folding(struct map_symbol *ms, bool unfold)
+{
+ ms->unfolded = unfold ? ms->has_children : false;
+}
+
+static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
+{
+ int n = 0;
+ struct rb_node *nd;
+
+ for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
+ struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
+ struct callchain_list *chain;
+ char folded_sign = ' '; /* No children */
+
+ list_for_each_entry(chain, &child->val, list) {
+ ++n;
+ /* We need this because we may not have children */
+ folded_sign = callchain_list__folded(chain);
+ if (folded_sign == '+')
+ break;
+ }
+
+ if (folded_sign == '-') /* Have children and they're unfolded */
+ n += callchain_node__count_rows_rb_tree(child);
+ }
+
+ return n;
+}
+
+static int callchain_node__count_rows(struct callchain_node *node)
+{
+ struct callchain_list *chain;
+ bool unfolded = false;
+ int n = 0;
+
+ list_for_each_entry(chain, &node->val, list) {
+ ++n;
+ unfolded = chain->ms.unfolded;
+ }
+
+ if (unfolded)
+ n += callchain_node__count_rows_rb_tree(node);
+
+ return n;
+}
+
+static int callchain__count_rows(struct rb_root *chain)
+{
+ struct rb_node *nd;
+ int n = 0;
+
+ for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
+ struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+ n += callchain_node__count_rows(node);
+ }
+
+ return n;
+}
+
+static bool map_symbol__toggle_fold(struct map_symbol *ms)
+{
+ if (!ms)
+ return false;
+
+ if (!ms->has_children)
+ return false;
+
+ ms->unfolded = !ms->unfolded;
+ return true;
+}
+
+static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
+{
+ struct rb_node *nd = rb_first(&node->rb_root);
+
+ for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
+ struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
+ struct callchain_list *chain;
+ bool first = true;
+
+ list_for_each_entry(chain, &child->val, list) {
+ if (first) {
+ first = false;
+ chain->ms.has_children = chain->list.next != &child->val ||
+ !RB_EMPTY_ROOT(&child->rb_root);
+ } else
+ chain->ms.has_children = chain->list.next == &child->val &&
+ !RB_EMPTY_ROOT(&child->rb_root);
+ }
+
+ callchain_node__init_have_children_rb_tree(child);
+ }
+}
+
+static void callchain_node__init_have_children(struct callchain_node *node,
+ bool has_sibling)
+{
+ struct callchain_list *chain;
+
+ chain = list_entry(node->val.next, struct callchain_list, list);
+ chain->ms.has_children = has_sibling;
+
+ if (!list_empty(&node->val)) {
+ chain = list_entry(node->val.prev, struct callchain_list, list);
+ chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root);
+ }
+
+ callchain_node__init_have_children_rb_tree(node);
+}
+
+static void callchain__init_have_children(struct rb_root *root)
+{
+ struct rb_node *nd = rb_first(root);
+ bool has_sibling = nd && rb_next(nd);
+
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+ callchain_node__init_have_children(node, has_sibling);
+ }
+}
+
+static void hist_entry__init_have_children(struct hist_entry *he)
+{
+ if (!he->init_have_children) {
+ he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
+ callchain__init_have_children(&he->sorted_chain);
+ he->init_have_children = true;
+ }
+}
+
+static bool hist_browser__toggle_fold(struct hist_browser *browser)
+{
+ if (map_symbol__toggle_fold(browser->selection)) {
+ struct hist_entry *he = browser->he_selection;
+
+ hist_entry__init_have_children(he);
+ browser->b.nr_entries -= he->nr_rows;
+ browser->nr_callchain_rows -= he->nr_rows;
+
+ if (he->ms.unfolded)
+ he->nr_rows = callchain__count_rows(&he->sorted_chain);
+ else
+ he->nr_rows = 0;
+
+ browser->b.nr_entries += he->nr_rows;
+ browser->nr_callchain_rows += he->nr_rows;
+
+ return true;
+ }
+
+ /* If it doesn't have children, no toggling performed */
+ return false;
+}
+
+static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
+{
+ int n = 0;
+ struct rb_node *nd;
+
+ for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
+ struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
+ struct callchain_list *chain;
+ bool has_children = false;
+
+ list_for_each_entry(chain, &child->val, list) {
+ ++n;
+ map_symbol__set_folding(&chain->ms, unfold);
+ has_children = chain->ms.has_children;
+ }
+
+ if (has_children)
+ n += callchain_node__set_folding_rb_tree(child, unfold);
+ }
+
+ return n;
+}
+
+static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
+{
+ struct callchain_list *chain;
+ bool has_children = false;
+ int n = 0;
+
+ list_for_each_entry(chain, &node->val, list) {
+ ++n;
+ map_symbol__set_folding(&chain->ms, unfold);
+ has_children = chain->ms.has_children;
+ }
+
+ if (has_children)
+ n += callchain_node__set_folding_rb_tree(node, unfold);
+
+ return n;
+}
+
+static int callchain__set_folding(struct rb_root *chain, bool unfold)
+{
+ struct rb_node *nd;
+ int n = 0;
+
+ for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
+ struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+ n += callchain_node__set_folding(node, unfold);
+ }
+
+ return n;
+}
+
+static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
+{
+ hist_entry__init_have_children(he);
+ map_symbol__set_folding(&he->ms, unfold);
+
+ if (he->ms.has_children) {
+ int n = callchain__set_folding(&he->sorted_chain, unfold);
+ he->nr_rows = unfold ? n : 0;
+ } else
+ he->nr_rows = 0;
+}
+
+static void
+__hist_browser__set_folding(struct hist_browser *browser, bool unfold)
+{
+ struct rb_node *nd;
+ struct hists *hists = browser->hists;
+
+ for (nd = rb_first(&hists->entries);
+ (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
+ nd = rb_next(nd)) {
+ struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
+ hist_entry__set_folding(he, unfold);
+ browser->nr_callchain_rows += he->nr_rows;
+ }
+}
+
+static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
+{
+ browser->nr_callchain_rows = 0;
+ __hist_browser__set_folding(browser, unfold);
+
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
+ /* Go to the start, we may be way after valid entries after a collapse */
+ ui_browser__reset_index(&browser->b);
+}
+
+static void ui_browser__warn_lost_events(struct ui_browser *browser)
+{
+ ui_browser__warning(browser, 4,
+ "Events are being lost, check IO/CPU overload!\n\n"
+ "You may want to run 'perf' using a RT scheduler policy:\n\n"
+ " perf top -r 80\n\n"
+ "Or reduce the sampling frequency.");
+}
+
+static int hist_browser__run(struct hist_browser *browser,
+ struct hist_browser_timer *hbt)
+{
+ int key;
+ char title[160];
+ int delay_secs = hbt ? hbt->refresh : 0;
+
+ browser->b.entries = &browser->hists->entries;
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
+
+ hists__browser_title(browser->hists, hbt, title, sizeof(title));
+
+ if (ui_browser__show(&browser->b, title,
+ "Press '?' for help on key bindings") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, delay_secs);
+
+ switch (key) {
+ case K_TIMER: {
+ u64 nr_entries;
+ hbt->timer(hbt->arg);
+
+ if (hist_browser__has_filter(browser))
+ hist_browser__update_nr_entries(browser);
+
+ nr_entries = hist_browser__nr_entries(browser);
+ ui_browser__update_nr_entries(&browser->b, nr_entries);
+
+ if (browser->hists->stats.nr_lost_warned !=
+ browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
+ browser->hists->stats.nr_lost_warned =
+ browser->hists->stats.nr_events[PERF_RECORD_LOST];
+ ui_browser__warn_lost_events(&browser->b);
+ }
+
+ hists__browser_title(browser->hists,
+ hbt, title, sizeof(title));
+ ui_browser__show_title(&browser->b, title);
+ continue;
+ }
+ case 'D': { /* Debug */
+ static int seq;
+ struct hist_entry *h = rb_entry(browser->b.top,
+ struct hist_entry, rb_node);
+ ui_helpline__pop();
+ ui_helpline__fpush("%d: nr_ent=(%d,%d), rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
+ seq++, browser->b.nr_entries,
+ browser->hists->nr_entries,
+ browser->b.rows,
+ browser->b.index,
+ browser->b.top_idx,
+ h->row_offset, h->nr_rows);
+ }
+ break;
+ case 'C':
+ /* Collapse the whole world. */
+ hist_browser__set_folding(browser, false);
+ break;
+ case 'E':
+ /* Expand the whole world. */
+ hist_browser__set_folding(browser, true);
+ break;
+ case 'H':
+ browser->show_headers = !browser->show_headers;
+ hist_browser__update_rows(browser);
+ break;
+ case K_ENTER:
+ if (hist_browser__toggle_fold(browser))
+ break;
+ /* fall thru */
+ default:
+ goto out;
+ }
+ }
+out:
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+struct callchain_print_arg {
+ /* for hists browser */
+ off_t row_offset;
+ bool is_current_entry;
+
+ /* for file dump */
+ FILE *fp;
+ int printed;
+};
+
+typedef void (*print_callchain_entry_fn)(struct hist_browser *browser,
+ struct callchain_list *chain,
+ const char *str, int offset,
+ unsigned short row,
+ struct callchain_print_arg *arg);
+
+static void hist_browser__show_callchain_entry(struct hist_browser *browser,
+ struct callchain_list *chain,
+ const char *str, int offset,
+ unsigned short row,
+ struct callchain_print_arg *arg)
+{
+ int color, width;
+ char folded_sign = callchain_list__folded(chain);
+ bool show_annotated = browser->show_dso && chain->ms.sym && symbol__annotation(chain->ms.sym)->src;
+
+ color = HE_COLORSET_NORMAL;
+ width = browser->b.width - (offset + 2);
+ if (ui_browser__is_current_entry(&browser->b, row)) {
+ browser->selection = &chain->ms;
+ color = HE_COLORSET_SELECTED;
+ arg->is_current_entry = true;
+ }
+
+ ui_browser__set_color(&browser->b, color);
+ hist_browser__gotorc(browser, row, 0);
+ slsmg_write_nstring(" ", offset);
+ slsmg_printf("%c", folded_sign);
+ ui_browser__write_graph(&browser->b, show_annotated ? SLSMG_RARROW_CHAR : ' ');
+ slsmg_write_nstring(str, width);
+}
+
+static void hist_browser__fprintf_callchain_entry(struct hist_browser *b __maybe_unused,
+ struct callchain_list *chain,
+ const char *str, int offset,
+ unsigned short row __maybe_unused,
+ struct callchain_print_arg *arg)
+{
+ char folded_sign = callchain_list__folded(chain);
+
+ arg->printed += fprintf(arg->fp, "%*s%c %s\n", offset, " ",
+ folded_sign, str);
+}
+
+typedef bool (*check_output_full_fn)(struct hist_browser *browser,
+ unsigned short row);
+
+static bool hist_browser__check_output_full(struct hist_browser *browser,
+ unsigned short row)
+{
+ return browser->b.rows == row;
+}
+
+static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_unused,
+ unsigned short row __maybe_unused)
+{
+ return false;
+}
+
+#define LEVEL_OFFSET_STEP 3
+
+static int hist_browser__show_callchain(struct hist_browser *browser,
+ struct rb_root *root, int level,
+ unsigned short row, u64 total,
+ print_callchain_entry_fn print,
+ struct callchain_print_arg *arg,
+ check_output_full_fn is_output_full)
+{
+ struct rb_node *node;
+ int first_row = row, offset = level * LEVEL_OFFSET_STEP;
+ u64 new_total;
+ bool need_percent;
+
+ node = rb_first(root);
+ need_percent = node && rb_next(node);
+
+ while (node) {
+ struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
+ struct rb_node *next = rb_next(node);
+ u64 cumul = callchain_cumul_hits(child);
+ struct callchain_list *chain;
+ char folded_sign = ' ';
+ int first = true;
+ int extra_offset = 0;
+
+ list_for_each_entry(chain, &child->val, list) {
+ char bf[1024], *alloc_str;
+ const char *str;
+ bool was_first = first;
+
+ if (first)
+ first = false;
+ else if (need_percent)
+ extra_offset = LEVEL_OFFSET_STEP;
+
+ folded_sign = callchain_list__folded(chain);
+ if (arg->row_offset != 0) {
+ arg->row_offset--;
+ goto do_next;
+ }
+
+ alloc_str = NULL;
+ str = callchain_list__sym_name(chain, bf, sizeof(bf),
+ browser->show_dso);
+
+ if (was_first && need_percent) {
+ double percent = cumul * 100.0 / total;
+
+ if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
+ str = "Not enough memory!";
+ else
+ str = alloc_str;
+ }
+
+ print(browser, chain, str, offset + extra_offset, row, arg);
+
+ free(alloc_str);
+
+ if (is_output_full(browser, ++row))
+ goto out;
+do_next:
+ if (folded_sign == '+')
+ break;
+ }
+
+ if (folded_sign == '-') {
+ const int new_level = level + (extra_offset ? 2 : 1);
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ new_total = child->children_hit;
+ else
+ new_total = total;
+
+ row += hist_browser__show_callchain(browser, &child->rb_root,
+ new_level, row, new_total,
+ print, arg, is_output_full);
+ }
+ if (is_output_full(browser, row))
+ break;
+ node = next;
+ }
+out:
+ return row - first_row;
+}
+
+struct hpp_arg {
+ struct ui_browser *b;
+ char folded_sign;
+ bool current_entry;
+};
+
+static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
+{
+ struct hpp_arg *arg = hpp->ptr;
+ int ret, len;
+ va_list args;
+ double percent;
+
+ va_start(args, fmt);
+ len = va_arg(args, int);
+ percent = va_arg(args, double);
+ va_end(args);
+
+ ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
+
+ ret = scnprintf(hpp->buf, hpp->size, fmt, len, percent);
+ slsmg_printf("%s", hpp->buf);
+
+ advance_hpp(hpp, ret);
+ return ret;
+}
+
+#define __HPP_COLOR_PERCENT_FN(_type, _field) \
+static u64 __hpp_get_##_field(struct hist_entry *he) \
+{ \
+ return he->stat._field; \
+} \
+ \
+static int \
+hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%", \
+ __hpp__slsmg_color_printf, true); \
+}
+
+#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
+static u64 __hpp_get_acc_##_field(struct hist_entry *he) \
+{ \
+ return he->stat_acc->_field; \
+} \
+ \
+static int \
+hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ if (!symbol_conf.cumulate_callchain) { \
+ int len = fmt->user_len ?: fmt->len; \
+ int ret = scnprintf(hpp->buf, hpp->size, \
+ "%*s", len, "N/A"); \
+ slsmg_printf("%s", hpp->buf); \
+ \
+ return ret; \
+ } \
+ return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \
+ " %*.2f%%", __hpp__slsmg_color_printf, true); \
+}
+
+__HPP_COLOR_PERCENT_FN(overhead, period)
+__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
+__HPP_COLOR_PERCENT_FN(overhead_us, period_us)
+__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
+__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
+__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period)
+
+#undef __HPP_COLOR_PERCENT_FN
+#undef __HPP_COLOR_ACC_PERCENT_FN
+
+void hist_browser__init_hpp(void)
+{
+ perf_hpp__format[PERF_HPP__OVERHEAD].color =
+ hist_browser__hpp_color_overhead;
+ perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
+ hist_browser__hpp_color_overhead_sys;
+ perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
+ hist_browser__hpp_color_overhead_us;
+ perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
+ hist_browser__hpp_color_overhead_guest_sys;
+ perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
+ hist_browser__hpp_color_overhead_guest_us;
+ perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
+ hist_browser__hpp_color_overhead_acc;
+}
+
+static int hist_browser__show_entry(struct hist_browser *browser,
+ struct hist_entry *entry,
+ unsigned short row)
+{
+ char s[256];
+ int printed = 0;
+ int width = browser->b.width;
+ char folded_sign = ' ';
+ bool current_entry = ui_browser__is_current_entry(&browser->b, row);
+ off_t row_offset = entry->row_offset;
+ bool first = true;
+ struct perf_hpp_fmt *fmt;
+
+ if (current_entry) {
+ browser->he_selection = entry;
+ browser->selection = &entry->ms;
+ }
+
+ if (symbol_conf.use_callchain) {
+ hist_entry__init_have_children(entry);
+ folded_sign = hist_entry__folded(entry);
+ }
+
+ if (row_offset == 0) {
+ struct hpp_arg arg = {
+ .b = &browser->b,
+ .folded_sign = folded_sign,
+ .current_entry = current_entry,
+ };
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ .ptr = &arg,
+ };
+
+ hist_browser__gotorc(browser, row, 0);
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ if (current_entry && browser->b.navkeypressed) {
+ ui_browser__set_color(&browser->b,
+ HE_COLORSET_SELECTED);
+ } else {
+ ui_browser__set_color(&browser->b,
+ HE_COLORSET_NORMAL);
+ }
+
+ if (first) {
+ if (symbol_conf.use_callchain) {
+ slsmg_printf("%c ", folded_sign);
+ width -= 2;
+ }
+ first = false;
+ } else {
+ slsmg_printf(" ");
+ width -= 2;
+ }
+
+ if (fmt->color) {
+ width -= fmt->color(fmt, &hpp, entry);
+ } else {
+ width -= fmt->entry(fmt, &hpp, entry);
+ slsmg_printf("%s", s);
+ }
+ }
+
+ /* The scroll bar isn't being used */
+ if (!browser->b.navkeypressed)
+ width += 1;
+
+ slsmg_write_nstring("", width);
+
+ ++row;
+ ++printed;
+ } else
+ --row_offset;
+
+ if (folded_sign == '-' && row != browser->b.rows) {
+ u64 total = hists__total_period(entry->hists);
+ struct callchain_print_arg arg = {
+ .row_offset = row_offset,
+ .is_current_entry = current_entry,
+ };
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL) {
+ if (symbol_conf.cumulate_callchain)
+ total = entry->stat_acc->period;
+ else
+ total = entry->stat.period;
+ }
+
+ printed += hist_browser__show_callchain(browser,
+ &entry->sorted_chain, 1, row, total,
+ hist_browser__show_callchain_entry, &arg,
+ hist_browser__check_output_full);
+
+ if (arg.is_current_entry)
+ browser->he_selection = entry;
+ }
+
+ return printed;
+}
+
+static int advance_hpp_check(struct perf_hpp *hpp, int inc)
+{
+ advance_hpp(hpp, inc);
+ return hpp->size <= 0;
+}
+
+static int hists__scnprintf_headers(char *buf, size_t size, struct hists *hists)
+{
+ struct perf_hpp dummy_hpp = {
+ .buf = buf,
+ .size = size,
+ };
+ struct perf_hpp_fmt *fmt;
+ size_t ret = 0;
+
+ if (symbol_conf.use_callchain) {
+ ret = scnprintf(buf, size, " ");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ return ret;
+ }
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ ret = fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists));
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+
+ ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " ");
+ if (advance_hpp_check(&dummy_hpp, ret))
+ break;
+ }
+
+ return ret;
+}
+
+static void hist_browser__show_headers(struct hist_browser *browser)
+{
+ char headers[1024];
+
+ hists__scnprintf_headers(headers, sizeof(headers), browser->hists);
+ ui_browser__gotorc(&browser->b, 0, 0);
+ ui_browser__set_color(&browser->b, HE_COLORSET_ROOT);
+ slsmg_write_nstring(headers, browser->b.width + 1);
+}
+
+static void ui_browser__hists_init_top(struct ui_browser *browser)
+{
+ if (browser->top == NULL) {
+ struct hist_browser *hb;
+
+ hb = container_of(browser, struct hist_browser, b);
+ browser->top = rb_first(&hb->hists->entries);
+ }
+}
+
+static unsigned int hist_browser__refresh(struct ui_browser *browser)
+{
+ unsigned row = 0;
+ u16 header_offset = 0;
+ struct rb_node *nd;
+ struct hist_browser *hb = container_of(browser, struct hist_browser, b);
+
+ if (hb->show_headers) {
+ hist_browser__show_headers(hb);
+ header_offset = 1;
+ }
+
+ ui_browser__hists_init_top(browser);
+
+ for (nd = browser->top; nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ float percent;
+
+ if (h->filtered)
+ continue;
+
+ percent = hist_entry__get_percent_limit(h);
+ if (percent < hb->min_pcnt)
+ continue;
+
+ row += hist_browser__show_entry(hb, h, row);
+ if (row == browser->rows)
+ break;
+ }
+
+ return row + header_offset;
+}
+
+static struct rb_node *hists__filter_entries(struct rb_node *nd,
+ float min_pcnt)
+{
+ while (nd != NULL) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ float percent = hist_entry__get_percent_limit(h);
+
+ if (!h->filtered && percent >= min_pcnt)
+ return nd;
+
+ nd = rb_next(nd);
+ }
+
+ return NULL;
+}
+
+static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
+ float min_pcnt)
+{
+ while (nd != NULL) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ float percent = hist_entry__get_percent_limit(h);
+
+ if (!h->filtered && percent >= min_pcnt)
+ return nd;
+
+ nd = rb_prev(nd);
+ }
+
+ return NULL;
+}
+
+static void ui_browser__hists_seek(struct ui_browser *browser,
+ off_t offset, int whence)
+{
+ struct hist_entry *h;
+ struct rb_node *nd;
+ bool first = true;
+ struct hist_browser *hb;
+
+ hb = container_of(browser, struct hist_browser, b);
+
+ if (browser->nr_entries == 0)
+ return;
+
+ ui_browser__hists_init_top(browser);
+
+ switch (whence) {
+ case SEEK_SET:
+ nd = hists__filter_entries(rb_first(browser->entries),
+ hb->min_pcnt);
+ break;
+ case SEEK_CUR:
+ nd = browser->top;
+ goto do_offset;
+ case SEEK_END:
+ nd = hists__filter_prev_entries(rb_last(browser->entries),
+ hb->min_pcnt);
+ first = false;
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * Moves not relative to the first visible entry invalidates its
+ * row_offset:
+ */
+ h = rb_entry(browser->top, struct hist_entry, rb_node);
+ h->row_offset = 0;
+
+ /*
+ * Here we have to check if nd is expanded (+), if it is we can't go
+ * the next top level hist_entry, instead we must compute an offset of
+ * what _not_ to show and not change the first visible entry.
+ *
+ * This offset increments when we are going from top to bottom and
+ * decreases when we're going from bottom to top.
+ *
+ * As we don't have backpointers to the top level in the callchains
+ * structure, we need to always print the whole hist_entry callchain,
+ * skipping the first ones that are before the first visible entry
+ * and stop when we printed enough lines to fill the screen.
+ */
+do_offset:
+ if (offset > 0) {
+ do {
+ h = rb_entry(nd, struct hist_entry, rb_node);
+ if (h->ms.unfolded) {
+ u16 remaining = h->nr_rows - h->row_offset;
+ if (offset > remaining) {
+ offset -= remaining;
+ h->row_offset = 0;
+ } else {
+ h->row_offset += offset;
+ offset = 0;
+ browser->top = nd;
+ break;
+ }
+ }
+ nd = hists__filter_entries(rb_next(nd), hb->min_pcnt);
+ if (nd == NULL)
+ break;
+ --offset;
+ browser->top = nd;
+ } while (offset != 0);
+ } else if (offset < 0) {
+ while (1) {
+ h = rb_entry(nd, struct hist_entry, rb_node);
+ if (h->ms.unfolded) {
+ if (first) {
+ if (-offset > h->row_offset) {
+ offset += h->row_offset;
+ h->row_offset = 0;
+ } else {
+ h->row_offset += offset;
+ offset = 0;
+ browser->top = nd;
+ break;
+ }
+ } else {
+ if (-offset > h->nr_rows) {
+ offset += h->nr_rows;
+ h->row_offset = 0;
+ } else {
+ h->row_offset = h->nr_rows + offset;
+ offset = 0;
+ browser->top = nd;
+ break;
+ }
+ }
+ }
+
+ nd = hists__filter_prev_entries(rb_prev(nd),
+ hb->min_pcnt);
+ if (nd == NULL)
+ break;
+ ++offset;
+ browser->top = nd;
+ if (offset == 0) {
+ /*
+ * Last unfiltered hist_entry, check if it is
+ * unfolded, if it is then we should have
+ * row_offset at its last entry.
+ */
+ h = rb_entry(nd, struct hist_entry, rb_node);
+ if (h->ms.unfolded)
+ h->row_offset = h->nr_rows;
+ break;
+ }
+ first = false;
+ }
+ } else {
+ browser->top = nd;
+ h = rb_entry(nd, struct hist_entry, rb_node);
+ h->row_offset = 0;
+ }
+}
+
+static int hist_browser__fprintf_callchain(struct hist_browser *browser,
+ struct hist_entry *he, FILE *fp)
+{
+ u64 total = hists__total_period(he->hists);
+ struct callchain_print_arg arg = {
+ .fp = fp,
+ };
+
+ if (symbol_conf.cumulate_callchain)
+ total = he->stat_acc->period;
+
+ hist_browser__show_callchain(browser, &he->sorted_chain, 1, 0, total,
+ hist_browser__fprintf_callchain_entry, &arg,
+ hist_browser__check_dump_full);
+ return arg.printed;
+}
+
+static int hist_browser__fprintf_entry(struct hist_browser *browser,
+ struct hist_entry *he, FILE *fp)
+{
+ char s[8192];
+ int printed = 0;
+ char folded_sign = ' ';
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ };
+ struct perf_hpp_fmt *fmt;
+ bool first = true;
+ int ret;
+
+ if (symbol_conf.use_callchain)
+ folded_sign = hist_entry__folded(he);
+
+ if (symbol_conf.use_callchain)
+ printed += fprintf(fp, "%c ", folded_sign);
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ if (!first) {
+ ret = scnprintf(hpp.buf, hpp.size, " ");
+ advance_hpp(&hpp, ret);
+ } else
+ first = false;
+
+ ret = fmt->entry(fmt, &hpp, he);
+ advance_hpp(&hpp, ret);
+ }
+ printed += fprintf(fp, "%s\n", rtrim(s));
+
+ if (folded_sign == '-')
+ printed += hist_browser__fprintf_callchain(browser, he, fp);
+
+ return printed;
+}
+
+static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
+{
+ struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
+ browser->min_pcnt);
+ int printed = 0;
+
+ while (nd) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+
+ printed += hist_browser__fprintf_entry(browser, h, fp);
+ nd = hists__filter_entries(rb_next(nd), browser->min_pcnt);
+ }
+
+ return printed;
+}
+
+static int hist_browser__dump(struct hist_browser *browser)
+{
+ char filename[64];
+ FILE *fp;
+
+ while (1) {
+ scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
+ if (access(filename, F_OK))
+ break;
+ /*
+ * XXX: Just an arbitrary lazy upper limit
+ */
+ if (++browser->print_seq == 8192) {
+ ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
+ return -1;
+ }
+ }
+
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ char bf[64];
+ const char *err = strerror_r(errno, bf, sizeof(bf));
+ ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
+ return -1;
+ }
+
+ ++browser->print_seq;
+ hist_browser__fprintf(browser, fp);
+ fclose(fp);
+ ui_helpline__fpush("%s written!", filename);
+
+ return 0;
+}
+
+static struct hist_browser *hist_browser__new(struct hists *hists)
+{
+ struct hist_browser *browser = zalloc(sizeof(*browser));
+
+ if (browser) {
+ browser->hists = hists;
+ browser->b.refresh = hist_browser__refresh;
+ browser->b.refresh_dimensions = hist_browser__refresh_dimensions;
+ browser->b.seek = ui_browser__hists_seek;
+ browser->b.use_navkeypressed = true;
+ browser->show_headers = symbol_conf.show_hist_headers;
+ }
+
+ return browser;
+}
+
+static void hist_browser__delete(struct hist_browser *browser)
+{
+ free(browser);
+}
+
+static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
+{
+ return browser->he_selection;
+}
+
+static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
+{
+ return browser->he_selection->thread;
+}
+
+/* Check whether the browser is for 'top' or 'report' */
+static inline bool is_report_browser(void *timer)
+{
+ return timer == NULL;
+}
+
+static int hists__browser_title(struct hists *hists,
+ struct hist_browser_timer *hbt,
+ char *bf, size_t size)
+{
+ char unit;
+ int printed;
+ const struct dso *dso = hists->dso_filter;
+ const struct thread *thread = hists->thread_filter;
+ unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
+ u64 nr_events = hists->stats.total_period;
+ struct perf_evsel *evsel = hists_to_evsel(hists);
+ const char *ev_name = perf_evsel__name(evsel);
+ char buf[512];
+ size_t buflen = sizeof(buf);
+
+ if (symbol_conf.filter_relative) {
+ nr_samples = hists->stats.nr_non_filtered_samples;
+ nr_events = hists->stats.total_non_filtered_period;
+ }
+
+ if (perf_evsel__is_group_event(evsel)) {
+ struct perf_evsel *pos;
+
+ perf_evsel__group_desc(evsel, buf, buflen);
+ ev_name = buf;
+
+ for_each_group_member(pos, evsel) {
+ struct hists *pos_hists = evsel__hists(pos);
+
+ if (symbol_conf.filter_relative) {
+ nr_samples += pos_hists->stats.nr_non_filtered_samples;
+ nr_events += pos_hists->stats.total_non_filtered_period;
+ } else {
+ nr_samples += pos_hists->stats.nr_events[PERF_RECORD_SAMPLE];
+ nr_events += pos_hists->stats.total_period;
+ }
+ }
+ }
+
+ nr_samples = convert_unit(nr_samples, &unit);
+ printed = scnprintf(bf, size,
+ "Samples: %lu%c of event '%s', Event count (approx.): %" PRIu64,
+ nr_samples, unit, ev_name, nr_events);
+
+
+ if (hists->uid_filter_str)
+ printed += snprintf(bf + printed, size - printed,
+ ", UID: %s", hists->uid_filter_str);
+ if (thread)
+ printed += scnprintf(bf + printed, size - printed,
+ ", Thread: %s(%d)",
+ (thread->comm_set ? thread__comm_str(thread) : ""),
+ thread->tid);
+ if (dso)
+ printed += scnprintf(bf + printed, size - printed,
+ ", DSO: %s", dso->short_name);
+ if (!is_report_browser(hbt)) {
+ struct perf_top *top = hbt->arg;
+
+ if (top->zero)
+ printed += scnprintf(bf + printed, size - printed, " [z]");
+ }
+
+ return printed;
+}
+
+static inline void free_popup_options(char **options, int n)
+{
+ int i;
+
+ for (i = 0; i < n; ++i)
+ zfree(&options[i]);
+}
+
+/*
+ * Only runtime switching of perf data file will make "input_name" point
+ * to a malloced buffer. So add "is_input_name_malloced" flag to decide
+ * whether we need to call free() for current "input_name" during the switch.
+ */
+static bool is_input_name_malloced = false;
+
+static int switch_data_file(void)
+{
+ char *pwd, *options[32], *abs_path[32], *tmp;
+ DIR *pwd_dir;
+ int nr_options = 0, choice = -1, ret = -1;
+ struct dirent *dent;
+
+ pwd = getenv("PWD");
+ if (!pwd)
+ return ret;
+
+ pwd_dir = opendir(pwd);
+ if (!pwd_dir)
+ return ret;
+
+ memset(options, 0, sizeof(options));
+ memset(options, 0, sizeof(abs_path));
+
+ while ((dent = readdir(pwd_dir))) {
+ char path[PATH_MAX];
+ u64 magic;
+ char *name = dent->d_name;
+ FILE *file;
+
+ if (!(dent->d_type == DT_REG))
+ continue;
+
+ snprintf(path, sizeof(path), "%s/%s", pwd, name);
+
+ file = fopen(path, "r");
+ if (!file)
+ continue;
+
+ if (fread(&magic, 1, 8, file) < 8)
+ goto close_file_and_continue;
+
+ if (is_perf_magic(magic)) {
+ options[nr_options] = strdup(name);
+ if (!options[nr_options])
+ goto close_file_and_continue;
+
+ abs_path[nr_options] = strdup(path);
+ if (!abs_path[nr_options]) {
+ zfree(&options[nr_options]);
+ ui__warning("Can't search all data files due to memory shortage.\n");
+ fclose(file);
+ break;
+ }
+
+ nr_options++;
+ }
+
+close_file_and_continue:
+ fclose(file);
+ if (nr_options >= 32) {
+ ui__warning("Too many perf data files in PWD!\n"
+ "Only the first 32 files will be listed.\n");
+ break;
+ }
+ }
+ closedir(pwd_dir);
+
+ if (nr_options) {
+ choice = ui__popup_menu(nr_options, options);
+ if (choice < nr_options && choice >= 0) {
+ tmp = strdup(abs_path[choice]);
+ if (tmp) {
+ if (is_input_name_malloced)
+ free((void *)input_name);
+ input_name = tmp;
+ is_input_name_malloced = true;
+ ret = 0;
+ } else
+ ui__warning("Data switch failed due to memory shortage!\n");
+ }
+ }
+
+ free_popup_options(options, nr_options);
+ free_popup_options(abs_path, nr_options);
+ return ret;
+}
+
+static void hist_browser__update_nr_entries(struct hist_browser *hb)
+{
+ u64 nr_entries = 0;
+ struct rb_node *nd = rb_first(&hb->hists->entries);
+
+ if (hb->min_pcnt == 0) {
+ hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
+ return;
+ }
+
+ while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
+ nr_entries++;
+ nd = rb_next(nd);
+ }
+
+ hb->nr_non_filtered_entries = nr_entries;
+}
+
+static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
+ const char *helpline,
+ bool left_exits,
+ struct hist_browser_timer *hbt,
+ float min_pcnt,
+ struct perf_session_env *env)
+{
+ struct hists *hists = evsel__hists(evsel);
+ struct hist_browser *browser = hist_browser__new(hists);
+ struct branch_info *bi;
+ struct pstack *fstack;
+ char *options[16];
+ int nr_options = 0;
+ int key = -1;
+ char buf[64];
+ char script_opt[64];
+ int delay_secs = hbt ? hbt->refresh : 0;
+ struct perf_hpp_fmt *fmt;
+
+#define HIST_BROWSER_HELP_COMMON \
+ "h/?/F1 Show this window\n" \
+ "UP/DOWN/PGUP\n" \
+ "PGDN/SPACE Navigate\n" \
+ "q/ESC/CTRL+C Exit browser\n\n" \
+ "For multiple event sessions:\n\n" \
+ "TAB/UNTAB Switch events\n\n" \
+ "For symbolic views (--sort has sym):\n\n" \
+ "-> Zoom into DSO/Threads & Annotate current symbol\n" \
+ "<- Zoom out\n" \
+ "a Annotate current symbol\n" \
+ "C Collapse all callchains\n" \
+ "d Zoom into current DSO\n" \
+ "E Expand all callchains\n" \
+ "F Toggle percentage of filtered entries\n" \
+ "H Display column headers\n" \
+
+ /* help messages are sorted by lexical order of the hotkey */
+ const char report_help[] = HIST_BROWSER_HELP_COMMON
+ "i Show header information\n"
+ "P Print histograms to perf.hist.N\n"
+ "r Run available scripts\n"
+ "s Switch to another data file in PWD\n"
+ "t Zoom into current Thread\n"
+ "V Verbose (DSO names in callchains, etc)\n"
+ "/ Filter symbol by name";
+ const char top_help[] = HIST_BROWSER_HELP_COMMON
+ "P Print histograms to perf.hist.N\n"
+ "t Zoom into current Thread\n"
+ "V Verbose (DSO names in callchains, etc)\n"
+ "z Toggle zeroing of samples\n"
+ "/ Filter symbol by name";
+
+ if (browser == NULL)
+ return -1;
+
+ if (min_pcnt) {
+ browser->min_pcnt = min_pcnt;
+ hist_browser__update_nr_entries(browser);
+ }
+
+ fstack = pstack__new(2);
+ if (fstack == NULL)
+ goto out;
+
+ ui_helpline__push(helpline);
+
+ memset(options, 0, sizeof(options));
+
+ perf_hpp__for_each_format(fmt)
+ perf_hpp__reset_width(fmt, hists);
+
+ if (symbol_conf.col_width_list_str)
+ perf_hpp__set_user_width(symbol_conf.col_width_list_str);
+
+ while (1) {
+ struct thread *thread = NULL;
+ const struct dso *dso = NULL;
+ int choice = 0,
+ annotate = -2, zoom_dso = -2, zoom_thread = -2,
+ annotate_f = -2, annotate_t = -2, browse_map = -2;
+ int scripts_comm = -2, scripts_symbol = -2,
+ scripts_all = -2, switch_data = -2;
+
+ nr_options = 0;
+
+ key = hist_browser__run(browser, hbt);
+
+ if (browser->he_selection != NULL) {
+ thread = hist_browser__selected_thread(browser);
+ dso = browser->selection->map ? browser->selection->map->dso : NULL;
+ }
+ switch (key) {
+ case K_TAB:
+ case K_UNTAB:
+ if (nr_events == 1)
+ continue;
+ /*
+ * Exit the browser, let hists__browser_tree
+ * go to the next or previous
+ */
+ goto out_free_stack;
+ case 'a':
+ if (!sort__has_sym) {
+ ui_browser__warning(&browser->b, delay_secs * 2,
+ "Annotation is only available for symbolic views, "
+ "include \"sym*\" in --sort to use it.");
+ continue;
+ }
+
+ if (browser->selection == NULL ||
+ browser->selection->sym == NULL ||
+ browser->selection->map->dso->annotate_warned)
+ continue;
+ goto do_annotate;
+ case 'P':
+ hist_browser__dump(browser);
+ continue;
+ case 'd':
+ goto zoom_dso;
+ case 'V':
+ browser->show_dso = !browser->show_dso;
+ continue;
+ case 't':
+ goto zoom_thread;
+ case '/':
+ if (ui_browser__input_window("Symbol to show",
+ "Please enter the name of symbol you want to see",
+ buf, "ENTER: OK, ESC: Cancel",
+ delay_secs * 2) == K_ENTER) {
+ hists->symbol_filter_str = *buf ? buf : NULL;
+ hists__filter_by_symbol(hists);
+ hist_browser__reset(browser);
+ }
+ continue;
+ case 'r':
+ if (is_report_browser(hbt))
+ goto do_scripts;
+ continue;
+ case 's':
+ if (is_report_browser(hbt))
+ goto do_data_switch;
+ continue;
+ case 'i':
+ /* env->arch is NULL for live-mode (i.e. perf top) */
+ if (env->arch)
+ tui__header_window(env);
+ continue;
+ case 'F':
+ symbol_conf.filter_relative ^= 1;
+ continue;
+ case 'z':
+ if (!is_report_browser(hbt)) {
+ struct perf_top *top = hbt->arg;
+
+ top->zero = !top->zero;
+ }
+ continue;
+ case K_F1:
+ case 'h':
+ case '?':
+ ui_browser__help_window(&browser->b,
+ is_report_browser(hbt) ? report_help : top_help);
+ continue;
+ case K_ENTER:
+ case K_RIGHT:
+ /* menu */
+ break;
+ case K_LEFT: {
+ const void *top;
+
+ if (pstack__empty(fstack)) {
+ /*
+ * Go back to the perf_evsel_menu__run or other user
+ */
+ if (left_exits)
+ goto out_free_stack;
+ continue;
+ }
+ top = pstack__pop(fstack);
+ if (top == &browser->hists->dso_filter)
+ goto zoom_out_dso;
+ if (top == &browser->hists->thread_filter)
+ goto zoom_out_thread;
+ continue;
+ }
+ case K_ESC:
+ if (!left_exits &&
+ !ui_browser__dialog_yesno(&browser->b,
+ "Do you really want to exit?"))
+ continue;
+ /* Fall thru */
+ case 'q':
+ case CTRL('c'):
+ goto out_free_stack;
+ default:
+ continue;
+ }
+
+ if (!sort__has_sym)
+ goto add_exit_option;
+
+ if (browser->selection == NULL)
+ goto skip_annotation;
+
+ if (sort__mode == SORT_MODE__BRANCH) {
+ bi = browser->he_selection->branch_info;
+
+ if (bi == NULL)
+ goto skip_annotation;
+
+ if (bi->from.sym != NULL &&
+ !bi->from.map->dso->annotate_warned &&
+ asprintf(&options[nr_options], "Annotate %s", bi->from.sym->name) > 0) {
+ annotate_f = nr_options++;
+ }
+
+ if (bi->to.sym != NULL &&
+ !bi->to.map->dso->annotate_warned &&
+ (bi->to.sym != bi->from.sym ||
+ bi->to.map->dso != bi->from.map->dso) &&
+ asprintf(&options[nr_options], "Annotate %s", bi->to.sym->name) > 0) {
+ annotate_t = nr_options++;
+ }
+ } else {
+ if (browser->selection->sym != NULL &&
+ !browser->selection->map->dso->annotate_warned) {
+ struct annotation *notes;
+
+ notes = symbol__annotation(browser->selection->sym);
+
+ if (notes->src &&
+ asprintf(&options[nr_options], "Annotate %s",
+ browser->selection->sym->name) > 0) {
+ annotate = nr_options++;
+ }
+ }
+ }
+skip_annotation:
+ if (thread != NULL &&
+ asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
+ (browser->hists->thread_filter ? "out of" : "into"),
+ (thread->comm_set ? thread__comm_str(thread) : ""),
+ thread->tid) > 0)
+ zoom_thread = nr_options++;
+
+ if (dso != NULL &&
+ asprintf(&options[nr_options], "Zoom %s %s DSO",
+ (browser->hists->dso_filter ? "out of" : "into"),
+ (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
+ zoom_dso = nr_options++;
+
+ if (browser->selection != NULL &&
+ browser->selection->map != NULL &&
+ asprintf(&options[nr_options], "Browse map details") > 0)
+ browse_map = nr_options++;
+
+ /* perf script support */
+ if (browser->he_selection) {
+ struct symbol *sym;
+
+ if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]",
+ thread__comm_str(browser->he_selection->thread)) > 0)
+ scripts_comm = nr_options++;
+
+ sym = browser->he_selection->ms.sym;
+ if (sym && sym->namelen &&
+ asprintf(&options[nr_options], "Run scripts for samples of symbol [%s]",
+ sym->name) > 0)
+ scripts_symbol = nr_options++;
+ }
+
+ if (asprintf(&options[nr_options], "Run scripts for all samples") > 0)
+ scripts_all = nr_options++;
+
+ if (is_report_browser(hbt) && asprintf(&options[nr_options],
+ "Switch to another data file in PWD") > 0)
+ switch_data = nr_options++;
+add_exit_option:
+ options[nr_options++] = (char *)"Exit";
+retry_popup_menu:
+ choice = ui__popup_menu(nr_options, options);
+
+ if (choice == nr_options - 1)
+ break;
+
+ if (choice == -1) {
+ free_popup_options(options, nr_options - 1);
+ continue;
+ }
+
+ if (choice == annotate || choice == annotate_t || choice == annotate_f) {
+ struct hist_entry *he;
+ struct annotation *notes;
+ struct map_symbol ms;
+ int err;
+do_annotate:
+ if (!objdump_path && perf_session_env__lookup_objdump(env))
+ continue;
+
+ he = hist_browser__selected_entry(browser);
+ if (he == NULL)
+ continue;
+
+ if (choice == annotate_f) {
+ ms.map = he->branch_info->from.map;
+ ms.sym = he->branch_info->from.sym;
+ } else if (choice == annotate_t) {
+ ms.map = he->branch_info->to.map;
+ ms.sym = he->branch_info->to.sym;
+ } else {
+ ms = *browser->selection;
+ }
+
+ notes = symbol__annotation(ms.sym);
+ if (!notes->src)
+ continue;
+
+ err = map_symbol__tui_annotate(&ms, evsel, hbt);
+ /*
+ * offer option to annotate the other branch source or target
+ * (if they exists) when returning from annotate
+ */
+ if ((err == 'q' || err == CTRL('c'))
+ && annotate_t != -2 && annotate_f != -2)
+ goto retry_popup_menu;
+
+ ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
+ if (err)
+ ui_browser__handle_resize(&browser->b);
+
+ } else if (choice == browse_map)
+ map__browse(browser->selection->map);
+ else if (choice == zoom_dso) {
+zoom_dso:
+ if (browser->hists->dso_filter) {
+ pstack__remove(fstack, &browser->hists->dso_filter);
+zoom_out_dso:
+ ui_helpline__pop();
+ browser->hists->dso_filter = NULL;
+ perf_hpp__set_elide(HISTC_DSO, false);
+ } 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);
+ browser->hists->dso_filter = dso;
+ perf_hpp__set_elide(HISTC_DSO, true);
+ pstack__push(fstack, &browser->hists->dso_filter);
+ }
+ hists__filter_by_dso(hists);
+ hist_browser__reset(browser);
+ } else if (choice == zoom_thread) {
+zoom_thread:
+ if (browser->hists->thread_filter) {
+ pstack__remove(fstack, &browser->hists->thread_filter);
+zoom_out_thread:
+ ui_helpline__pop();
+ thread__zput(browser->hists->thread_filter);
+ perf_hpp__set_elide(HISTC_THREAD, false);
+ } else {
+ ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
+ thread->comm_set ? thread__comm_str(thread) : "",
+ thread->tid);
+ browser->hists->thread_filter = thread__get(thread);
+ perf_hpp__set_elide(HISTC_THREAD, false);
+ pstack__push(fstack, &browser->hists->thread_filter);
+ }
+ hists__filter_by_thread(hists);
+ hist_browser__reset(browser);
+ }
+ /* perf scripts support */
+ else if (choice == scripts_all || choice == scripts_comm ||
+ choice == scripts_symbol) {
+do_scripts:
+ memset(script_opt, 0, 64);
+
+ if (choice == scripts_comm)
+ sprintf(script_opt, " -c %s ", thread__comm_str(browser->he_selection->thread));
+
+ if (choice == scripts_symbol)
+ sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name);
+
+ script_browse(script_opt);
+ }
+ /* Switch to another data file */
+ else if (choice == switch_data) {
+do_data_switch:
+ if (!switch_data_file()) {
+ key = K_SWITCH_INPUT_DATA;
+ break;
+ } else
+ ui__warning("Won't switch the data files due to\n"
+ "no valid data file get selected!\n");
+ }
+ }
+out_free_stack:
+ pstack__delete(fstack);
+out:
+ hist_browser__delete(browser);
+ free_popup_options(options, nr_options - 1);
+ return key;
+}
+
+struct perf_evsel_menu {
+ struct ui_browser b;
+ struct perf_evsel *selection;
+ bool lost_events, lost_events_warned;
+ float min_pcnt;
+ struct perf_session_env *env;
+};
+
+static void perf_evsel_menu__write(struct ui_browser *browser,
+ void *entry, int row)
+{
+ struct perf_evsel_menu *menu = container_of(browser,
+ struct perf_evsel_menu, b);
+ struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
+ struct hists *hists = evsel__hists(evsel);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ unsigned long nr_events = hists->stats.nr_events[PERF_RECORD_SAMPLE];
+ const char *ev_name = perf_evsel__name(evsel);
+ char bf[256], unit;
+ const char *warn = " ";
+ size_t printed;
+
+ ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
+ HE_COLORSET_NORMAL);
+
+ if (perf_evsel__is_group_event(evsel)) {
+ struct perf_evsel *pos;
+
+ ev_name = perf_evsel__group_name(evsel);
+
+ for_each_group_member(pos, evsel) {
+ struct hists *pos_hists = evsel__hists(pos);
+ nr_events += pos_hists->stats.nr_events[PERF_RECORD_SAMPLE];
+ }
+ }
+
+ nr_events = convert_unit(nr_events, &unit);
+ printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
+ unit, unit == ' ' ? "" : " ", ev_name);
+ slsmg_printf("%s", bf);
+
+ nr_events = hists->stats.nr_events[PERF_RECORD_LOST];
+ if (nr_events != 0) {
+ menu->lost_events = true;
+ if (!current_entry)
+ ui_browser__set_color(browser, HE_COLORSET_TOP);
+ nr_events = convert_unit(nr_events, &unit);
+ printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
+ nr_events, unit, unit == ' ' ? "" : " ");
+ warn = bf;
+ }
+
+ slsmg_write_nstring(warn, browser->width - printed);
+
+ if (current_entry)
+ menu->selection = evsel;
+}
+
+static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
+ int nr_events, const char *help,
+ struct hist_browser_timer *hbt)
+{
+ struct perf_evlist *evlist = menu->b.priv;
+ struct perf_evsel *pos;
+ const char *title = "Available samples";
+ int delay_secs = hbt ? hbt->refresh : 0;
+ int key;
+
+ if (ui_browser__show(&menu->b, title,
+ "ESC: exit, ENTER|->: Browse histograms") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&menu->b, delay_secs);
+
+ switch (key) {
+ case K_TIMER:
+ hbt->timer(hbt->arg);
+
+ if (!menu->lost_events_warned && menu->lost_events) {
+ ui_browser__warn_lost_events(&menu->b);
+ menu->lost_events_warned = true;
+ }
+ continue;
+ case K_RIGHT:
+ case K_ENTER:
+ if (!menu->selection)
+ continue;
+ pos = menu->selection;
+browse_hists:
+ perf_evlist__set_selected(evlist, pos);
+ /*
+ * Give the calling tool a chance to populate the non
+ * default evsel resorted hists tree.
+ */
+ if (hbt)
+ hbt->timer(hbt->arg);
+ key = perf_evsel__hists_browse(pos, nr_events, help,
+ true, hbt,
+ menu->min_pcnt,
+ menu->env);
+ ui_browser__show_title(&menu->b, title);
+ switch (key) {
+ case K_TAB:
+ if (pos->node.next == &evlist->entries)
+ pos = perf_evlist__first(evlist);
+ else
+ pos = perf_evsel__next(pos);
+ goto browse_hists;
+ case K_UNTAB:
+ if (pos->node.prev == &evlist->entries)
+ pos = perf_evlist__last(evlist);
+ else
+ pos = perf_evsel__prev(pos);
+ goto browse_hists;
+ case K_ESC:
+ if (!ui_browser__dialog_yesno(&menu->b,
+ "Do you really want to exit?"))
+ continue;
+ /* Fall thru */
+ case K_SWITCH_INPUT_DATA:
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ default:
+ continue;
+ }
+ case K_LEFT:
+ continue;
+ case K_ESC:
+ if (!ui_browser__dialog_yesno(&menu->b,
+ "Do you really want to exit?"))
+ continue;
+ /* Fall thru */
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ default:
+ continue;
+ }
+ }
+
+out:
+ ui_browser__hide(&menu->b);
+ return key;
+}
+
+static bool filter_group_entries(struct ui_browser *browser __maybe_unused,
+ void *entry)
+{
+ struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
+
+ if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel))
+ return true;
+
+ return false;
+}
+
+static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
+ int nr_entries, const char *help,
+ struct hist_browser_timer *hbt,
+ float min_pcnt,
+ struct perf_session_env *env)
+{
+ struct perf_evsel *pos;
+ struct perf_evsel_menu menu = {
+ .b = {
+ .entries = &evlist->entries,
+ .refresh = ui_browser__list_head_refresh,
+ .seek = ui_browser__list_head_seek,
+ .write = perf_evsel_menu__write,
+ .filter = filter_group_entries,
+ .nr_entries = nr_entries,
+ .priv = evlist,
+ },
+ .min_pcnt = min_pcnt,
+ .env = env,
+ };
+
+ ui_helpline__push("Press ESC to exit");
+
+ evlist__for_each(evlist, pos) {
+ const char *ev_name = perf_evsel__name(pos);
+ size_t line_len = strlen(ev_name) + 7;
+
+ if (menu.b.width < line_len)
+ menu.b.width = line_len;
+ }
+
+ return perf_evsel_menu__run(&menu, nr_entries, help, hbt);
+}
+
+int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
+ struct hist_browser_timer *hbt,
+ float min_pcnt,
+ struct perf_session_env *env)
+{
+ int nr_entries = evlist->nr_entries;
+
+single_entry:
+ if (nr_entries == 1) {
+ struct perf_evsel *first = perf_evlist__first(evlist);
+
+ return perf_evsel__hists_browse(first, nr_entries, help,
+ false, hbt, min_pcnt,
+ env);
+ }
+
+ if (symbol_conf.event_group) {
+ struct perf_evsel *pos;
+
+ nr_entries = 0;
+ evlist__for_each(evlist, pos) {
+ if (perf_evsel__is_group_leader(pos))
+ nr_entries++;
+ }
+
+ if (nr_entries == 1)
+ goto single_entry;
+ }
+
+ return __perf_evlist__tui_browse_hists(evlist, nr_entries, help,
+ hbt, min_pcnt, env);
+}
diff --git a/tools/perf/ui/browsers/map.c b/tools/perf/ui/browsers/map.c
new file mode 100644
index 000000000..b11639f33
--- /dev/null
+++ b/tools/perf/ui/browsers/map.c
@@ -0,0 +1,130 @@
+#include "../libslang.h"
+#include <elf.h>
+#include <inttypes.h>
+#include <sys/ttydefaults.h>
+#include <string.h>
+#include <linux/bitops.h>
+#include "../../util/util.h"
+#include "../../util/debug.h"
+#include "../../util/symbol.h"
+#include "../browser.h"
+#include "../helpline.h"
+#include "../keysyms.h"
+#include "map.h"
+
+struct map_browser {
+ struct ui_browser b;
+ struct map *map;
+ u8 addrlen;
+};
+
+static void map_browser__write(struct ui_browser *browser, void *nd, int row)
+{
+ struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
+ struct map_browser *mb = container_of(browser, struct map_browser, b);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ int width;
+
+ ui_browser__set_percent_color(browser, 0, current_entry);
+ slsmg_printf("%*" PRIx64 " %*" PRIx64 " %c ",
+ mb->addrlen, sym->start, mb->addrlen, sym->end,
+ sym->binding == STB_GLOBAL ? 'g' :
+ sym->binding == STB_LOCAL ? 'l' : 'w');
+ width = browser->width - ((mb->addrlen * 2) + 4);
+ if (width > 0)
+ slsmg_write_nstring(sym->name, width);
+}
+
+/* FIXME uber-kludgy, see comment on cmd_report... */
+static u32 *symbol__browser_index(struct symbol *browser)
+{
+ return ((void *)browser) - sizeof(struct rb_node) - sizeof(u32);
+}
+
+static int map_browser__search(struct map_browser *browser)
+{
+ char target[512];
+ struct symbol *sym;
+ int err = ui_browser__input_window("Search by name/addr",
+ "Prefix with 0x to search by address",
+ target, "ENTER: OK, ESC: Cancel", 0);
+ if (err != K_ENTER)
+ return -1;
+
+ if (target[0] == '0' && tolower(target[1]) == 'x') {
+ u64 addr = strtoull(target, NULL, 16);
+ sym = map__find_symbol(browser->map, addr, NULL);
+ } else
+ sym = map__find_symbol_by_name(browser->map, target, NULL);
+
+ if (sym != NULL) {
+ u32 *idx = symbol__browser_index(sym);
+
+ browser->b.top = &sym->rb_node;
+ browser->b.index = browser->b.top_idx = *idx;
+ } else
+ ui_helpline__fpush("%s not found!", target);
+
+ return 0;
+}
+
+static int map_browser__run(struct map_browser *browser)
+{
+ int key;
+
+ if (ui_browser__show(&browser->b, browser->map->dso->long_name,
+ "Press <- or ESC to exit, %s / to search",
+ verbose ? "" : "restart with -v to use") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, 0);
+
+ switch (key) {
+ case '/':
+ if (verbose)
+ map_browser__search(browser);
+ default:
+ break;
+ case K_LEFT:
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ }
+ }
+out:
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+int map__browse(struct map *map)
+{
+ struct map_browser mb = {
+ .b = {
+ .entries = &map->dso->symbols[map->type],
+ .refresh = ui_browser__rb_tree_refresh,
+ .seek = ui_browser__rb_tree_seek,
+ .write = map_browser__write,
+ },
+ .map = map,
+ };
+ struct rb_node *nd;
+ char tmp[BITS_PER_LONG / 4];
+ u64 maxaddr = 0;
+
+ for (nd = rb_first(mb.b.entries); nd; nd = rb_next(nd)) {
+ struct symbol *pos = rb_entry(nd, struct symbol, rb_node);
+
+ if (maxaddr < pos->end)
+ maxaddr = pos->end;
+ if (verbose) {
+ u32 *idx = symbol__browser_index(pos);
+ *idx = mb.b.nr_entries;
+ }
+ ++mb.b.nr_entries;
+ }
+
+ mb.addrlen = snprintf(tmp, sizeof(tmp), "%" PRIx64, maxaddr);
+ return map_browser__run(&mb);
+}
diff --git a/tools/perf/ui/browsers/map.h b/tools/perf/ui/browsers/map.h
new file mode 100644
index 000000000..2d58e4b3e
--- /dev/null
+++ b/tools/perf/ui/browsers/map.h
@@ -0,0 +1,6 @@
+#ifndef _PERF_UI_MAP_BROWSER_H_
+#define _PERF_UI_MAP_BROWSER_H_ 1
+struct map;
+
+int map__browse(struct map *map);
+#endif /* _PERF_UI_MAP_BROWSER_H_ */
diff --git a/tools/perf/ui/browsers/scripts.c b/tools/perf/ui/browsers/scripts.c
new file mode 100644
index 000000000..402d2bd30
--- /dev/null
+++ b/tools/perf/ui/browsers/scripts.c
@@ -0,0 +1,187 @@
+#include <elf.h>
+#include <inttypes.h>
+#include <sys/ttydefaults.h>
+#include <string.h>
+#include "../../util/sort.h"
+#include "../../util/util.h"
+#include "../../util/hist.h"
+#include "../../util/debug.h"
+#include "../../util/symbol.h"
+#include "../browser.h"
+#include "../helpline.h"
+#include "../libslang.h"
+
+/* 2048 lines should be enough for a script output */
+#define MAX_LINES 2048
+
+/* 160 bytes for one output line */
+#define AVERAGE_LINE_LEN 160
+
+struct script_line {
+ struct list_head node;
+ char line[AVERAGE_LINE_LEN];
+};
+
+struct perf_script_browser {
+ struct ui_browser b;
+ struct list_head entries;
+ const char *script_name;
+ int nr_lines;
+};
+
+#define SCRIPT_NAMELEN 128
+#define SCRIPT_MAX_NO 64
+/*
+ * Usually the full path for a script is:
+ * /home/username/libexec/perf-core/scripts/python/xxx.py
+ * /home/username/libexec/perf-core/scripts/perl/xxx.pl
+ * So 256 should be long enough to contain the full path.
+ */
+#define SCRIPT_FULLPATH_LEN 256
+
+/*
+ * When success, will copy the full path of the selected script
+ * into the buffer pointed by script_name, and return 0.
+ * Return -1 on failure.
+ */
+static int list_scripts(char *script_name)
+{
+ char *buf, *names[SCRIPT_MAX_NO], *paths[SCRIPT_MAX_NO];
+ int i, num, choice, ret = -1;
+
+ /* Preset the script name to SCRIPT_NAMELEN */
+ buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN));
+ if (!buf)
+ return ret;
+
+ for (i = 0; i < SCRIPT_MAX_NO; i++) {
+ names[i] = buf + i * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN);
+ paths[i] = names[i] + SCRIPT_NAMELEN;
+ }
+
+ num = find_scripts(names, paths);
+ if (num > 0) {
+ choice = ui__popup_menu(num, names);
+ if (choice < num && choice >= 0) {
+ strcpy(script_name, paths[choice]);
+ ret = 0;
+ }
+ }
+
+ free(buf);
+ return ret;
+}
+
+static void script_browser__write(struct ui_browser *browser,
+ void *entry, int row)
+{
+ struct script_line *sline = list_entry(entry, struct script_line, node);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+
+ ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
+ HE_COLORSET_NORMAL);
+
+ slsmg_write_nstring(sline->line, browser->width);
+}
+
+static int script_browser__run(struct perf_script_browser *browser)
+{
+ int key;
+
+ if (ui_browser__show(&browser->b, browser->script_name,
+ "Press <- or ESC to exit") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, 0);
+
+ /* We can add some special key handling here if needed */
+ break;
+ }
+
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+
+int script_browse(const char *script_opt)
+{
+ char cmd[SCRIPT_FULLPATH_LEN*2], script_name[SCRIPT_FULLPATH_LEN];
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t retlen;
+ int ret = -1, nr_entries = 0;
+ FILE *fp;
+ void *buf;
+ struct script_line *sline;
+
+ struct perf_script_browser script = {
+ .b = {
+ .refresh = ui_browser__list_head_refresh,
+ .seek = ui_browser__list_head_seek,
+ .write = script_browser__write,
+ },
+ .script_name = script_name,
+ };
+
+ INIT_LIST_HEAD(&script.entries);
+
+ /* Save each line of the output in one struct script_line object. */
+ buf = zalloc((sizeof(*sline)) * MAX_LINES);
+ if (!buf)
+ return -1;
+ sline = buf;
+
+ memset(script_name, 0, SCRIPT_FULLPATH_LEN);
+ if (list_scripts(script_name))
+ goto exit;
+
+ sprintf(cmd, "perf script -s %s ", script_name);
+
+ if (script_opt)
+ strcat(cmd, script_opt);
+
+ if (input_name) {
+ strcat(cmd, " -i ");
+ strcat(cmd, input_name);
+ }
+
+ strcat(cmd, " 2>&1");
+
+ fp = popen(cmd, "r");
+ if (!fp)
+ goto exit;
+
+ while ((retlen = getline(&line, &len, fp)) != -1) {
+ strncpy(sline->line, line, AVERAGE_LINE_LEN);
+
+ /* If one output line is very large, just cut it short */
+ if (retlen >= AVERAGE_LINE_LEN) {
+ sline->line[AVERAGE_LINE_LEN - 1] = '\0';
+ sline->line[AVERAGE_LINE_LEN - 2] = '\n';
+ }
+ list_add_tail(&sline->node, &script.entries);
+
+ if (script.b.width < retlen)
+ script.b.width = retlen;
+
+ if (nr_entries++ >= MAX_LINES - 1)
+ break;
+ sline++;
+ }
+
+ if (script.b.width > AVERAGE_LINE_LEN)
+ script.b.width = AVERAGE_LINE_LEN;
+
+ free(line);
+ pclose(fp);
+
+ script.nr_lines = nr_entries;
+ script.b.nr_entries = nr_entries;
+ script.b.entries = &script.entries;
+
+ ret = script_browser__run(&script);
+exit:
+ free(buf);
+ return ret;
+}