diff options
author | Ingo Molnar <mingo@elte.hu> | 2010-08-11 09:39:10 +0200 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2010-08-11 09:39:10 +0200 |
commit | b3e84ffa21f916e3354a12a7f19169c9febe96d0 (patch) | |
tree | 1248316ef0cf5bee08309d492cf925fd87662ea8 /tools | |
parent | 1c250d709fdc8aa5bf42d90be99428a01a256a55 (diff) | |
parent | 4694153c252a6ae19704b5bb66466050256395a4 (diff) | |
download | linux-b3e84ffa21f916e3354a12a7f19169c9febe96d0.tar.gz linux-b3e84ffa21f916e3354a12a7f19169c9febe96d0.tar.bz2 linux-b3e84ffa21f916e3354a12a7f19169c9febe96d0.zip |
Merge branch 'perf/core' into perf/urgent
Diffstat (limited to 'tools')
-rw-r--r-- | tools/perf/Makefile | 25 | ||||
-rw-r--r-- | tools/perf/builtin-annotate.c | 2 | ||||
-rw-r--r-- | tools/perf/util/debug.c | 2 | ||||
-rw-r--r-- | tools/perf/util/debug.h | 9 | ||||
-rw-r--r-- | tools/perf/util/hist.c | 13 | ||||
-rw-r--r-- | tools/perf/util/hist.h | 3 | ||||
-rw-r--r-- | tools/perf/util/pstack.h | 2 | ||||
-rw-r--r-- | tools/perf/util/symbol.c | 10 | ||||
-rw-r--r-- | tools/perf/util/ui/browser.c | 76 | ||||
-rw-r--r-- | tools/perf/util/ui/browser.h | 11 | ||||
-rw-r--r-- | tools/perf/util/ui/browsers/annotate.c | 240 | ||||
-rw-r--r-- | tools/perf/util/ui/browsers/hists.c (renamed from tools/perf/util/newt.c) | 1375 | ||||
-rw-r--r-- | tools/perf/util/ui/browsers/map.c | 161 | ||||
-rw-r--r-- | tools/perf/util/ui/browsers/map.h | 6 | ||||
-rw-r--r-- | tools/perf/util/ui/helpline.c | 69 | ||||
-rw-r--r-- | tools/perf/util/ui/helpline.h | 11 | ||||
-rw-r--r-- | tools/perf/util/ui/libslang.h | 27 | ||||
-rw-r--r-- | tools/perf/util/ui/progress.c | 60 | ||||
-rw-r--r-- | tools/perf/util/ui/progress.h | 11 | ||||
-rw-r--r-- | tools/perf/util/ui/setup.c | 42 | ||||
-rw-r--r-- | tools/perf/util/ui/util.c | 114 | ||||
-rw-r--r-- | tools/perf/util/ui/util.h | 10 |
22 files changed, 1270 insertions, 1009 deletions
diff --git a/tools/perf/Makefile b/tools/perf/Makefile index d5bce768b4bf..41abb90df50d 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -158,7 +158,7 @@ all:: # Define NO_DWARF if you do not want debug-info analysis feature at all. $(shell sh -c 'mkdir -p $(OUTPUT)scripts/{perl,python}/Perf-Trace-Util/' 2> /dev/null) -$(shell sh -c 'mkdir -p $(OUTPUT)util/{ui,scripting-engines}/' 2> /dev/null) +$(shell sh -c 'mkdir -p $(OUTPUT)util/{ui/browsers,scripting-engines}/' 2> /dev/null) $(shell sh -c 'mkdir $(OUTPUT)bench' 2> /dev/null) $(OUTPUT)PERF-VERSION-FILE: .FORCE-PERF-VERSION-FILE @@ -567,9 +567,20 @@ else # Fedora has /usr/include/slang/slang.h, but ubuntu /usr/include/slang.h BASIC_CFLAGS += -I/usr/include/slang EXTLIBS += -lnewt -lslang - LIB_OBJS += $(OUTPUT)util/newt.o + LIB_OBJS += $(OUTPUT)util/ui/setup.o LIB_OBJS += $(OUTPUT)util/ui/browser.o + LIB_OBJS += $(OUTPUT)util/ui/browsers/annotate.o + LIB_OBJS += $(OUTPUT)util/ui/browsers/hists.o + LIB_OBJS += $(OUTPUT)util/ui/browsers/map.o + LIB_OBJS += $(OUTPUT)util/ui/helpline.o + LIB_OBJS += $(OUTPUT)util/ui/progress.o + LIB_OBJS += $(OUTPUT)util/ui/util.o LIB_H += util/ui/browser.h + LIB_H += util/ui/browsers/map.h + LIB_H += util/ui/helpline.h + LIB_H += util/ui/libslang.h + LIB_H += util/ui/progress.h + LIB_H += util/ui/util.h endif endif @@ -967,10 +978,16 @@ $(OUTPUT)builtin-init-db.o: builtin-init-db.c $(OUTPUT)PERF-CFLAGS $(OUTPUT)util/config.o: util/config.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DETC_PERFCONFIG='"$(ETC_PERFCONFIG_SQ)"' $< -$(OUTPUT)util/newt.o: util/newt.c $(OUTPUT)PERF-CFLAGS +$(OUTPUT)util/ui/browser.o: util/ui/browser.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< -$(OUTPUT)util/ui/browser.o: util/ui/browser.c $(OUTPUT)PERF-CFLAGS +$(OUTPUT)util/ui/browsers/annotate.o: util/ui/browsers/annotate.c $(OUTPUT)PERF-CFLAGS + $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< + +$(OUTPUT)util/ui/browsers/hists.o: util/ui/browsers/hists.c $(OUTPUT)PERF-CFLAGS + $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< + +$(OUTPUT)util/ui/browsers/map.o: util/ui/browsers/map.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) -DENABLE_SLFUTURE_CONST $< $(OUTPUT)util/rbtree.o: ../../lib/rbtree.c $(OUTPUT)PERF-CFLAGS diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index fd20670ce986..1478dc64bf15 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -285,7 +285,7 @@ static int hist_entry__tty_annotate(struct hist_entry *he) LIST_HEAD(head); struct objdump_line *pos, *n; - if (hist_entry__annotate(he, &head) < 0) + if (hist_entry__annotate(he, &head, 0) < 0) return -1; if (full_paths) diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 318dab15d177..f9c7e3ad1aa7 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -23,7 +23,7 @@ int eprintf(int level, const char *fmt, ...) if (verbose >= level) { va_start(args, fmt); if (use_browser > 0) - ret = browser__show_help(fmt, args); + ret = ui_helpline__show_help(fmt, args); else ret = vfprintf(stderr, fmt, args); va_end(args); diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 047ac3324ebe..7a17ee061bcb 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -14,7 +14,7 @@ void trace_event(event_t *event); struct ui_progress; #ifdef NO_NEWT_SUPPORT -static inline int browser__show_help(const char *format __used, va_list ap __used) +static inline int ui_helpline__show_help(const char *format __used, va_list ap __used) { return 0; } @@ -30,10 +30,9 @@ static inline void ui_progress__update(struct ui_progress *self __used, static inline void ui_progress__delete(struct ui_progress *self __used) {} #else -int browser__show_help(const char *format, va_list ap); -struct ui_progress *ui_progress__new(const char *title, u64 total); -void ui_progress__update(struct ui_progress *self, u64 curr); -void ui_progress__delete(struct ui_progress *self); +extern char ui_helpline__last_msg[]; +int ui_helpline__show_help(const char *format, va_list ap); +#include "ui/progress.h" #endif #endif /* __PERF_DEBUG_H */ diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 62ec9b0e4b9a..be22ae6ef055 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -983,9 +983,9 @@ int hist_entry__inc_addr_samples(struct hist_entry *self, u64 ip) return 0; } -static struct objdump_line *objdump_line__new(s64 offset, char *line) +static struct objdump_line *objdump_line__new(s64 offset, char *line, size_t privsize) { - struct objdump_line *self = malloc(sizeof(*self)); + struct objdump_line *self = malloc(sizeof(*self) + privsize); if (self != NULL) { self->offset = offset; @@ -1017,7 +1017,7 @@ struct objdump_line *objdump__get_next_ip_line(struct list_head *head, } static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, - struct list_head *head) + struct list_head *head, size_t privsize) { struct symbol *sym = self->ms.sym; struct objdump_line *objdump_line; @@ -1068,7 +1068,7 @@ static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, offset = -1; } - objdump_line = objdump_line__new(offset, line); + objdump_line = objdump_line__new(offset, line, privsize); if (objdump_line == NULL) { free(line); return -1; @@ -1078,7 +1078,8 @@ static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, return 0; } -int hist_entry__annotate(struct hist_entry *self, struct list_head *head) +int hist_entry__annotate(struct hist_entry *self, struct list_head *head, + size_t privsize) { struct symbol *sym = self->ms.sym; struct map *map = self->ms.map; @@ -1143,7 +1144,7 @@ fallback: goto out_free_filename; while (!feof(file)) - if (hist_entry__parse_objdump_line(self, file, head) < 0) + if (hist_entry__parse_objdump_line(self, file, head, privsize) < 0) break; pclose(file); diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 65a48db46a29..587d375d3430 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -101,7 +101,8 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, bool show_displacement, FILE *fp); int hist_entry__inc_addr_samples(struct hist_entry *self, u64 ip); -int hist_entry__annotate(struct hist_entry *self, struct list_head *head); +int hist_entry__annotate(struct hist_entry *self, struct list_head *head, + size_t privsize); void hists__filter_by_dso(struct hists *self, const struct dso *dso); void hists__filter_by_thread(struct hists *self, const struct thread *thread); diff --git a/tools/perf/util/pstack.h b/tools/perf/util/pstack.h index 5ad07023504b..4cedea59f518 100644 --- a/tools/perf/util/pstack.h +++ b/tools/perf/util/pstack.h @@ -1,6 +1,8 @@ #ifndef _PERF_PSTACK_ #define _PERF_PSTACK_ +#include <stdbool.h> + struct pstack; struct pstack *pstack__new(unsigned short max_nr_entries); void pstack__delete(struct pstack *self); diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index b6f5970f9106..1a367734e016 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -1079,6 +1079,16 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, if (!is_label && !elf_sym__is_a(&sym, map->type)) continue; + /* Reject ARM ELF "mapping symbols": these aren't unique and + * don't identify functions, so will confuse the profile + * output: */ + if (ehdr.e_machine == EM_ARM) { + if (!strcmp(elf_name, "$a") || + !strcmp(elf_name, "$d") || + !strcmp(elf_name, "$t")) + continue; + } + if (opdsec && sym.st_shndx == opdidx) { u32 offset = sym.st_value - opdshdr.sh_addr; u64 *opd = opddata->d_buf + offset; diff --git a/tools/perf/util/ui/browser.c b/tools/perf/util/ui/browser.c index 0b2b9306312d..66f2d583d8c4 100644 --- a/tools/perf/util/ui/browser.c +++ b/tools/perf/util/ui/browser.c @@ -16,6 +16,7 @@ #include <stdlib.h> #include <sys/ttydefaults.h> #include "browser.h" +#include "helpline.h" #include "../color.h" #include "../util.h" @@ -49,7 +50,7 @@ void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whenc pos = head->next; break; case SEEK_CUR: - pos = self->first_visible_entry; + pos = self->top; break; case SEEK_END: pos = head->prev; @@ -66,7 +67,7 @@ void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whenc pos = pos->prev; } - self->first_visible_entry = pos; + self->top = pos; } void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence) @@ -79,7 +80,7 @@ void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence) nd = rb_first(root); break; case SEEK_CUR: - nd = self->first_visible_entry; + nd = self->top; break; case SEEK_END: nd = rb_last(root); @@ -96,7 +97,7 @@ void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence) nd = rb_prev(nd); } - self->first_visible_entry = nd; + self->top = nd; } unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self) @@ -104,13 +105,13 @@ unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self) struct rb_node *nd; int row = 0; - if (self->first_visible_entry == NULL) - self->first_visible_entry = rb_first(self->entries); + if (self->top == NULL) + self->top = rb_first(self->entries); - nd = self->first_visible_entry; + nd = self->top; while (nd != NULL) { - SLsmg_gotorc(self->top + row, self->left); + SLsmg_gotorc(self->y + row, self->x); self->write(self, nd, row); if (++row == self->height) break; @@ -122,7 +123,7 @@ unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self) bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row) { - return (self->first_visible_entry_idx + row) == self->index; + return self->top_idx + row == self->index; } void ui_browser__refresh_dimensions(struct ui_browser *self) @@ -135,18 +136,21 @@ void ui_browser__refresh_dimensions(struct ui_browser *self) 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; + self->y = (rows - self->height) / 2; + self->x = (cols - self->width) / 2; } void ui_browser__reset_index(struct ui_browser *self) { - self->index = self->first_visible_entry_idx = 0; + self->index = self->top_idx = 0; self->seek(self, 0, SEEK_SET); } -int ui_browser__show(struct ui_browser *self, const char *title) +int ui_browser__show(struct ui_browser *self, const char *title, + const char *helpline, ...) { + va_list ap; + if (self->form != NULL) { newtFormDestroy(self->form); newtPopWindow(); @@ -169,10 +173,23 @@ int ui_browser__show(struct ui_browser *self, const char *title) newtFormAddHotKey(self->form, NEWT_KEY_PGDN); newtFormAddHotKey(self->form, NEWT_KEY_HOME); newtFormAddHotKey(self->form, NEWT_KEY_END); + newtFormAddHotKey(self->form, ' '); newtFormAddComponent(self->form, self->sb); + + va_start(ap, helpline); + ui_helpline__vpush(helpline, ap); + va_end(ap); return 0; } +void ui_browser__hide(struct ui_browser *self) +{ + newtFormDestroy(self->form); + newtPopWindow(); + self->form = NULL; + ui_helpline__pop(); +} + int ui_browser__refresh(struct ui_browser *self) { int row; @@ -180,7 +197,7 @@ int ui_browser__refresh(struct ui_browser *self) newtScrollbarSet(self->sb, self->index, self->nr_entries - 1); row = self->refresh(self); SLsmg_set_color(HE_COLORSET_NORMAL); - SLsmg_fill_region(self->top + row, self->left, + SLsmg_fill_region(self->y + row, self->x, self->height - row, self->width, ' '); return 0; @@ -205,8 +222,8 @@ int ui_browser__run(struct ui_browser *self, struct newtExitStruct *es) if (self->index == self->nr_entries - 1) break; ++self->index; - if (self->index == self->first_visible_entry_idx + self->height) { - ++self->first_visible_entry_idx; + if (self->index == self->top_idx + self->height) { + ++self->top_idx; self->seek(self, +1, SEEK_CUR); } break; @@ -214,34 +231,34 @@ int ui_browser__run(struct ui_browser *self, struct newtExitStruct *es) if (self->index == 0) break; --self->index; - if (self->index < self->first_visible_entry_idx) { - --self->first_visible_entry_idx; + if (self->index < self->top_idx) { + --self->top_idx; self->seek(self, -1, SEEK_CUR); } break; case NEWT_KEY_PGDN: case ' ': - if (self->first_visible_entry_idx + self->height > self->nr_entries - 1) + if (self->top_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; + self->top_idx += offset; self->seek(self, +offset, SEEK_CUR); break; case NEWT_KEY_PGUP: - if (self->first_visible_entry_idx == 0) + if (self->top_idx == 0) break; - if (self->first_visible_entry_idx < self->height) - offset = self->first_visible_entry_idx; + if (self->top_idx < self->height) + offset = self->top_idx; else offset = self->height; self->index -= offset; - self->first_visible_entry_idx -= offset; + self->top_idx -= offset; self->seek(self, -offset, SEEK_CUR); break; case NEWT_KEY_HOME: @@ -253,7 +270,7 @@ int ui_browser__run(struct ui_browser *self, struct newtExitStruct *es) offset = self->nr_entries - 1; self->index = self->nr_entries - 1; - self->first_visible_entry_idx = self->index - offset; + self->top_idx = self->index - offset; self->seek(self, -offset, SEEK_END); break; default: @@ -271,14 +288,13 @@ unsigned int ui_browser__list_head_refresh(struct ui_browser *self) struct list_head *head = self->entries; int row = 0; - if (self->first_visible_entry == NULL || - self->first_visible_entry == self->entries) - self->first_visible_entry = head->next; + if (self->top == NULL || self->top == self->entries) + self->top = head->next; - pos = self->first_visible_entry; + pos = self->top; list_for_each_from(pos, head) { - SLsmg_gotorc(self->top + row, self->left); + SLsmg_gotorc(self->y + row, self->x); self->write(self, pos, row); if (++row == self->height) break; diff --git a/tools/perf/util/ui/browser.h b/tools/perf/util/ui/browser.h index bcc4391405bd..0b9f829214f7 100644 --- a/tools/perf/util/ui/browser.h +++ b/tools/perf/util/ui/browser.h @@ -3,6 +3,7 @@ #include <stdbool.h> #include <newt.h> +#include <sys/types.h> #include "../types.h" #define HE_COLORSET_TOP 50 @@ -13,9 +14,9 @@ struct ui_browser { newtComponent form, sb; - u64 index, first_visible_entry_idx; - void *first_visible_entry, *entries; - u16 top, left, width, height; + u64 index, top_idx; + void *top, *entries; + u16 y, x, width, height; void *priv; unsigned int (*refresh)(struct ui_browser *self); void (*write)(struct ui_browser *self, void *entry, int row); @@ -29,7 +30,9 @@ bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row); void ui_browser__refresh_dimensions(struct ui_browser *self); void ui_browser__reset_index(struct ui_browser *self); -int ui_browser__show(struct ui_browser *self, const char *title); +int ui_browser__show(struct ui_browser *self, const char *title, + const char *helpline, ...); +void ui_browser__hide(struct ui_browser *self); int ui_browser__refresh(struct ui_browser *self); int ui_browser__run(struct ui_browser *self, struct newtExitStruct *es); diff --git a/tools/perf/util/ui/browsers/annotate.c b/tools/perf/util/ui/browsers/annotate.c new file mode 100644 index 000000000000..55ff792459ac --- /dev/null +++ b/tools/perf/util/ui/browsers/annotate.c @@ -0,0 +1,240 @@ +#include "../browser.h" +#include "../helpline.h" +#include "../libslang.h" +#include "../../hist.h" +#include "../../sort.h" +#include "../../symbol.h" + +static void ui__error_window(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + newtWinMessagev((char *)"Error", (char *)"Ok", (char *)fmt, ap); + va_end(ap); +} + +struct annotate_browser { + struct ui_browser b; + struct rb_root entries; + struct rb_node *curr_hot; +}; + +struct objdump_line_rb_node { + struct rb_node rb_node; + double percent; + u32 idx; +}; + +static inline +struct objdump_line_rb_node *objdump_line__rb(struct objdump_line *self) +{ + return (struct objdump_line_rb_node *)(self + 1); +} + +static void annotate_browser__write(struct ui_browser *self, void *entry, int row) +{ + struct objdump_line *ol = rb_entry(entry, struct objdump_line, node); + bool current_entry = ui_browser__is_current_entry(self, row); + int width = self->width; + + if (ol->offset != -1) { + struct objdump_line_rb_node *olrb = objdump_line__rb(ol); + int color = ui_browser__percent_color(olrb->percent, current_entry); + SLsmg_set_color(color); + slsmg_printf(" %7.2f ", olrb->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 (!*ol->line) + slsmg_write_nstring(" ", width - 18); + else + slsmg_write_nstring(ol->line, width - 18); +} + +static double objdump_line__calc_percent(struct objdump_line *self, + struct list_head *head, + struct symbol *sym) +{ + double percent = 0.0; + + if (self->offset != -1) { + int len = sym->end - sym->start; + unsigned int hits = 0; + 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; + } + + return percent; +} + +static void objdump__insert_line(struct rb_root *self, + struct objdump_line_rb_node *line) +{ + struct rb_node **p = &self->rb_node; + struct rb_node *parent = NULL; + struct objdump_line_rb_node *l; + + while (*p != NULL) { + parent = *p; + l = rb_entry(parent, struct objdump_line_rb_node, rb_node); + if (line->percent < l->percent) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + rb_link_node(&line->rb_node, parent, p); + rb_insert_color(&line->rb_node, self); +} + +static void annotate_browser__set_top(struct annotate_browser *self, + struct rb_node *nd) +{ + struct objdump_line_rb_node *rbpos; + struct objdump_line *pos; + unsigned back; + + ui_browser__refresh_dimensions(&self->b); + back = self->b.height / 2; + rbpos = rb_entry(nd, struct objdump_line_rb_node, rb_node); + pos = ((struct objdump_line *)rbpos) - 1; + self->b.top_idx = self->b.index = rbpos->idx; + + while (self->b.top_idx != 0 && back != 0) { + pos = list_entry(pos->node.prev, struct objdump_line, node); + + --self->b.top_idx; + --back; + } + + self->b.top = pos; + self->curr_hot = nd; +} + +static int annotate_browser__run(struct annotate_browser *self, + struct newtExitStruct *es) +{ + struct rb_node *nd; + struct hist_entry *he = self->b.priv; + + if (ui_browser__show(&self->b, he->ms.sym->name, + "<- or ESC: exit, TAB/shift+TAB: cycle thru samples") < 0) + return -1; + + newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); + + nd = self->curr_hot; + if (nd) { + newtFormAddHotKey(self->b.form, NEWT_KEY_TAB); + newtFormAddHotKey(self->b.form, NEWT_KEY_UNTAB); + } + + while (1) { + ui_browser__run(&self->b, es); + + if (es->reason != NEWT_EXIT_HOTKEY) + break; + + switch (es->u.key) { + case NEWT_KEY_TAB: + nd = rb_prev(nd); + if (nd == NULL) + nd = rb_last(&self->entries); + annotate_browser__set_top(self, nd); + break; + case NEWT_KEY_UNTAB: + nd = rb_next(nd); + if (nd == NULL) + nd = rb_first(&self->entries); + annotate_browser__set_top(self, nd); + break; + default: + goto out; + } + } +out: + ui_browser__hide(&self->b); + return 0; +} + +int hist_entry__tui_annotate(struct hist_entry *self) +{ + struct newtExitStruct es; + struct objdump_line *pos, *n; + struct objdump_line_rb_node *rbpos; + LIST_HEAD(head); + struct annotate_browser browser = { + .b = { + .entries = &head, + .refresh = ui_browser__list_head_refresh, + .seek = ui_browser__list_head_seek, + .write = annotate_browser__write, + .priv = self, + }, + }; + int ret; + + if (self->ms.sym == NULL) + return -1; + + if (self->ms.map->dso->annotate_warned) + return -1; + + if (hist_entry__annotate(self, &head, sizeof(*rbpos)) < 0) { + ui__error_window(ui_helpline__last_msg); + return -1; + } + + ui_helpline__push("Press <- or ESC to exit"); + + list_for_each_entry(pos, &head, node) { + size_t line_len = strlen(pos->line); + if (browser.b.width < line_len) + browser.b.width = line_len; + rbpos = objdump_line__rb(pos); + rbpos->idx = browser.b.nr_entries++; + rbpos->percent = objdump_line__calc_percent(pos, &head, self->ms.sym); + if (rbpos->percent < 0.01) + continue; + objdump__insert_line(&browser.entries, rbpos); + } + + /* + * Position the browser at the hottest line. + */ + browser.curr_hot = rb_last(&browser.entries); + if (browser.curr_hot) + annotate_browser__set_top(&browser, browser.curr_hot); + + browser.b.width += 18; /* Percentage */ + ret = annotate_browser__run(&browser, &es); + list_for_each_entry_safe(pos, n, &head, node) { + list_del(&pos->node); + objdump_line__free(pos); + } + return ret; +} diff --git a/tools/perf/util/newt.c b/tools/perf/util/ui/browsers/hists.c index 266a9e06525b..dafdf6775d77 100644 --- a/tools/perf/util/newt.c +++ b/tools/perf/util/ui/browsers/hists.c @@ -1,897 +1,272 @@ #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 <signal.h> +#include "../libslang.h" #include <stdlib.h> -#include <elf.h> +#include <string.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" -#include "ui/browser.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 - -newtComponent newt_form__new(void); - -struct ui_progress { - newtComponent form, scale; -}; - -struct ui_progress *ui_progress__new(const char *title, u64 total) -{ - struct ui_progress *self = malloc(sizeof(*self)); - - if (self != NULL) { - int cols; - - if (use_browser <= 0) - return self; - newtGetScreenSize(&cols, NULL); - cols -= 4; - newtCenteredWindow(cols, 1, title); - self->form = newtForm(NULL, NULL, 0); - if (self->form == NULL) - goto out_free_self; - self->scale = newtScale(0, 0, cols, total); - if (self->scale == NULL) - goto out_free_form; - newtFormAddComponent(self->form, self->scale); - newtRefresh(); - } +#include <linux/rbtree.h> - return self; +#include "../../hist.h" +#include "../../pstack.h" +#include "../../sort.h" +#include "../../util.h" -out_free_form: - newtFormDestroy(self->form); -out_free_self: - free(self); - return NULL; -} +#include "../browser.h" +#include "../helpline.h" +#include "../util.h" +#include "map.h" -void ui_progress__update(struct ui_progress *self, u64 curr) -{ - /* - * FIXME: We should have a per UI backend way of showing progress, - * stdio will just show a percentage as NN%, etc. - */ - if (use_browser <= 0) - return; - newtScaleSet(self->scale, curr); - newtRefresh(); -} +struct hist_browser { + struct ui_browser b; + struct hists *hists; + struct hist_entry *he_selection; + struct map_symbol *selection; +}; -void ui_progress__delete(struct ui_progress *self) +static void hist_browser__refresh_dimensions(struct hist_browser *self) { - if (use_browser > 0) { - newtFormDestroy(self->form); - newtPopWindow(); - } - free(self); + /* 3 == +/- toggle symbol before actual hist_entry rendering */ + self->b.width = 3 + (hists__sort_list_width(self->hists) + + sizeof("[k]")); } -static void ui_helpline__pop(void) +static void hist_browser__reset(struct hist_browser *self) { - newtPopHelpLine(); + self->b.nr_entries = self->hists->nr_entries; + hist_browser__refresh_dimensions(self); + ui_browser__reset_index(&self->b); } -static void ui_helpline__push(const char *msg) +static char tree__folded_sign(bool unfolded) { - newtPushHelpLine(msg); + return unfolded ? '-' : '+'; } -static void ui_helpline__vpush(const char *fmt, va_list ap) +static char map_symbol__folded(const struct map_symbol *self) { - char *s; - - if (vasprintf(&s, fmt, ap) < 0) - vfprintf(stderr, fmt, ap); - else { - ui_helpline__push(s); - free(s); - } + return self->has_children ? tree__folded_sign(self->unfolded) : ' '; } -static void ui_helpline__fpush(const char *fmt, ...) +static char hist_entry__folded(const struct hist_entry *self) { - va_list ap; - - va_start(ap, fmt); - ui_helpline__vpush(fmt, ap); - va_end(ap); + return map_symbol__folded(&self->ms); } -static void ui_helpline__puts(const char *msg) +static char callchain_list__folded(const struct callchain_list *self) { - ui_helpline__pop(); - ui_helpline__push(msg); + return map_symbol__folded(&self->ms); } -static int ui_entry__read(const char *title, char *bf, size_t size, int width) +static int callchain_node__count_rows_rb_tree(struct callchain_node *self) { - struct newtExitStruct es; - newtComponent form, entry; - const char *result; - int err = -1; - - newtCenteredWindow(width, 1, title); - form = newtForm(NULL, NULL, 0); - if (form == NULL) - return -1; + int n = 0; + struct rb_node *nd; - entry = newtEntry(0, 0, "0x", width, &result, NEWT_FLAG_SCROLL); - if (entry == NULL) - goto out_free_form; + for (nd = rb_first(&self->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 */ - newtFormAddComponent(form, entry); - newtFormAddHotKey(form, NEWT_KEY_ENTER); - newtFormAddHotKey(form, NEWT_KEY_ESCAPE); - newtFormAddHotKey(form, NEWT_KEY_LEFT); - newtFormAddHotKey(form, CTRL('c')); - newtFormRun(form, &es); + 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 (result != NULL) { - strncpy(bf, result, size); - err = 0; + if (folded_sign == '-') /* Have children and they're unfolded */ + n += callchain_node__count_rows_rb_tree(child); } -out_free_form: - newtPopWindow(); - newtFormDestroy(form); - return 0; -} -static char browser__last_msg[1024]; + return n; +} -int browser__show_help(const char *format, va_list ap) +static int callchain_node__count_rows(struct callchain_node *node) { - int ret; - static int backlog; - - ret = vsnprintf(browser__last_msg + backlog, - sizeof(browser__last_msg) - backlog, format, ap); - backlog += ret; + struct callchain_list *chain; + bool unfolded = false; + int n = 0; - if (browser__last_msg[backlog - 1] == '\n') { - ui_helpline__puts(browser__last_msg); - newtRefresh(); - backlog = 0; + list_for_each_entry(chain, &node->val, list) { + ++n; + unfolded = chain->ms.unfolded; } - return ret; -} - -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'); - newtFormAddHotKey(self, CTRL('c')); -} + if (unfolded) + n += callchain_node__count_rows_rb_tree(node); -newtComponent newt_form__new(void) -{ - newtComponent self = newtForm(NULL, NULL, 0); - if (self) - newt_form__set_exit_keys(self); - return self; + return n; } -static int popup_menu(int argc, char * const argv[]) +static int callchain__count_rows(struct rb_root *chain) { - struct newtExitStruct es; - int i, rc = -1, max_len = 5; - newtComponent listbox, form = newt_form__new(); - - if (form == NULL) - return -1; - - listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); - if (listbox == NULL) - goto out_destroy_form; - - newtFormAddComponent(form, listbox); + struct rb_node *nd; + int n = 0; - for (i = 0; i < argc; ++i) { - int len = strlen(argv[i]); - if (len > max_len) - max_len = len; - if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i)) - goto out_destroy_form; + 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); } - newtCenteredWindow(max_len, argc, NULL); - newtFormRun(form, &es); - rc = newtListboxGetCurrent(listbox) - NULL; - if (es.reason == NEWT_EXIT_HOTKEY) - rc = -1; - newtPopWindow(); -out_destroy_form: - newtFormDestroy(form); - return rc; + return n; } -static int ui__help_window(const char *text) +static bool map_symbol__toggle_fold(struct map_symbol *self) { - 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; -} + if (!self->has_children) + return false; -static bool dialog_yesno(const char *msg) -{ - /* newtWinChoice should really be accepting const char pointers... */ - char yes[] = "Yes", no[] = "No"; - return newtWinChoice(NULL, yes, no, (char *)msg) == 1; + self->unfolded = !self->unfolded; + return true; } -static void ui__error_window(const char *fmt, ...) +static void callchain_node__init_have_children_rb_tree(struct callchain_node *self) { - va_list ap; + struct rb_node *nd = rb_first(&self->rb_root); - va_start(ap, fmt); - newtWinMessagev((char *)"Error", (char *)"Ok", (char *)fmt, ap); - va_end(ap); -} + for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { + struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); + struct callchain_list *chain; + int first = true; -static void annotate_browser__write(struct ui_browser *self, void *entry, int row) -{ - struct objdump_line *ol = rb_entry(entry, struct objdump_line, node); - bool current_entry = ui_browser__is_current_entry(self, row); - int width = self->width; - - if (ol->offset != -1) { - struct hist_entry *he = self->priv; - struct symbol *sym = he->ms.sym; - int len = he->ms.sym->end - he->ms.sym->start; - 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 = ol->offset; - struct objdump_line *next = objdump__get_next_ip_line(self->entries, ol); - - while (offset < (s64)len && - (next == NULL || offset < next->offset)) { - if (sym_ext) { - percent += sym_ext[offset].percent; + list_for_each_entry(chain, &child->val, list) { + if (first) { + first = false; + chain->ms.has_children = chain->list.next != &child->val || + rb_first(&child->rb_root) != NULL; } else - hits += h->ip[offset]; - - ++offset; + chain->ms.has_children = chain->list.next == &child->val && + rb_first(&child->rb_root) != NULL; } - 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); + callchain_node__init_have_children_rb_tree(child); } - - SLsmg_write_char(':'); - slsmg_write_nstring(" ", 8); - if (!*ol->line) - slsmg_write_nstring(" ", width - 18); - else - slsmg_write_nstring(ol->line, width - 18); } -static char *callchain_list__sym_name(struct callchain_list *self, - char *bf, size_t bfsize) -{ - if (self->ms.sym) - return self->ms.sym->name; - - snprintf(bf, bfsize, "%#Lx", self->ip); - return bf; -} - -int hist_entry__tui_annotate(struct hist_entry *self) +static void callchain_node__init_have_children(struct callchain_node *self) { - struct newtExitStruct es; - struct objdump_line *pos, *n; - LIST_HEAD(head); - struct ui_browser browser = { - .entries = &head, - .refresh = ui_browser__list_head_refresh, - .seek = ui_browser__list_head_seek, - .write = annotate_browser__write, - .priv = self, - }; - int ret; - - if (self->ms.sym == NULL) - return -1; - - if (self->ms.map->dso->annotate_warned) - return -1; - - if (hist_entry__annotate(self, &head) < 0) { - ui__error_window(browser__last_msg); - return -1; - } - - ui_helpline__push("Press <- or ESC to exit"); + struct callchain_list *chain; - 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; - } + list_for_each_entry(chain, &self->val, list) + chain->ms.has_children = rb_first(&self->rb_root) != NULL; - browser.width += 18; /* Percentage */ - ui_browser__show(&browser, self->ms.sym->name); - newtFormAddHotKey(browser.form, ' '); - ret = ui_browser__run(&browser, &es); - newtFormDestroy(browser.form); - newtPopWindow(); - list_for_each_entry_safe(pos, n, &head, node) { - list_del(&pos->node); - objdump_line__free(pos); - } - ui_helpline__pop(); - return ret; + callchain_node__init_have_children_rb_tree(self); } -/* -------------------------------------------------------------------- */ - -struct map_browser { - struct ui_browser b; - struct map *map; - u16 namelen; - u8 addrlen; -}; - -static void map_browser__write(struct ui_browser *self, void *nd, int row) +static void callchain__init_have_children(struct rb_root *self) { - struct symbol *sym = rb_entry(nd, struct symbol, rb_node); - struct map_browser *mb = container_of(self, struct map_browser, b); - bool current_entry = ui_browser__is_current_entry(self, row); - int color = ui_browser__percent_color(0, current_entry); - - SLsmg_set_color(color); - slsmg_printf("%*llx %*llx %c ", - mb->addrlen, sym->start, mb->addrlen, sym->end, - sym->binding == STB_GLOBAL ? 'g' : - sym->binding == STB_LOCAL ? 'l' : 'w'); - slsmg_write_nstring(sym->name, mb->namelen); -} + struct rb_node *nd; -/* FIXME uber-kludgy, see comment on cmd_report... */ -static u32 *symbol__browser_index(struct symbol *self) -{ - return ((void *)self) - sizeof(struct rb_node) - sizeof(u32); + for (nd = rb_first(self); nd; nd = rb_next(nd)) { + struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + callchain_node__init_have_children(node); + } } -static int map_browser__search(struct map_browser *self) +static void hist_entry__init_have_children(struct hist_entry *self) { - char target[512]; - struct symbol *sym; - int err = ui_entry__read("Search by name/addr", target, sizeof(target), 40); - - if (err) - return err; - - if (target[0] == '0' && tolower(target[1]) == 'x') { - u64 addr = strtoull(target, NULL, 16); - sym = map__find_symbol(self->map, addr, NULL); - } else - sym = map__find_symbol_by_name(self->map, target, NULL); - - if (sym != NULL) { - u32 *idx = symbol__browser_index(sym); - - self->b.first_visible_entry = &sym->rb_node; - self->b.index = self->b.first_visible_entry_idx = *idx; - } else - ui_helpline__fpush("%s not found!", target); - - return 0; + if (!self->init_have_children) { + callchain__init_have_children(&self->sorted_chain); + self->init_have_children = true; + } } -static int map_browser__run(struct map_browser *self, struct newtExitStruct *es) +static bool hist_browser__toggle_fold(struct hist_browser *self) { - if (ui_browser__show(&self->b, self->map->dso->long_name) < 0) - return -1; - - ui_helpline__fpush("Press <- or ESC to exit, %s / to search", - verbose ? "" : "restart with -v to use"); - newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); - newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); - if (verbose) - newtFormAddHotKey(self->b.form, '/'); + if (map_symbol__toggle_fold(self->selection)) { + struct hist_entry *he = self->he_selection; - while (1) { - ui_browser__run(&self->b, es); + hist_entry__init_have_children(he); + self->hists->nr_entries -= he->nr_rows; - if (es->reason != NEWT_EXIT_HOTKEY) - break; - if (verbose && es->u.key == '/') - map_browser__search(self); + if (he->ms.unfolded) + he->nr_rows = callchain__count_rows(&he->sorted_chain); else - break; - } - - newtFormDestroy(self->b.form); - newtPopWindow(); - ui_helpline__pop(); - return 0; -} + he->nr_rows = 0; + self->hists->nr_entries += he->nr_rows; + self->b.nr_entries = self->hists->nr_entries; -static int map__browse(struct map *self) -{ - struct map_browser mb = { - .b = { - .entries = &self->dso->symbols[self->type], - .refresh = ui_browser__rb_tree_refresh, - .seek = ui_browser__rb_tree_seek, - .write = map_browser__write, - }, - .map = self, - }; - struct newtExitStruct es; - 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 (mb.namelen < pos->namelen) - mb.namelen = pos->namelen; - if (maxaddr < pos->end) - maxaddr = pos->end; - if (verbose) { - u32 *idx = symbol__browser_index(pos); - *idx = mb.b.nr_entries; - } - ++mb.b.nr_entries; + return true; } - mb.addrlen = snprintf(tmp, sizeof(tmp), "%llx", maxaddr); - mb.b.width += mb.addrlen * 2 + 4 + mb.namelen; - return map_browser__run(&mb, &es); + /* If it doesn't have children, no toggling performed */ + return false; } -/* -------------------------------------------------------------------- */ - -struct hist_browser { - struct ui_browser b; - struct hists *hists; - struct hist_entry *he_selection; - struct map_symbol *selection; -}; - -static void hist_browser__reset(struct hist_browser *self); static int hist_browser__run(struct hist_browser *self, const char *title, - struct newtExitStruct *es); -static unsigned int hist_browser__refresh(struct ui_browser *self); -static void ui_browser__hists_seek(struct ui_browser *self, - off_t offset, int whence); - -static struct hist_browser *hist_browser__new(struct hists *hists) -{ - struct hist_browser *self = zalloc(sizeof(*self)); - - if (self) { - self->hists = hists; - self->b.refresh = hist_browser__refresh; - self->b.seek = ui_browser__hists_seek; - } - - return self; -} - -static void hist_browser__delete(struct hist_browser *self) -{ - newtFormDestroy(self->b.form); - newtPopWindow(); - free(self); -} - -static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) -{ - return self->he_selection; -} - -static struct thread *hist_browser__selected_thread(struct hist_browser *self) + struct newtExitStruct *es) { - return self->he_selection->thread; -} + char str[256], unit; + unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE]; -static int hist_browser__title(char *bf, size_t size, const char *ev_name, - const struct dso *dso, const struct thread *thread) -{ - int printed = 0; + self->b.entries = &self->hists->entries; + self->b.nr_entries = self->hists->nr_entries; - 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, "Event: %s", ev_name); -} + hist_browser__refresh_dimensions(self); -int hists__browse(struct hists *self, const char *helpline, const char *ev_name) -{ - struct hist_browser *browser = hist_browser__new(self); - struct pstack *fstack; - const struct thread *thread_filter = NULL; - const struct dso *dso_filter = NULL; - struct newtExitStruct es; - char msg[160]; - int key = -1; + nr_events = convert_unit(nr_events, &unit); + snprintf(str, sizeof(str), "Events: %lu%c ", + nr_events, unit); + newtDrawRootText(0, 0, str); - if (browser == NULL) + if (ui_browser__show(&self->b, title, + "Press '?' for help on key bindings") < 0) return -1; - fstack = pstack__new(2); - if (fstack == NULL) - goto out; - - ui_helpline__push(helpline); + newtFormAddHotKey(self->b.form, 'a'); + newtFormAddHotKey(self->b.form, '?'); + newtFormAddHotKey(self->b.form, 'h'); + newtFormAddHotKey(self->b.form, 'd'); + newtFormAddHotKey(self->b.form, 'D'); + newtFormAddHotKey(self->b.form, 't'); - hist_browser__title(msg, sizeof(msg), ev_name, - dso_filter, thread_filter); + newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); + newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT); + newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); while (1) { - 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, - browse_map = -2; + ui_browser__run(&self->b, es); - if (hist_browser__run(browser, msg, &es)) + if (es->reason != NEWT_EXIT_HOTKEY) break; - - thread = hist_browser__selected_thread(browser); - dso = browser->selection->map ? browser->selection->map->dso : NULL; - - if (es.reason == NEWT_EXIT_HOTKEY) { - key = es.u.key; - - switch (key) { - case NEWT_KEY_F1: - goto do_help; - case NEWT_KEY_TAB: - case NEWT_KEY_UNTAB: - /* - * Exit the browser, let hists__browser_tree - * go to the next or previous - */ - goto out_free_stack; - default:; - } - - key = toupper(key); - switch (key) { - case 'A': - if (browser->selection->map == NULL && - browser->selection->map->dso->annotate_warned) - continue; - goto do_annotate; - 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 (is_exit_key(key)) { - if (key == NEWT_KEY_ESCAPE) { - if (dialog_yesno("Do you really want to exit?")) - break; - else - continue; - } else - break; - } - - 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; - } + switch (es->u.key) { + case 'D': { /* Debug */ + static int seq; + struct hist_entry *h = rb_entry(self->b.top, + struct hist_entry, rb_node); + ui_helpline__pop(); + ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", + seq++, self->b.nr_entries, + self->hists->nr_entries, + self->b.height, + self->b.index, + self->b.top_idx, + h->row_offset, h->nr_rows); } - - 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++; - - if (browser->selection->map != NULL && - asprintf(&options[nr_options], "Browse map details") > 0) - browse_map = 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 (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; - } - - he = hist_browser__selected_entry(browser); - if (he == NULL) - continue; - - hist_entry__tui_annotate(he); - } else if (choice == browse_map) - map__browse(browser->selection->map); - 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), ev_name, - dso_filter, thread_filter); - hist_browser__reset(browser); - } 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), ev_name, - dso_filter, thread_filter); - hist_browser__reset(browser); - } - } -out_free_stack: - pstack__delete(fstack); -out: - hist_browser__delete(browser); - return key; -} - -int hists__tui_browse_tree(struct rb_root *self, const char *help) -{ - struct rb_node *first = rb_first(self), *nd = first, *next; - int key = 0; - - while (nd) { - struct hists *hists = rb_entry(nd, struct hists, rb_node); - const char *ev_name = __event_name(hists->type, hists->config); - - key = hists__browse(hists, help, ev_name); - - if (is_exit_key(key)) - break; - - switch (key) { - case NEWT_KEY_TAB: - next = rb_next(nd); - if (next) - nd = next; - break; - case NEWT_KEY_UNTAB: - if (nd == first) - continue; - nd = rb_prev(nd); + case NEWT_KEY_ENTER: + if (hist_browser__toggle_fold(self)) + break; + /* fall thru */ default: - break; - } - } - - return key; -} - -static void newt_suspend(void *d __used) -{ - newtSuspend(); - raise(SIGTSTP); - newtResume(); -} - -void setup_browser(void) -{ - if (!isatty(1) || !use_browser || dump_trace) { - use_browser = 0; - setup_pager(); - return; - } - - use_browser = 1; - newtInit(); - newtCls(); - newtSetSuspendCallback(newt_suspend, NULL); - ui_helpline__puts(" "); - ui_browser__init(); -} - -void exit_browser(bool wait_for_ok) -{ - if (use_browser > 0) { - if (wait_for_ok) { - char title[] = "Fatal Error", ok[] = "Ok"; - newtWinMessage(title, ok, browser__last_msg); + return 0; } - newtFinished(); } -} -static void hist_browser__refresh_dimensions(struct hist_browser *self) -{ - /* 3 == +/- toggle symbol before actual hist_entry rendering */ - self->b.width = 3 + (hists__sort_list_width(self->hists) + - sizeof("[k]")); -} - -static void hist_browser__reset(struct hist_browser *self) -{ - self->b.nr_entries = self->hists->nr_entries; - hist_browser__refresh_dimensions(self); - ui_browser__reset_index(&self->b); -} - -static char tree__folded_sign(bool unfolded) -{ - return unfolded ? '-' : '+'; -} - -static char map_symbol__folded(const struct map_symbol *self) -{ - return self->has_children ? tree__folded_sign(self->unfolded) : ' '; -} - -static char hist_entry__folded(const struct hist_entry *self) -{ - return map_symbol__folded(&self->ms); -} - -static char callchain_list__folded(const struct callchain_list *self) -{ - return map_symbol__folded(&self->ms); + ui_browser__hide(&self->b); + return 0; } -static bool map_symbol__toggle_fold(struct map_symbol *self) +static char *callchain_list__sym_name(struct callchain_list *self, + char *bf, size_t bfsize) { - if (!self->has_children) - return false; + if (self->ms.sym) + return self->ms.sym->name; - self->unfolded = !self->unfolded; - return true; + snprintf(bf, bfsize, "%#Lx", self->ip); + return bf; } #define LEVEL_OFFSET_STEP 3 @@ -967,7 +342,7 @@ static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, } SLsmg_set_color(color); - SLsmg_gotorc(self->b.top + row, self->b.left); + SLsmg_gotorc(self->b.y + row, self->b.x); slsmg_write_nstring(" ", offset + extra_offset); slsmg_printf("%c ", folded_sign); slsmg_write_nstring(str, width); @@ -1030,7 +405,7 @@ static int hist_browser__show_callchain_node(struct hist_browser *self, } s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); - SLsmg_gotorc(self->b.top + row, self->b.left); + SLsmg_gotorc(self->b.y + row, self->b.x); SLsmg_set_color(color); slsmg_write_nstring(" ", offset); slsmg_printf("%c ", folded_sign); @@ -1110,7 +485,7 @@ static int hist_browser__show_entry(struct hist_browser *self, } SLsmg_set_color(color); - SLsmg_gotorc(self->b.top + row, self->b.left); + SLsmg_gotorc(self->b.y + row, self->b.x); if (symbol_conf.use_callchain) { slsmg_printf("%c ", folded_sign); width -= 2; @@ -1138,10 +513,10 @@ static unsigned int hist_browser__refresh(struct ui_browser *self) struct rb_node *nd; struct hist_browser *hb = container_of(self, struct hist_browser, b); - if (self->first_visible_entry == NULL) - self->first_visible_entry = rb_first(&hb->hists->entries); + if (self->top == NULL) + self->top = rb_first(&hb->hists->entries); - for (nd = self->first_visible_entry; nd; nd = rb_next(nd)) { + for (nd = self->top; nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); if (h->filtered) @@ -1155,57 +530,6 @@ static unsigned int hist_browser__refresh(struct ui_browser *self) return row; } -static void callchain_node__init_have_children_rb_tree(struct callchain_node *self) -{ - struct rb_node *nd = rb_first(&self->rb_root); - - for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { - struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); - struct callchain_list *chain; - int first = true; - - list_for_each_entry(chain, &child->val, list) { - if (first) { - first = false; - chain->ms.has_children = chain->list.next != &child->val || - rb_first(&child->rb_root) != NULL; - } else - chain->ms.has_children = chain->list.next == &child->val && - rb_first(&child->rb_root) != NULL; - } - - callchain_node__init_have_children_rb_tree(child); - } -} - -static void callchain_node__init_have_children(struct callchain_node *self) -{ - struct callchain_list *chain; - - list_for_each_entry(chain, &self->val, list) - chain->ms.has_children = rb_first(&self->rb_root) != NULL; - - callchain_node__init_have_children_rb_tree(self); -} - -static void callchain__init_have_children(struct rb_root *self) -{ - struct rb_node *nd; - - for (nd = rb_first(self); nd; nd = rb_next(nd)) { - struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); - callchain_node__init_have_children(node); - } -} - -static void hist_entry__init_have_children(struct hist_entry *self) -{ - if (!self->init_have_children) { - callchain__init_have_children(&self->sorted_chain); - self->init_have_children = true; - } -} - static struct rb_node *hists__filter_entries(struct rb_node *nd) { while (nd != NULL) { @@ -1244,7 +568,7 @@ static void ui_browser__hists_seek(struct ui_browser *self, nd = hists__filter_entries(rb_first(self->entries)); break; case SEEK_CUR: - nd = self->first_visible_entry; + nd = self->top; goto do_offset; case SEEK_END: nd = hists__filter_prev_entries(rb_last(self->entries)); @@ -1258,7 +582,7 @@ static void ui_browser__hists_seek(struct ui_browser *self, * Moves not relative to the first visible entry invalidates its * row_offset: */ - h = rb_entry(self->first_visible_entry, struct hist_entry, rb_node); + h = rb_entry(self->top, struct hist_entry, rb_node); h->row_offset = 0; /* @@ -1286,7 +610,7 @@ do_offset: } else { h->row_offset += offset; offset = 0; - self->first_visible_entry = nd; + self->top = nd; break; } } @@ -1294,7 +618,7 @@ do_offset: if (nd == NULL) break; --offset; - self->first_visible_entry = nd; + self->top = nd; } while (offset != 0); } else if (offset < 0) { while (1) { @@ -1307,7 +631,7 @@ do_offset: } else { h->row_offset += offset; offset = 0; - self->first_visible_entry = nd; + self->top = nd; break; } } else { @@ -1317,7 +641,7 @@ do_offset: } else { h->row_offset = h->nr_rows + offset; offset = 0; - self->first_visible_entry = nd; + self->top = nd; break; } } @@ -1327,7 +651,7 @@ do_offset: if (nd == NULL) break; ++offset; - self->first_visible_entry = nd; + self->top = nd; if (offset == 0) { /* * Last unfiltered hist_entry, check if it is @@ -1342,146 +666,283 @@ do_offset: first = false; } } else { - self->first_visible_entry = nd; + self->top = nd; h = rb_entry(nd, struct hist_entry, rb_node); h->row_offset = 0; } } -static int callchain_node__count_rows_rb_tree(struct callchain_node *self) +static struct hist_browser *hist_browser__new(struct hists *hists) { - int n = 0; - struct rb_node *nd; - - for (nd = rb_first(&self->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; - } + struct hist_browser *self = zalloc(sizeof(*self)); - if (folded_sign == '-') /* Have children and they're unfolded */ - n += callchain_node__count_rows_rb_tree(child); + if (self) { + self->hists = hists; + self->b.refresh = hist_browser__refresh; + self->b.seek = ui_browser__hists_seek; } - return n; + return self; } -static int callchain_node__count_rows(struct callchain_node *node) +static void hist_browser__delete(struct hist_browser *self) { - 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); + newtFormDestroy(self->b.form); + newtPopWindow(); + free(self); +} - return n; +static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) +{ + return self->he_selection; } -static int callchain__count_rows(struct rb_root *chain) +static struct thread *hist_browser__selected_thread(struct hist_browser *self) { - struct rb_node *nd; - int n = 0; + return self->he_selection->thread; +} - 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); - } +static int hist_browser__title(char *bf, size_t size, const char *ev_name, + const struct dso *dso, const struct thread *thread) +{ + int printed = 0; - return n; + 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, "Event: %s", ev_name); } -static bool hist_browser__toggle_fold(struct hist_browser *self) +int hists__browse(struct hists *self, const char *helpline, const char *ev_name) { - if (map_symbol__toggle_fold(self->selection)) { - struct hist_entry *he = self->he_selection; + struct hist_browser *browser = hist_browser__new(self); + struct pstack *fstack; + const struct thread *thread_filter = NULL; + const struct dso *dso_filter = NULL; + struct newtExitStruct es; + char msg[160]; + int key = -1; - hist_entry__init_have_children(he); - self->hists->nr_entries -= he->nr_rows; + if (browser == NULL) + return -1; - if (he->ms.unfolded) - he->nr_rows = callchain__count_rows(&he->sorted_chain); - else - he->nr_rows = 0; - self->hists->nr_entries += he->nr_rows; - self->b.nr_entries = self->hists->nr_entries; + fstack = pstack__new(2); + if (fstack == NULL) + goto out; - return true; - } + ui_helpline__push(helpline); - /* If it doesn't have children, no toggling performed */ - return false; -} + hist_browser__title(msg, sizeof(msg), ev_name, + dso_filter, thread_filter); -static int hist_browser__run(struct hist_browser *self, const char *title, - struct newtExitStruct *es) -{ - char str[256], unit; - unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE]; + while (1) { + 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, + browse_map = -2; - self->b.entries = &self->hists->entries; - self->b.nr_entries = self->hists->nr_entries; + if (hist_browser__run(browser, msg, &es)) + break; - hist_browser__refresh_dimensions(self); + thread = hist_browser__selected_thread(browser); + dso = browser->selection->map ? browser->selection->map->dso : NULL; - nr_events = convert_unit(nr_events, &unit); - snprintf(str, sizeof(str), "Events: %lu%c ", - nr_events, unit); - newtDrawRootText(0, 0, str); + if (es.reason == NEWT_EXIT_HOTKEY) { + key = es.u.key; - if (ui_browser__show(&self->b, title) < 0) - return -1; + switch (key) { + case NEWT_KEY_F1: + goto do_help; + case NEWT_KEY_TAB: + case NEWT_KEY_UNTAB: + /* + * Exit the browser, let hists__browser_tree + * go to the next or previous + */ + goto out_free_stack; + default:; + } - newtFormAddHotKey(self->b.form, 'A'); - newtFormAddHotKey(self->b.form, 'a'); - newtFormAddHotKey(self->b.form, '?'); - newtFormAddHotKey(self->b.form, 'h'); - newtFormAddHotKey(self->b.form, 'H'); - newtFormAddHotKey(self->b.form, 'd'); + switch (key) { + case 'a': + if (browser->selection->map == NULL && + browser->selection->map->dso->annotate_warned) + continue; + goto do_annotate; + 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 (is_exit_key(key)) { + if (key == NEWT_KEY_ESCAPE && + !ui__dialog_yesno("Do you really want to exit?")) + continue; + break; + } - newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); - newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT); - newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); + if (es.u.key == NEWT_KEY_LEFT) { + const void *top; - while (1) { - ui_browser__run(&self->b, es); + 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; + } + } - if (es->reason != NEWT_EXIT_HOTKEY) + 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++; + + if (browser->selection->map != NULL && + asprintf(&options[nr_options], "Browse map details") > 0) + browse_map = nr_options++; + + options[nr_options++] = (char *)"Exit"; + + choice = ui__popup_menu(nr_options, options); + + for (i = 0; i < nr_options - 1; ++i) + free(options[i]); + + if (choice == nr_options - 1) break; - switch (es->u.key) { - case 'd': { /* Debug */ - static int seq; - struct hist_entry *h = rb_entry(self->b.first_visible_entry, - struct hist_entry, rb_node); - ui_helpline__pop(); - ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", - seq++, self->b.nr_entries, - self->hists->nr_entries, - self->b.height, - self->b.index, - self->b.first_visible_entry_idx, - h->row_offset, h->nr_rows); - } + + if (choice == -1) continue; - case NEWT_KEY_ENTER: - if (hist_browser__toggle_fold(self)) - break; - /* fall thru */ + + if (choice == annotate) { + struct hist_entry *he; +do_annotate: + 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; + } + + he = hist_browser__selected_entry(browser); + if (he == NULL) + continue; + + hist_entry__tui_annotate(he); + } else if (choice == browse_map) + map__browse(browser->selection->map); + 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), ev_name, + dso_filter, thread_filter); + hist_browser__reset(browser); + } 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), ev_name, + dso_filter, thread_filter); + hist_browser__reset(browser); + } + } +out_free_stack: + pstack__delete(fstack); +out: + hist_browser__delete(browser); + return key; +} + +int hists__tui_browse_tree(struct rb_root *self, const char *help) +{ + struct rb_node *first = rb_first(self), *nd = first, *next; + int key = 0; + + while (nd) { + struct hists *hists = rb_entry(nd, struct hists, rb_node); + const char *ev_name = __event_name(hists->type, hists->config); + + key = hists__browse(hists, help, ev_name); + + if (is_exit_key(key)) + break; + + switch (key) { + case NEWT_KEY_TAB: + next = rb_next(nd); + if (next) + nd = next; + break; + case NEWT_KEY_UNTAB: + if (nd == first) + continue; + nd = rb_prev(nd); default: - return 0; + break; } } - return 0; + + return key; } diff --git a/tools/perf/util/ui/browsers/map.c b/tools/perf/util/ui/browsers/map.c new file mode 100644 index 000000000000..142b825b42bf --- /dev/null +++ b/tools/perf/util/ui/browsers/map.c @@ -0,0 +1,161 @@ +#include "../libslang.h" +#include <elf.h> +#include <newt.h> +#include <sys/ttydefaults.h> +#include <ctype.h> +#include <string.h> +#include <linux/bitops.h> +#include "../../debug.h" +#include "../../symbol.h" +#include "../browser.h" +#include "../helpline.h" +#include "map.h" + +static int ui_entry__read(const char *title, char *bf, size_t size, int width) +{ + struct newtExitStruct es; + newtComponent form, entry; + const char *result; + int err = -1; + + newtCenteredWindow(width, 1, title); + form = newtForm(NULL, NULL, 0); + if (form == NULL) + return -1; + + entry = newtEntry(0, 0, "0x", width, &result, NEWT_FLAG_SCROLL); + if (entry == NULL) + goto out_free_form; + + newtFormAddComponent(form, entry); + newtFormAddHotKey(form, NEWT_KEY_ENTER); + newtFormAddHotKey(form, NEWT_KEY_ESCAPE); + newtFormAddHotKey(form, NEWT_KEY_LEFT); + newtFormAddHotKey(form, CTRL('c')); + newtFormRun(form, &es); + + if (result != NULL) { + strncpy(bf, result, size); + err = 0; + } +out_free_form: + newtPopWindow(); + newtFormDestroy(form); + return 0; +} + +struct map_browser { + struct ui_browser b; + struct map *map; + u16 namelen; + u8 addrlen; +}; + +static void map_browser__write(struct ui_browser *self, void *nd, int row) +{ + struct symbol *sym = rb_entry(nd, struct symbol, rb_node); + struct map_browser *mb = container_of(self, struct map_browser, b); + bool current_entry = ui_browser__is_current_entry(self, row); + int color = ui_browser__percent_color(0, current_entry); + + SLsmg_set_color(color); + slsmg_printf("%*llx %*llx %c ", + mb->addrlen, sym->start, mb->addrlen, sym->end, + sym->binding == STB_GLOBAL ? 'g' : + sym->binding == STB_LOCAL ? 'l' : 'w'); + slsmg_write_nstring(sym->name, mb->namelen); +} + +/* FIXME uber-kludgy, see comment on cmd_report... */ +static u32 *symbol__browser_index(struct symbol *self) +{ + return ((void *)self) - sizeof(struct rb_node) - sizeof(u32); +} + +static int map_browser__search(struct map_browser *self) +{ + char target[512]; + struct symbol *sym; + int err = ui_entry__read("Search by name/addr", target, sizeof(target), 40); + + if (err) + return err; + + if (target[0] == '0' && tolower(target[1]) == 'x') { + u64 addr = strtoull(target, NULL, 16); + sym = map__find_symbol(self->map, addr, NULL); + } else + sym = map__find_symbol_by_name(self->map, target, NULL); + + if (sym != NULL) { + u32 *idx = symbol__browser_index(sym); + + self->b.top = &sym->rb_node; + self->b.index = self->b.top_idx = *idx; + } else + ui_helpline__fpush("%s not found!", target); + + return 0; +} + +static int map_browser__run(struct map_browser *self, struct newtExitStruct *es) +{ + if (ui_browser__show(&self->b, self->map->dso->long_name, + "Press <- or ESC to exit, %s / to search", + verbose ? "" : "restart with -v to use") < 0) + return -1; + + newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); + newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); + if (verbose) + newtFormAddHotKey(self->b.form, '/'); + + while (1) { + ui_browser__run(&self->b, es); + + if (es->reason != NEWT_EXIT_HOTKEY) + break; + if (verbose && es->u.key == '/') + map_browser__search(self); + else + break; + } + + ui_browser__hide(&self->b); + return 0; +} + +int map__browse(struct map *self) +{ + struct map_browser mb = { + .b = { + .entries = &self->dso->symbols[self->type], + .refresh = ui_browser__rb_tree_refresh, + .seek = ui_browser__rb_tree_seek, + .write = map_browser__write, + }, + .map = self, + }; + struct newtExitStruct es; + 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 (mb.namelen < pos->namelen) + mb.namelen = pos->namelen; + 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), "%llx", maxaddr); + mb.b.width += mb.addrlen * 2 + 4 + mb.namelen; + return map_browser__run(&mb, &es); +} diff --git a/tools/perf/util/ui/browsers/map.h b/tools/perf/util/ui/browsers/map.h new file mode 100644 index 000000000000..df8581a43e17 --- /dev/null +++ b/tools/perf/util/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 *self); +#endif /* _PERF_UI_MAP_BROWSER_H_ */ diff --git a/tools/perf/util/ui/helpline.c b/tools/perf/util/ui/helpline.c new file mode 100644 index 000000000000..8d79daa4458a --- /dev/null +++ b/tools/perf/util/ui/helpline.c @@ -0,0 +1,69 @@ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <newt.h> + +#include "../debug.h" +#include "helpline.h" + +void ui_helpline__pop(void) +{ + newtPopHelpLine(); +} + +void ui_helpline__push(const char *msg) +{ + newtPushHelpLine(msg); +} + +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); + } +} + +void ui_helpline__fpush(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + ui_helpline__vpush(fmt, ap); + va_end(ap); +} + +void ui_helpline__puts(const char *msg) +{ + ui_helpline__pop(); + ui_helpline__push(msg); +} + +void ui_helpline__init(void) +{ + ui_helpline__puts(" "); +} + +char ui_helpline__last_msg[1024]; + +int ui_helpline__show_help(const char *format, va_list ap) +{ + int ret; + static int backlog; + + ret = vsnprintf(ui_helpline__last_msg + backlog, + sizeof(ui_helpline__last_msg) - backlog, format, ap); + backlog += ret; + + if (ui_helpline__last_msg[backlog - 1] == '\n') { + ui_helpline__puts(ui_helpline__last_msg); + newtRefresh(); + backlog = 0; + } + + return ret; +} diff --git a/tools/perf/util/ui/helpline.h b/tools/perf/util/ui/helpline.h new file mode 100644 index 000000000000..ab6028d0c401 --- /dev/null +++ b/tools/perf/util/ui/helpline.h @@ -0,0 +1,11 @@ +#ifndef _PERF_UI_HELPLINE_H_ +#define _PERF_UI_HELPLINE_H_ 1 + +void ui_helpline__init(void); +void ui_helpline__pop(void); +void ui_helpline__push(const char *msg); +void ui_helpline__vpush(const char *fmt, va_list ap); +void ui_helpline__fpush(const char *fmt, ...); +void ui_helpline__puts(const char *msg); + +#endif /* _PERF_UI_HELPLINE_H_ */ diff --git a/tools/perf/util/ui/libslang.h b/tools/perf/util/ui/libslang.h new file mode 100644 index 000000000000..5623da8e8080 --- /dev/null +++ b/tools/perf/util/ui/libslang.h @@ -0,0 +1,27 @@ +#ifndef _PERF_UI_SLANG_H_ +#define _PERF_UI_SLANG_H_ 1 +/* + * 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> + +#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 + +#endif /* _PERF_UI_SLANG_H_ */ diff --git a/tools/perf/util/ui/progress.c b/tools/perf/util/ui/progress.c new file mode 100644 index 000000000000..d7fc399d36b3 --- /dev/null +++ b/tools/perf/util/ui/progress.c @@ -0,0 +1,60 @@ +#include <stdlib.h> +#include <newt.h> +#include "../cache.h" +#include "progress.h" + +struct ui_progress { + newtComponent form, scale; +}; + +struct ui_progress *ui_progress__new(const char *title, u64 total) +{ + struct ui_progress *self = malloc(sizeof(*self)); + + if (self != NULL) { + int cols; + + if (use_browser <= 0) + return self; + newtGetScreenSize(&cols, NULL); + cols -= 4; + newtCenteredWindow(cols, 1, title); + self->form = newtForm(NULL, NULL, 0); + if (self->form == NULL) + goto out_free_self; + self->scale = newtScale(0, 0, cols, total); + if (self->scale == NULL) + goto out_free_form; + newtFormAddComponent(self->form, self->scale); + newtRefresh(); + } + + return self; + +out_free_form: + newtFormDestroy(self->form); +out_free_self: + free(self); + return NULL; +} + +void ui_progress__update(struct ui_progress *self, u64 curr) +{ + /* + * FIXME: We should have a per UI backend way of showing progress, + * stdio will just show a percentage as NN%, etc. + */ + if (use_browser <= 0) + return; + newtScaleSet(self->scale, curr); + newtRefresh(); +} + +void ui_progress__delete(struct ui_progress *self) +{ + if (use_browser > 0) { + newtFormDestroy(self->form); + newtPopWindow(); + } + free(self); +} diff --git a/tools/perf/util/ui/progress.h b/tools/perf/util/ui/progress.h new file mode 100644 index 000000000000..a3820a0beb5b --- /dev/null +++ b/tools/perf/util/ui/progress.h @@ -0,0 +1,11 @@ +#ifndef _PERF_UI_PROGRESS_H_ +#define _PERF_UI_PROGRESS_H_ 1 + +struct ui_progress; + +struct ui_progress *ui_progress__new(const char *title, u64 total); +void ui_progress__delete(struct ui_progress *self); + +void ui_progress__update(struct ui_progress *self, u64 curr); + +#endif diff --git a/tools/perf/util/ui/setup.c b/tools/perf/util/ui/setup.c new file mode 100644 index 000000000000..662085032eb7 --- /dev/null +++ b/tools/perf/util/ui/setup.c @@ -0,0 +1,42 @@ +#include <newt.h> +#include <signal.h> +#include <stdbool.h> + +#include "../cache.h" +#include "../debug.h" +#include "browser.h" +#include "helpline.h" + +static void newt_suspend(void *d __used) +{ + newtSuspend(); + raise(SIGTSTP); + newtResume(); +} + +void setup_browser(void) +{ + if (!isatty(1) || !use_browser || dump_trace) { + use_browser = 0; + setup_pager(); + return; + } + + use_browser = 1; + newtInit(); + newtCls(); + newtSetSuspendCallback(newt_suspend, NULL); + ui_helpline__init(); + ui_browser__init(); +} + +void exit_browser(bool wait_for_ok) +{ + if (use_browser > 0) { + if (wait_for_ok) { + char title[] = "Fatal Error", ok[] = "Ok"; + newtWinMessage(title, ok, ui_helpline__last_msg); + } + newtFinished(); + } +} diff --git a/tools/perf/util/ui/util.c b/tools/perf/util/ui/util.c new file mode 100644 index 000000000000..04600e26ceea --- /dev/null +++ b/tools/perf/util/ui/util.c @@ -0,0 +1,114 @@ +#include <newt.h> +#include <signal.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <sys/ttydefaults.h> + +#include "../cache.h" +#include "../debug.h" +#include "browser.h" +#include "helpline.h" +#include "util.h" + +newtComponent newt_form__new(void); + +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'); + newtFormAddHotKey(self, CTRL('c')); +} + +newtComponent newt_form__new(void) +{ + newtComponent self = newtForm(NULL, NULL, 0); + if (self) + newt_form__set_exit_keys(self); + return self; +} + +int ui__popup_menu(int argc, char * const argv[]) +{ + struct newtExitStruct es; + int i, rc = -1, max_len = 5; + newtComponent listbox, form = newt_form__new(); + + if (form == NULL) + return -1; + + listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); + if (listbox == NULL) + goto out_destroy_form; + + newtFormAddComponent(form, listbox); + + for (i = 0; i < argc; ++i) { + int len = strlen(argv[i]); + if (len > max_len) + max_len = len; + if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i)) + goto out_destroy_form; + } + + newtCenteredWindow(max_len, argc, NULL); + newtFormRun(form, &es); + rc = newtListboxGetCurrent(listbox) - NULL; + if (es.reason == NEWT_EXIT_HOTKEY) + rc = -1; + newtPopWindow(); +out_destroy_form: + newtFormDestroy(form); + return rc; +} + +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; +} + +bool ui__dialog_yesno(const char *msg) +{ + /* newtWinChoice should really be accepting const char pointers... */ + char yes[] = "Yes", no[] = "No"; + return newtWinChoice(NULL, yes, no, (char *)msg) == 1; +} diff --git a/tools/perf/util/ui/util.h b/tools/perf/util/ui/util.h new file mode 100644 index 000000000000..afcbc1d99531 --- /dev/null +++ b/tools/perf/util/ui/util.h @@ -0,0 +1,10 @@ +#ifndef _PERF_UI_UTIL_H_ +#define _PERF_UI_UTIL_H_ 1 + +#include <stdbool.h> + +int ui__popup_menu(int argc, char * const argv[]); +int ui__help_window(const char *text); +bool ui__dialog_yesno(const char *msg); + +#endif /* _PERF_UI_UTIL_H_ */ |