diff options
Diffstat (limited to 'tools/perf/ui')
41 files changed, 7660 insertions, 2420 deletions
diff --git a/tools/perf/ui/Build b/tools/perf/ui/Build new file mode 100644 index 000000000000..6005f813c9e3 --- /dev/null +++ b/tools/perf/ui/Build @@ -0,0 +1,13 @@ +perf-ui-y += setup.o +perf-ui-y += helpline.o +perf-ui-y += progress.o +perf-ui-y += util.o +perf-ui-y += hist.o +perf-ui-y += stdio/hist.o + +CFLAGS_setup.o += -DLIBDIR="BUILD_STR($(LIBDIR))" + +perf-ui-$(CONFIG_SLANG) += browser.o +perf-ui-$(CONFIG_SLANG) += keysyms.o +perf-ui-$(CONFIG_SLANG) += browsers/ +perf-ui-$(CONFIG_SLANG) += tui/ diff --git a/tools/perf/ui/browser.c b/tools/perf/ui/browser.c index bbc782e364b0..dc88427b4ae5 100644 --- a/tools/perf/ui/browser.c +++ b/tools/perf/ui/browser.c @@ -1,18 +1,21 @@ -#include "../util.h" -#include "../cache.h" -#include "../../perf.h" +// SPDX-License-Identifier: GPL-2.0 +#include "../util/string2.h" +#include "../util/config.h" #include "libslang.h" #include "ui.h" #include "util.h" #include <linux/compiler.h> #include <linux/list.h> #include <linux/rbtree.h> +#include <linux/string.h> #include <stdlib.h> #include <sys/ttydefaults.h> #include "browser.h" #include "helpline.h" #include "keysyms.h" -#include "../color.h" +#include "../util/color.h" +#include <linux/ctype.h> +#include <linux/zalloc.h> static int ui_browser__percent_color(struct ui_browser *browser, double percent, bool current) @@ -41,11 +44,36 @@ void ui_browser__set_percent_color(struct ui_browser *browser, ui_browser__set_color(browser, color); } -void ui_browser__gotorc(struct ui_browser *browser, int y, int x) +void ui_browser__gotorc_title(struct ui_browser *browser, int y, int x) { SLsmg_gotorc(browser->y + y, browser->x + x); } +void ui_browser__gotorc(struct ui_browser *browser, int y, int x) +{ + SLsmg_gotorc(browser->y + y + browser->extra_title_lines, browser->x + x); +} + +void ui_browser__write_nstring(struct ui_browser *browser __maybe_unused, const char *msg, + unsigned int width) +{ + SLsmg_write_nstring(msg, width); +} + +void ui_browser__vprintf(struct ui_browser *browser __maybe_unused, const char *fmt, va_list args) +{ + SLsmg_vprintf(fmt, args); +} + +void ui_browser__printf(struct ui_browser *browser __maybe_unused, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + ui_browser__vprintf(browser, fmt, args); + va_end(args); +} + static struct list_head * ui_browser__list_head_filter_entries(struct ui_browser *browser, struct list_head *pos) @@ -150,7 +178,7 @@ unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser) while (nd != NULL) { ui_browser__gotorc(browser, row, 0); browser->write(browser, nd, row); - if (++row == browser->height) + if (++row == browser->rows) break; nd = rb_next(nd); } @@ -166,7 +194,8 @@ bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row) void ui_browser__refresh_dimensions(struct ui_browser *browser) { browser->width = SLtt_Screen_Cols - 1; - browser->height = SLtt_Screen_Rows - 2; + browser->height = browser->rows = SLtt_Screen_Rows - 2; + browser->rows -= browser->extra_title_lines; browser->y = 1; browser->x = 0; } @@ -174,7 +203,7 @@ void ui_browser__refresh_dimensions(struct ui_browser *browser) void ui_browser__handle_resize(struct ui_browser *browser) { ui__refresh_dimensions(false); - ui_browser__show(browser, browser->title, ui_helpline__current); + ui_browser__show(browser, browser->title ?: "", ui_helpline__current); ui_browser__refresh(browser); } @@ -194,7 +223,7 @@ int ui_browser__warning(struct ui_browser *browser, int timeout, ui_helpline__vpush(format, args); va_end(args); } else { - while ((key == ui__question_window("Warning!", text, + while ((key = ui__question_window("Warning!", text, "Press any key...", timeout)) == K_RESIZE) ui_browser__handle_resize(browser); @@ -204,6 +233,14 @@ int ui_browser__warning(struct ui_browser *browser, int timeout, return key; } +int ui_browser__warn_unhandled_hotkey(struct ui_browser *browser, int key, int timeout, const char *help) +{ + char kname[32]; + + key_name(key, kname, sizeof(kname)); + return ui_browser__warning(browser, timeout, "\n'%s' key not associated%s!\n", kname, help ?: ""); +} + int ui_browser__help_window(struct ui_browser *browser, const char *text) { int key; @@ -234,14 +271,14 @@ void __ui_browser__show_title(struct ui_browser *browser, const char *title) { SLsmg_gotorc(0, 0); ui_browser__set_color(browser, HE_COLORSET_ROOT); - slsmg_write_nstring(title, browser->width + 1); + ui_browser__write_nstring(browser, title, browser->width + 1); } void ui_browser__show_title(struct ui_browser *browser, const char *title) { - pthread_mutex_lock(&ui__lock); + mutex_lock(&ui__lock); __ui_browser__show_title(browser, title); - pthread_mutex_unlock(&ui__lock); + mutex_unlock(&ui__lock); } int ui_browser__show(struct ui_browser *browser, const char *title, @@ -250,38 +287,41 @@ int ui_browser__show(struct ui_browser *browser, const char *title, int err; va_list ap; - ui_browser__refresh_dimensions(browser); + if (browser->refresh_dimensions == NULL) + browser->refresh_dimensions = ui_browser__refresh_dimensions; - pthread_mutex_lock(&ui__lock); + browser->refresh_dimensions(browser); + + mutex_lock(&ui__lock); __ui_browser__show_title(browser, title); - browser->title = title; - free(browser->helpline); - browser->helpline = NULL; + free(browser->title); + browser->title = strdup(title); + zfree(&browser->helpline); va_start(ap, helpline); err = vasprintf(&browser->helpline, helpline, ap); va_end(ap); if (err > 0) ui_helpline__push(browser->helpline); - pthread_mutex_unlock(&ui__lock); + mutex_unlock(&ui__lock); return err ? 0 : -1; } -void ui_browser__hide(struct ui_browser *browser __maybe_unused) +void ui_browser__hide(struct ui_browser *browser) { - pthread_mutex_lock(&ui__lock); + mutex_lock(&ui__lock); ui_helpline__pop(); - free(browser->helpline); - browser->helpline = NULL; - pthread_mutex_unlock(&ui__lock); + zfree(&browser->helpline); + zfree(&browser->title); + mutex_unlock(&ui__lock); } static void ui_browser__scrollbar_set(struct ui_browser *browser) { int height = browser->height, h = 0, pct = 0, col = browser->width, - row = browser->y - 1; + row = 0; if (browser->nr_entries > 1) { pct = ((browser->index * (browser->height - 1)) / @@ -312,17 +352,19 @@ static int __ui_browser__refresh(struct ui_browser *browser) else width += 1; - SLsmg_fill_region(browser->y + row, browser->x, - browser->height - row, width, ' '); + SLsmg_fill_region(browser->y + row + browser->extra_title_lines, browser->x, + browser->rows - row, width, ' '); + if (browser->nr_entries == 0 && browser->no_samples_msg) + __ui__info_window(NULL, browser->no_samples_msg, NULL); return 0; } int ui_browser__refresh(struct ui_browser *browser) { - pthread_mutex_lock(&ui__lock); + mutex_lock(&ui__lock); __ui_browser__refresh(browser); - pthread_mutex_unlock(&ui__lock); + mutex_unlock(&ui__lock); return 0; } @@ -358,10 +400,10 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) while (1) { off_t offset; - pthread_mutex_lock(&ui__lock); + mutex_lock(&ui__lock); err = __ui_browser__refresh(browser); SLsmg_refresh(); - pthread_mutex_unlock(&ui__lock); + mutex_unlock(&ui__lock); if (err < 0) break; @@ -369,7 +411,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) if (key == K_RESIZE) { ui__refresh_dimensions(false); - ui_browser__refresh_dimensions(browser); + browser->refresh_dimensions(browser); __ui_browser__show_title(browser, browser->title); ui_helpline__puts(browser->helpline); continue; @@ -377,6 +419,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) if (browser->use_navkeypressed && !browser->navkeypressed) { if (key == K_DOWN || key == K_UP || + (browser->columns && (key == K_LEFT || key == K_RIGHT)) || key == K_PGDN || key == K_PGUP || key == K_HOME || key == K_END || key == ' ') { @@ -391,7 +434,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) if (browser->index == browser->nr_entries - 1) break; ++browser->index; - if (browser->index == browser->top_idx + browser->height) { + if (browser->index == browser->top_idx + browser->rows) { ++browser->top_idx; browser->seek(browser, +1, SEEK_CUR); } @@ -405,12 +448,26 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) browser->seek(browser, -1, SEEK_CUR); } break; + case K_RIGHT: + if (!browser->columns) + goto out; + if (browser->horiz_scroll < browser->columns - 1) + ++browser->horiz_scroll; + break; + case K_LEFT: + if (!browser->columns) + goto out; + if (browser->horiz_scroll != 0) + --browser->horiz_scroll; + else + goto out; + break; case K_PGDN: case ' ': - if (browser->top_idx + browser->height > browser->nr_entries - 1) + if (browser->top_idx + browser->rows > browser->nr_entries - 1) break; - offset = browser->height; + offset = browser->rows; if (browser->index + offset > browser->nr_entries - 1) offset = browser->nr_entries - 1 - browser->index; browser->index += offset; @@ -421,10 +478,10 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) if (browser->top_idx == 0) break; - if (browser->top_idx < browser->height) + if (browser->top_idx < browser->rows) offset = browser->top_idx; else - offset = browser->height; + offset = browser->rows; browser->index -= offset; browser->top_idx -= offset; @@ -434,7 +491,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) ui_browser__reset_index(browser); break; case K_END: - offset = browser->height - 1; + offset = browser->rows - 1; if (offset >= browser->nr_entries) offset = browser->nr_entries - 1; @@ -443,6 +500,7 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) browser->seek(browser, -offset, SEEK_END); break; default: + out: return key; } } @@ -464,7 +522,7 @@ unsigned int ui_browser__list_head_refresh(struct ui_browser *browser) if (!browser->filter || !browser->filter(browser, pos)) { ui_browser__gotorc(browser, row, 0); browser->write(browser, pos, row); - if (++row == browser->height) + if (++row == browser->rows) break; } } @@ -498,11 +556,11 @@ static struct ui_browser_colorset { .colorset = HE_COLORSET_SELECTED, .name = "selected", .fg = "black", - .bg = "lightgray", + .bg = "yellow", }, { - .colorset = HE_COLORSET_CODE, - .name = "code", + .colorset = HE_COLORSET_JUMP_ARROWS, + .name = "jump_arrows", .fg = "blue", .bg = "default", }, @@ -531,7 +589,7 @@ static int ui_browser__color_config(const char *var, const char *value, int i; /* same dir for all commands */ - if (prefixcmp(var, "colors.") != 0) + if (!strstarts(var, "colors.") != 0) return 0; for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) { @@ -549,7 +607,7 @@ static int ui_browser__color_config(const char *var, const char *value, break; *bg = '\0'; - while (isspace(*++bg)); + bg = skip_spaces(bg + 1); ui_browser__colorsets[i].bg = bg; ui_browser__colorsets[i].fg = fg; return 0; @@ -566,14 +624,16 @@ void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence) browser->top = browser->entries; break; case SEEK_CUR: - browser->top = browser->top + browser->top_idx + offset; + browser->top = (char **)browser->top + offset; break; case SEEK_END: - browser->top = browser->top + browser->nr_entries + offset; + browser->top = (char **)browser->entries + browser->nr_entries - 1 + offset; break; default: return; } + assert((char **)browser->top < (char **)browser->entries + browser->nr_entries); + assert((char **)browser->top >= (char **)browser->entries); } unsigned int ui_browser__argv_refresh(struct ui_browser *browser) @@ -585,11 +645,13 @@ unsigned int ui_browser__argv_refresh(struct ui_browser *browser) browser->top = browser->entries; pos = (char **)browser->top; - while (idx < browser->nr_entries) { + while (idx < browser->nr_entries && + row < (unsigned)SLtt_Screen_Rows - 1) { + assert(pos < (char **)browser->entries + browser->nr_entries); if (!browser->filter || !browser->filter(browser, *pos)) { ui_browser__gotorc(browser, row, 0); browser->write(browser, pos, row); - if (++row == browser->height) + if (++row == browser->rows) break; } @@ -625,7 +687,7 @@ static void __ui_browser__line_arrow_up(struct ui_browser *browser, SLsmg_set_char_set(1); - if (start < browser->top_idx + browser->height) { + if (start < browser->top_idx + browser->rows) { row = start - browser->top_idx; ui_browser__gotorc(browser, row, column); SLsmg_write_char(SLSMG_LLCORN_CHAR); @@ -635,7 +697,7 @@ static void __ui_browser__line_arrow_up(struct ui_browser *browser, if (row-- == 0) goto out; } else - row = browser->height - 1; + row = browser->rows - 1; if (end > browser->top_idx) end_row = end - browser->top_idx; @@ -672,21 +734,21 @@ static void __ui_browser__line_arrow_down(struct ui_browser *browser, ui_browser__gotorc(browser, row, column + 1); SLsmg_draw_hline(2); - if (row++ == 0) + if (++row == 0) goto out; } else row = 0; - if (end >= browser->top_idx + browser->height) - end_row = browser->height - 1; + if (end >= browser->top_idx + browser->rows) + end_row = browser->rows - 1; else - end_row = end - browser->top_idx;; + end_row = end - browser->top_idx; ui_browser__gotorc(browser, row, column); SLsmg_draw_vline(end_row - row + 1); ui_browser__gotorc(browser, end_row, column); - if (end < browser->top_idx + browser->height) { + if (end < browser->top_idx + browser->rows) { SLsmg_write_char(SLSMG_LLCORN_CHAR); ui_browser__gotorc(browser, end_row, column + 1); SLsmg_write_char(SLSMG_HLINE_CHAR); @@ -706,6 +768,50 @@ void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column, __ui_browser__line_arrow_down(browser, column, start, end); } +void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column, + unsigned int row, int diff, bool arrow_down) +{ + int end_row; + + if (diff <= 0) + return; + + SLsmg_set_char_set(1); + + if (arrow_down) { + if (row + diff <= browser->top_idx) + return; + + end_row = row + diff - browser->top_idx; + ui_browser__gotorc(browser, end_row, column - 1); + SLsmg_write_char(SLSMG_LTEE_CHAR); + + while (--end_row >= 0 && end_row > (int)(row - browser->top_idx)) { + ui_browser__gotorc(browser, end_row, column - 1); + SLsmg_draw_vline(1); + } + + end_row = (int)(row - browser->top_idx); + if (end_row >= 0) { + ui_browser__gotorc(browser, end_row, column - 1); + SLsmg_write_char(SLSMG_ULCORN_CHAR); + ui_browser__gotorc(browser, end_row, column); + SLsmg_draw_hline(2); + } + } else { + if (row < browser->top_idx) + return; + + end_row = row - browser->top_idx; + ui_browser__gotorc(browser, end_row, column - 1); + SLsmg_write_char(SLSMG_LTEE_CHAR); + ui_browser__gotorc(browser, end_row, column); + SLsmg_draw_hline(2); + } + + SLsmg_set_char_set(0); +} + void ui_browser__init(void) { int i = 0; @@ -714,8 +820,6 @@ void ui_browser__init(void) while (ui_browser__colorsets[i].name) { struct ui_browser_colorset *c = &ui_browser__colorsets[i++]; - sltt_set_color(c->colorset, c->name, c->fg, c->bg); + SLtt_set_color(c->colorset, c->name, c->fg, c->bg); } - - annotate_browser__init(); } diff --git a/tools/perf/ui/browser.h b/tools/perf/ui/browser.h index 404ff66a3e36..9d4404f9b87f 100644 --- a/tools/perf/ui/browser.h +++ b/tools/perf/ui/browser.h @@ -1,52 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_UI_BROWSER_H_ #define _PERF_UI_BROWSER_H_ 1 -#include <stdbool.h> +#include <linux/types.h> +#include <stdarg.h> #include <sys/types.h> -#include "../types.h" #define HE_COLORSET_TOP 50 #define HE_COLORSET_MEDIUM 51 #define HE_COLORSET_NORMAL 52 #define HE_COLORSET_SELECTED 53 -#define HE_COLORSET_CODE 54 +#define HE_COLORSET_JUMP_ARROWS 54 #define HE_COLORSET_ADDR 55 #define HE_COLORSET_ROOT 56 struct ui_browser { u64 index, top_idx; void *top, *entries; - u16 y, x, width, height; + u16 y, x, width, height, rows, columns, horiz_scroll; + u8 extra_title_lines; int current_color; void *priv; - const char *title; + char *title; char *helpline; - unsigned int (*refresh)(struct ui_browser *self); - void (*write)(struct ui_browser *self, void *entry, int row); - void (*seek)(struct ui_browser *self, off_t offset, int whence); - bool (*filter)(struct ui_browser *self, void *entry); + const char *no_samples_msg; + void (*refresh_dimensions)(struct ui_browser *browser); + unsigned int (*refresh)(struct ui_browser *browser); + void (*write)(struct ui_browser *browser, void *entry, int row); + void (*seek)(struct ui_browser *browser, off_t offset, int whence); + bool (*filter)(struct ui_browser *browser, void *entry); u32 nr_entries; bool navkeypressed; bool use_navkeypressed; }; int ui_browser__set_color(struct ui_browser *browser, int color); -void ui_browser__set_percent_color(struct ui_browser *self, +void ui_browser__set_percent_color(struct ui_browser *browser, double percent, bool current); -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); +bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row); +void ui_browser__refresh_dimensions(struct ui_browser *browser); +void ui_browser__reset_index(struct ui_browser *browser); -void ui_browser__gotorc(struct ui_browser *self, int y, int x); +void ui_browser__gotorc_title(struct ui_browser *browser, int y, int x); +void ui_browser__gotorc(struct ui_browser *browser, int y, int x); +void ui_browser__write_nstring(struct ui_browser *browser, const char *msg, + unsigned int width); +void ui_browser__vprintf(struct ui_browser *browser, const char *fmt, va_list args); +void ui_browser__printf(struct ui_browser *browser, const char *fmt, ...); void ui_browser__write_graph(struct ui_browser *browser, int graph); void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column, u64 start, u64 end); +void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column, + unsigned int row, int diff, bool arrow_down); void __ui_browser__show_title(struct ui_browser *browser, const char *title); void ui_browser__show_title(struct ui_browser *browser, const char *title); -int ui_browser__show(struct ui_browser *self, const char *title, +int ui_browser__show(struct ui_browser *browser, const char *title, const char *helpline, ...); -void ui_browser__hide(struct ui_browser *self); -int ui_browser__refresh(struct ui_browser *self); +void ui_browser__hide(struct ui_browser *browser); +int ui_browser__refresh(struct ui_browser *browser); int ui_browser__run(struct ui_browser *browser, int delay_secs); void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries); void ui_browser__handle_resize(struct ui_browser *browser); @@ -55,20 +66,22 @@ void __ui_browser__vline(struct ui_browser *browser, unsigned int column, int ui_browser__warning(struct ui_browser *browser, int timeout, const char *format, ...); +int ui_browser__warn_unhandled_hotkey(struct ui_browser *browser, int key, int timeout, const char *help); int ui_browser__help_window(struct ui_browser *browser, const char *text); bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text); int ui_browser__input_window(const char *title, const char *text, char *input, const char *exit_msg, int delay_sec); +struct perf_session; +int tui__header_window(struct perf_session *session); void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence); unsigned int ui_browser__argv_refresh(struct ui_browser *browser); -void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence); -unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self); +void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence); +unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser); -void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whence); -unsigned int ui_browser__list_head_refresh(struct ui_browser *self); +void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence); +unsigned int ui_browser__list_head_refresh(struct ui_browser *browser); void ui_browser__init(void); -void annotate_browser__init(void); #endif /* _PERF_UI_BROWSER_H_ */ diff --git a/tools/perf/ui/browsers/Build b/tools/perf/ui/browsers/Build new file mode 100644 index 000000000000..a07489e44765 --- /dev/null +++ b/tools/perf/ui/browsers/Build @@ -0,0 +1,7 @@ +perf-ui-y += annotate.o +perf-ui-y += annotate-data.o +perf-ui-y += hists.o +perf-ui-y += map.o +perf-ui-y += scripts.o +perf-ui-y += header.o +perf-ui-y += res_sample.o diff --git a/tools/perf/ui/browsers/annotate-data.c b/tools/perf/ui/browsers/annotate-data.c new file mode 100644 index 000000000000..aa8c89fe2e82 --- /dev/null +++ b/tools/perf/ui/browsers/annotate-data.c @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <inttypes.h> +#include <string.h> +#include <linux/zalloc.h> +#include <sys/ttydefaults.h> + +#include "ui/browser.h" +#include "ui/helpline.h" +#include "ui/keysyms.h" +#include "ui/ui.h" +#include "util/annotate.h" +#include "util/annotate-data.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/sort.h" + +#define FOLDED_SIGN '+' +#define UNFOLD_SIGN '-' +#define NOCHLD_SIGN ' ' + +struct browser_entry { + struct list_head node; + struct annotated_member *data; + struct type_hist_entry *hists; + struct browser_entry *parent; + struct list_head children; + int indent; /*indentation level, starts from 0 */ + int nr_entries; /* # of visible entries: self + descendents */ + bool folded; /* only can be false when it has children */ +}; + +struct annotated_data_browser { + struct ui_browser b; + struct list_head entries; + struct browser_entry *curr; + int nr_events; +}; + +static struct annotated_data_browser *get_browser(struct ui_browser *uib) +{ + return container_of(uib, struct annotated_data_browser, b); +} + +static void update_hist_entry(struct type_hist_entry *dst, + struct type_hist_entry *src) +{ + dst->nr_samples += src->nr_samples; + dst->period += src->period; +} + +static int get_member_overhead(struct annotated_data_type *adt, + struct browser_entry *entry, + struct evsel *leader) +{ + struct annotated_member *member = entry->data; + int i, k; + + for (i = 0; i < member->size; i++) { + struct type_hist *h; + struct evsel *evsel; + int offset = member->offset + i; + + k = 0; + for_each_group_evsel(evsel, leader) { + if (symbol_conf.skip_empty && + evsel__hists(evsel)->stats.nr_samples == 0) + continue; + + h = adt->histograms[evsel->core.idx]; + update_hist_entry(&entry->hists[k++], &h->addr[offset]); + } + } + return 0; +} + +static int add_child_entries(struct annotated_data_browser *browser, + struct browser_entry *parent, + struct annotated_data_type *adt, + struct annotated_member *member, + struct evsel *evsel, int indent) +{ + struct annotated_member *pos; + struct browser_entry *entry; + struct list_head *parent_list; + + entry = zalloc(sizeof(*entry)); + if (entry == NULL) + return -1; + + entry->hists = calloc(browser->nr_events, sizeof(*entry->hists)); + if (entry->hists == NULL) { + free(entry); + return -1; + } + + entry->data = member; + entry->parent = parent; + entry->indent = indent; + if (get_member_overhead(adt, entry, evsel) < 0) { + free(entry); + return -1; + } + + INIT_LIST_HEAD(&entry->children); + if (parent) + parent_list = &parent->children; + else + parent_list = &browser->entries; + + list_add_tail(&entry->node, parent_list); + + list_for_each_entry(pos, &member->children, node) { + int nr = add_child_entries(browser, entry, adt, pos, evsel, + indent + 1); + if (nr < 0) + return nr; + } + + /* add an entry for the closing bracket ("}") */ + if (!list_empty(&member->children)) { + struct browser_entry *bracket; + + bracket = zalloc(sizeof(*bracket)); + if (bracket == NULL) + return -1; + + bracket->indent = indent; + bracket->parent = entry; + bracket->folded = true; + bracket->nr_entries = 1; + + INIT_LIST_HEAD(&bracket->children); + list_add_tail(&bracket->node, &entry->children); + } + + /* fold child entries by default */ + entry->folded = true; + entry->nr_entries = 1; + return 0; +} + +static u32 count_visible_entries(struct annotated_data_browser *browser) +{ + int nr = 0; + struct browser_entry *entry; + + list_for_each_entry(entry, &browser->entries, node) + nr += entry->nr_entries; + + return nr; +} + +static int annotated_data_browser__collect_entries(struct annotated_data_browser *browser) +{ + struct hist_entry *he = browser->b.priv; + struct annotated_data_type *adt = he->mem_type; + struct evsel *evsel = hists_to_evsel(he->hists); + + INIT_LIST_HEAD(&browser->entries); + + add_child_entries(browser, /*parent=*/NULL, adt, &adt->self, evsel, + /*indent=*/0); + + browser->b.entries = &browser->entries; + browser->b.nr_entries = count_visible_entries(browser); + return 0; +} + +static void annotated_data_browser__delete_entries(struct annotated_data_browser *browser) +{ + struct browser_entry *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &browser->entries, node) { + list_del_init(&pos->node); + zfree(&pos->hists); + free(pos); + } +} + +static struct browser_entry *get_first_child(struct browser_entry *entry) +{ + if (list_empty(&entry->children)) + return NULL; + + return list_first_entry(&entry->children, struct browser_entry, node); +} + +static struct browser_entry *get_last_child(struct browser_entry *entry) +{ + if (list_empty(&entry->children)) + return NULL; + + return list_last_entry(&entry->children, struct browser_entry, node); +} + +static bool is_first_child(struct browser_entry *entry) +{ + /* This will be checked in a different way */ + if (entry->parent == NULL) + return false; + + return get_first_child(entry->parent) == entry; +} + +static bool is_last_child(struct browser_entry *entry) +{ + /* This will be checked in a different way */ + if (entry->parent == NULL) + return false; + + return get_last_child(entry->parent) == entry; +} + +static struct browser_entry *browser__prev_entry(struct ui_browser *uib, + struct browser_entry *entry) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *first; + + first = list_first_entry(&browser->entries, struct browser_entry, node); + + while (entry != first) { + if (is_first_child(entry)) + entry = entry->parent; + else { + entry = list_prev_entry(entry, node); + while (!entry->folded) + entry = get_last_child(entry); + } + + if (!uib->filter || !uib->filter(uib, &entry->node)) + return entry; + } + return first; +} + +static struct browser_entry *browser__next_entry(struct ui_browser *uib, + struct browser_entry *entry) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *last; + + last = list_last_entry(&browser->entries, struct browser_entry, node); + while (!last->folded) + last = get_last_child(last); + + while (entry != last) { + if (!entry->folded) + entry = get_first_child(entry); + else { + while (is_last_child(entry)) + entry = entry->parent; + + entry = list_next_entry(entry, node); + } + + if (!uib->filter || !uib->filter(uib, &entry->node)) + return entry; + } + return last; +} + +static void browser__seek(struct ui_browser *uib, off_t offset, int whence) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *entry; + + if (uib->nr_entries == 0) + return; + + switch (whence) { + case SEEK_SET: + entry = list_first_entry(&browser->entries, typeof(*entry), node); + if (uib->filter && uib->filter(uib, &entry->node)) + entry = browser__next_entry(uib, entry); + break; + case SEEK_CUR: + entry = list_entry(uib->top, typeof(*entry), node); + break; + case SEEK_END: + entry = list_last_entry(&browser->entries, typeof(*entry), node); + while (!entry->folded) + entry = get_last_child(entry); + if (uib->filter && uib->filter(uib, &entry->node)) + entry = browser__prev_entry(uib, entry); + break; + default: + return; + } + + assert(entry != NULL); + + if (offset > 0) { + while (offset-- != 0) + entry = browser__next_entry(uib, entry); + } else { + while (offset++ != 0) + entry = browser__prev_entry(uib, entry); + } + + uib->top = &entry->node; +} + +static unsigned int browser__refresh(struct ui_browser *uib) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *entry, *next; + int row = 0; + + if (uib->top == NULL || uib->top == uib->entries) + browser__seek(uib, SEEK_SET, 0); + + entry = list_entry(uib->top, typeof(*entry), node); + + while (true) { + if (!uib->filter || !uib->filter(uib, &entry->node)) { + ui_browser__gotorc(uib, row, 0); + uib->write(uib, entry, row); + if (uib->top_idx + row == uib->index) + browser->curr = entry; + if (++row == uib->rows) + break; + } + next = browser__next_entry(uib, entry); + if (next == entry) + break; + + entry = next; + } + + return row; +} + +static int browser__show(struct ui_browser *uib) +{ + struct hist_entry *he = uib->priv; + struct annotated_data_type *adt = he->mem_type; + struct annotated_data_browser *browser = get_browser(uib); + const char *help = "Press 'h' for help on key bindings"; + char title[256]; + + snprintf(title, sizeof(title), "Annotate type: '%s' (%d samples)", + adt->self.type_name, he->stat.nr_events); + + if (ui_browser__show(uib, title, help) < 0) + return -1; + + /* second line header */ + ui_browser__gotorc_title(uib, 0, 0); + ui_browser__set_color(uib, HE_COLORSET_ROOT); + + if (symbol_conf.show_total_period) + strcpy(title, "Period"); + else if (symbol_conf.show_nr_samples) + strcpy(title, "Samples"); + else + strcpy(title, "Percent"); + + ui_browser__printf(uib, "%*s %10s %10s %10s %s", + 2 + 11 * (browser->nr_events - 1), "", + title, "Offset", "Size", "Field"); + ui_browser__write_nstring(uib, "", uib->width); + return 0; +} + +static void browser__write_overhead(struct ui_browser *uib, + struct type_hist *total, + struct type_hist_entry *hist, int row) +{ + u64 period = hist->period; + double percent = total->period ? (100.0 * period / total->period) : 0; + bool current = ui_browser__is_current_entry(uib, row); + int nr_samples = 0; + + ui_browser__set_percent_color(uib, percent, current); + + if (symbol_conf.show_total_period) + ui_browser__printf(uib, " %10" PRIu64, period); + else if (symbol_conf.show_nr_samples) + ui_browser__printf(uib, " %10d", nr_samples); + else + ui_browser__printf(uib, " %10.2f", percent); + + ui_browser__set_percent_color(uib, 0, current); +} + +static void browser__write(struct ui_browser *uib, void *entry, int row) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *be = entry; + struct annotated_member *member = be->data; + struct hist_entry *he = uib->priv; + struct annotated_data_type *adt = he->mem_type; + struct evsel *leader = hists_to_evsel(he->hists); + struct evsel *evsel; + int idx = 0; + bool current = ui_browser__is_current_entry(uib, row); + + if (member == NULL) { + /* print the closing bracket */ + ui_browser__set_percent_color(uib, 0, current); + ui_browser__printf(uib, "%c ", NOCHLD_SIGN); + ui_browser__write_nstring(uib, "", 11 * browser->nr_events); + ui_browser__printf(uib, " %10s %10s %*s};", + "", "", be->indent * 4, ""); + ui_browser__write_nstring(uib, "", uib->width); + return; + } + + ui_browser__set_percent_color(uib, 0, current); + + if (!list_empty(&be->children)) + ui_browser__printf(uib, "%c ", be->folded ? FOLDED_SIGN : UNFOLD_SIGN); + else + ui_browser__printf(uib, "%c ", NOCHLD_SIGN); + + /* print the number */ + for_each_group_evsel(evsel, leader) { + struct type_hist *h = adt->histograms[evsel->core.idx]; + + if (symbol_conf.skip_empty && + evsel__hists(evsel)->stats.nr_samples == 0) + continue; + + browser__write_overhead(uib, h, &be->hists[idx++], row); + } + + /* print type info */ + if (be->indent == 0 && !member->var_name) { + ui_browser__printf(uib, " %#10x %#10x %s%s", + member->offset, member->size, + member->type_name, + list_empty(&member->children) || be->folded? ";" : " {"); + } else { + ui_browser__printf(uib, " %#10x %#10x %*s%s\t%s%s", + member->offset, member->size, + be->indent * 4, "", member->type_name, + member->var_name ?: "", + list_empty(&member->children) || be->folded ? ";" : " {"); + } + /* fill the rest */ + ui_browser__write_nstring(uib, "", uib->width); +} + +static void annotated_data_browser__fold(struct annotated_data_browser *browser, + struct browser_entry *entry, + bool recursive) +{ + struct browser_entry *child; + + if (list_empty(&entry->children)) + return; + if (entry->folded && !recursive) + return; + + if (recursive) { + list_for_each_entry(child, &entry->children, node) + annotated_data_browser__fold(browser, child, true); + } + + entry->nr_entries = 1; + entry->folded = true; +} + +static void annotated_data_browser__unfold(struct annotated_data_browser *browser, + struct browser_entry *entry, + bool recursive) +{ + struct browser_entry *child; + int nr_entries; + + if (list_empty(&entry->children)) + return; + if (!entry->folded && !recursive) + return; + + nr_entries = 1; /* for self */ + list_for_each_entry(child, &entry->children, node) { + if (recursive) + annotated_data_browser__unfold(browser, child, true); + + nr_entries += child->nr_entries; + } + + entry->nr_entries = nr_entries; + entry->folded = false; +} + +static void annotated_data_browser__toggle_fold(struct annotated_data_browser *browser, + bool recursive) +{ + struct browser_entry *curr = browser->curr; + struct browser_entry *parent; + + parent = curr->parent; + while (parent) { + parent->nr_entries -= curr->nr_entries; + parent = parent->parent; + } + browser->b.nr_entries -= curr->nr_entries; + + if (curr->folded) + annotated_data_browser__unfold(browser, curr, recursive); + else + annotated_data_browser__fold(browser, curr, recursive); + + parent = curr->parent; + while (parent) { + parent->nr_entries += curr->nr_entries; + parent = parent->parent; + } + browser->b.nr_entries += curr->nr_entries; + + assert(browser->b.nr_entries == count_visible_entries(browser)); +} + +static int annotated_data_browser__run(struct annotated_data_browser *browser, + struct evsel *evsel __maybe_unused, + struct hist_browser_timer *hbt) +{ + int delay_secs = hbt ? hbt->refresh : 0; + int key; + + if (browser__show(&browser->b) < 0) + return -1; + + while (1) { + key = ui_browser__run(&browser->b, delay_secs); + + switch (key) { + case K_TIMER: + if (hbt) + hbt->timer(hbt->arg); + continue; + case K_F1: + case 'h': + ui_browser__help_window(&browser->b, + "UP/DOWN/PGUP\n" + "PGDN/SPACE Navigate\n" + "</> Move to prev/next symbol\n" + "e Expand/Collapse current entry\n" + "E Expand/Collapse all children of the current\n" + "q/ESC/CTRL+C Exit\n\n"); + continue; + case 'e': + annotated_data_browser__toggle_fold(browser, + /*recursive=*/false); + break; + case 'E': + annotated_data_browser__toggle_fold(browser, + /*recursive=*/true); + break; + case K_LEFT: + case '<': + case '>': + case K_ESC: + case 'q': + case CTRL('c'): + goto out; + default: + ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs, ", use 'h'/F1 to see actions"); + continue; + } + } +out: + ui_browser__hide(&browser->b); + return key; +} + +int hist_entry__annotate_data_tui(struct hist_entry *he, struct evsel *evsel, + struct hist_browser_timer *hbt) +{ + struct annotated_data_browser browser = { + .b = { + .refresh = browser__refresh, + .seek = browser__seek, + .write = browser__write, + .priv = he, + .extra_title_lines = 1, + }, + .nr_events = 1, + }; + int ret; + + ui_helpline__push("Press ESC to exit"); + + if (evsel__is_group_event(evsel)) { + struct evsel *pos; + int nr = 0; + + for_each_group_evsel(pos, evsel) { + if (!symbol_conf.skip_empty || + evsel__hists(pos)->stats.nr_samples) + nr++; + } + browser.nr_events = nr; + } + + ret = annotated_data_browser__collect_entries(&browser); + if (ret < 0) + goto out; + + /* To get the top and current entry */ + browser__refresh(&browser.b); + /* Show the first-level child entries by default */ + annotated_data_browser__toggle_fold(&browser, /*recursive=*/false); + + ret = annotated_data_browser__run(&browser, evsel, hbt); + +out: + annotated_data_browser__delete_entries(&browser); + + return ret; +} diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c index cc64d3f7fc36..36aca8d6d003 100644 --- a/tools/perf/ui/browsers/annotate.c +++ b/tools/perf/ui/browsers/annotate.c @@ -1,302 +1,307 @@ -#include "../../util/util.h" +// SPDX-License-Identifier: GPL-2.0 #include "../browser.h" #include "../helpline.h" -#include "../libslang.h" #include "../ui.h" -#include "../util.h" #include "../../util/annotate.h" +#include "../../util/debug.h" +#include "../../util/debuginfo.h" +#include "../../util/dso.h" +#include "../../util/hashmap.h" #include "../../util/hist.h" #include "../../util/sort.h" +#include "../../util/map.h" +#include "../../util/mutex.h" #include "../../util/symbol.h" #include "../../util/evsel.h" -#include <pthread.h> +#include "../../util/evlist.h" +#include "../../util/thread.h" +#include <inttypes.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/zalloc.h> +#include <sys/ttydefaults.h> +#include <asm/bug.h> + +struct arch; -struct browser_disasm_line { - struct rb_node rb_node; - u32 idx; - int idx_asm; - int jump_sources; +struct annotate_browser { + struct ui_browser b; + struct rb_root entries; + struct rb_node *curr_hot; + struct annotation_line *selection; + struct arch *arch; /* - * actual length of this array is saved on the nr_events field - * of the struct annotate_browser + * perf top can delete hist_entry anytime. Callers should make sure + * its lifetime. */ - double percent[1]; + struct hist_entry *he; + struct debuginfo *dbg; + struct evsel *evsel; + struct hashmap *type_hash; + bool searching_backwards; + char search_bf[128]; }; -static struct annotate_browser_opt { - bool hide_src_code, - use_offset, - jump_arrows, - show_nr_jumps; -} annotate_browser__opts = { - .use_offset = true, - .jump_arrows = true, -}; +/* A copy of target hist_entry for perf top. */ +static struct hist_entry annotate_he; -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 size_t type_hash(long key, void *ctx __maybe_unused) +{ + return key; +} -static inline struct browser_disasm_line *disasm_line__browser(struct disasm_line *dl) +static bool type_equal(long key1, long key2, void *ctx __maybe_unused) { - return (struct browser_disasm_line *)(dl + 1); + return key1 == key2; } -static bool disasm_line__filter(struct ui_browser *browser __maybe_unused, - void *entry) +static inline struct annotation *browser__annotation(struct ui_browser *browser) { - if (annotate_browser__opts.hide_src_code) { - struct disasm_line *dl = list_entry(entry, struct disasm_line, node); - return dl->offset == -1; - } + struct map_symbol *ms = browser->priv; + return symbol__annotation(ms->sym); +} - return false; +static bool disasm_line__filter(struct ui_browser *browser __maybe_unused, void *entry) +{ + struct annotation_line *al = list_entry(entry, struct annotation_line, node); + return annotation_line__filter(al); } -static int annotate_browser__jumps_percent_color(struct annotate_browser *browser, - int nr, bool current) +static int ui_browser__jumps_percent_color(struct ui_browser *browser, int nr, bool current) { - if (current && (!browser->b.use_navkeypressed || browser->b.navkeypressed)) + struct annotation *notes = browser__annotation(browser); + + if (current && (!browser->use_navkeypressed || browser->navkeypressed)) return HE_COLORSET_SELECTED; - if (nr == browser->max_jump_sources) + if (nr == notes->src->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) +static int ui_browser__set_jumps_percent_color(void *browser, int nr, bool current) +{ + int color = ui_browser__jumps_percent_color(browser, nr, current); + return ui_browser__set_color(browser, color); +} + +static int annotate_browser__set_color(void *browser, int color) +{ + return ui_browser__set_color(browser, color); +} + +static void annotate_browser__write_graph(void *browser, int graph) { - int color = annotate_browser__jumps_percent_color(browser, nr, current); - return ui_browser__set_color(&browser->b, color); + ui_browser__write_graph(browser, graph); +} + +static void annotate_browser__set_percent_color(void *browser, double percent, bool current) +{ + ui_browser__set_percent_color(browser, percent, current); +} + +static void annotate_browser__printf(void *browser, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + ui_browser__vprintf(browser, fmt, args); + va_end(args); } 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(' '); + struct annotation *notes = browser__annotation(browser); + struct annotation_line *al = list_entry(entry, struct annotation_line, node); + const bool is_current_entry = ui_browser__is_current_entry(browser, row); + struct annotation_write_ops ops = { + .first_line = row == 0, + .current_entry = is_current_entry, + .change_color = (!annotate_opts.hide_src_code && + (!is_current_entry || + (browser->use_navkeypressed && + !browser->navkeypressed))), + .width = browser->width, + .obj = browser, + .set_color = annotate_browser__set_color, + .set_percent_color = annotate_browser__set_percent_color, + .set_jumps_percent_color = ui_browser__set_jumps_percent_color, + .printf = annotate_browser__printf, + .write_graph = annotate_browser__write_graph, + }; + struct annotation_print_data apd = { + .he = ab->he, + .arch = ab->arch, + .evsel = ab->evsel, + .dbg = ab->dbg, + }; /* 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) { - 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, " "); - } - } + ops.width += 1; - 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(' '); - } - } + if (!IS_ERR_OR_NULL(ab->type_hash)) + apd.type_hash = ab->type_hash; - disasm_line__scnprintf(dl, bf, sizeof(bf), !annotate_browser__opts.use_offset); - slsmg_write_nstring(bf, width - pcnt_width - 3 - printed); - } + annotation_line__write(al, notes, &ops, &apd); - if (current_entry) - ab->selection = dl; + if (ops.current_entry) + ab->selection = al; } -static bool disasm_line__is_valid_jump(struct disasm_line *dl, struct symbol *sym) +static int is_fused(struct annotate_browser *ab, struct disasm_line *cursor) { - if (!dl || !dl->ins || !ins__is_jump(dl->ins) - || !disasm_line__has_offset(dl) - || dl->ops.target.offset >= symbol__size(sym)) - return false; + struct disasm_line *pos = list_prev_entry(cursor, al.node); + const char *name; + int diff = 1; - return true; + while (pos && pos->al.offset == -1) { + pos = list_prev_entry(pos, al.node); + if (!annotate_opts.hide_src_code) + diff++; + } + + if (!pos) + return 0; + + if (ins__is_lock(&pos->ins)) + name = pos->ops.locked.ins.name; + else + name = pos->ins.name; + + if (!name || !cursor->ins.name) + return 0; + + if (ins__is_fused(ab->arch, name, cursor->ins.name)) + return diff; + return 0; } 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; + struct disasm_line *cursor = disasm_line(ab->selection); + struct annotation_line *target; unsigned int from, to; struct map_symbol *ms = ab->b.priv; struct symbol *sym = ms->sym; - u8 pcnt_width = 7; + struct annotation *notes = symbol__annotation(sym); + u8 pcnt_width = annotation__pcnt_width(notes); + u8 cntr_width = annotation__br_cntr_width(); + int width; + int diff = 0; /* PLT symbols contain external offsets */ if (strstr(sym->name, "@plt")) return; - if (!disasm_line__is_valid_jump(cursor, sym)) + if (!disasm_line__is_valid_local_jump(cursor, sym)) return; - target = ab->offsets[cursor->ops.target.offset]; - if (!target) + /* + * This first was seen with a gcc function, _cpp_lex_token, that + * has the usual jumps: + * + * │1159e6c: ↓ jne 115aa32 <_cpp_lex_token@@Base+0xf92> + * + * I.e. jumps to a label inside that function (_cpp_lex_token), and + * those works, but also this kind: + * + * │1159e8b: ↓ jne c469be <cpp_named_operator2name@@Base+0xa72> + * + * I.e. jumps to another function, outside _cpp_lex_token, which + * are not being correctly handled generating as a side effect references + * to ab->offset[] entries that are set to NULL, so to make this code + * more robust, check that here. + * + * A proper fix for will be put in place, looking at the function + * name right after the '<' token and probably treating this like a + * 'call' instruction. + */ + target = annotated_source__get_line(notes->src, cursor->ops.target.offset); + if (target == NULL) { + ui_helpline__printf("WARN: jump target inconsistency, press 'o', notes->offsets[%#x] = NULL\n", + cursor->ops.target.offset); 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; + if (annotate_opts.hide_src_code) { + from = cursor->al.idx_asm; + to = target->idx_asm; } else { - from = (u64)bcursor->idx; - to = (u64)btarget->idx; + from = (u64)cursor->al.idx; + to = (u64)target->idx; } - pcnt_width *= ab->nr_events; + width = annotation__cycles_width(notes); - ui_browser__set_color(browser, HE_COLORSET_CODE); - __ui_browser__line_arrow(browser, pcnt_width + 2 + ab->addr_width, + ui_browser__set_color(browser, HE_COLORSET_JUMP_ARROWS); + __ui_browser__line_arrow(browser, + pcnt_width + 2 + notes->src->widths.addr + width + cntr_width, from, to); + + diff = is_fused(ab, cursor); + if (diff > 0) { + ui_browser__mark_fused(browser, + pcnt_width + 3 + notes->src->widths.addr + width + cntr_width, + from - diff, diff, to > from); + } } static unsigned int annotate_browser__refresh(struct ui_browser *browser) { - struct annotate_browser *ab = container_of(browser, struct annotate_browser, b); + struct annotation *notes = browser__annotation(browser); int ret = ui_browser__list_head_refresh(browser); - int pcnt_width; + int pcnt_width = annotation__pcnt_width(notes); - pcnt_width = 7 * ab->nr_events; - - if (annotate_browser__opts.jump_arrows) + if (annotate_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); + __ui_browser__vline(browser, pcnt_width, 0, browser->rows - 1); return ret; } -static int disasm__cmp(struct browser_disasm_line *a, - struct browser_disasm_line *b, int nr_pcnt) +static double disasm__cmp(struct annotation_line *a, struct annotation_line *b, + int percent_type) { int i; - for (i = 0; i < nr_pcnt; i++) { - if (a->percent[i] == b->percent[i]) + for (i = 0; i < a->data_nr; i++) { + if (a->data[i].percent[percent_type] == b->data[i].percent[percent_type]) continue; - return a->percent[i] < b->percent[i]; + return a->data[i].percent[percent_type] - + b->data[i].percent[percent_type]; } return 0; } -static void disasm_rb_tree__insert(struct rb_root *root, struct browser_disasm_line *bdl, - int nr_events) +static void disasm_rb_tree__insert(struct annotate_browser *browser, + struct annotation_line *al) { + struct rb_root *root = &browser->entries; struct rb_node **p = &root->rb_node; struct rb_node *parent = NULL; - struct browser_disasm_line *l; + struct annotation_line *l; while (*p != NULL) { parent = *p; - l = rb_entry(parent, struct browser_disasm_line, rb_node); + l = rb_entry(parent, struct annotation_line, rb_node); - if (disasm__cmp(bdl, l, nr_events)) + if (disasm__cmp(al, l, annotate_opts.percent_type) < 0) p = &(*p)->rb_left; else p = &(*p)->rb_right; } - rb_link_node(&bdl->rb_node, parent, p); - rb_insert_color(&bdl->rb_node, root); + rb_link_node(&al->rb_node, parent, p); + rb_insert_color(&al->rb_node, root); } static void annotate_browser__set_top(struct annotate_browser *browser, - struct disasm_line *pos, u32 idx) + struct annotation_line *pos, u32 idx) { unsigned back; @@ -305,9 +310,9 @@ static void annotate_browser__set_top(struct annotate_browser *browser, 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); + pos = list_entry(pos->node.prev, struct annotation_line, node); - if (disasm_line__filter(&browser->b, &pos->node)) + if (annotation_line__filter(pos)) continue; --browser->b.top_idx; @@ -321,147 +326,293 @@ static void annotate_browser__set_top(struct annotate_browser *browser, 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; + struct annotation_line * pos = rb_entry(nd, struct annotation_line, rb_node); + u32 idx = pos->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; + if (annotate_opts.hide_src_code) + idx = pos->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 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); + struct disasm_line *pos; browser->entries = RB_ROOT; - pthread_mutex_lock(¬es->lock); + annotation__lock(notes); + + symbol__calc_percent(sym, evsel); - list_for_each_entry(pos, ¬es->src->source, node) { - struct browser_disasm_line *bpos = disasm_line__browser(pos); - const char *path = NULL; + list_for_each_entry(pos, ¬es->src->source, al.node) { double max_percent = 0.0; int i; - if (pos->offset == -1) { - RB_CLEAR_NODE(&bpos->rb_node); + if (pos->al.offset == -1) { + RB_CLEAR_NODE(&pos->al.rb_node); continue; } - next = disasm__get_next_ip_line(¬es->src->source, pos); + for (i = 0; i < pos->al.data_nr; i++) { + double percent; - 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); + percent = annotation_data__percent(&pos->al.data[i], + annotate_opts.percent_type); - if (max_percent < bpos->percent[i]) - max_percent = bpos->percent[i]; + if (max_percent < percent) + max_percent = percent; } - if (max_percent < 0.01) { - RB_CLEAR_NODE(&bpos->rb_node); + if (max_percent < 0.01 && (!pos->al.cycles || pos->al.cycles->ipc == 0)) { + RB_CLEAR_NODE(&pos->al.rb_node); continue; } - disasm_rb_tree__insert(&browser->entries, bpos, - browser->nr_events); + disasm_rb_tree__insert(browser, &pos->al); } - pthread_mutex_unlock(¬es->lock); + annotation__unlock(notes); browser->curr_hot = rb_last(&browser->entries); } -static bool annotate_browser__toggle_source(struct annotate_browser *browser) +static struct annotation_line *annotate_browser__find_new_asm_line( + struct annotate_browser *browser, + int idx_asm) +{ + struct annotation_line *al; + struct list_head *head = browser->b.entries; + + /* find an annotation line in the new list with the same idx_asm */ + list_for_each_entry(al, head, node) { + if (al->idx_asm == idx_asm) + return al; + } + + /* There are no asm lines */ + return NULL; +} + +static struct annotation_line *annotate_browser__find_next_asm_line( + struct annotate_browser *browser, + struct annotation_line *al) +{ + struct annotation_line *it = al; + + /* find next asm line */ + list_for_each_entry_continue(it, browser->b.entries, node) { + if (it->idx_asm >= 0) + return it; + } + + /* no asm line found forwards, try backwards */ + it = al; + list_for_each_entry_continue_reverse(it, browser->b.entries, node) { + if (it->idx_asm >= 0) + return it; + } + + /* There are no asm lines */ + return NULL; +} + +static bool annotation__has_source(struct annotation *notes) +{ + struct annotation_line *al; + bool found_asm = false; + + /* Let's skip the first non-asm lines which present regardless of source. */ + list_for_each_entry(al, ¬es->src->source, node) { + if (al->offset >= 0) { + found_asm = true; + break; + } + } + + if (found_asm) { + /* After assembly lines, any line without offset means source. */ + list_for_each_entry_continue(al, ¬es->src->source, node) { + if (al->offset == -1) + return true; + } + } + return false; +} + +static bool annotate_browser__toggle_source(struct annotate_browser *browser, + struct evsel *evsel) { - struct disasm_line *dl; - struct browser_disasm_line *bdl; + struct annotation *notes = browser__annotation(&browser->b); + struct annotation_line *al; 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); + al = list_entry(browser->b.top, struct annotation_line, node); - if (annotate_browser__opts.hide_src_code) { - if (bdl->idx_asm < offset) - offset = bdl->idx; + if (!annotate_opts.annotate_src) + annotate_opts.annotate_src = true; - browser->b.nr_entries = browser->nr_entries; - annotate_browser__opts.hide_src_code = false; + /* + * It's about to get source code annotation for the first time. + * Drop the existing annotation_lines and get the new one with source. + * And then move to the original line at the same asm index. + */ + if (annotate_opts.hide_src_code && !notes->src->tried_source) { + struct map_symbol *ms = browser->b.priv; + int orig_idx_asm = al->idx_asm; + + /* annotate again with source code info */ + annotate_opts.hide_src_code = false; + annotated_source__purge(notes->src); + symbol__annotate2(ms, evsel, &browser->arch); + annotate_opts.hide_src_code = true; + + /* should be after annotated_source__purge() */ + notes->src->tried_source = true; + + if (!annotation__has_source(notes)) + ui__warning("Annotation has no source code."); + + browser->b.entries = ¬es->src->source; + al = annotate_browser__find_new_asm_line(browser, orig_idx_asm); + if (unlikely(al == NULL)) { + al = list_first_entry(¬es->src->source, + struct annotation_line, node); + } + browser->b.seek(&browser->b, al->idx_asm, SEEK_SET); + } + + if (annotate_opts.hide_src_code) { + if (al->idx_asm < offset) + offset = al->idx; + + browser->b.nr_entries = notes->src->nr_entries; + annotate_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; + browser->b.top_idx = al->idx - offset; + browser->b.index = al->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 (al->idx_asm < 0) { + /* move cursor to next asm line */ + al = annotate_browser__find_next_asm_line(browser, al); + if (!al) { + browser->b.seek(&browser->b, -offset, SEEK_CUR); + return false; + } } - if (bdl->idx_asm < offset) - offset = bdl->idx_asm; + if (al->idx_asm < offset) + offset = al->idx_asm; - browser->b.nr_entries = browser->nr_asm_entries; - annotate_browser__opts.hide_src_code = true; + browser->b.nr_entries = notes->src->nr_asm_entries; + annotate_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; + browser->b.top_idx = al->idx_asm - offset; + browser->b.index = al->idx_asm; } + if (annotate_opts.hide_src_code_on_title) + annotate_opts.hide_src_code_on_title = false; + return true; } -static void annotate_browser__init_asm_mode(struct annotate_browser *browser) +#define SYM_TITLE_MAX_SIZE (PATH_MAX + 64) + +static void annotate_browser__show_full_location(struct ui_browser *browser) { - ui_browser__reset_index(&browser->b); - browser->b.nr_entries = browser->nr_asm_entries; + struct annotate_browser *ab = container_of(browser, struct annotate_browser, b); + struct disasm_line *cursor = disasm_line(ab->selection); + struct annotation_line *al = &cursor->al; + + if (al->offset != -1) + ui_helpline__puts("Only available for source code lines."); + else if (al->fileloc == NULL) + ui_helpline__puts("No source file location."); + else { + char help_line[SYM_TITLE_MAX_SIZE]; + sprintf (help_line, "Source file location: %s", al->fileloc); + ui_helpline__puts(help_line); + } } +static void ui_browser__init_asm_mode(struct ui_browser *browser) +{ + struct annotation *notes = browser__annotation(browser); + ui_browser__reset_index(browser); + browser->nr_entries = notes->src->nr_asm_entries; +} + +static int sym_title(struct symbol *sym, struct map *map, char *title, + size_t sz, int percent_type) +{ + return snprintf(title, sz, "%s %s [Percent: %s] %s", sym->name, + dso__long_name(map__dso(map)), + percent_type_str(percent_type), + annotate_opts.code_with_type ? "[Type]" : ""); +} + +static void annotate_browser__show_function_title(struct annotate_browser *browser) +{ + struct ui_browser *b = &browser->b; + struct map_symbol *ms = b->priv; + struct symbol *sym = ms->sym; + char title[SYM_TITLE_MAX_SIZE]; + + sym_title(sym, ms->map, title, sizeof(title), annotate_opts.percent_type); + + ui_browser__gotorc_title(b, 0, 0); + ui_browser__set_color(b, HE_COLORSET_ROOT); + ui_browser__write_nstring(b, title, b->width + 1); +} + +/* + * This can be called from external jumps, i.e. jumps from one function + * to another, like from the kernel's entry_SYSCALL_64 function to the + * swapgs_restore_regs_and_return_to_usermode() function. + * + * So all we check here is that dl->ops.target.sym is set, if it is, just + * go to that function and when exiting from its disassembly, come back + * to the calling function. + */ static bool annotate_browser__callq(struct annotate_browser *browser, - struct perf_evsel *evsel, + struct evsel *evsel, struct hist_browser_timer *hbt) { - struct map_symbol *ms = browser->b.priv; - struct disasm_line *dl = browser->selection; - struct symbol *sym = ms->sym; + struct map_symbol *ms = browser->b.priv, target_ms; + struct disasm_line *dl = disasm_line(browser->selection); struct annotation *notes; - struct symbol *target; - u64 ip; - if (!ins__is_call(dl->ins)) - return false; - - ip = ms->map->map_ip(ms->map, dl->ops.target.addr); - target = map__find_symbol(ms->map, ip, NULL); - if (target == NULL) { + if (!dl->ops.target.sym) { ui_helpline__puts("The called function was not found."); return true; } - notes = symbol__annotation(target); - pthread_mutex_lock(¬es->lock); + notes = symbol__annotation(dl->ops.target.sym); + annotation__lock(notes); - if (notes->src == NULL && symbol__alloc_hist(target) < 0) { - pthread_mutex_unlock(¬es->lock); + if (!symbol__hists(dl->ops.target.sym, evsel->evlist->core.nr_entries)) { + annotation__unlock(notes); ui__warning("Not enough memory for annotating '%s' symbol!\n", - target->name); + dl->ops.target.sym->name); return true; } - pthread_mutex_unlock(¬es->lock); - symbol__tui_annotate(target, ms->map, evsel, hbt); - ui_browser__show_title(&browser->b, sym->name); + target_ms.maps = ms->maps; + target_ms.map = ms->map; + target_ms.sym = dl->ops.target.sym; + annotation__unlock(notes); + __hist_entry__tui_annotate(browser->he, &target_ms, evsel, hbt, NO_ADDR); + + /* + * The annotate_browser above changed the title with the target function + * and now it's back to the original function. Refresh the header line + * for the original function again. + */ + annotate_browser__show_function_title(browser); return true; } @@ -469,59 +620,64 @@ 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 annotation *notes = browser__annotation(&browser->b); struct disasm_line *pos; *idx = 0; - list_for_each_entry(pos, ¬es->src->source, node) { - if (pos->offset == offset) + list_for_each_entry(pos, ¬es->src->source, al.node) { + if (pos->al.offset == offset) return pos; - if (!disasm_line__filter(&browser->b, &pos->node)) + if (!annotation_line__filter(&pos->al)) ++*idx; } return NULL; } -static bool annotate_browser__jump(struct annotate_browser *browser) +static bool annotate_browser__jump(struct annotate_browser *browser, + struct evsel *evsel, + struct hist_browser_timer *hbt) { - struct disasm_line *dl = browser->selection; + struct disasm_line *dl = disasm_line(browser->selection); + u64 offset; s64 idx; - if (!ins__is_jump(dl->ins)) + if (!ins__is_jump(&dl->ins)) return false; - dl = annotate_browser__find_offset(browser, dl->ops.target.offset, &idx); + if (dl->ops.target.outside) { + annotate_browser__callq(browser, evsel, hbt); + return true; + } + + offset = dl->ops.target.offset; + dl = annotate_browser__find_offset(browser, offset, &idx); if (dl == NULL) { - ui_helpline__puts("Invallid jump offset"); + ui_helpline__printf("Invalid jump offset: %" PRIx64, offset); return true; } - annotate_browser__set_top(browser, dl, idx); - + annotate_browser__set_top(browser, &dl->al, idx); + return true; } static -struct disasm_line *annotate_browser__find_string(struct annotate_browser *browser, +struct annotation_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; + struct annotation *notes = browser__annotation(&browser->b); + struct annotation_line *al = browser->selection; *idx = browser->b.index; - list_for_each_entry_continue(pos, ¬es->src->source, node) { - if (disasm_line__filter(&browser->b, &pos->node)) + list_for_each_entry_continue(al, ¬es->src->source, node) { + if (annotation_line__filter(al)) continue; ++*idx; - if (pos->line && strstr(pos->line, s) != NULL) - return pos; + if (al->line && strstr(al->line, s) != NULL) + return al; } return NULL; @@ -529,38 +685,36 @@ struct disasm_line *annotate_browser__find_string(struct annotate_browser *brows static bool __annotate_browser__search(struct annotate_browser *browser) { - struct disasm_line *dl; + struct annotation_line *al; s64 idx; - dl = annotate_browser__find_string(browser, browser->search_bf, &idx); - if (dl == NULL) { + al = annotate_browser__find_string(browser, browser->search_bf, &idx); + if (al == NULL) { ui_helpline__puts("String not found!"); return false; } - annotate_browser__set_top(browser, dl, idx); + annotate_browser__set_top(browser, al, idx); browser->searching_backwards = false; return true; } static -struct disasm_line *annotate_browser__find_string_reverse(struct annotate_browser *browser, +struct annotation_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; + struct annotation *notes = browser__annotation(&browser->b); + struct annotation_line *al = browser->selection; *idx = browser->b.index; - list_for_each_entry_continue_reverse(pos, ¬es->src->source, node) { - if (disasm_line__filter(&browser->b, &pos->node)) + list_for_each_entry_continue_reverse(al, ¬es->src->source, node) { + if (annotation_line__filter(al)) continue; --*idx; - if (pos->line && strstr(pos->line, s) != NULL) - return pos; + if (al->line && strstr(al->line, s) != NULL) + return al; } return NULL; @@ -568,16 +722,16 @@ struct disasm_line *annotate_browser__find_string_reverse(struct annotate_browse static bool __annotate_browser__search_reverse(struct annotate_browser *browser) { - struct disasm_line *dl; + struct annotation_line *al; s64 idx; - dl = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx); - if (dl == NULL) { + al = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx); + if (al == NULL) { ui_helpline__puts("String not found!"); return false; } - annotate_browser__set_top(browser, dl, idx); + annotate_browser__set_top(browser, al, idx); browser->searching_backwards = true; return true; } @@ -630,35 +784,122 @@ bool annotate_browser__continue_search_reverse(struct annotate_browser *browser, return __annotate_browser__search_reverse(browser); } -static void annotate_browser__update_addr_width(struct annotate_browser *browser) +static int annotate_browser__show(struct annotate_browser *browser, char *title, const char *help) { - if (annotate_browser__opts.use_offset) - browser->target_width = browser->min_addr_width; - else - browser->target_width = browser->max_addr_width; + if (ui_browser__show(&browser->b, title, help) < 0) + return -1; + + annotate_browser__show_function_title(browser); + return 0; +} + +static void +switch_percent_type(struct annotation_options *opts, bool base) +{ + switch (opts->percent_type) { + case PERCENT_HITS_LOCAL: + if (base) + opts->percent_type = PERCENT_PERIOD_LOCAL; + else + opts->percent_type = PERCENT_HITS_GLOBAL; + break; + case PERCENT_HITS_GLOBAL: + if (base) + opts->percent_type = PERCENT_PERIOD_GLOBAL; + else + opts->percent_type = PERCENT_HITS_LOCAL; + break; + case PERCENT_PERIOD_LOCAL: + if (base) + opts->percent_type = PERCENT_HITS_LOCAL; + else + opts->percent_type = PERCENT_PERIOD_GLOBAL; + break; + case PERCENT_PERIOD_GLOBAL: + if (base) + opts->percent_type = PERCENT_HITS_GLOBAL; + else + opts->percent_type = PERCENT_PERIOD_LOCAL; + break; + default: + WARN_ON(1); + } +} - browser->addr_width = browser->target_width; +static int annotate__scnprintf_title(struct hists *hists, char *bf, size_t size) +{ + int printed = hists__scnprintf_title(hists, bf, size); - if (annotate_browser__opts.show_nr_jumps) - browser->addr_width += browser->jumps_width + 1; + if (!annotate_opts.hide_src_code_on_title) { + printed += scnprintf(bf + printed, size - printed, " [source: %s]", + annotate_opts.hide_src_code ? "OFF" : "On"); + } + + return printed; +} + +static void annotate_browser__debuginfo_warning(struct annotate_browser *browser) +{ + struct map_symbol *ms = browser->b.priv; + struct dso *dso = map__dso(ms->map); + + if (browser->dbg == NULL && annotate_opts.code_with_type && + !dso__debuginfo_warned(dso)) { + ui__warning("DWARF debuginfo not found.\n\n" + "Data-type in this DSO will not be displayed.\n" + "Please make sure to have debug information."); + dso__set_debuginfo_warned(dso); + } +} + +static s64 annotate_browser__curr_hot_offset(struct annotate_browser *browser) +{ + struct annotation_line *al = NULL; + + if (browser->curr_hot) + al = rb_entry(browser->curr_hot, struct annotation_line, rb_node); + + return al ? al->offset : 0; +} + +static void annotate_browser__symbol_annotate_error(struct annotate_browser *browser, int err) +{ + struct map_symbol *ms = browser->b.priv; + struct symbol *sym = ms->sym; + struct dso *dso = map__dso(ms->map); + char msg[BUFSIZ]; + + dso__set_annotate_warned(dso); + symbol__strerror_disassemble(ms, err, msg, sizeof(msg)); + ui__error("Couldn't annotate %s:\n%s", sym->name, msg); } static int annotate_browser__run(struct annotate_browser *browser, - struct perf_evsel *evsel, + struct evsel *evsel, struct hist_browser_timer *hbt) { struct rb_node *nd = NULL; + struct hists *hists = evsel__hists(evsel); struct map_symbol *ms = browser->b.priv; struct symbol *sym = ms->sym; + struct annotation *notes = symbol__annotation(ms->sym); const char *help = "Press 'h' for help on key bindings"; int delay_secs = hbt ? hbt->refresh : 0; + char *br_cntr_text = NULL; + char title[256]; int key; - if (ui_browser__show(&browser->b, sym->name, help) < 0) + annotate__scnprintf_title(hists, title, sizeof(title)); + if (annotate_browser__show(browser, title, help) < 0) return -1; annotate_browser__calc_percent(browser, evsel); + if (browser->selection != NULL) { + browser->curr_hot = &browser->selection->rb_node; + browser->b.use_navkeypressed = false; + } + if (browser->curr_hot) { annotate_browser__set_rb_top(browser, browser->curr_hot); browser->b.navkeypressed = false; @@ -666,6 +907,10 @@ static int annotate_browser__run(struct annotate_browser *browser, nd = browser->curr_hot; + annotation_br_cntr_abbr_list(&br_cntr_text, evsel, false); + + annotate_browser__debuginfo_warning(browser); + while (1) { key = ui_browser__run(&browser->b, delay_secs); @@ -685,8 +930,11 @@ static int annotate_browser__run(struct annotate_browser *browser, if (hbt) hbt->timer(hbt->arg); - if (delay_secs != 0) - symbol__annotate_decay_histogram(sym, evsel->idx); + if (delay_secs != 0) { + symbol__annotate_decay_histogram(sym, evsel); + annotate__scnprintf_title(hists, title, sizeof(title)); + annotate_browser__show(browser, title, help); + } continue; case K_TAB: if (nd != NULL) { @@ -697,11 +945,11 @@ static int annotate_browser__run(struct annotate_browser *browser, nd = browser->curr_hot; break; case K_UNTAB: - if (nd != NULL) + if (nd != NULL) { nd = rb_next(nd); if (nd == NULL) nd = rb_first(&browser->entries); - else + } else nd = browser->curr_hot; break; case K_F1: @@ -709,41 +957,76 @@ static int annotate_browser__run(struct annotate_browser *browser, ui_browser__help_window(&browser->b, "UP/DOWN/PGUP\n" "PGDN/SPACE Navigate\n" + "</> Move to prev/next symbol\n" "q/ESC/CTRL+C Exit\n\n" - "-> Go to target\n" - "<- Exit\n" - "H Cycle thru hottest instructions\n" + "ENTER Go to target\n" + "H Go to hottest instruction\n" + "TAB/shift+TAB 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" + "O Bump offset level (jump targets -> +call -> all -> cycle thru)\n" "s Toggle source code view\n" + "t Circulate percent, total period, samples view\n" + "c Show min/max cycle\n" "/ Search string\n" + "k Toggle line numbers\n" + "l Show full source file location\n" + "P Print to [symbol_name].annotation file.\n" "r Run available scripts\n" - "? Search previous string\n"); + "p Toggle percent type [local/global]\n" + "b Toggle percent base [period/hits]\n" + "B Branch counter abbr list (Optional)\n" + "? Search string backwards\n" + "f Toggle showing offsets to full address\n" + "T Toggle data type display\n"); continue; case 'r': - { - script_browse(NULL); - continue; - } + script_browse(NULL, NULL); + annotate_browser__show(browser, title, help); + continue; + case 'k': + annotate_opts.show_linenr = !annotate_opts.show_linenr; + continue; + case 'l': + annotate_browser__show_full_location (&browser->b); + continue; case 'H': nd = browser->curr_hot; break; - case 's': - if (annotate_browser__toggle_source(browser)) + case 's': { + struct annotation_line *al = NULL; + s64 offset = annotate_browser__curr_hot_offset(browser); + + if (annotate_browser__toggle_source(browser, evsel)) ui_helpline__puts(help); + + /* Update the annotation browser's rb_tree, and reset the nd */ + annotate_browser__calc_percent(browser, evsel); + /* Try to find the same asm line as before */ + al = annotated_source__get_line(notes->src, offset); + browser->curr_hot = al ? &al->rb_node : NULL; + nd = browser->curr_hot; + + annotate__scnprintf_title(hists, title, sizeof(title)); + annotate_browser__show(browser, title, help); continue; + } case 'o': - annotate_browser__opts.use_offset = !annotate_browser__opts.use_offset; - annotate_browser__update_addr_width(browser); + annotate_opts.use_offset = !annotate_opts.use_offset; + annotation__update_column_widths(notes); + continue; + case 'O': + if (++annotate_opts.offset_level > ANNOTATION__MAX_OFFSET_LEVEL) + annotate_opts.offset_level = ANNOTATION__MIN_OFFSET_LEVEL; continue; case 'j': - annotate_browser__opts.jump_arrows = !annotate_browser__opts.jump_arrows; + annotate_opts.jump_arrows = !annotate_opts.jump_arrows; continue; case 'J': - annotate_browser__opts.show_nr_jumps = !annotate_browser__opts.show_nr_jumps; - annotate_browser__update_addr_width(browser); + annotate_opts.show_nr_jumps = !annotate_opts.show_nr_jumps; + annotation__update_column_widths(notes); continue; case '/': if (annotate_browser__search(browser, delay_secs)) { @@ -769,31 +1052,86 @@ show_help: browser->b.height, browser->b.index, browser->b.top_idx, - browser->nr_asm_entries); + notes->src->nr_asm_entries); } continue; case K_ENTER: case K_RIGHT: + { + struct disasm_line *dl = disasm_line(browser->selection); + 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; + else if (!dl->ins.ops) + goto show_sup_ins; + else if (ins__is_ret(&dl->ins)) goto out; - } else if (!(annotate_browser__jump(browser) || + else if (!(annotate_browser__jump(browser, evsel, hbt) || annotate_browser__callq(browser, evsel, hbt))) { show_sup_ins: - ui_helpline__puts("Actions are only available for 'callq', 'retq' & jump instructions."); + ui_helpline__puts("Actions are only available for function call/return & jump/branch instructions."); } continue; + } + case 'P': + map_symbol__annotation_dump(ms, evsel, browser->he); + continue; + case 't': + if (symbol_conf.show_total_period) { + symbol_conf.show_total_period = false; + symbol_conf.show_nr_samples = true; + } else if (symbol_conf.show_nr_samples) + symbol_conf.show_nr_samples = false; + else + symbol_conf.show_total_period = true; + annotation__update_column_widths(notes); + continue; + case 'c': + if (annotate_opts.show_minmax_cycle) + annotate_opts.show_minmax_cycle = false; + else + annotate_opts.show_minmax_cycle = true; + annotation__update_column_widths(notes); + continue; + case 'p': + case 'b': + switch_percent_type(&annotate_opts, key == 'b'); + annotate__scnprintf_title(hists, title, sizeof(title)); + annotate_browser__show(browser, title, help); + continue; + case 'B': + if (br_cntr_text) + ui_browser__help_window(&browser->b, br_cntr_text); + else { + ui_browser__help_window(&browser->b, + "\n The branch counter is not available.\n"); + } + continue; + case 'f': + annotation__toggle_full_addr(notes, ms); + continue; + case 'T': + annotate_opts.code_with_type ^= 1; + if (browser->dbg == NULL) + browser->dbg = dso__debuginfo(map__dso(ms->map)); + if (browser->type_hash == NULL) { + browser->type_hash = hashmap__new(type_hash, type_equal, + /*ctx=*/NULL); + } + annotate_browser__show(browser, title, help); + annotate_browser__debuginfo_warning(browser); + continue; case K_LEFT: + case '<': + case '>': case K_ESC: case 'q': case CTRL('c'): goto out; default: + ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs, ", use 'h'/F1 to see actions"); continue; } @@ -802,208 +1140,130 @@ show_sup_ins: } out: ui_browser__hide(&browser->b); + free(br_cntr_text); return key; } -int hist_entry__tui_annotate(struct hist_entry *he, struct perf_evsel *evsel, - struct hist_browser_timer *hbt) +int hist_entry__tui_annotate(struct hist_entry *he, struct evsel *evsel, + struct hist_browser_timer *hbt, u64 al_addr) { - return symbol__tui_annotate(he->ms.sym, he->ms.map, 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; + /* reset abort key so that it can get Ctrl-C as a key */ + SLang_reset_tty(); + SLang_init_tty(0, 0, 0); + SLtty_set_suspend_state(true); - 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; - } - + return __hist_entry__tui_annotate(he, &he->ms, evsel, hbt, al_addr); } -static inline int width_jumps(int n) +int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms, + struct evsel *evsel, + struct hist_browser_timer *hbt, u64 al_addr) { - 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 symbol *sym = ms->sym; + struct annotation *notes = symbol__annotation(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, + .extra_title_lines = 1, /* for hists__scnprintf_title() */ + .priv = ms, .use_navkeypressed = true, }, + .he = he, + .evsel = evsel, }; - int ret = -1; - int nr_pcnt = 1; - size_t sizeof_bdl = sizeof(struct browser_disasm_line); + struct dso *dso; + int ret = -1, err; + int not_annotated = list_empty(¬es->src->source); if (sym == NULL) return -1; - size = symbol__size(sym); - - if (map->dso->annotate_warned) + dso = map__dso(ms->map); + if (dso__annotate_warned(dso)) return -1; - browser.offsets = zalloc(size * sizeof(struct disasm_line *)); - if (browser.offsets == NULL) { - ui__error("Not enough memory!"); - return -1; - } + if (not_annotated || !sym->annotate2) { + err = symbol__annotate2(ms, evsel, &browser.arch); + if (err) { + annotate_browser__symbol_annotate_error(&browser, err); + return -1; + } - if (perf_evsel__is_group_event(evsel)) { - nr_pcnt = evsel->nr_members; - sizeof_bdl += sizeof(double) * (nr_pcnt - 1); + if (!annotate_opts.hide_src_code) { + notes->src->tried_source = true; + if (!annotation__has_source(notes)) + ui__warning("Annotation has no source code."); + } + } else { + err = evsel__get_arch(evsel, &browser.arch); + if (err) { + annotate_browser__symbol_annotate_error(&browser, err); + return -1; + } } - if (symbol__annotate(sym, map, sizeof_bdl) < 0) { - ui__error("%s", ui_helpline__last_msg); - goto out_free_offsets; - } + /* Copy necessary information when it's called from perf top */ + if (hbt != NULL && he != &annotate_he) { + annotate_he.hists = he->hists; + annotate_he.thread = thread__get(he->thread); + annotate_he.cpumode = he->cpumode; + map_symbol__copy(&annotate_he.ms, ms); - ui_helpline__push("Press <- or ESC to exit"); + browser.he = &annotate_he; + } - notes = symbol__annotation(sym); - browser.start = map__rip_2objdump(map, sym->start); + ui_helpline__push("Press ESC to exit"); - list_for_each_entry(pos, ¬es->src->source, node) { - struct browser_disasm_line *bpos; - size_t line_len = strlen(pos->line); + if (annotate_opts.code_with_type) { + browser.dbg = dso__debuginfo(dso); + browser.type_hash = hashmap__new(type_hash, type_equal, /*ctx=*/NULL); + } - 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 = ¬es->src->source, + browser.b.width = notes->src->widths.max_line_len; + browser.b.nr_entries = notes->src->nr_entries; + browser.b.entries = ¬es->src->source; browser.b.width += 18; /* Percentage */ - if (annotate_browser__opts.hide_src_code) - annotate_browser__init_asm_mode(&browser); + if (annotate_opts.hide_src_code) + ui_browser__init_asm_mode(&browser.b); - annotate_browser__update_addr_width(&browser); + /* + * If al_addr is set, it means that there should be a line + * intentionally selected, not based on the percentages + * which caculated by the event sampling. In this case, we + * convey this information into the browser selection, where + * the selection in other cases should be empty. + */ + if (al_addr != NO_ADDR) { + struct annotation_line *al = annotated_source__get_line(notes->src, + al_addr - sym->start); - ret = annotate_browser__run(&browser, evsel, hbt); - list_for_each_entry_safe(pos, n, ¬es->src->source, node) { - list_del(&pos->node); - disasm_line__free(pos); + browser.selection = al; } -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_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); -} + ret = annotate_browser__run(&browser, evsel, hbt); -static int annotate__config(const char *var, const char *value, - void *data __maybe_unused) -{ - struct annotate_config *cfg; - const char *name; + debuginfo__delete(browser.dbg); - if (prefixcmp(var, "annotate.") != 0) - return 0; + if (!IS_ERR_OR_NULL(browser.type_hash)) { + struct hashmap_entry *cur; + size_t bkt; - name = var + 9; - cfg = bsearch(name, annotate__configs, ARRAY_SIZE(annotate__configs), - sizeof(struct annotate_config), annotate_config__cmp); + hashmap__for_each_entry(browser.type_hash, cur, bkt) + zfree(&cur->pvalue); + hashmap__free(browser.type_hash); + } - if (cfg == NULL) - return -1; + if (not_annotated && !notes->src->tried_source) + annotated_source__purge(notes->src); - *cfg->value = perf_config_bool(name, value); - return 0; -} + if (hbt != NULL && he != &annotate_he) { + thread__zput(annotate_he.thread); + map_symbol__exit(&annotate_he.ms); + } -void annotate_browser__init(void) -{ - perf_config(annotate__config, NULL); + return ret; } diff --git a/tools/perf/ui/browsers/header.c b/tools/perf/ui/browsers/header.c new file mode 100644 index 000000000000..5b5ca32e3eef --- /dev/null +++ b/tools/perf/ui/browsers/header.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +#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" + +#include <sys/ttydefaults.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); + + ui_browser__write_nstring(browser, str, browser->width); +} + +static int list_menu__run(struct ui_browser *menu) +{ + int key; + unsigned long offset; + static 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: + ui_browser__warn_unhandled_hotkey(menu, key, 0, ", use 'h'/'?'/F1 to see actions"); + 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 *session) +{ + int i, argc = 0; + char **argv; + char *ptr, *pos; + size_t size; + FILE *fp = open_memstream(&ptr, &size); + + 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 index fc0bd3843d34..08fecbe28a52 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -1,50 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <dirent.h> +#include <errno.h> +#include <inttypes.h> #include <stdio.h> -#include "../libslang.h" #include <stdlib.h> #include <string.h> #include <linux/rbtree.h> - +#include <linux/string.h> +#include <sys/ttydefaults.h> +#include <linux/time64.h> +#include <linux/zalloc.h> + +#include "../../util/debug.h" +#include "../../util/dso.h" +#include "../../util/callchain.h" #include "../../util/evsel.h" #include "../../util/evlist.h" +#include "../../util/header.h" #include "../../util/hist.h" +#include "../../util/machine.h" +#include "../../util/map.h" +#include "../../util/maps.h" +#include "../../util/symbol.h" +#include "../../util/map_symbol.h" +#include "../../util/branch.h" #include "../../util/pstack.h" #include "../../util/sort.h" +#include "../../util/top.h" +#include "../../util/thread.h" +#include "../../util/block-info.h" #include "../../util/util.h" #include "../../arch/common.h" -#include "../browser.h" +#include "../browsers/hists.h" #include "../helpline.h" #include "../util.h" #include "../ui.h" #include "map.h" +#include "annotate.h" +#include "annotate-data.h" +#include "srcline.h" +#include "string2.h" +#include "units.h" +#include "time-utils.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; - float min_pcnt; - u64 nr_pcnt_entries; -}; +#include <linux/ctype.h> extern void hist_browser__init_hpp(void); -static int hists__browser_title(struct hists *hists, char *bf, size_t size, - const char *ev_name); +static int hists_browser__scnprintf_title(struct hist_browser *browser, 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 || symbol_conf.has_filter || hb->c2c_filter; +} + +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_cached(&hists->entries); + (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL; + nd = rb_hierarchy_next(nd)) { + struct hist_entry *he = + rb_entry(nd, struct hist_entry, rb_node); + + if (he->leaf && he->unfolded) + unfolded_rows += he->nr_rows; + } + return unfolded_rows; +} + +static void hist_browser__set_title_space(struct hist_browser *hb) +{ + struct ui_browser *browser = &hb->b; + struct hists *hists = hb->hists; + struct perf_hpp_list *hpp_list = hists->hpp_list; + + browser->extra_title_lines = hb->show_headers ? hpp_list->nr_header_lines : 0; +} + +static u32 hist_browser__nr_entries(struct hist_browser *hb) +{ + u32 nr_entries; + + if (symbol_conf.report_hierarchy) + nr_entries = hb->nr_hierarchy_entries; + else 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; + struct hists *hists = hb->hists; + struct perf_hpp_list *hpp_list = hists->hpp_list; + u16 index_row; + + if (!hb->show_headers) { + browser->rows += browser->extra_title_lines; + browser->extra_title_lines = 0; + return; + } + + browser->extra_title_lines = hpp_list->nr_header_lines; + browser->rows -= browser->extra_title_lines; + /* + * Verify if we were at the last line and that line isn't + * visible 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 hist_browser *browser) +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->b.width = 3 + (hists__sort_list_width(browser->hists) + - sizeof("[k]")); + 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); } static void hist_browser__reset(struct hist_browser *browser) { - browser->b.nr_entries = browser->hists->nr_entries; - hist_browser__refresh_dimensions(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); } @@ -53,24 +159,19 @@ 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); + return he->has_children ? tree__folded_sign(he->unfolded) : ' '; } static char callchain_list__folded(const struct callchain_list *cl) { - return map_symbol__folded(&cl->ms); + return cl->has_children ? tree__folded_sign(cl->unfolded) : ' '; } -static void map_symbol__set_folding(struct map_symbol *ms, bool unfold) +static void callchain_list__set_folding(struct callchain_list *cl, bool unfold) { - ms->unfolded = unfold ? ms->has_children : false; + cl->unfolded = unfold ? cl->has_children : false; } static int callchain_node__count_rows_rb_tree(struct callchain_node *node) @@ -85,6 +186,7 @@ static int callchain_node__count_rows_rb_tree(struct callchain_node *node) 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 == '+') @@ -98,15 +200,55 @@ static int callchain_node__count_rows_rb_tree(struct callchain_node *node) return n; } +static int callchain_node__count_flat_rows(struct callchain_node *node) +{ + struct callchain_list *chain; + char folded_sign = 0; + int n = 0; + + list_for_each_entry(chain, &node->parent_val, list) { + if (!folded_sign) { + /* only check first chain list entry */ + folded_sign = callchain_list__folded(chain); + if (folded_sign == '+') + return 1; + } + n++; + } + + list_for_each_entry(chain, &node->val, list) { + if (!folded_sign) { + /* node->parent_val list might be empty */ + folded_sign = callchain_list__folded(chain); + if (folded_sign == '+') + return 1; + } + n++; + } + + return n; +} + +static int callchain_node__count_folded_rows(struct callchain_node *node __maybe_unused) +{ + return 1; +} + static int callchain_node__count_rows(struct callchain_node *node) { struct callchain_list *chain; bool unfolded = false; int n = 0; + if (callchain_param.mode == CHAIN_FLAT) + return callchain_node__count_flat_rows(node); + else if (callchain_param.mode == CHAIN_FOLDED) + return callchain_node__count_folded_rows(node); + list_for_each_entry(chain, &node->val, list) { ++n; - unfolded = chain->ms.unfolded; + + unfolded = chain->unfolded; } if (unfolded) @@ -128,15 +270,59 @@ static int callchain__count_rows(struct rb_root *chain) return n; } -static bool map_symbol__toggle_fold(struct map_symbol *ms) +static int hierarchy_count_rows(struct hist_browser *hb, struct hist_entry *he, + bool include_children) { - if (!ms) + int count = 0; + struct rb_node *node; + struct hist_entry *child; + + if (he->leaf) + return callchain__count_rows(&he->sorted_chain); + + if (he->has_no_entry) + return 1; + + node = rb_first_cached(&he->hroot_out); + while (node) { + float percent; + + child = rb_entry(node, struct hist_entry, rb_node); + percent = hist_entry__get_percent_limit(child); + + if (!child->filtered && percent >= hb->min_pcnt) { + count++; + + if (include_children && child->unfolded) + count += hierarchy_count_rows(hb, child, true); + } + + node = rb_next(node); + } + return count; +} + +static bool hist_entry__toggle_fold(struct hist_entry *he) +{ + if (!he) + return false; + + if (!he->has_children) return false; - if (!ms->has_children) + he->unfolded = !he->unfolded; + return true; +} + +static bool callchain_list__toggle_fold(struct callchain_list *cl) +{ + if (!cl) return false; - ms->unfolded = !ms->unfolded; + if (!cl->has_children) + return false; + + cl->unfolded = !cl->unfolded; return true; } @@ -152,10 +338,10 @@ static void callchain_node__init_have_children_rb_tree(struct callchain_node *no list_for_each_entry(chain, &child->val, list) { if (first) { first = false; - chain->ms.has_children = chain->list.next != &child->val || + chain->has_children = chain->list.next != &child->val || !RB_EMPTY_ROOT(&child->rb_root); } else - chain->ms.has_children = chain->list.next == &child->val && + chain->has_children = chain->list.next == &child->val && !RB_EMPTY_ROOT(&child->rb_root); } @@ -163,49 +349,157 @@ static void callchain_node__init_have_children_rb_tree(struct callchain_node *no } } -static void callchain_node__init_have_children(struct callchain_node *node) +static void callchain_node__init_have_children(struct callchain_node *node, + bool has_sibling) { struct callchain_list *chain; - list_for_each_entry(chain, &node->val, list) - chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root); + chain = list_entry(node->val.next, struct callchain_list, list); + chain->has_children = has_sibling; + + if (!list_empty(&node->val)) { + chain = list_entry(node->val.prev, struct callchain_list, list); + chain->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; + 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); + callchain_node__init_have_children(node, has_sibling); + if (callchain_param.mode == CHAIN_FLAT || + callchain_param.mode == CHAIN_FOLDED) + callchain_node__make_parent_list(node); } } 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); + if (he->init_have_children) + return; + + if (he->leaf) { + he->has_children = !RB_EMPTY_ROOT(&he->sorted_chain); callchain__init_have_children(&he->sorted_chain); - he->init_have_children = true; + } else { + he->has_children = !RB_EMPTY_ROOT(&he->hroot_out.rb_root); } + + he->init_have_children = true; +} + +static bool hist_browser__selection_has_children(struct hist_browser *browser) +{ + struct hist_entry *he = browser->he_selection; + struct map_symbol *ms = browser->selection; + + if (!he || !ms) + return false; + + if (ms == &he->ms) + return he->has_children; + + return container_of(ms, struct callchain_list, ms)->has_children; +} + +static bool hist_browser__selection_unfolded(struct hist_browser *browser) +{ + struct hist_entry *he = browser->he_selection; + struct map_symbol *ms = browser->selection; + + if (!he || !ms) + return false; + + if (ms == &he->ms) + return he->unfolded; + + return container_of(ms, struct callchain_list, ms)->unfolded; +} + +static char *hist_browser__selection_sym_name(struct hist_browser *browser, char *bf, size_t size) +{ + struct hist_entry *he = browser->he_selection; + struct map_symbol *ms = browser->selection; + struct callchain_list *callchain_entry; + + if (!he || !ms) + return NULL; + + if (ms == &he->ms) { + hist_entry__sym_snprintf(he, bf, size, 0); + return bf + 4; // skip the level, e.g. '[k] ' + } + + callchain_entry = container_of(ms, struct callchain_list, ms); + return callchain_list__sym_name(callchain_entry, bf, size, browser->show_dso); } static bool hist_browser__toggle_fold(struct hist_browser *browser) { - if (map_symbol__toggle_fold(browser->selection)) { - struct hist_entry *he = browser->he_selection; + struct hist_entry *he = browser->he_selection; + struct map_symbol *ms = browser->selection; + struct callchain_list *cl = container_of(ms, struct callchain_list, ms); + bool has_children; + + if (!he || !ms) + return false; + + if (ms == &he->ms) + has_children = hist_entry__toggle_fold(he); + else + has_children = callchain_list__toggle_fold(cl); + + if (has_children) { + int child_rows = 0; hist_entry__init_have_children(he); - browser->hists->nr_entries -= he->nr_rows; + browser->b.nr_entries -= he->nr_rows; - if (he->ms.unfolded) - he->nr_rows = callchain__count_rows(&he->sorted_chain); + if (he->leaf) + browser->nr_callchain_rows -= he->nr_rows; else + browser->nr_hierarchy_entries -= he->nr_rows; + + if (symbol_conf.report_hierarchy) + child_rows = hierarchy_count_rows(browser, he, true); + + if (he->unfolded) { + if (he->leaf) + he->nr_rows = callchain__count_rows( + &he->sorted_chain); + else + he->nr_rows = hierarchy_count_rows(browser, he, false); + + /* account grand children */ + if (symbol_conf.report_hierarchy) + browser->b.nr_entries += child_rows - he->nr_rows; + + if (!he->leaf && he->nr_rows == 0) { + he->has_no_entry = true; + he->nr_rows = 1; + } + } else { + if (symbol_conf.report_hierarchy) + browser->b.nr_entries -= child_rows - he->nr_rows; + + if (he->has_no_entry) + he->has_no_entry = false; + he->nr_rows = 0; - browser->hists->nr_entries += he->nr_rows; - browser->b.nr_entries = browser->hists->nr_entries; + } + + browser->b.nr_entries += he->nr_rows; + + if (he->leaf) + browser->nr_callchain_rows += he->nr_rows; + else + browser->nr_hierarchy_entries += he->nr_rows; return true; } @@ -226,8 +520,8 @@ static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool list_for_each_entry(chain, &child->val, list) { ++n; - map_symbol__set_folding(&chain->ms, unfold); - has_children = chain->ms.has_children; + callchain_list__set_folding(chain, unfold); + has_children = chain->has_children; } if (has_children) @@ -245,8 +539,8 @@ static int callchain_node__set_folding(struct callchain_node *node, bool unfold) list_for_each_entry(chain, &node->val, list) { ++n; - map_symbol__set_folding(&chain->ms, unfold); - has_children = chain->ms.has_children; + callchain_list__set_folding(chain, unfold); + has_children = chain->has_children; } if (has_children) @@ -268,39 +562,98 @@ static int callchain__set_folding(struct rb_root *chain, bool unfold) return n; } -static void hist_entry__set_folding(struct hist_entry *he, bool unfold) +static int hierarchy_set_folding(struct hist_browser *hb, struct hist_entry *he, + bool unfold __maybe_unused) +{ + float percent; + struct rb_node *nd; + struct hist_entry *child; + int n = 0; + + for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) { + child = rb_entry(nd, struct hist_entry, rb_node); + percent = hist_entry__get_percent_limit(child); + if (!child->filtered && percent >= hb->min_pcnt) + n++; + } + + return n; +} + +static void hist_entry__set_folding(struct hist_entry *he, + struct hist_browser *hb, bool unfold) { hist_entry__init_have_children(he); - map_symbol__set_folding(&he->ms, unfold); + he->unfolded = unfold ? he->has_children : false; + + if (he->has_children) { + int n; + + if (he->leaf) + n = callchain__set_folding(&he->sorted_chain, unfold); + else + n = hierarchy_set_folding(hb, he, 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 hists__set_folding(struct hists *hists, bool unfold) +static void +__hist_browser__set_folding(struct hist_browser *browser, bool unfold) { struct rb_node *nd; + struct hist_entry *he; + double percent; + + nd = rb_first_cached(&browser->hists->entries); + while (nd) { + he = rb_entry(nd, struct hist_entry, rb_node); - hists->nr_entries = 0; + /* set folding state even if it's currently folded */ + nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD); - for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { - struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); - hist_entry__set_folding(he, unfold); - hists->nr_entries += 1 + he->nr_rows; + hist_entry__set_folding(he, browser, unfold); + + percent = hist_entry__get_percent_limit(he); + if (he->filtered || percent < browser->min_pcnt) + continue; + + if (!he->depth || unfold) + browser->nr_hierarchy_entries++; + if (he->leaf) + browser->nr_callchain_rows += he->nr_rows; + else if (unfold && !hist_entry__has_hierarchy_children(he, browser->min_pcnt)) { + browser->nr_hierarchy_entries++; + he->has_no_entry = true; + he->nr_rows = 1; + } else + he->has_no_entry = false; } } static void hist_browser__set_folding(struct hist_browser *browser, bool unfold) { - hists__set_folding(browser->hists, unfold); - browser->b.nr_entries = browser->hists->nr_entries; + browser->nr_hierarchy_entries = 0; + 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 hist_browser__set_folding_selected(struct hist_browser *browser, bool unfold) +{ + if (!browser->he_selection) + return; + + if (unfold == browser->he_selection->unfolded) + return; + + hist_browser__toggle_fold(browser); +} + static void ui_browser__warn_lost_events(struct ui_browser *browser) { ui_browser__warning(browser, 4, @@ -310,403 +663,643 @@ static void ui_browser__warn_lost_events(struct ui_browser *browser) "Or reduce the sampling frequency."); } -static void hist_browser__update_pcnt_entries(struct hist_browser *hb); +static int hist_browser__title(struct hist_browser *browser, char *bf, size_t size) +{ + return browser->title ? browser->title(browser, bf, size) : 0; +} -static int hist_browser__run(struct hist_browser *browser, const char *ev_name, - struct hist_browser_timer *hbt) +static int hist_browser__handle_hotkey(struct hist_browser *browser, bool warn_lost_event, char *title, size_t size, int key) +{ + switch (key) { + case K_TIMER: { + struct hist_browser_timer *hbt = browser->hbt; + struct evsel *evsel = hists_to_evsel(browser->hists); + u64 nr_entries; + + WARN_ON_ONCE(!hbt); + + if (hbt) + hbt->timer(hbt->arg); + + if (hist_browser__has_filter(browser) || symbol_conf.report_hierarchy) + hist_browser__update_nr_entries(browser); + + nr_entries = hist_browser__nr_entries(browser); + ui_browser__update_nr_entries(&browser->b, nr_entries); + + if (warn_lost_event && + (evsel->evlist->stats.nr_lost_warned != + evsel->evlist->stats.nr_events[PERF_RECORD_LOST])) { + evsel->evlist->stats.nr_lost_warned = + evsel->evlist->stats.nr_events[PERF_RECORD_LOST]; + ui_browser__warn_lost_events(&browser->b); + } + + hist_browser__title(browser, title, size); + ui_browser__show_title(&browser->b, title); + break; + } + case 'D': { /* Debug */ + struct hist_entry *h = rb_entry(browser->b.top, struct hist_entry, rb_node); + static int seq; + + ui_helpline__pop(); + ui_helpline__fpush("%d: nr_ent=(%d,%d), etl: %d, rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", + seq++, browser->b.nr_entries, browser->hists->nr_entries, + browser->b.extra_title_lines, 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 'c': + /* Collapse the selected entry. */ + hist_browser__set_folding_selected(browser, false); + break; + case 'E': + /* Expand the whole world. */ + hist_browser__set_folding(browser, true); + break; + case 'e': + /* Toggle expand/collapse the selected entry. */ + hist_browser__toggle_fold(browser); + break; + case 'H': + browser->show_headers = !browser->show_headers; + hist_browser__update_rows(browser); + break; + case '+': + if (hist_browser__toggle_fold(browser)) + break; + /* fall thru */ + default: + return -1; + } + + return 0; +} + +int hist_browser__run(struct hist_browser *browser, const char *help, + bool warn_lost_event, int key) { - int key; char title[160]; + struct hist_browser_timer *hbt = browser->hbt; int delay_secs = hbt ? hbt->refresh : 0; browser->b.entries = &browser->hists->entries; - browser->b.nr_entries = browser->hists->nr_entries; - if (browser->min_pcnt) - browser->b.nr_entries = browser->nr_pcnt_entries; + browser->b.nr_entries = hist_browser__nr_entries(browser); - hist_browser__refresh_dimensions(browser); - hists__browser_title(browser->hists, title, sizeof(title), ev_name); + hist_browser__title(browser, title, sizeof(title)); - if (ui_browser__show(&browser->b, title, - "Press '?' for help on key bindings") < 0) + if (ui_browser__show(&browser->b, title, "%s", help) < 0) return -1; + if (key && hist_browser__handle_hotkey(browser, warn_lost_event, title, sizeof(title), key)) + goto out; + while (1) { key = ui_browser__run(&browser->b, delay_secs); - switch (key) { - case K_TIMER: { - u64 nr_entries; - hbt->timer(hbt->arg); - - if (browser->min_pcnt) { - hist_browser__update_pcnt_entries(browser); - nr_entries = browser->nr_pcnt_entries; - } else { - nr_entries = browser->hists->nr_entries; - } - - 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, title, sizeof(title), ev_name); - 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), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", - seq++, browser->b.nr_entries, - browser->hists->nr_entries, - browser->b.height, - browser->b.index, - browser->b.top_idx, - h->row_offset, h->nr_rows); - } + if (hist_browser__handle_hotkey(browser, warn_lost_event, title, sizeof(title), key)) 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 K_ENTER: - if (hist_browser__toggle_fold(browser)) - break; - /* fall thru */ - default: - goto out; - } } out: ui_browser__hide(&browser->b); return key; } -static char *callchain_list__sym_name(struct callchain_list *cl, - char *bf, size_t bfsize, bool show_dso) +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 printed; + 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; + } - if (cl->ms.sym) - printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name); - else - printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip); + ui_browser__set_color(&browser->b, color); + ui_browser__gotorc(&browser->b, row, 0); + ui_browser__write_nstring(&browser->b, " ", offset); + ui_browser__printf(&browser->b, "%c", folded_sign); + ui_browser__write_graph(&browser->b, show_annotated ? SLSMG_RARROW_CHAR : ' '); + ui_browser__write_nstring(&browser->b, 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); +} - if (show_dso) - scnprintf(bf + printed, bfsize - printed, " %s", - cl->ms.map ? cl->ms.map->dso->short_name : "unknown"); +typedef bool (*check_output_full_fn)(struct hist_browser *browser, + unsigned short row); - return bf; +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_node_rb_tree(struct hist_browser *browser, - struct callchain_node *chain_node, - u64 total, int level, - unsigned short row, - off_t *row_offset, - bool *is_current_entry) +static int hist_browser__show_callchain_list(struct hist_browser *browser, + struct callchain_node *node, + struct callchain_list *chain, + unsigned short row, u64 total, + bool need_percent, int offset, + print_callchain_entry_fn print, + struct callchain_print_arg *arg) +{ + char bf[1024], *alloc_str; + char buf[64], *alloc_str2; + const char *str; + int ret = 1; + + if (arg->row_offset != 0) { + arg->row_offset--; + return 0; + } + + alloc_str = NULL; + alloc_str2 = NULL; + + str = callchain_list__sym_name(chain, bf, sizeof(bf), + browser->show_dso); + + if (symbol_conf.show_branchflag_count) { + callchain_list_counts__printf_value(chain, NULL, + buf, sizeof(buf)); + + if (asprintf(&alloc_str2, "%s%s", str, buf) < 0) + str = "Not enough memory!"; + else + str = alloc_str2; + } + + if (need_percent) { + callchain_node__scnprintf_value(node, buf, sizeof(buf), + total); + + if (asprintf(&alloc_str, "%s %s", buf, str) < 0) + str = "Not enough memory!"; + else + str = alloc_str; + } + + print(browser, chain, str, offset, row, arg); + free(alloc_str); + free(alloc_str2); + + return ret; +} + +static bool check_percent_display(struct rb_node *node, u64 parent_total) +{ + struct callchain_node *child; + + if (node == NULL) + return false; + + if (rb_next(node)) + return true; + + child = rb_entry(node, struct callchain_node, rb_node); + return callchain_cumul_hits(child) != parent_total; +} + +static int hist_browser__show_callchain_flat(struct hist_browser *browser, + struct rb_root *root, + unsigned short row, u64 total, + u64 parent_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, width, offset = level * LEVEL_OFFSET_STEP; - u64 new_total, remaining; + int first_row = row, offset = LEVEL_OFFSET_STEP; + bool need_percent; - if (callchain_param.mode == CHAIN_GRAPH_REL) - new_total = chain_node->children_hit; - else - new_total = total; + node = rb_first(root); + need_percent = check_percent_display(node, parent_total); - remaining = new_total; - node = rb_first(&chain_node->rb_root); 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; - remaining -= cumul; - - list_for_each_entry(chain, &child->val, list) { - char bf[1024], *alloc_str; - const char *str; - int color; + list_for_each_entry(chain, &child->parent_val, list) { bool was_first = first; if (first) first = false; - else + else if (need_percent) extra_offset = LEVEL_OFFSET_STEP; folded_sign = callchain_list__folded(chain); - if (*row_offset != 0) { - --*row_offset; - goto do_next; - } - alloc_str = NULL; - str = callchain_list__sym_name(chain, bf, sizeof(bf), - browser->show_dso); - if (was_first) { - double percent = cumul * 100.0 / new_total; + row += hist_browser__show_callchain_list(browser, child, + chain, row, total, + was_first && need_percent, + offset + extra_offset, + print, arg); - if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) - str = "Not enough memory!"; - else - str = alloc_str; - } + if (is_output_full(browser, row)) + goto out; - color = HE_COLORSET_NORMAL; - width = browser->b.width - (offset + extra_offset + 2); - if (ui_browser__is_current_entry(&browser->b, row)) { - browser->selection = &chain->ms; - color = HE_COLORSET_SELECTED; - *is_current_entry = true; - } + if (folded_sign == '+') + goto next; + } - ui_browser__set_color(&browser->b, color); - ui_browser__gotorc(&browser->b, row, 0); - slsmg_write_nstring(" ", offset + extra_offset); - slsmg_printf("%c ", folded_sign); - slsmg_write_nstring(str, width); - free(alloc_str); + list_for_each_entry(chain, &child->val, list) { + bool was_first = first; - if (++row == browser->b.height) + if (first) + first = false; + else if (need_percent) + extra_offset = LEVEL_OFFSET_STEP; + + folded_sign = callchain_list__folded(chain); + + row += hist_browser__show_callchain_list(browser, child, + chain, row, total, + was_first && need_percent, + offset + extra_offset, + print, arg); + + 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); - row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total, - new_level, row, row_offset, - is_current_entry); - } - if (row == browser->b.height) - goto out; +next: + if (is_output_full(browser, row)) + break; node = next; } out: return row - first_row; } -static int hist_browser__show_callchain_node(struct hist_browser *browser, - struct callchain_node *node, - int level, unsigned short row, - off_t *row_offset, - bool *is_current_entry) +static char *hist_browser__folded_callchain_str(struct hist_browser *browser, + struct callchain_list *chain, + char *value_str, char *old_str) { - struct callchain_list *chain; - int first_row = row, - offset = level * LEVEL_OFFSET_STEP, - width = browser->b.width - offset; - char folded_sign = ' '; + char bf[1024]; + const char *str; + char *new; + + str = callchain_list__sym_name(chain, bf, sizeof(bf), + browser->show_dso); + if (old_str) { + if (asprintf(&new, "%s%s%s", old_str, + symbol_conf.field_sep ?: ";", str) < 0) + new = NULL; + } else { + if (value_str) { + if (asprintf(&new, "%s %s", value_str, str) < 0) + new = NULL; + } else { + if (asprintf(&new, "%s", str) < 0) + new = NULL; + } + } + return new; +} - list_for_each_entry(chain, &node->val, list) { - char bf[1024], *s; - int color; +static int hist_browser__show_callchain_folded(struct hist_browser *browser, + struct rb_root *root, + unsigned short row, u64 total, + u64 parent_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_OFFSET_STEP; + bool need_percent; - folded_sign = callchain_list__folded(chain); + node = rb_first(root); + need_percent = check_percent_display(node, parent_total); - if (*row_offset != 0) { - --*row_offset; - continue; + while (node) { + struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); + struct rb_node *next = rb_next(node); + struct callchain_list *chain, *first_chain = NULL; + int first = true; + char *value_str = NULL, *value_str_alloc = NULL; + char *chain_str = NULL, *chain_str_alloc = NULL; + + if (arg->row_offset != 0) { + arg->row_offset--; + goto next; } - color = HE_COLORSET_NORMAL; - if (ui_browser__is_current_entry(&browser->b, row)) { - browser->selection = &chain->ms; - color = HE_COLORSET_SELECTED; - *is_current_entry = true; + if (need_percent) { + char buf[64]; + + callchain_node__scnprintf_value(child, buf, sizeof(buf), total); + if (asprintf(&value_str, "%s", buf) < 0) { + value_str = (char *)"<...>"; + goto do_print; + } + value_str_alloc = value_str; } - s = callchain_list__sym_name(chain, bf, sizeof(bf), - browser->show_dso); - ui_browser__gotorc(&browser->b, row, 0); - ui_browser__set_color(&browser->b, color); - slsmg_write_nstring(" ", offset); - slsmg_printf("%c ", folded_sign); - slsmg_write_nstring(s, width - 2); + list_for_each_entry(chain, &child->parent_val, list) { + chain_str = hist_browser__folded_callchain_str(browser, + chain, value_str, chain_str); + if (first) { + first = false; + first_chain = chain; + } - if (++row == browser->b.height) - goto out; - } + if (chain_str == NULL) { + chain_str = (char *)"Not enough memory!"; + goto do_print; + } - if (folded_sign == '-') - row += hist_browser__show_callchain_node_rb_tree(browser, node, - browser->hists->stats.total_period, - level + 1, row, - row_offset, - is_current_entry); -out: - return row - first_row; -} + chain_str_alloc = chain_str; + } -static int hist_browser__show_callchain(struct hist_browser *browser, - struct rb_root *chain, - int level, unsigned short row, - off_t *row_offset, - bool *is_current_entry) -{ - struct rb_node *nd; - int first_row = row; + list_for_each_entry(chain, &child->val, list) { + chain_str = hist_browser__folded_callchain_str(browser, + chain, value_str, chain_str); + if (first) { + first = false; + first_chain = chain; + } - for (nd = rb_first(chain); nd; nd = rb_next(nd)) { - struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + if (chain_str == NULL) { + chain_str = (char *)"Not enough memory!"; + goto do_print; + } + + chain_str_alloc = chain_str; + } + +do_print: + print(browser, first_chain, chain_str, offset, row++, arg); + free(value_str_alloc); + free(chain_str_alloc); - row += hist_browser__show_callchain_node(browser, node, level, - row, row_offset, - is_current_entry); - if (row == browser->b.height) +next: + if (is_output_full(browser, row)) break; + node = next; } return row - first_row; } -struct hpp_arg { - struct ui_browser *b; - char folded_sign; - bool current_entry; -}; - -static int __hpp__color_callchain(struct hpp_arg *arg) +static int hist_browser__show_callchain_graph(struct hist_browser *browser, + struct rb_root *root, int level, + unsigned short row, u64 total, + u64 parent_total, + print_callchain_entry_fn print, + struct callchain_print_arg *arg, + check_output_full_fn is_output_full) { - if (!symbol_conf.use_callchain) - return 0; + struct rb_node *node; + int first_row = row, offset = level * LEVEL_OFFSET_STEP; + bool need_percent; + u64 percent_total = total; - slsmg_printf("%c ", arg->folded_sign); - return 2; -} + if (callchain_param.mode == CHAIN_GRAPH_REL) + percent_total = parent_total; -static int __hpp__color_fmt(struct perf_hpp *hpp, struct hist_entry *he, - u64 (*get_field)(struct hist_entry *), - int (*callchain_cb)(struct hpp_arg *)) -{ - int ret = 0; - double percent = 0.0; - struct hists *hists = he->hists; - struct hpp_arg *arg = hpp->ptr; + node = rb_first(root); + need_percent = check_percent_display(node, parent_total); - if (hists->stats.total_period) - percent = 100.0 * get_field(he) / hists->stats.total_period; + while (node) { + struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); + struct rb_node *next = rb_next(node); + struct callchain_list *chain; + char folded_sign = ' '; + int first = true; + int extra_offset = 0; - ui_browser__set_percent_color(arg->b, percent, arg->current_entry); + list_for_each_entry(chain, &child->val, list) { + bool was_first = first; - if (callchain_cb) - ret += callchain_cb(arg); + if (first) + first = false; + else if (need_percent) + extra_offset = LEVEL_OFFSET_STEP; - ret += scnprintf(hpp->buf, hpp->size, "%6.2f%%", percent); - slsmg_printf("%s", hpp->buf); + folded_sign = callchain_list__folded(chain); - if (symbol_conf.event_group) { - int prev_idx, idx_delta; - struct perf_evsel *evsel = hists_to_evsel(hists); - struct hist_entry *pair; - int nr_members = evsel->nr_members; + row += hist_browser__show_callchain_list(browser, child, + chain, row, percent_total, + was_first && need_percent, + offset + extra_offset, + print, arg); - if (nr_members <= 1) - goto out; + if (is_output_full(browser, row)) + goto out; - prev_idx = perf_evsel__group_idx(evsel); + if (folded_sign == '+') + break; + } - list_for_each_entry(pair, &he->pairs.head, pairs.node) { - u64 period = get_field(pair); - u64 total = pair->hists->stats.total_period; + if (folded_sign == '-') { + const int new_level = level + (extra_offset ? 2 : 1); - if (!total) - continue; + row += hist_browser__show_callchain_graph(browser, &child->rb_root, + new_level, row, total, + child->children_hit, + print, arg, is_output_full); + } + if (is_output_full(browser, row)) + break; + node = next; + } +out: + return row - first_row; +} - evsel = hists_to_evsel(pair->hists); - idx_delta = perf_evsel__group_idx(evsel) - prev_idx - 1; +static int hist_browser__show_callchain(struct hist_browser *browser, + struct hist_entry *entry, int level, + unsigned short row, + print_callchain_entry_fn print, + struct callchain_print_arg *arg, + check_output_full_fn is_output_full) +{ + u64 total = hists__total_period(entry->hists); + u64 parent_total; + int printed; - while (idx_delta--) { - /* - * zero-fill group members in the middle which - * have no sample - */ - ui_browser__set_percent_color(arg->b, 0.0, - arg->current_entry); - ret += scnprintf(hpp->buf, hpp->size, - " %6.2f%%", 0.0); - slsmg_printf("%s", hpp->buf); - } + if (symbol_conf.cumulate_callchain) + parent_total = entry->stat_acc->period; + else + parent_total = entry->stat.period; + + if (callchain_param.mode == CHAIN_FLAT) { + printed = hist_browser__show_callchain_flat(browser, + &entry->sorted_chain, row, + total, parent_total, print, arg, + is_output_full); + } else if (callchain_param.mode == CHAIN_FOLDED) { + printed = hist_browser__show_callchain_folded(browser, + &entry->sorted_chain, row, + total, parent_total, print, arg, + is_output_full); + } else { + printed = hist_browser__show_callchain_graph(browser, + &entry->sorted_chain, level, row, + total, parent_total, print, arg, + is_output_full); + } - percent = 100.0 * period / total; - ui_browser__set_percent_color(arg->b, percent, - arg->current_entry); - ret += scnprintf(hpp->buf, hpp->size, - " %6.2f%%", percent); - slsmg_printf("%s", hpp->buf); + if (arg->is_current_entry) + browser->he_selection = entry; - prev_idx = perf_evsel__group_idx(evsel); - } + return printed; +} + +struct hpp_arg { + struct ui_browser *b; + char folded_sign; + bool current_entry; +}; - idx_delta = nr_members - prev_idx - 1; +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; - while (idx_delta--) { - /* - * zero-fill group members at last which have no sample - */ - ui_browser__set_percent_color(arg->b, 0.0, - arg->current_entry); - ret += scnprintf(hpp->buf, hpp->size, - " %6.2f%%", 0.0); - slsmg_printf("%s", hpp->buf); - } - } -out: - if (!arg->current_entry || !arg->b->navkeypressed) - ui_browser__set_color(arg->b, HE_COLORSET_NORMAL); + 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); + ui_browser__printf(arg->b, "%s", hpp->buf); return ret; } -#define __HPP_COLOR_PERCENT_FN(_type, _field, _cb) \ +#define __HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \ static u64 __hpp_get_##_field(struct hist_entry *he) \ { \ return he->stat._field; \ } \ \ -static int hist_browser__hpp_color_##_type(struct perf_hpp *hpp, \ - struct hist_entry *he) \ +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, _fmttype); \ +} + +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \ +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) \ { \ - return __hpp__color_fmt(hpp, he, __hpp_get_##_field, _cb); \ + if (!symbol_conf.cumulate_callchain) { \ + struct hpp_arg *arg = hpp->ptr; \ + int len = fmt->user_len ?: fmt->len; \ + int ret = scnprintf(hpp->buf, hpp->size, \ + "%*s", len, "N/A"); \ + ui_browser__printf(arg->b, "%s", hpp->buf); \ + \ + return ret; \ + } \ + return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \ + " %*.2f%%", __hpp__slsmg_color_printf, \ + _fmttype); \ } -__HPP_COLOR_PERCENT_FN(overhead, period, __hpp__color_callchain) -__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys, NULL) -__HPP_COLOR_PERCENT_FN(overhead_us, period_us, NULL) -__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys, NULL) -__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us, NULL) +#define __HPP_COLOR_MEM_STAT_FN(_name, _type) \ +static int \ +hist_browser__hpp_color_mem_stat_##_name(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \ + " %5.1f%%", __hpp__slsmg_color_printf);\ +} + +__HPP_COLOR_PERCENT_FN(overhead, period, PERF_HPP_FMT_TYPE__PERCENT) +__HPP_COLOR_PERCENT_FN(latency, latency, PERF_HPP_FMT_TYPE__LATENCY) +__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys, PERF_HPP_FMT_TYPE__PERCENT) +__HPP_COLOR_PERCENT_FN(overhead_us, period_us, PERF_HPP_FMT_TYPE__PERCENT) +__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys, PERF_HPP_FMT_TYPE__PERCENT) +__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us, PERF_HPP_FMT_TYPE__PERCENT) +__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period, PERF_HPP_FMT_TYPE__PERCENT) +__HPP_COLOR_ACC_PERCENT_FN(latency_acc, latency, PERF_HPP_FMT_TYPE__LATENCY) +__HPP_COLOR_MEM_STAT_FN(op, OP) +__HPP_COLOR_MEM_STAT_FN(cache, CACHE) +__HPP_COLOR_MEM_STAT_FN(memory, MEMORY) +__HPP_COLOR_MEM_STAT_FN(snoop, SNOOP) +__HPP_COLOR_MEM_STAT_FN(dtlb, DTLB) #undef __HPP_COLOR_PERCENT_FN +#undef __HPP_COLOR_ACC_PERCENT_FN +#undef __HPP_COLOR_MEM_STAT_FN void hist_browser__init_hpp(void) { - perf_hpp__column_enable(PERF_HPP__OVERHEAD); - - perf_hpp__init(); - perf_hpp__format[PERF_HPP__OVERHEAD].color = hist_browser__hpp_color_overhead; + perf_hpp__format[PERF_HPP__LATENCY].color = + hist_browser__hpp_color_latency; perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = hist_browser__hpp_color_overhead_sys; perf_hpp__format[PERF_HPP__OVERHEAD_US].color = @@ -715,17 +1308,33 @@ void hist_browser__init_hpp(void) 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; + perf_hpp__format[PERF_HPP__LATENCY_ACC].color = + hist_browser__hpp_color_latency_acc; + perf_hpp__format[PERF_HPP__MEM_STAT_OP].color = + hist_browser__hpp_color_mem_stat_op; + perf_hpp__format[PERF_HPP__MEM_STAT_CACHE].color = + hist_browser__hpp_color_mem_stat_cache; + perf_hpp__format[PERF_HPP__MEM_STAT_MEMORY].color = + hist_browser__hpp_color_mem_stat_memory; + perf_hpp__format[PERF_HPP__MEM_STAT_SNOOP].color = + hist_browser__hpp_color_mem_stat_snoop; + perf_hpp__format[PERF_HPP__MEM_STAT_DTLB].color = + hist_browser__hpp_color_mem_stat_dtlb; + + res_sample_init(); } 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); + bool use_callchain = hist_entry__has_callchains(entry) && symbol_conf.use_callchain; off_t row_offset = entry->row_offset; bool first = true; struct perf_hpp_fmt *fmt; @@ -735,69 +1344,512 @@ static int hist_browser__show_entry(struct hist_browser *browser, browser->selection = &entry->ms; } - if (symbol_conf.use_callchain) { + if (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, + .b = &browser->b, .folded_sign = folded_sign, .current_entry = current_entry, }; - struct perf_hpp hpp = { - .buf = s, - .size = sizeof(s), - .ptr = &arg, - }; + int column = 0; ui_browser__gotorc(&browser->b, row, 0); - perf_hpp__for_each_format(fmt) { - if (!first) { - slsmg_printf(" "); + hists__for_each_format(browser->hists, fmt) { + char s[2048]; + struct perf_hpp hpp = { + .buf = s, + .size = sizeof(s), + .ptr = &arg, + }; + + if (perf_hpp__should_skip(fmt, entry->hists) || + column++ < browser->b.horiz_scroll) + 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 (use_callchain) { + ui_browser__printf(&browser->b, "%c ", folded_sign); + width -= 2; + } + first = false; + } else { + ui_browser__printf(&browser->b, " "); width -= 2; } - first = false; if (fmt->color) { - width -= fmt->color(&hpp, entry); + int ret = fmt->color(fmt, &hpp, entry); + hist_entry__snprintf_alignment(entry, &hpp, fmt, ret); + /* + * fmt->color() already used ui_browser to + * print the non alignment bits, skip it (+ret): + */ + ui_browser__printf(&browser->b, "%s", s + ret); } else { - width -= fmt->entry(&hpp, entry); - slsmg_printf("%s", s); + hist_entry__snprintf_alignment(entry, &hpp, fmt, fmt->entry(fmt, &hpp, entry)); + ui_browser__printf(&browser->b, "%s", s); } + width -= hpp.buf - s; } /* The scroll bar isn't being used */ if (!browser->b.navkeypressed) width += 1; - hist_entry__sort_snprintf(entry, s, sizeof(s), browser->hists); - slsmg_write_nstring(s, width); + ui_browser__write_nstring(&browser->b, "", width); + ++row; ++printed; } else --row_offset; - if (folded_sign == '-' && row != browser->b.height) { - printed += hist_browser__show_callchain(browser, &entry->sorted_chain, - 1, row, &row_offset, - ¤t_entry); - if (current_entry) - browser->he_selection = entry; + if (folded_sign == '-' && row != browser->b.rows) { + struct callchain_print_arg arg = { + .row_offset = row_offset, + .is_current_entry = current_entry, + }; + + printed += hist_browser__show_callchain(browser, + entry, 1, row, + hist_browser__show_callchain_entry, + &arg, + hist_browser__check_output_full); + } + + return printed; +} + +static int hist_browser__show_hierarchy_entry(struct hist_browser *browser, + struct hist_entry *entry, + unsigned short row, + int level) +{ + 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; + struct perf_hpp_list_node *fmt_node; + struct hpp_arg arg = { + .b = &browser->b, + .current_entry = current_entry, + }; + int column = 0; + int hierarchy_indent = (entry->hists->nr_hpp_node - 2) * HIERARCHY_INDENT; + + if (current_entry) { + browser->he_selection = entry; + browser->selection = &entry->ms; + } + + hist_entry__init_have_children(entry); + folded_sign = hist_entry__folded(entry); + arg.folded_sign = folded_sign; + + if (entry->leaf && row_offset) { + row_offset--; + goto show_callchain; + } + + ui_browser__gotorc(&browser->b, row, 0); + + 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); + + ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT); + width -= level * HIERARCHY_INDENT; + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&entry->hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + char s[2048]; + struct perf_hpp hpp = { + .buf = s, + .size = sizeof(s), + .ptr = &arg, + }; + + if (perf_hpp__should_skip(fmt, entry->hists) || + column++ < browser->b.horiz_scroll) + 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) { + ui_browser__printf(&browser->b, "%c ", folded_sign); + width -= 2; + first = false; + } else { + ui_browser__printf(&browser->b, " "); + width -= 2; + } + + if (fmt->color) { + int ret = fmt->color(fmt, &hpp, entry); + hist_entry__snprintf_alignment(entry, &hpp, fmt, ret); + /* + * fmt->color() already used ui_browser to + * print the non alignment bits, skip it (+ret): + */ + ui_browser__printf(&browser->b, "%s", s + ret); + } else { + int ret = fmt->entry(fmt, &hpp, entry); + hist_entry__snprintf_alignment(entry, &hpp, fmt, ret); + ui_browser__printf(&browser->b, "%s", s); + } + width -= hpp.buf - s; + } + + if (!first) { + ui_browser__write_nstring(&browser->b, "", hierarchy_indent); + width -= hierarchy_indent; + } + + if (column >= browser->b.horiz_scroll) { + char s[2048]; + struct perf_hpp hpp = { + .buf = s, + .size = sizeof(s), + .ptr = &arg, + }; + + 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); + } + + perf_hpp_list__for_each_format(entry->hpp_list, fmt) { + if (first) { + ui_browser__printf(&browser->b, "%c ", folded_sign); + first = false; + } else { + ui_browser__write_nstring(&browser->b, "", 2); + } + + width -= 2; + + /* + * No need to call hist_entry__snprintf_alignment() + * since this fmt is always the last column in the + * hierarchy mode. + */ + if (fmt->color) { + width -= fmt->color(fmt, &hpp, entry); + } else { + int i = 0; + + width -= fmt->entry(fmt, &hpp, entry); + ui_browser__printf(&browser->b, "%s", skip_spaces(s)); + + while (isspace(s[i++])) + width++; + } + } + } + + /* The scroll bar isn't being used */ + if (!browser->b.navkeypressed) + width += 1; + + ui_browser__write_nstring(&browser->b, "", width); + + ++row; + ++printed; + +show_callchain: + if (entry->leaf && folded_sign == '-' && row != browser->b.rows) { + struct callchain_print_arg carg = { + .row_offset = row_offset, + }; + + printed += hist_browser__show_callchain(browser, entry, + level + 1, row, + hist_browser__show_callchain_entry, &carg, + hist_browser__check_output_full); } return printed; } +static int hist_browser__show_no_entry(struct hist_browser *browser, + unsigned short row, int level) +{ + int width = browser->b.width; + bool current_entry = ui_browser__is_current_entry(&browser->b, row); + bool first = true; + int column = 0; + int ret; + struct perf_hpp_fmt *fmt; + struct perf_hpp_list_node *fmt_node; + int indent = browser->hists->nr_hpp_node - 2; + + if (current_entry) { + browser->he_selection = NULL; + browser->selection = NULL; + } + + ui_browser__gotorc(&browser->b, row, 0); + + 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); + + ui_browser__write_nstring(&browser->b, "", level * HIERARCHY_INDENT); + width -= level * HIERARCHY_INDENT; + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&browser->hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (perf_hpp__should_skip(fmt, browser->hists) || + column++ < browser->b.horiz_scroll) + continue; + + ret = fmt->width(fmt, NULL, browser->hists); + + if (first) { + /* for folded sign */ + first = false; + ret++; + } else { + /* space between columns */ + ret += 2; + } + + ui_browser__write_nstring(&browser->b, "", ret); + width -= ret; + } + + ui_browser__write_nstring(&browser->b, "", indent * HIERARCHY_INDENT); + width -= indent * HIERARCHY_INDENT; + + if (column >= browser->b.horiz_scroll) { + char buf[32]; + + ret = snprintf(buf, sizeof(buf), "no entry >= %.2f%%", browser->min_pcnt); + ui_browser__printf(&browser->b, " %s", buf); + width -= ret + 2; + } + + /* The scroll bar isn't being used */ + if (!browser->b.navkeypressed) + width += 1; + + ui_browser__write_nstring(&browser->b, "", width); + return 1; +} + +static int advance_hpp_check(struct perf_hpp *hpp, int inc) +{ + advance_hpp(hpp, inc); + return hpp->size <= 0; +} + +static int +hists_browser__scnprintf_headers(struct hist_browser *browser, char *buf, + size_t size, int line) +{ + struct hists *hists = browser->hists; + struct perf_hpp dummy_hpp = { + .buf = buf, + .size = size, + }; + struct perf_hpp_fmt *fmt; + size_t ret = 0; + int column = 0; + int span = 0; + + if (hists__has_callchains(hists) && symbol_conf.use_callchain) { + ret = scnprintf(buf, size, " "); + if (advance_hpp_check(&dummy_hpp, ret)) + return ret; + } + + hists__for_each_format(browser->hists, fmt) { + if (perf_hpp__should_skip(fmt, hists) || column++ < browser->b.horiz_scroll) + continue; + + ret = fmt->header(fmt, &dummy_hpp, hists, line, &span); + if (advance_hpp_check(&dummy_hpp, ret)) + break; + + if (span) + continue; + + ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " "); + if (advance_hpp_check(&dummy_hpp, ret)) + break; + } + + return ret; +} + +static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *browser, + char *buf, size_t size, int line) +{ + struct hists *hists = browser->hists; + struct perf_hpp dummy_hpp = { + .buf = buf, + .size = size, + }; + struct perf_hpp_fmt *fmt; + struct perf_hpp_list_node *fmt_node; + size_t ret = 0; + int column = 0; + int indent = hists->nr_hpp_node - 2; + bool first_node, first_col; + + ret = scnprintf(buf, size, " "); + if (advance_hpp_check(&dummy_hpp, ret)) + return ret; + + first_node = true; + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (column++ < browser->b.horiz_scroll) + continue; + + ret = fmt->header(fmt, &dummy_hpp, hists, line, NULL); + if (advance_hpp_check(&dummy_hpp, ret)) + break; + + ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " "); + if (advance_hpp_check(&dummy_hpp, ret)) + break; + + first_node = false; + } + + if (line < hists->hpp_list->nr_header_lines - 1) + return ret; + + if (!first_node) { + ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "%*s", + indent * HIERARCHY_INDENT, ""); + if (advance_hpp_check(&dummy_hpp, ret)) + return ret; + } + + first_node = true; + list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { + if (!first_node) { + ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, " / "); + if (advance_hpp_check(&dummy_hpp, ret)) + break; + } + first_node = false; + + first_col = true; + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + char *start; + + if (perf_hpp__should_skip(fmt, hists)) + continue; + + if (!first_col) { + ret = scnprintf(dummy_hpp.buf, dummy_hpp.size, "+"); + if (advance_hpp_check(&dummy_hpp, ret)) + break; + } + first_col = false; + + ret = fmt->header(fmt, &dummy_hpp, hists, line, NULL); + dummy_hpp.buf[ret] = '\0'; + + start = strim(dummy_hpp.buf); + ret = strlen(start); + + if (start != dummy_hpp.buf) + memmove(dummy_hpp.buf, start, ret + 1); + + if (advance_hpp_check(&dummy_hpp, ret)) + break; + } + } + + return ret; +} + +static void hists_browser__hierarchy_headers(struct hist_browser *browser) +{ + struct perf_hpp_list *hpp_list = browser->hists->hpp_list; + char headers[1024]; + int line; + + for (line = 0; line < hpp_list->nr_header_lines; line++) { + hists_browser__scnprintf_hierarchy_headers(browser, headers, + sizeof(headers), line); + + ui_browser__gotorc_title(&browser->b, line, 0); + ui_browser__set_color(&browser->b, HE_COLORSET_ROOT); + ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1); + } +} + +static void hists_browser__headers(struct hist_browser *browser) +{ + struct hists *hists = browser->hists; + struct perf_hpp_list *hpp_list = hists->hpp_list; + + int line; + + for (line = 0; line < hpp_list->nr_header_lines; line++) { + char headers[1024]; + + hists_browser__scnprintf_headers(browser, headers, + sizeof(headers), line); + + ui_browser__gotorc_title(&browser->b, line, 0); + ui_browser__set_color(&browser->b, HE_COLORSET_ROOT); + ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1); + } +} + +static void hist_browser__show_headers(struct hist_browser *browser) +{ + if (symbol_conf.report_hierarchy) + hists_browser__hierarchy_headers(browser); + else + hists_browser__headers(browser); +} + 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); + browser->top = rb_first_cached(&hb->hists->entries); } } @@ -807,21 +1859,46 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser) struct rb_node *nd; struct hist_browser *hb = container_of(browser, struct hist_browser, b); + if (hb->show_headers) + hist_browser__show_headers(hb); + ui_browser__hists_init_top(browser); + hb->he_selection = NULL; + hb->selection = NULL; - for (nd = browser->top; nd; nd = rb_next(nd)) { + for (nd = browser->top; nd; nd = rb_hierarchy_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hb->hists->stats.total_period; + float percent; - if (h->filtered) + if (h->filtered) { + /* let it move to sibling */ + h->unfolded = false; continue; + } + + if (symbol_conf.report_individual_block) + percent = block_info__total_cycles_percent(h); + else + percent = hist_entry__get_percent_limit(h); if (percent < hb->min_pcnt) continue; - row += hist_browser__show_entry(hb, h, row); - if (row == browser->height) + if (symbol_conf.report_hierarchy) { + row += hist_browser__show_hierarchy_entry(hb, h, row, + h->depth); + if (row == browser->rows) + break; + + if (h->has_no_entry) { + hist_browser__show_no_entry(hb, row, h->depth + 1); + row++; + } + } else { + row += hist_browser__show_entry(hb, h, row); + } + + if (row == browser->rows) break; } @@ -829,39 +1906,39 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser) } static struct rb_node *hists__filter_entries(struct rb_node *nd, - struct hists *hists, float min_pcnt) { while (nd != NULL) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hists->stats.total_period; - - if (percent < min_pcnt) - return NULL; + float percent = hist_entry__get_percent_limit(h); - if (!h->filtered) + if (!h->filtered && percent >= min_pcnt) return nd; - nd = rb_next(nd); + /* + * If it's filtered, its all children also were filtered. + * So move to sibling node. + */ + if (rb_next(nd)) + nd = rb_next(nd); + else + nd = rb_hierarchy_next(nd); } return NULL; } static struct rb_node *hists__filter_prev_entries(struct rb_node *nd, - struct hists *hists, float min_pcnt) { while (nd != NULL) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hists->stats.total_period; + float percent = hist_entry__get_percent_limit(h); if (!h->filtered && percent >= min_pcnt) return nd; - nd = rb_prev(nd); + nd = rb_hierarchy_prev(nd); } return NULL; @@ -885,14 +1962,14 @@ static void ui_browser__hists_seek(struct ui_browser *browser, switch (whence) { case SEEK_SET: nd = hists__filter_entries(rb_first(browser->entries), - hb->hists, hb->min_pcnt); + 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->hists, hb->min_pcnt); + nd = rb_hierarchy_last(rb_last(browser->entries)); + nd = hists__filter_prev_entries(nd, hb->min_pcnt); first = false; break; default: @@ -920,10 +1997,13 @@ static void ui_browser__hists_seek(struct ui_browser *browser, * and stop when we printed enough lines to fill the screen. */ do_offset: + if (!nd) + return; + if (offset > 0) { do { h = rb_entry(nd, struct hist_entry, rb_node); - if (h->ms.unfolded) { + if (h->unfolded && h->leaf) { u16 remaining = h->nr_rows - h->row_offset; if (offset > remaining) { offset -= remaining; @@ -935,7 +2015,7 @@ do_offset: break; } } - nd = hists__filter_entries(rb_next(nd), hb->hists, + nd = hists__filter_entries(rb_hierarchy_next(nd), hb->min_pcnt); if (nd == NULL) break; @@ -945,7 +2025,7 @@ do_offset: } else if (offset < 0) { while (1) { h = rb_entry(nd, struct hist_entry, rb_node); - if (h->ms.unfolded) { + if (h->unfolded && h->leaf) { if (first) { if (-offset > h->row_offset) { offset += h->row_offset; @@ -969,7 +2049,7 @@ do_offset: } } - nd = hists__filter_prev_entries(rb_prev(nd), hb->hists, + nd = hists__filter_prev_entries(rb_hierarchy_prev(nd), hb->min_pcnt); if (nd == NULL) break; @@ -982,7 +2062,7 @@ do_offset: * row_offset at its last entry. */ h = rb_entry(nd, struct hist_entry, rb_node); - if (h->ms.unfolded) + if (h->unfolded && h->leaf) h->row_offset = h->nr_rows; break; } @@ -995,144 +2075,116 @@ do_offset: } } -static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser, - struct callchain_node *chain_node, - u64 total, int level, - FILE *fp) +static int hist_browser__fprintf_callchain(struct hist_browser *browser, + struct hist_entry *he, FILE *fp, + int level) { - struct rb_node *node; - int offset = level * LEVEL_OFFSET_STEP; - u64 new_total, remaining; - int printed = 0; - - if (callchain_param.mode == CHAIN_GRAPH_REL) - new_total = chain_node->children_hit; - else - new_total = total; - - remaining = new_total; - node = rb_first(&chain_node->rb_root); - 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; - - remaining -= cumul; - - 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 - extra_offset = LEVEL_OFFSET_STEP; - - folded_sign = callchain_list__folded(chain); - - alloc_str = NULL; - str = callchain_list__sym_name(chain, bf, sizeof(bf), - browser->show_dso); - if (was_first) { - double percent = cumul * 100.0 / new_total; - - if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) - str = "Not enough memory!"; - else - str = alloc_str; - } - - printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str); - free(alloc_str); - if (folded_sign == '+') - break; - } - - if (folded_sign == '-') { - const int new_level = level + (extra_offset ? 2 : 1); - printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total, - new_level, fp); - } - - node = next; - } + struct callchain_print_arg arg = { + .fp = fp, + }; - return printed; + hist_browser__show_callchain(browser, he, level, 0, + hist_browser__fprintf_callchain_entry, &arg, + hist_browser__check_dump_full); + return arg.printed; } -static int hist_browser__fprintf_callchain_node(struct hist_browser *browser, - struct callchain_node *node, - int level, FILE *fp) +static int hist_browser__fprintf_entry(struct hist_browser *browser, + struct hist_entry *he, FILE *fp) { - struct callchain_list *chain; - int offset = level * LEVEL_OFFSET_STEP; - char folded_sign = ' '; + 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; - list_for_each_entry(chain, &node->val, list) { - char bf[1024], *s; - - folded_sign = callchain_list__folded(chain); - s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso); - printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s); + if (hist_entry__has_callchains(he) && symbol_conf.use_callchain) { + folded_sign = hist_entry__folded(he); + printed += fprintf(fp, "%c ", folded_sign); } - if (folded_sign == '-') - printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node, - browser->hists->stats.total_period, - level + 1, fp); - return printed; -} - -static int hist_browser__fprintf_callchain(struct hist_browser *browser, - struct rb_root *chain, int level, FILE *fp) -{ - struct rb_node *nd; - int printed = 0; + hists__for_each_format(browser->hists, fmt) { + if (perf_hpp__should_skip(fmt, he->hists)) + continue; - for (nd = rb_first(chain); nd; nd = rb_next(nd)) { - struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); + if (!first) { + ret = scnprintf(hpp.buf, hpp.size, " "); + advance_hpp(&hpp, ret); + } else + first = false; - printed += hist_browser__fprintf_callchain_node(browser, node, level, fp); + ret = fmt->entry(fmt, &hpp, he); + ret = hist_entry__snprintf_alignment(he, &hpp, fmt, ret); + advance_hpp(&hpp, ret); } + printed += fprintf(fp, "%s\n", s); + + if (folded_sign == '-') + printed += hist_browser__fprintf_callchain(browser, he, fp, 1); return printed; } -static int hist_browser__fprintf_entry(struct hist_browser *browser, - struct hist_entry *he, FILE *fp) + +static int hist_browser__fprintf_hierarchy_entry(struct hist_browser *browser, + struct hist_entry *he, + FILE *fp, int level) { char s[8192]; - double percent; int printed = 0; char folded_sign = ' '; + struct perf_hpp hpp = { + .buf = s, + .size = sizeof(s), + }; + struct perf_hpp_fmt *fmt; + struct perf_hpp_list_node *fmt_node; + bool first = true; + int ret; + int hierarchy_indent = (he->hists->nr_hpp_node - 2) * HIERARCHY_INDENT; + + printed = fprintf(fp, "%*s", level * HIERARCHY_INDENT, ""); + + folded_sign = hist_entry__folded(he); + printed += fprintf(fp, "%c", folded_sign); + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&he->hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (!first) { + ret = scnprintf(hpp.buf, hpp.size, " "); + advance_hpp(&hpp, ret); + } else + first = false; - if (symbol_conf.use_callchain) - folded_sign = hist_entry__folded(he); - - hist_entry__sort_snprintf(he, s, sizeof(s), browser->hists); - percent = (he->stat.period * 100.0) / browser->hists->stats.total_period; - - if (symbol_conf.use_callchain) - printed += fprintf(fp, "%c ", folded_sign); + ret = fmt->entry(fmt, &hpp, he); + advance_hpp(&hpp, ret); + } - printed += fprintf(fp, " %5.2f%%", percent); + ret = scnprintf(hpp.buf, hpp.size, "%*s", hierarchy_indent, ""); + advance_hpp(&hpp, ret); - if (symbol_conf.show_nr_samples) - printed += fprintf(fp, " %11u", he->stat.nr_events); + perf_hpp_list__for_each_format(he->hpp_list, fmt) { + ret = scnprintf(hpp.buf, hpp.size, " "); + advance_hpp(&hpp, ret); - if (symbol_conf.show_total_period) - printed += fprintf(fp, " %12" PRIu64, he->stat.period); + ret = fmt->entry(fmt, &hpp, he); + advance_hpp(&hpp, ret); + } - printed += fprintf(fp, "%s\n", rtrim(s)); + strim(s); + printed += fprintf(fp, "%s\n", s); - if (folded_sign == '-') - printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp); + if (he->leaf && folded_sign == '-') { + printed += hist_browser__fprintf_callchain(browser, he, fp, + he->depth + 1); + } return printed; } @@ -1140,15 +2192,21 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser, static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp) { struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries), - browser->hists, 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->hists, + if (symbol_conf.report_hierarchy) { + printed += hist_browser__fprintf_hierarchy_entry(browser, + h, fp, + h->depth); + } else { + printed += hist_browser__fprintf_entry(browser, h, fp); + } + + nd = hists__filter_entries(rb_hierarchy_next(nd), browser->min_pcnt); } @@ -1176,7 +2234,7 @@ static int hist_browser__dump(struct hist_browser *browser) fp = fopen(filename, "w"); if (fp == NULL) { char bf[64]; - const char *err = strerror_r(errno, bf, sizeof(bf)); + const char *err = str_error_r(errno, bf, sizeof(bf)); ui_helpline__fpush("Couldn't write to %s: %s", filename, err); return -1; } @@ -1189,21 +2247,64 @@ static int hist_browser__dump(struct hist_browser *browser) return 0; } -static struct hist_browser *hist_browser__new(struct hists *hists) +void hist_browser__init(struct hist_browser *browser, + struct hists *hists) +{ + struct perf_hpp_fmt *fmt; + + 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; + hist_browser__set_title_space(browser); + + if (symbol_conf.report_hierarchy) { + struct perf_hpp_list_node *fmt_node; + + /* count overhead columns (in the first node) */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) + ++browser->b.columns; + + /* add a single column for whole hierarchy sort keys*/ + ++browser->b.columns; + } else { + hists__for_each_format(hists, fmt) + ++browser->b.columns; + } + + hists__reset_column_width(hists); +} + +struct hist_browser *hist_browser__new(struct hists *hists) { struct hist_browser *browser = zalloc(sizeof(*browser)); + if (browser) + hist_browser__init(browser, hists); + + return browser; +} + +static struct hist_browser * +perf_evsel_browser__new(struct evsel *evsel, + struct hist_browser_timer *hbt, + struct perf_env *env) +{ + struct hist_browser *browser = hist_browser__new(evsel__hists(evsel)); + if (browser) { - browser->hists = hists; - browser->b.refresh = hist_browser__refresh; - browser->b.seek = ui_browser__hists_seek; - browser->b.use_navkeypressed = true; + browser->hbt = hbt; + browser->env = env; + browser->title = hists_browser__scnprintf_title; } - return browser; } -static void hist_browser__delete(struct hist_browser *browser) +void hist_browser__delete(struct hist_browser *browser) { free(browser); } @@ -1218,48 +2319,40 @@ static struct thread *hist_browser__selected_thread(struct hist_browser *browser return browser->he_selection->thread; } -static int hists__browser_title(struct hists *hists, char *bf, size_t size, - const char *ev_name) +static struct res_sample *hist_browser__selected_res_sample(struct hist_browser *browser) { - 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); - char buf[512]; - size_t buflen = sizeof(buf); - - if (perf_evsel__is_group_event(evsel)) { - struct perf_evsel *pos; - - perf_evsel__group_desc(evsel, buf, buflen); - ev_name = buf; + return browser->he_selection ? browser->he_selection->res_samples : NULL; +} - for_each_group_member(pos, evsel) { - nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; - nr_events += pos->hists.stats.total_period; - } - } +/* Check whether the browser is for 'top' or 'report' */ +static inline bool is_report_browser(void *timer) +{ + return timer == NULL; +} - nr_samples = convert_unit(nr_samples, &unit); - printed = scnprintf(bf, size, - "Samples: %lu%c of event '%s', Event count (approx.): %lu", - nr_samples, unit, ev_name, nr_events); +static int hists_browser__scnprintf_title(struct hist_browser *browser, char *bf, size_t size) +{ + struct hist_browser_timer *hbt = browser->hbt; + int printed = __hists__scnprintf_title(browser->hists, bf, size, !is_report_browser(hbt)); + if (!is_report_browser(hbt)) { + struct perf_top *top = hbt->arg; - 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 : ""), - thread->pid); - if (dso) + " lost: %" PRIu64 "/%" PRIu64, + top->lost, top->lost_total); + printed += scnprintf(bf + printed, size - printed, - ", DSO: %s", dso->short_name); + " drop: %" PRIu64 "/%" PRIu64, + top->drop, top->drop_total); + + if (top->zero) + printed += scnprintf(bf + printed, size - printed, " [z]"); + + perf_top__reset_sample_counters(top); + } + + return printed; } @@ -1267,16 +2360,8 @@ static inline void free_popup_options(char **options, int n) { int i; - for (i = 0; i < n; ++i) { - free(options[i]); - options[i] = NULL; - } -} - -/* Check whether the browser is for 'top' or 'report' */ -static inline bool is_report_browser(void *timer) -{ - return timer == NULL; + for (i = 0; i < n; ++i) + zfree(&options[i]); } /* @@ -1302,7 +2387,7 @@ static int switch_data_file(void) return ret; memset(options, 0, sizeof(options)); - memset(options, 0, sizeof(abs_path)); + memset(abs_path, 0, sizeof(abs_path)); while ((dent = readdir(pwd_dir))) { char path[PATH_MAX]; @@ -1329,7 +2414,7 @@ static int switch_data_file(void) abs_path[nr_options] = strdup(path); if (!abs_path[nr_options]) { - free(options[nr_options]); + zfree(&options[nr_options]); ui__warning("Can't search all data files due to memory shortage.\n"); fclose(file); break; @@ -1349,7 +2434,7 @@ close_file_and_continue: closedir(pwd_dir); if (nr_options) { - choice = ui__popup_menu(nr_options, options); + choice = ui__popup_menu(nr_options, options, NULL); if (choice < nr_options && choice >= 0) { tmp = strdup(abs_path[choice]); if (tmp) { @@ -1368,70 +2453,647 @@ close_file_and_continue: return ret; } -static void hist_browser__update_pcnt_entries(struct hist_browser *hb) +struct popup_action { + unsigned long time; + struct thread *thread; + int (*fn)(struct hist_browser *browser, struct popup_action *act); + struct map_symbol ms; + int socket; + enum rstype rstype; + +}; + +static int +do_annotate(struct hist_browser *browser, struct popup_action *act) +{ + struct evsel *evsel; + struct annotation *notes; + struct hist_entry *he; + int err; + + if (!annotate_opts.objdump_path && + perf_env__lookup_objdump(browser->env, &annotate_opts.objdump_path)) + return 0; + + notes = symbol__annotation(act->ms.sym); + if (!notes->src) + return 0; + + if (browser->block_evsel) + evsel = browser->block_evsel; + else + evsel = hists_to_evsel(browser->hists); + + he = hist_browser__selected_entry(browser); + err = __hist_entry__tui_annotate(he, &act->ms, evsel, browser->hbt, NO_ADDR); + /* + * offer option to annotate the other branch source or target + * (if they exists) when returning from annotate + */ + if ((err == 'q' || err == CTRL('c')) && he->branch_info) + return 1; + + ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); + if (err) + ui_browser__handle_resize(&browser->b); + return 0; +} + +static struct symbol *symbol__new_unresolved(u64 addr, struct map *map) +{ + struct annotated_source *src; + struct symbol *sym; + char name[64]; + + snprintf(name, sizeof(name), "%.*" PRIx64, BITS_PER_LONG / 4, addr); + + sym = symbol__new(addr, ANNOTATION_DUMMY_LEN, 0, 0, name); + if (sym) { + src = symbol__hists(sym, 1); + if (!src) { + symbol__delete(sym); + return NULL; + } + + dso__insert_symbol(map__dso(map), sym); + } + + return sym; +} + +static int +add_annotate_opt(struct popup_action *act, char **optstr, + struct map_symbol *ms, + u64 addr) +{ + struct dso *dso; + + if (!ms->map || (dso = map__dso(ms->map)) == NULL || dso__annotate_warned(dso)) + return 0; + + if (!ms->sym) + ms->sym = symbol__new_unresolved(addr, ms->map); + + if (ms->sym == NULL || symbol__annotation(ms->sym)->src == NULL) + return 0; + + if (asprintf(optstr, "Annotate %s", ms->sym->name) < 0) + return 0; + + act->ms = *ms; + act->fn = do_annotate; + return 1; +} + +static int +do_annotate_type(struct hist_browser *browser, struct popup_action *act __maybe_unused) +{ + struct hist_entry *he = browser->he_selection; + + hist_entry__annotate_data_tui(he, hists_to_evsel(browser->hists), browser->hbt); + ui_browser__handle_resize(&browser->b); + return 0; +} + +static int +add_annotate_type_opt(struct popup_action *act, char **optstr, + struct hist_entry *he) +{ + if (he == NULL || he->mem_type == NULL || he->mem_type->histograms == NULL) + return 0; + + if (asprintf(optstr, "Annotate type %s", he->mem_type->self.type_name) < 0) + return 0; + + act->fn = do_annotate_type; + return 1; +} + +static int +do_zoom_thread(struct hist_browser *browser, struct popup_action *act) +{ + struct thread *thread = act->thread; + + if ((!hists__has(browser->hists, thread) && + !hists__has(browser->hists, comm)) || thread == NULL) + return 0; + + if (browser->hists->thread_filter) { + pstack__remove(browser->pstack, &browser->hists->thread_filter); + perf_hpp__set_elide(HISTC_THREAD, false); + thread__zput(browser->hists->thread_filter); + ui_helpline__pop(); + } else { + const char *comm_set_str = + thread__comm_set(thread) ? thread__comm_str(thread) : ""; + + if (hists__has(browser->hists, thread)) { + ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s(%d) thread\"", + comm_set_str, thread__tid(thread)); + } else { + ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s thread\"", + comm_set_str); + } + + browser->hists->thread_filter = thread__get(thread); + perf_hpp__set_elide(HISTC_THREAD, false); + pstack__push(browser->pstack, &browser->hists->thread_filter); + } + + hists__filter_by_thread(browser->hists); + hist_browser__reset(browser); + return 0; +} + +static int +add_thread_opt(struct hist_browser *browser, struct popup_action *act, + char **optstr, struct thread *thread) +{ + int ret; + const char *comm_set_str, *in_out; + + if ((!hists__has(browser->hists, thread) && + !hists__has(browser->hists, comm)) || thread == NULL) + return 0; + + in_out = browser->hists->thread_filter ? "out of" : "into"; + comm_set_str = thread__comm_set(thread) ? thread__comm_str(thread) : ""; + if (hists__has(browser->hists, thread)) { + ret = asprintf(optstr, "Zoom %s %s(%d) thread", + in_out, comm_set_str, thread__tid(thread)); + } else { + ret = asprintf(optstr, "Zoom %s %s thread", in_out, comm_set_str); + } + if (ret < 0) + return 0; + + act->thread = thread; + act->fn = do_zoom_thread; + return 1; +} + +static int hists_browser__zoom_map(struct hist_browser *browser, struct map *map) +{ + if (!hists__has(browser->hists, dso) || map == NULL) + return 0; + + if (browser->hists->dso_filter) { + pstack__remove(browser->pstack, &browser->hists->dso_filter); + perf_hpp__set_elide(HISTC_DSO, false); + browser->hists->dso_filter = NULL; + ui_helpline__pop(); + } else { + struct dso *dso = map__dso(map); + ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s DSO\"", + __map__is_kernel(map) ? "the Kernel" : dso__short_name(dso)); + browser->hists->dso_filter = dso; + perf_hpp__set_elide(HISTC_DSO, true); + pstack__push(browser->pstack, &browser->hists->dso_filter); + } + + hists__filter_by_dso(browser->hists); + hist_browser__reset(browser); + return 0; +} + +static int +do_zoom_dso(struct hist_browser *browser, struct popup_action *act) +{ + return hists_browser__zoom_map(browser, act->ms.map); +} + +static int +add_dso_opt(struct hist_browser *browser, struct popup_action *act, + char **optstr, struct map *map) +{ + if (!hists__has(browser->hists, dso) || map == NULL) + return 0; + + if (asprintf(optstr, "Zoom %s %s DSO (use the 'k' hotkey to zoom directly into the kernel)", + browser->hists->dso_filter ? "out of" : "into", + __map__is_kernel(map) ? "the Kernel" : dso__short_name(map__dso(map))) < 0) + return 0; + + act->ms.map = map; + act->fn = do_zoom_dso; + return 1; +} + +static int do_toggle_callchain(struct hist_browser *browser, struct popup_action *act __maybe_unused) +{ + hist_browser__toggle_fold(browser); + return 0; +} + +static int add_callchain_toggle_opt(struct hist_browser *browser, struct popup_action *act, char **optstr) +{ + char sym_name[512]; + + if (!hist_browser__selection_has_children(browser)) + return 0; + + if (asprintf(optstr, "%s [%s] callchain (one level, same as '+' hotkey, use 'e'/'c' for the whole main level entry)", + hist_browser__selection_unfolded(browser) ? "Collapse" : "Expand", + hist_browser__selection_sym_name(browser, sym_name, sizeof(sym_name))) < 0) + return 0; + + act->fn = do_toggle_callchain; + return 1; +} + +static int +do_browse_map(struct hist_browser *browser __maybe_unused, + struct popup_action *act) +{ + map__browse(act->ms.map); + return 0; +} + +static int +add_map_opt(struct hist_browser *browser, + struct popup_action *act, char **optstr, struct map *map) +{ + if (!hists__has(browser->hists, dso) || map == NULL) + return 0; + + if (asprintf(optstr, "Browse map details") < 0) + return 0; + + act->ms.map = map; + act->fn = do_browse_map; + return 1; +} + +static int +do_run_script(struct hist_browser *browser, + struct popup_action *act) +{ + char *script_opt; + int len; + int n = 0; + + len = 100; + if (act->thread) + len += strlen(thread__comm_str(act->thread)); + else if (act->ms.sym) + len += strlen(act->ms.sym->name); + script_opt = malloc(len); + if (!script_opt) + return -1; + + script_opt[0] = 0; + if (act->thread) { + n = scnprintf(script_opt, len, " -c %s ", + thread__comm_str(act->thread)); + } else if (act->ms.sym) { + n = scnprintf(script_opt, len, " -S %s ", + act->ms.sym->name); + } + + if (act->time) { + char start[32], end[32]; + unsigned long starttime = act->time; + unsigned long endtime = act->time + symbol_conf.time_quantum; + + if (starttime == endtime) { /* Display 1ms as fallback */ + starttime -= 1*NSEC_PER_MSEC; + endtime += 1*NSEC_PER_MSEC; + } + timestamp__scnprintf_usec(starttime, start, sizeof start); + timestamp__scnprintf_usec(endtime, end, sizeof end); + n += snprintf(script_opt + n, len - n, " --time %s,%s", start, end); + } + + script_browse(script_opt, hists_to_evsel(browser->hists)); + free(script_opt); + return 0; +} + +static int +do_res_sample_script(struct hist_browser *browser, + struct popup_action *act) +{ + struct hist_entry *he; + + he = hist_browser__selected_entry(browser); + res_sample_browse(he->res_samples, he->num_res, hists_to_evsel(browser->hists), act->rstype); + return 0; +} + +static int +add_script_opt_2(struct popup_action *act, char **optstr, + struct thread *thread, struct symbol *sym, + const char *tstr) +{ + + if (thread) { + if (asprintf(optstr, "Run scripts for samples of thread [%s]%s", + thread__comm_str(thread), tstr) < 0) + return 0; + } else if (sym) { + if (asprintf(optstr, "Run scripts for samples of symbol [%s]%s", + sym->name, tstr) < 0) + return 0; + } else { + if (asprintf(optstr, "Run scripts for all samples%s", tstr) < 0) + return 0; + } + + act->thread = thread; + act->ms.sym = sym; + act->fn = do_run_script; + return 1; +} + +static int +add_script_opt(struct hist_browser *browser, + struct popup_action *act, char **optstr, + struct thread *thread, struct symbol *sym) +{ + int n, j; + struct hist_entry *he; + + n = add_script_opt_2(act, optstr, thread, sym, ""); + + he = hist_browser__selected_entry(browser); + if (sort_order && strstr(sort_order, "time")) { + char tstr[128]; + + optstr++; + act++; + j = sprintf(tstr, " in "); + j += timestamp__scnprintf_usec(he->time, tstr + j, + sizeof tstr - j); + j += sprintf(tstr + j, "-"); + timestamp__scnprintf_usec(he->time + symbol_conf.time_quantum, + tstr + j, sizeof tstr - j); + n += add_script_opt_2(act, optstr, thread, sym, tstr); + act->time = he->time; + } + return n; +} + +static int +add_res_sample_opt(struct hist_browser *browser __maybe_unused, + struct popup_action *act, char **optstr, + struct res_sample *res_sample, + enum rstype type) +{ + if (!res_sample) + return 0; + + if (asprintf(optstr, "Show context for individual samples %s", + type == A_ASM ? "with assembler" : + type == A_SOURCE ? "with source" : "") < 0) + return 0; + + act->fn = do_res_sample_script; + act->rstype = type; + return 1; +} + +static int +do_switch_data(struct hist_browser *browser __maybe_unused, + struct popup_action *act __maybe_unused) +{ + if (switch_data_file()) { + ui__warning("Won't switch the data files due to\n" + "no valid data file get selected!\n"); + return 0; + } + + return K_SWITCH_INPUT_DATA; +} + +static int +add_switch_opt(struct hist_browser *browser, + struct popup_action *act, char **optstr) +{ + if (!is_report_browser(browser->hbt)) + return 0; + + if (asprintf(optstr, "Switch to another data file in PWD") < 0) + return 0; + + act->fn = do_switch_data; + return 1; +} + +static int +do_exit_browser(struct hist_browser *browser __maybe_unused, + struct popup_action *act __maybe_unused) +{ + return 0; +} + +static int +add_exit_opt(struct hist_browser *browser __maybe_unused, + struct popup_action *act, char **optstr) +{ + if (asprintf(optstr, "Exit") < 0) + return 0; + + act->fn = do_exit_browser; + return 1; +} + +static int +do_zoom_socket(struct hist_browser *browser, struct popup_action *act) +{ + if (!hists__has(browser->hists, socket) || act->socket < 0) + return 0; + + if (browser->hists->socket_filter > -1) { + pstack__remove(browser->pstack, &browser->hists->socket_filter); + browser->hists->socket_filter = -1; + perf_hpp__set_elide(HISTC_SOCKET, false); + } else { + browser->hists->socket_filter = act->socket; + perf_hpp__set_elide(HISTC_SOCKET, true); + pstack__push(browser->pstack, &browser->hists->socket_filter); + } + + hists__filter_by_socket(browser->hists); + hist_browser__reset(browser); + return 0; +} + +static int +add_socket_opt(struct hist_browser *browser, struct popup_action *act, + char **optstr, int socket_id) +{ + if (!hists__has(browser->hists, socket) || socket_id < 0) + return 0; + + if (asprintf(optstr, "Zoom %s Processor Socket %d", + (browser->hists->socket_filter > -1) ? "out of" : "into", + socket_id) < 0) + return 0; + + act->socket = socket_id; + act->fn = do_zoom_socket; + return 1; +} + +static void hist_browser__update_nr_entries(struct hist_browser *hb) { u64 nr_entries = 0; - struct rb_node *nd = rb_first(&hb->hists->entries); + struct rb_node *nd = rb_first_cached(&hb->hists->entries); - while (nd) { + if (hb->min_pcnt == 0 && !symbol_conf.report_hierarchy) { + 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 = hists__filter_entries(rb_next(nd), hb->hists, - hb->min_pcnt); + nd = rb_hierarchy_next(nd); } - hb->nr_pcnt_entries = nr_entries; + hb->nr_non_filtered_entries = nr_entries; + hb->nr_hierarchy_entries = nr_entries; } -static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, - const char *helpline, const char *ev_name, - bool left_exits, - struct hist_browser_timer *hbt, - float min_pcnt, - struct perf_session_env *env) +static void hist_browser__update_percent_limit(struct hist_browser *hb, + double percent) { - struct hists *hists = &evsel->hists; - struct hist_browser *browser = hist_browser__new(hists); - struct branch_info *bi; - struct pstack *fstack; - char *options[16]; + struct hist_entry *he; + struct rb_node *nd = rb_first_cached(&hb->hists->entries); + u64 total = hists__total_period(hb->hists); + u64 min_callchain_hits = total * (percent / 100); + + hb->min_pcnt = callchain_param.min_percent = percent; + + while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) { + he = rb_entry(nd, struct hist_entry, rb_node); + + if (he->has_no_entry) { + he->has_no_entry = false; + he->nr_rows = 0; + } + + if (!he->leaf || !hist_entry__has_callchains(he) || !symbol_conf.use_callchain) + goto next; + + if (callchain_param.mode == CHAIN_GRAPH_REL) { + total = he->stat.period; + + if (symbol_conf.cumulate_callchain) + total = he->stat_acc->period; + + min_callchain_hits = total * (percent / 100); + } + + callchain_param.sort(&he->sorted_chain, he->callchain, + min_callchain_hits, &callchain_param); + +next: + nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD); + + /* force to re-evaluate folding state of callchains */ + he->init_have_children = false; + hist_entry__set_folding(he, hb, false); + } +} + +static int evsel__hists_browse(struct evsel *evsel, int nr_events, const char *helpline, + bool left_exits, struct hist_browser_timer *hbt, float min_pcnt, + struct perf_env *env, bool warn_lost_event) +{ + struct hists *hists = evsel__hists(evsel); + struct hist_browser *browser = perf_evsel_browser__new(evsel, hbt, env); + struct branch_info *bi = NULL; +#define MAX_OPTIONS 16 + char *options[MAX_OPTIONS]; + struct popup_action actions[MAX_OPTIONS]; int nr_options = 0; int key = -1; - char buf[64]; - char script_opt[64]; + char buf[128]; int delay_secs = hbt ? hbt->refresh : 0; +#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 or go back to previous screen\n\n" \ + "For multiple event sessions:\n\n" \ + "TAB/UNTAB Switch events\n\n" \ + "For symbolic views (--sort has sym):\n\n" \ + "ENTER Zoom into DSO/Threads & Annotate current symbol\n" \ + "ESC Zoom out\n" \ + "+ Expand/Collapse one callchain level\n" \ + "a Annotate current symbol\n" \ + "C Collapse all callchains\n" \ + "d Zoom into current DSO\n" \ + "e Expand/Collapse main entry callchains\n" \ + "E Expand all callchains\n" \ + "F Toggle percentage of filtered entries\n" \ + "H Display column headers\n" \ + "k Zoom into the kernel map\n" \ + "L Change percent limit\n" \ + "m Display context menu\n" \ + "S Zoom into current Processor Socket\n" \ + + /* help messages are sorted by lexical order of the hotkey */ + static 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\n" + "0-9 Sort by event n in group"; + static 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" + "f Enable/Disable events\n" + "/ Filter symbol by name"; + if (browser == NULL) return -1; - if (min_pcnt) { + /* reset abort key so that it can get Ctrl-C as a key */ + SLang_reset_tty(); + SLang_init_tty(0, 0, 0); + SLtty_set_suspend_state(true); + + if (min_pcnt) browser->min_pcnt = min_pcnt; - hist_browser__update_pcnt_entries(browser); - } + hist_browser__update_nr_entries(browser); - fstack = pstack__new(2); - if (fstack == NULL) + browser->pstack = pstack__new(3); + if (browser->pstack == NULL) goto out; ui_helpline__push(helpline); memset(options, 0, sizeof(options)); + memset(actions, 0, sizeof(actions)); - while (1) { - const 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; + if (symbol_conf.col_width_list_str) + perf_hpp__set_user_width(symbol_conf.col_width_list_str); - nr_options = 0; + if (!is_report_browser(hbt)) + browser->b.no_samples_msg = "Collecting samples..."; + + while (1) { + struct thread *thread = NULL; + struct map *map = NULL; + int choice; + int socked_id = -1; - key = hist_browser__run(browser, ev_name, hbt); + key = 0; // reset key +do_hotkey: // key came straight from options ui__popup_menu() + choice = nr_options = 0; + key = hist_browser__run(browser, helpline, warn_lost_event, key); if (browser->he_selection != NULL) { thread = hist_browser__selected_thread(browser); - dso = browser->selection->map ? browser->selection->map->dso : NULL; + map = browser->selection->map; + socked_id = browser->he_selection->socket; } switch (key) { case K_TAB: @@ -1443,32 +3105,110 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, * go to the next or previous */ goto out_free_stack; + case '0' ... '9': + if (!symbol_conf.event_group || + evsel->core.nr_members < 2) { + snprintf(buf, sizeof(buf), + "Sort by index only available with group events!"); + helpline = buf; + continue; + } + + if (key - '0' == symbol_conf.group_sort_idx) + continue; + + symbol_conf.group_sort_idx = key - '0'; + + if (symbol_conf.group_sort_idx >= evsel->core.nr_members) { + snprintf(buf, sizeof(buf), + "Max event group index to sort is %d (index from 0 to %d)", + evsel->core.nr_members - 1, + evsel->core.nr_members - 1); + helpline = buf; + continue; + } + + key = K_RELOAD; + goto out_free_stack; case 'a': - if (!sort__has_sym) { + if (!hists__has(hists, 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) + if (!browser->selection || + !browser->selection->map || + !map__dso(browser->selection->map) || + dso__annotate_warned(map__dso(browser->selection->map))) { continue; - goto do_annotate; + } + + if (!browser->selection->sym) { + if (!browser->he_selection) + continue; + + if (sort__mode == SORT_MODE__BRANCH) { + bi = browser->he_selection->branch_info; + if (!bi || !bi->to.ms.map) + continue; + + actions->ms.sym = symbol__new_unresolved(bi->to.al_addr, bi->to.ms.map); + actions->ms.map = bi->to.ms.map; + } else { + actions->ms.sym = symbol__new_unresolved(browser->he_selection->ip, + browser->selection->map); + actions->ms.map = browser->selection->map; + } + + if (!actions->ms.sym) + continue; + } else { + if (symbol__annotation(browser->selection->sym)->src == NULL) { + ui_browser__warning(&browser->b, delay_secs * 2, + "No samples for the \"%s\" symbol.\n\n" + "Probably appeared just in a callchain", + browser->selection->sym->name); + continue; + } + + actions->ms.map = browser->selection->map; + actions->ms.sym = browser->selection->sym; + } + + do_annotate(browser, actions); + continue; case 'P': hist_browser__dump(browser); continue; case 'd': - goto zoom_dso; + actions->ms.map = map; + do_zoom_dso(browser, actions); + continue; + case 'k': + if (browser->selection != NULL) + hists_browser__zoom_map(browser, + maps__machine(browser->selection->maps)->vmlinux_map); + continue; case 'V': - browser->show_dso = !browser->show_dso; + verbose = (verbose + 1) % 4; + browser->show_dso = verbose > 0; + ui_helpline__fpush("Verbosity level set to %d\n", + verbose); continue; case 't': - goto zoom_thread; + actions->thread = thread; + do_zoom_thread(browser, actions); + continue; + case 'S': + actions->socket = socked_id; + do_zoom_socket(browser, actions); + continue; case '/': if (ui_browser__input_window("Symbol to show", - "Please enter the name of symbol you want to see", + "Please enter the name of symbol you want to see.\n" + "To remove the filter later, press / + ENTER.", buf, "ENTER: OK, ESC: Cancel", delay_secs * 2) == K_ENTER) { hists->symbol_filter_str = *buf ? buf : NULL; @@ -1477,289 +3217,258 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, } continue; case 'r': - if (is_report_browser(hbt)) - goto do_scripts; + if (is_report_browser(hbt)) { + actions->thread = NULL; + actions->ms.sym = NULL; + do_run_script(browser, actions); + } continue; case 's': - if (is_report_browser(hbt)) - goto do_data_switch; + if (is_report_browser(hbt)) { + key = do_switch_data(browser, actions); + if (key == K_SWITCH_INPUT_DATA) + goto out_free_stack; + } + continue; + case 'i': + /* env->arch is NULL for live-mode (i.e. perf top) */ + if (env->arch) + tui__header_window(evsel__session(evsel)); + 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 'L': + if (ui_browser__input_window("Percent Limit", + "Please enter the value you want to hide entries under that percent.", + buf, "ENTER: OK, ESC: Cancel", + delay_secs * 2) == K_ENTER) { + char *end; + double new_percent = strtod(buf, &end); + + if (new_percent < 0 || new_percent > 100) { + ui_browser__warning(&browser->b, delay_secs * 2, + "Invalid percent: %.2f", new_percent); + continue; + } + + hist_browser__update_percent_limit(browser, new_percent); + hist_browser__reset(browser); + } continue; case K_F1: case 'h': case '?': ui_browser__help_window(&browser->b, - "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" - "E Expand all callchains\n" - "d Zoom into current DSO\n" - "t Zoom into current Thread\n" - "r Run available scripts('perf report' only)\n" - "s Switch to another data file in PWD ('perf report' only)\n" - "P Print histograms to perf.hist.N\n" - "V Verbose (DSO names in callchains, etc)\n" - "/ Filter symbol by name"); + is_report_browser(hbt) ? report_help : top_help); continue; case K_ENTER: case K_RIGHT: + case 'm': /* menu */ break; + case K_ESC: case K_LEFT: { const void *top; - if (pstack__empty(fstack)) { + if (pstack__empty(browser->pstack)) { /* * Go back to the perf_evsel_menu__run or other user */ if (left_exits) goto out_free_stack; + + if (key == K_ESC && + ui_browser__dialog_yesno(&browser->b, + "Do you really want to exit?")) + 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; + actions->ms.map = map; + top = pstack__peek(browser->pstack); + if (top == &browser->hists->dso_filter) { + /* + * No need to set actions->dso here since + * it's just to remove the current filter. + */ + do_zoom_dso(browser, actions); + } else if (top == &browser->hists->thread_filter) { + actions->thread = thread; + do_zoom_thread(browser, actions); + } else if (top == &browser->hists->socket_filter) { + do_zoom_socket(browser, actions); + } 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; + case 'f': + if (!is_report_browser(hbt)) { + struct perf_top *top = hbt->arg; + + evlist__toggle_enable(top->evlist); + /* + * No need to refresh, resort/decay histogram + * entries if we are not collecting samples: + */ + if (top->evlist->enabled) { + helpline = "Press 'f' to disable the events or 'h' to see other hotkeys"; + hbt->refresh = delay_secs; + } else { + helpline = "Press 'f' again to re-enable the events"; + hbt->refresh = 0; + } + continue; + } + /* Fall thru */ default: + helpline = "Press '?' for help on key bindings"; + ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs, + ", use 'h'/'?'/F1 to see actions"); continue; } - if (!sort__has_sym) - goto add_exit_option; + if (!hists__has(hists, sym) || browser->selection == NULL) + goto skip_annotation; if (sort__mode == SORT_MODE__BRANCH) { - bi = browser->he_selection->branch_info; - if (browser->selection != NULL && - bi && - 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 (browser->selection != NULL && - bi && - 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 != NULL && - 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 (browser->he_selection) + bi = browser->he_selection->branch_info; + + if (bi == NULL) + goto skip_annotation; + + nr_options += add_annotate_opt(&actions[nr_options], + &options[nr_options], + &bi->from.ms, + bi->from.al_addr); + if (bi->to.ms.sym != bi->from.ms.sym) + nr_options += add_annotate_opt(&actions[nr_options], + &options[nr_options], + &bi->to.ms, + bi->to.al_addr); + } else if (browser->he_selection) { + nr_options += add_annotate_opt(&actions[nr_options], + &options[nr_options], + browser->selection, + browser->he_selection->ip); } - - if (thread != NULL && - asprintf(&options[nr_options], "Zoom %s %s(%d) thread", - (browser->hists->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", - (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++; - +skip_annotation: + nr_options += add_annotate_type_opt(&actions[nr_options], + &options[nr_options], + browser->he_selection); + nr_options += add_thread_opt(browser, &actions[nr_options], + &options[nr_options], thread); + nr_options += add_dso_opt(browser, &actions[nr_options], + &options[nr_options], map); + nr_options += add_callchain_toggle_opt(browser, &actions[nr_options], &options[nr_options]); + nr_options += add_map_opt(browser, &actions[nr_options], + &options[nr_options], + browser->selection ? + browser->selection->map : NULL); + nr_options += add_socket_opt(browser, &actions[nr_options], + &options[nr_options], + socked_id); /* perf script support */ - if (browser->he_selection) { - struct symbol *sym; - - if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]", - browser->he_selection->thread->comm) > 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 (!is_report_browser(hbt)) + goto skip_scripting; - 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; - int err; -do_annotate: - if (!objdump_path && perf_session_env__lookup_objdump(env)) - continue; - - he = hist_browser__selected_entry(browser); - if (he == NULL) - continue; - - /* - * we stash the branch_info symbol + map into the - * the ms so we don't have to rewrite all the annotation - * code to use branch_info. - * in branch mode, the ms struct is not used - */ - if (choice == annotate_f) { - he->ms.sym = he->branch_info->from.sym; - he->ms.map = he->branch_info->from.map; - } else if (choice == annotate_t) { - he->ms.sym = he->branch_info->to.sym; - he->ms.map = he->branch_info->to.map; + if (browser->he_selection) { + if (hists__has(hists, thread) && thread) { + nr_options += add_script_opt(browser, + &actions[nr_options], + &options[nr_options], + thread, NULL); } - - /* - * Don't let this be freed, say, by hists__decay_entry. - */ - he->used = true; - err = hist_entry__tui_annotate(he, evsel, hbt); - he->used = false; /* - * offer option to annotate the other branch source or target - * (if they exists) when returning from annotate + * Note that browser->selection != NULL + * when browser->he_selection is not NULL, + * so we don't need to check browser->selection + * before fetching browser->selection->sym like what + * we do before fetching browser->selection->map. + * + * See hist_browser__show_entry. */ - 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; - sort_dso.elide = 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; - sort_dso.elide = true; - pstack__push(fstack, &browser->hists->dso_filter); + if (hists__has(hists, sym) && browser->selection->sym) { + nr_options += add_script_opt(browser, + &actions[nr_options], + &options[nr_options], + NULL, browser->selection->sym); } - 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(); - browser->hists->thread_filter = NULL; - sort_thread.elide = false; - } else { - ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", - thread->comm_set ? thread->comm : "", - thread->pid); - browser->hists->thread_filter = thread; - sort_thread.elide = true; - 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 ", browser->he_selection->thread->comm); + nr_options += add_script_opt(browser, &actions[nr_options], + &options[nr_options], NULL, NULL); + nr_options += add_res_sample_opt(browser, &actions[nr_options], + &options[nr_options], + hist_browser__selected_res_sample(browser), + A_NORMAL); + nr_options += add_res_sample_opt(browser, &actions[nr_options], + &options[nr_options], + hist_browser__selected_res_sample(browser), + A_ASM); + nr_options += add_res_sample_opt(browser, &actions[nr_options], + &options[nr_options], + hist_browser__selected_res_sample(browser), + A_SOURCE); + nr_options += add_switch_opt(browser, &actions[nr_options], + &options[nr_options]); +skip_scripting: + nr_options += add_exit_opt(browser, &actions[nr_options], + &options[nr_options]); - if (choice == scripts_symbol) - sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name); + do { + struct popup_action *act; - 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; + choice = ui__popup_menu(nr_options, options, &key); + if (choice == -1) break; - } else - ui__warning("Won't switch the data files due to\n" - "no valid data file get selected!\n"); - } + + if (choice == nr_options) + goto do_hotkey; + + act = &actions[choice]; + key = act->fn(browser, act); + } while (key == 1); + + if (key == K_SWITCH_INPUT_DATA) + break; } out_free_stack: - pstack__delete(fstack); + pstack__delete(browser->pstack); out: hist_browser__delete(browser); - free_popup_options(options, nr_options - 1); + free_popup_options(options, MAX_OPTIONS); return key; } -struct perf_evsel_menu { +struct evsel_menu { struct ui_browser b; - struct perf_evsel *selection; + struct evsel *selection; bool lost_events, lost_events_warned; float min_pcnt; - struct perf_session_env *env; + struct perf_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 evsel_menu *menu = container_of(browser, + struct evsel_menu, b); + struct evsel *evsel = list_entry(entry, struct evsel, core.node); + struct hists *hists = evsel__hists(evsel); bool current_entry = ui_browser__is_current_entry(browser, row); - unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE]; - const char *ev_name = perf_evsel__name(evsel); + unsigned long nr_events = hists->stats.nr_samples; + const char *ev_name = evsel__name(evsel); char bf[256], unit; const char *warn = " "; size_t printed; @@ -1767,22 +3476,23 @@ static void perf_evsel_menu__write(struct ui_browser *browser, ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : HE_COLORSET_NORMAL); - if (perf_evsel__is_group_event(evsel)) { - struct perf_evsel *pos; + if (evsel__is_group_event(evsel)) { + struct evsel *pos; - ev_name = perf_evsel__group_name(evsel); + ev_name = evsel__group_name(evsel); for_each_group_member(pos, evsel) { - nr_events += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE]; + struct hists *pos_hists = evsel__hists(pos); + nr_events += pos_hists->stats.nr_samples; } } 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); + ui_browser__printf(browser, "%s", bf); - nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST]; + nr_events = evsel->evlist->stats.nr_events[PERF_RECORD_LOST]; if (nr_events != 0) { menu->lost_events = true; if (!current_entry) @@ -1793,19 +3503,20 @@ static void perf_evsel_menu__write(struct ui_browser *browser, warn = bf; } - slsmg_write_nstring(warn, browser->width - printed); + ui_browser__write_nstring(browser, warn, browser->width - printed); if (current_entry) menu->selection = evsel; } -static int perf_evsel_menu__run(struct perf_evsel_menu *menu, +static int perf_evsel_menu__run(struct evsel_menu *menu, int nr_events, const char *help, - struct hist_browser_timer *hbt) + struct hist_browser_timer *hbt, + bool warn_lost_event) { - struct perf_evlist *evlist = menu->b.priv; - struct perf_evsel *pos; - const char *ev_name, *title = "Available samples"; + struct evlist *evlist = menu->b.priv; + struct evsel *pos; + const char *title = "Available samples"; int delay_secs = hbt ? hbt->refresh : 0; int key; @@ -1818,9 +3529,12 @@ static int perf_evsel_menu__run(struct perf_evsel_menu *menu, switch (key) { case K_TIMER: - hbt->timer(hbt->arg); + if (hbt) + hbt->timer(hbt->arg); - if (!menu->lost_events_warned && menu->lost_events) { + if (!menu->lost_events_warned && + menu->lost_events && + warn_lost_event) { ui_browser__warn_lost_events(&menu->b); menu->lost_events_warned = true; } @@ -1831,41 +3545,36 @@ static int perf_evsel_menu__run(struct perf_evsel_menu *menu, continue; pos = menu->selection; browse_hists: - perf_evlist__set_selected(evlist, pos); + 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); - ev_name = perf_evsel__name(pos); - key = perf_evsel__hists_browse(pos, nr_events, help, - ev_name, true, hbt, - menu->min_pcnt, - menu->env); + key = evsel__hists_browse(pos, nr_events, help, true, hbt, + menu->min_pcnt, menu->env, + warn_lost_event); ui_browser__show_title(&menu->b, title); switch (key) { case K_TAB: - if (pos->node.next == &evlist->entries) - pos = list_entry(evlist->entries.next, struct perf_evsel, node); + if (pos->core.node.next == &evlist->core.entries) + pos = evlist__first(evlist); else - pos = list_entry(pos->node.next, struct perf_evsel, node); + pos = evsel__next(pos); goto browse_hists; case K_UNTAB: - if (pos->node.prev == &evlist->entries) - pos = list_entry(evlist->entries.prev, struct perf_evsel, node); + if (pos->core.node.prev == &evlist->core.entries) + pos = evlist__last(evlist); else - pos = list_entry(pos->node.prev, struct perf_evsel, node); + pos = 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 K_RELOAD: case 'q': case CTRL('c'): goto out; + case K_ESC: default: continue; } @@ -1880,6 +3589,7 @@ browse_hists: case CTRL('c'): goto out; default: + ui_browser__warn_unhandled_hotkey(&menu->b, key, delay_secs, NULL); continue; } } @@ -1889,27 +3599,25 @@ out: return key; } -static bool filter_group_entries(struct ui_browser *self __maybe_unused, +static bool filter_group_entries(struct ui_browser *browser __maybe_unused, void *entry) { - struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node); + struct evsel *evsel = list_entry(entry, struct evsel, core.node); - if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel)) + if (symbol_conf.event_group && !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) +static int __evlist__tui_browse_hists(struct evlist *evlist, int nr_entries, const char *help, + struct hist_browser_timer *hbt, float min_pcnt, struct perf_env *env, + bool warn_lost_event) { - struct perf_evsel *pos; - struct perf_evsel_menu menu = { + struct evsel *pos; + struct evsel_menu menu = { .b = { - .entries = &evlist->entries, + .entries = &evlist->core.entries, .refresh = ui_browser__list_head_refresh, .seek = ui_browser__list_head_seek, .write = perf_evsel_menu__write, @@ -1923,47 +3631,152 @@ static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, ui_helpline__push("Press ESC to exit"); - list_for_each_entry(pos, &evlist->entries, node) { - const char *ev_name = perf_evsel__name(pos); + evlist__for_each_entry(evlist, pos) { + const char *ev_name = 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); + return perf_evsel_menu__run(&menu, nr_entries, help, + hbt, warn_lost_event); +} + +static bool evlist__single_entry(struct evlist *evlist) +{ + int nr_entries = evlist->core.nr_entries; + + if (nr_entries == 1) + return true; + + if (nr_entries == 2) { + struct evsel *last = evlist__last(evlist); + + if (evsel__is_dummy_event(last)) + return true; + } + + return false; } -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 evlist__tui_browse_hists(struct evlist *evlist, const char *help, struct hist_browser_timer *hbt, + float min_pcnt, struct perf_env *env, bool warn_lost_event) { - int nr_entries = evlist->nr_entries; + int nr_entries = evlist->core.nr_entries; -single_entry: - if (nr_entries == 1) { - struct perf_evsel *first = list_entry(evlist->entries.next, - struct perf_evsel, node); - const char *ev_name = perf_evsel__name(first); + if (evlist__single_entry(evlist)) { +single_entry: { + struct evsel *first = evlist__first(evlist); - return perf_evsel__hists_browse(first, nr_entries, help, - ev_name, false, hbt, min_pcnt, - env); + return evsel__hists_browse(first, nr_entries, help, false, hbt, min_pcnt, + env, warn_lost_event); + } } if (symbol_conf.event_group) { - struct perf_evsel *pos; + struct evsel *pos; nr_entries = 0; - list_for_each_entry(pos, &evlist->entries, node) - if (perf_evsel__is_group_leader(pos)) + evlist__for_each_entry(evlist, pos) { + if (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); + return __evlist__tui_browse_hists(evlist, nr_entries, help, hbt, min_pcnt, env, + warn_lost_event); +} + +static int block_hists_browser__title(struct hist_browser *browser, char *bf, + size_t size) +{ + struct hists *hists = evsel__hists(browser->block_evsel); + const char *evname = evsel__name(browser->block_evsel); + unsigned long nr_samples = hists->stats.nr_samples; + int ret; + + ret = scnprintf(bf, size, "# Samples: %lu", nr_samples); + if (evname) + scnprintf(bf + ret, size - ret, " of event '%s'", evname); + + return 0; +} + +int block_hists_tui_browse(struct block_hist *bh, struct evsel *evsel, + float min_percent, struct perf_env *env) +{ + struct hists *hists = &bh->block_hists; + struct hist_browser *browser; + int key = -1; + struct popup_action action; + char *br_cntr_text = NULL; + static const char help[] = + " q/ESC Quit \n" + " B Branch counter abbr list (Optional)\n"; + + browser = hist_browser__new(hists); + if (!browser) + return -1; + + browser->block_evsel = evsel; + browser->title = block_hists_browser__title; + browser->min_pcnt = min_percent; + browser->env = env; + + /* reset abort key so that it can get Ctrl-C as a key */ + SLang_reset_tty(); + SLang_init_tty(0, 0, 0); + SLtty_set_suspend_state(true); + + memset(&action, 0, sizeof(action)); + + if (!annotation_br_cntr_abbr_list(&br_cntr_text, evsel, false)) + annotate_opts.show_br_cntr = true; + + while (1) { + key = hist_browser__run(browser, "? - help", true, 0); + + switch (key) { + case 'q': + case K_ESC: + goto out; + case '?': + ui_browser__help_window(&browser->b, help); + break; + case 'a': + case K_ENTER: + if (!browser->selection || + !browser->selection->sym) { + continue; + } + + action.ms.map = browser->selection->map; + action.ms.sym = browser->selection->sym; + do_annotate(browser, &action); + continue; + case 'B': + if (br_cntr_text) { + ui__question_window("Branch counter abbr list", + br_cntr_text, "Press any key...", 0); + } else { + ui__question_window("Branch counter abbr list", + "\n The branch counter is not available.\n", + "Press any key...", 0); + } + continue; + default: + ui_browser__warn_unhandled_hotkey(&browser->b, key, 0, + ", use '?' to see actions"); + continue; + } + } + +out: + hist_browser__delete(browser); + free(br_cntr_text); + return 0; } diff --git a/tools/perf/ui/browsers/hists.h b/tools/perf/ui/browsers/hists.h new file mode 100644 index 000000000000..de46f6c56b0e --- /dev/null +++ b/tools/perf/ui/browsers/hists.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _PERF_UI_BROWSER_HISTS_H_ +#define _PERF_UI_BROWSER_HISTS_H_ 1 + +#include "ui/browser.h" + +struct evsel; + +struct hist_browser { + struct ui_browser b; + struct hists *hists; + struct hist_entry *he_selection; + struct map_symbol *selection; + struct hist_browser_timer *hbt; + struct pstack *pstack; + struct perf_env *env; + struct evsel *block_evsel; + int print_seq; + bool show_dso; + bool show_headers; + float min_pcnt; + u64 nr_non_filtered_entries; + u64 nr_hierarchy_entries; + u64 nr_callchain_rows; + bool c2c_filter; + + /* Get title string. */ + int (*title)(struct hist_browser *browser, + char *bf, size_t size); +}; + +struct hist_browser *hist_browser__new(struct hists *hists); +void hist_browser__delete(struct hist_browser *browser); +int hist_browser__run(struct hist_browser *browser, const char *help, + bool warn_lost_event, int key); +void hist_browser__init(struct hist_browser *browser, + struct hists *hists); +#endif /* _PERF_UI_BROWSER_HISTS_H_ */ diff --git a/tools/perf/ui/browsers/map.c b/tools/perf/ui/browsers/map.c index 95c7cfb8f2c6..c61ba3174a24 100644 --- a/tools/perf/ui/browsers/map.c +++ b/tools/perf/ui/browsers/map.c @@ -1,47 +1,51 @@ -#include "../libslang.h" +// SPDX-License-Identifier: GPL-2.0 #include <elf.h> #include <inttypes.h> #include <sys/ttydefaults.h> +#include <stdlib.h> #include <string.h> #include <linux/bitops.h> -#include "../../util/util.h" #include "../../util/debug.h" +#include "../../util/map.h" +#include "../../util/dso.h" #include "../../util/symbol.h" #include "../browser.h" #include "../helpline.h" #include "../keysyms.h" #include "map.h" +#include <linux/ctype.h> + struct map_browser { struct ui_browser b; struct map *map; u8 addrlen; }; -static void map_browser__write(struct ui_browser *self, void *nd, int row) +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(self, struct map_browser, b); - bool current_entry = ui_browser__is_current_entry(self, row); + 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(self, 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 = self->width - ((mb->addrlen * 2) + 4); + ui_browser__set_percent_color(browser, 0, current_entry); + ui_browser__printf(browser, "%*" 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); + ui_browser__write_nstring(browser, sym->name, width); } /* FIXME uber-kludgy, see comment on cmd_report... */ -static u32 *symbol__browser_index(struct symbol *self) +static u32 *symbol__browser_index(struct symbol *browser) { - return ((void *)self) - sizeof(struct rb_node) - sizeof(u32); + return ((void *)browser) - sizeof(struct rb_node) - sizeof(u32); } -static int map_browser__search(struct map_browser *self) +static int map_browser__search(struct map_browser *browser) { char target[512]; struct symbol *sym; @@ -53,39 +57,41 @@ static int map_browser__search(struct map_browser *self) if (target[0] == '0' && tolower(target[1]) == 'x') { u64 addr = strtoull(target, NULL, 16); - sym = map__find_symbol(self->map, addr, NULL); + sym = map__find_symbol(browser->map, addr); } else - sym = map__find_symbol_by_name(self->map, target, NULL); + sym = map__find_symbol_by_name(browser->map, target); if (sym != NULL) { u32 *idx = symbol__browser_index(sym); - self->b.top = &sym->rb_node; - self->b.index = self->b.top_idx = *idx; + 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 *self) +static int map_browser__run(struct map_browser *browser) { int key; - 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) + if (ui_browser__show(&browser->b, dso__long_name(map__dso(browser->map)), + "Press ESC to exit, %s / to search", + verbose > 0 ? "" : "restart with -v to use") < 0) return -1; while (1) { - key = ui_browser__run(&self->b, 0); + key = ui_browser__run(&browser->b, 0); switch (key) { case '/': - if (verbose) - map_browser__search(self); + if (verbose > 0) + map_browser__search(browser); + /* fall thru */ default: - break; + ui_browser__warn_unhandled_hotkey(&browser->b, key, 0, NULL); + continue; case K_LEFT: case K_ESC: case 'q': @@ -94,20 +100,20 @@ static int map_browser__run(struct map_browser *self) } } out: - ui_browser__hide(&self->b); + ui_browser__hide(&browser->b); return key; } -int map__browse(struct map *self) +int map__browse(struct map *map) { struct map_browser mb = { .b = { - .entries = &self->dso->symbols[self->type], + .entries = dso__symbols(map__dso(map)), .refresh = ui_browser__rb_tree_refresh, .seek = ui_browser__rb_tree_seek, .write = map_browser__write, }, - .map = self, + .map = map, }; struct rb_node *nd; char tmp[BITS_PER_LONG / 4]; @@ -118,7 +124,7 @@ int map__browse(struct map *self) if (maxaddr < pos->end) maxaddr = pos->end; - if (verbose) { + if (verbose > 0) { u32 *idx = symbol__browser_index(pos); *idx = mb.b.nr_entries; } diff --git a/tools/perf/ui/browsers/map.h b/tools/perf/ui/browsers/map.h index df8581a43e17..0ed7dbb3a373 100644 --- a/tools/perf/ui/browsers/map.h +++ b/tools/perf/ui/browsers/map.h @@ -1,6 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_UI_MAP_BROWSER_H_ #define _PERF_UI_MAP_BROWSER_H_ 1 struct map; -int map__browse(struct map *self); +int map__browse(struct map *map); #endif /* _PERF_UI_MAP_BROWSER_H_ */ diff --git a/tools/perf/ui/browsers/res_sample.c b/tools/perf/ui/browsers/res_sample.c new file mode 100644 index 000000000000..5f60e515b12e --- /dev/null +++ b/tools/perf/ui/browsers/res_sample.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Display a menu with individual samples to browse with perf script */ +#include "hist.h" +#include "evsel.h" +#include "hists.h" +#include "sort.h" +#include "config.h" +#include "time-utils.h" +#include "../util.h" +#include "../../util/util.h" // perf_exe() +#include "../../perf.h" +#include <stdlib.h> +#include <string.h> +#include <linux/time64.h> +#include <linux/zalloc.h> + +static u64 context_len = 10 * NSEC_PER_MSEC; + +static int res_sample_config(const char *var, const char *value, void *data __maybe_unused) +{ + if (!strcmp(var, "samples.context")) + return perf_config_u64(&context_len, var, value); + return 0; +} + +void res_sample_init(void) +{ + perf_config(res_sample_config, NULL); +} + +int res_sample_browse(struct res_sample *res_samples, int num_res, + struct evsel *evsel, enum rstype rstype) +{ + char **names; + int i, n; + int choice; + char *cmd; + char pbuf[256], tidbuf[32], cpubuf[32]; + const char *perf = perf_exe(pbuf, sizeof pbuf); + char trange[128], tsample[64]; + struct res_sample *r; + char extra_format[256]; + + names = calloc(num_res, sizeof(char *)); + if (!names) + return -1; + for (i = 0; i < num_res; i++) { + char tbuf[64]; + + timestamp__scnprintf_nsec(res_samples[i].time, tbuf, sizeof tbuf); + if (asprintf(&names[i], "%s: CPU %d tid %d", tbuf, + res_samples[i].cpu, res_samples[i].tid) < 0) { + while (--i >= 0) + zfree(&names[i]); + free(names); + return -1; + } + } + choice = ui__popup_menu(num_res, names, NULL); + for (i = 0; i < num_res; i++) + zfree(&names[i]); + free(names); + + if (choice < 0 || choice >= num_res) + return -1; + r = &res_samples[choice]; + + n = timestamp__scnprintf_nsec(r->time - context_len, trange, sizeof trange); + trange[n++] = ','; + timestamp__scnprintf_nsec(r->time + context_len, trange + n, sizeof trange - n); + + timestamp__scnprintf_nsec(r->time, tsample, sizeof tsample); + + attr_to_script(extra_format, &evsel->core.attr); + + if (asprintf(&cmd, "%s script %s%s --time %s %s%s %s%s --ns %s %s %s %s %s | less +/%s", + perf, + input_name ? "-i " : "", + input_name ? input_name : "", + trange, + r->cpu >= 0 ? "--cpu " : "", + r->cpu >= 0 ? (sprintf(cpubuf, "%d", r->cpu), cpubuf) : "", + r->tid ? "--tid " : "", + r->tid ? (sprintf(tidbuf, "%d", r->tid), tidbuf) : "", + extra_format, + rstype == A_ASM ? "-F +disasm" : + rstype == A_SOURCE ? "-F +srcline,+srccode" : "", + symbol_conf.inline_name ? "--inline" : "", + "--show-lost-events ", + r->tid ? "--show-switch-events --show-task-events " : "", + tsample) < 0) + return -1; + run_script(cmd); + free(cmd); + return 0; +} diff --git a/tools/perf/ui/browsers/scripts.c b/tools/perf/ui/browsers/scripts.c index 12f009e61e94..1e8c2c2f952d 100644 --- a/tools/perf/ui/browsers/scripts.c +++ b/tools/perf/ui/browsers/scripts.c @@ -1,33 +1,19 @@ -#include <elf.h> -#include <inttypes.h> -#include <sys/ttydefaults.h> -#include <string.h> -#include "../../util/sort.h" -#include "../../util/util.h" +// SPDX-License-Identifier: GPL-2.0 +#include "../../util/util.h" // perf_exe() +#include "../util.h" +#include "../../util/evlist.h" #include "../../util/hist.h" #include "../../util/debug.h" +#include "../../util/session.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; -}; +#include "config.h" +#include <linux/err.h> +#include <linux/string.h> +#include <linux/zalloc.h> +#include <subcmd/exec-cmd.h> +#include <stdlib.h> #define SCRIPT_NAMELEN 128 #define SCRIPT_MAX_NO 64 @@ -39,150 +25,343 @@ struct perf_script_browser { */ #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; +struct script_config { + const char **names; + char **paths; + int index; + const char *perf; + char extra_format[256]; +}; - /* Preset the script name to SCRIPT_NAMELEN */ - buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN)); - if (!buf) - return ret; +void attr_to_script(char *extra_format, struct perf_event_attr *attr) +{ + extra_format[0] = 0; + if (attr->read_format & PERF_FORMAT_GROUP) + strcat(extra_format, " -F +metric"); + if (attr->sample_type & PERF_SAMPLE_BRANCH_STACK) + strcat(extra_format, " -F +brstackinsn --xed"); + if (attr->sample_type & PERF_SAMPLE_REGS_INTR) + strcat(extra_format, " -F +iregs"); + if (attr->sample_type & PERF_SAMPLE_REGS_USER) + strcat(extra_format, " -F +uregs"); + if (attr->sample_type & PERF_SAMPLE_PHYS_ADDR) + strcat(extra_format, " -F +phys_addr"); +} - for (i = 0; i < SCRIPT_MAX_NO; i++) { - names[i] = buf + i * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN); - paths[i] = names[i] + SCRIPT_NAMELEN; - } +static int add_script_option(const char *name, const char *opt, + struct script_config *c) +{ + c->names[c->index] = name; + if (asprintf(&c->paths[c->index], + "%s script %s -F +metric %s %s", + c->perf, opt, symbol_conf.inline_name ? " --inline" : "", + c->extra_format) < 0) + return -1; + c->index++; + return 0; +} - 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; - } - } +static int scripts_config(const char *var, const char *value, void *data) +{ + struct script_config *c = data; - free(buf); - return ret; + if (!strstarts(var, "scripts.")) + return -1; + if (c->index >= SCRIPT_MAX_NO) + return -1; + c->names[c->index] = strdup(var + 7); + if (!c->names[c->index]) + return -1; + if (asprintf(&c->paths[c->index], "%s %s", value, + c->extra_format) < 0) + return -1; + c->index++; + return 0; } -static void script_browser__write(struct ui_browser *browser, - void *entry, int row) +/* + * Some scripts specify the required events in their "xxx-record" file, + * this function will check if the events in perf.data match those + * mentioned in the "xxx-record". + * + * Fixme: All existing "xxx-record" are all in good formats "-e event ", + * which is covered well now. And new parsing code should be added to + * cover the future complex formats like event groups etc. + */ +static int check_ev_match(int dir_fd, const char *scriptname, struct perf_session *session) { - struct script_line *sline = list_entry(entry, struct script_line, node); - bool current_entry = ui_browser__is_current_entry(browser, row); + char line[BUFSIZ]; + FILE *fp; + + { + char filename[NAME_MAX + 5]; + int fd; + + scnprintf(filename, sizeof(filename), "bin/%s-record", scriptname); + fd = openat(dir_fd, filename, O_RDONLY); + if (fd == -1) + return -1; + fp = fdopen(fd, "r"); + if (!fp) + return -1; + } - ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : - HE_COLORSET_NORMAL); + while (fgets(line, sizeof(line), fp)) { + char *p = skip_spaces(line); + + if (*p == '#') + continue; + + while (strlen(p)) { + int match, len; + struct evsel *pos; + char evname[128]; + + p = strstr(p, "-e"); + if (!p) + break; + + p += 2; + p = skip_spaces(p); + len = strcspn(p, " \t"); + if (!len) + break; + + snprintf(evname, len + 1, "%s", p); + + match = 0; + evlist__for_each_entry(session->evlist, pos) { + if (evsel__name_is(pos, evname)) { + match = 1; + break; + } + } + + if (!match) { + fclose(fp); + return -1; + } + } + } - slsmg_write_nstring(sline->line, browser->width); + fclose(fp); + return 0; } -static int script_browser__run(struct perf_script_browser *self) +/* + * Return -1 if none is found, otherwise the actual scripts number. + * + * Currently the only user of this function is the script browser, which + * will list all statically runnable scripts, select one, execute it and + * show the output in a perf browser. + */ +static int find_scripts(char **scripts_array, char **scripts_path_array, int num, + int pathlen) { - int key; - - if (ui_browser__show(&self->b, self->script_name, - "Press <- or ESC to exit") < 0) + struct dirent *script_dirent, *lang_dirent; + int scripts_dir_fd, lang_dir_fd; + DIR *scripts_dir, *lang_dir; + struct perf_session *session; + struct perf_data data = { + .path = input_name, + .mode = PERF_DATA_MODE_READ, + }; + char *temp; + int i = 0; + const char *exec_path = get_argv_exec_path(); + + session = perf_session__new(&data, NULL); + if (IS_ERR(session)) + return PTR_ERR(session); + + { + char scripts_path[PATH_MAX]; + + snprintf(scripts_path, sizeof(scripts_path), "%s/scripts", exec_path); + scripts_dir_fd = open(scripts_path, O_DIRECTORY); + pr_err("Failed to open directory '%s'", scripts_path); + if (scripts_dir_fd == -1) { + perf_session__delete(session); + return -1; + } + } + scripts_dir = fdopendir(scripts_dir_fd); + if (!scripts_dir) { + close(scripts_dir_fd); + perf_session__delete(session); return -1; + } - while (1) { - key = ui_browser__run(&self->b, 0); - - /* We can add some special key handling here if needed */ - break; + while ((lang_dirent = readdir(scripts_dir)) != NULL) { + if (lang_dirent->d_type != DT_DIR && + (lang_dirent->d_type == DT_UNKNOWN && + !is_directory_at(scripts_dir_fd, lang_dirent->d_name))) + continue; + if (!strcmp(lang_dirent->d_name, ".") || !strcmp(lang_dirent->d_name, "..")) + continue; + +#ifndef HAVE_LIBPERL_SUPPORT + if (strstr(lang_dirent->d_name, "perl")) + continue; +#endif +#ifndef HAVE_LIBPYTHON_SUPPORT + if (strstr(lang_dirent->d_name, "python")) + continue; +#endif + + lang_dir_fd = openat(scripts_dir_fd, lang_dirent->d_name, O_DIRECTORY); + if (lang_dir_fd == -1) + continue; + lang_dir = fdopendir(lang_dir_fd); + if (!lang_dir) { + close(lang_dir_fd); + continue; + } + while ((script_dirent = readdir(lang_dir)) != NULL) { + if (script_dirent->d_type == DT_DIR) + continue; + if (script_dirent->d_type == DT_UNKNOWN && + is_directory_at(lang_dir_fd, script_dirent->d_name)) + continue; + /* Skip those real time scripts: xxxtop.p[yl] */ + if (strstr(script_dirent->d_name, "top.")) + continue; + if (i >= num) + break; + scnprintf(scripts_path_array[i], pathlen, "%s/scripts/%s/%s", + exec_path, + lang_dirent->d_name, + script_dirent->d_name); + temp = strchr(script_dirent->d_name, '.'); + snprintf(scripts_array[i], + (temp - script_dirent->d_name) + 1, + "%s", script_dirent->d_name); + + if (check_ev_match(lang_dir_fd, scripts_array[i], session)) + continue; + + i++; + } + closedir(lang_dir); } - ui_browser__hide(&self->b); - return key; + closedir(scripts_dir); + perf_session__delete(session); + return i; } - -int script_browse(const char *script_opt) +/* + * 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, bool *custom, + struct evsel *evsel) { - 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, + char *buf, *paths[SCRIPT_MAX_NO], *names[SCRIPT_MAX_NO]; + int i, num, choice; + int ret = 0; + int max_std, custom_perf; + char pbuf[256]; + const char *perf = perf_exe(pbuf, sizeof pbuf); + struct script_config scriptc = { + .names = (const char **)names, + .paths = paths, + .perf = perf }; - INIT_LIST_HEAD(&script.entries); + script_name[0] = 0; - /* Save each line of the output in one struct script_line object. */ - buf = zalloc((sizeof(*sline)) * MAX_LINES); + /* Preset the script name to SCRIPT_NAMELEN */ + buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN)); 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); + if (evsel) + attr_to_script(scriptc.extra_format, &evsel->core.attr); + add_script_option("Show individual samples", "", &scriptc); + add_script_option("Show individual samples with assembler", "-F +disasm", + &scriptc); + add_script_option("Show individual samples with source", "-F +srcline,+srccode", + &scriptc); + perf_config(scripts_config, &scriptc); + custom_perf = scriptc.index; + add_script_option("Show samples with custom perf script arguments", "", &scriptc); + i = scriptc.index; + max_std = i; + + for (; i < SCRIPT_MAX_NO; i++) { + names[i] = buf + (i - max_std) * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN); + paths[i] = names[i] + SCRIPT_NAMELEN; } - 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'; + num = find_scripts(names + max_std, paths + max_std, SCRIPT_MAX_NO - max_std, + SCRIPT_FULLPATH_LEN); + if (num < 0) + num = 0; + choice = ui__popup_menu(num + max_std, (char * const *)names, NULL); + if (choice < 0) { + ret = -1; + goto out; + } + if (choice == custom_perf) { + char script_args[50]; + int key = ui_browser__input_window("perf script command", + "Enter perf script command line (without perf script prefix)", + script_args, "", 0); + if (key != K_ENTER) { + ret = -1; + goto out; } - list_add_tail(&sline->node, &script.entries); + sprintf(script_name, "%s script %s", perf, script_args); + } else if (choice < num + max_std) { + strcpy(script_name, paths[choice]); + } + *custom = choice >= max_std; + +out: + free(buf); + for (i = 0; i < max_std; i++) + zfree(&paths[i]); + return ret; +} - if (script.b.width < retlen) - script.b.width = retlen; +void run_script(char *cmd) +{ + pr_debug("Running %s\n", cmd); + SLang_reset_tty(); + if (system(cmd) < 0) + pr_warning("Cannot run %s\n", cmd); + /* + * SLang doesn't seem to reset the whole terminal, so be more + * forceful to get back to the original state. + */ + printf("\033[c\033[H\033[J"); + fflush(stdout); + SLang_init_tty(0, 0, 0); + SLtty_set_suspend_state(true); + SLsmg_refresh(); +} - if (nr_entries++ >= MAX_LINES - 1) - break; - sline++; - } +int script_browse(const char *script_opt, struct evsel *evsel) +{ + char *cmd, script_name[SCRIPT_FULLPATH_LEN]; + bool custom = false; - if (script.b.width > AVERAGE_LINE_LEN) - script.b.width = AVERAGE_LINE_LEN; + memset(script_name, 0, SCRIPT_FULLPATH_LEN); + if (list_scripts(script_name, &custom, evsel)) + return -1; - if (line) - free(line); - pclose(fp); + if (asprintf(&cmd, "%s%s %s %s%s 2>&1 | less", + custom ? "perf script -s " : "", + script_name, + script_opt ? script_opt : "", + input_name ? "-i " : "", + input_name ? input_name : "") < 0) + return -1; - script.nr_lines = nr_entries; - script.b.nr_entries = nr_entries; - script.b.entries = &script.entries; + run_script(cmd); + free(cmd); - ret = script_browser__run(&script); -exit: - free(buf); - return ret; + return 0; } diff --git a/tools/perf/ui/gtk/Build b/tools/perf/ui/gtk/Build new file mode 100644 index 000000000000..eef708c502f4 --- /dev/null +++ b/tools/perf/ui/gtk/Build @@ -0,0 +1,14 @@ +CFLAGS_gtk += -fPIC $(GTK_CFLAGS) -Wno-deprecated-declarations + +gtk-y += browser.o +gtk-y += hists.o +gtk-y += setup.o +gtk-y += util.o +gtk-y += helpline.o +gtk-y += progress.o +gtk-y += annotate.o +gtk-y += zalloc.o + +$(OUTPUT)ui/gtk/zalloc.o: ../lib/zalloc.c FORCE + $(call rule_mkdir) + $(call if_changed_dep,cc_o_c) diff --git a/tools/perf/ui/gtk/annotate.c b/tools/perf/ui/gtk/annotate.c index f538794615db..8920e298420a 100644 --- a/tools/perf/ui/gtk/annotate.c +++ b/tools/perf/ui/gtk/annotate.c @@ -1,9 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 #include "gtk.h" +#include "util/sort.h" #include "util/debug.h" #include "util/annotate.h" +#include "util/evlist.h" #include "util/evsel.h" +#include "util/map.h" +#include "util/dso.h" +#include "util/symbol.h" #include "ui/helpline.h" - +#include <inttypes.h> +#include <signal.h> enum { ANN_COL__PERCENT, @@ -20,23 +27,31 @@ static const char *const col_names[] = { }; static int perf_gtk__get_percent(char *buf, size_t size, struct symbol *sym, - struct disasm_line *dl, int evidx) + struct disasm_line *dl, const struct evsel *evsel) { + struct annotation *notes; struct sym_hist *symhist; + struct sym_hist_entry *entry; double percent = 0.0; const char *markup; int ret = 0; + u64 nr_samples = 0; strcpy(buf, ""); - if (dl->offset == (s64) -1) + if (dl->al.offset == (s64) -1) return 0; - symhist = annotation__histogram(symbol__annotation(sym), evidx); - if (!symbol_conf.event_group && !symhist->addr[dl->offset]) + notes = symbol__annotation(sym); + symhist = annotation__histogram(notes, evsel); + entry = annotated_source__hist_entry(notes->src, evsel, dl->al.offset); + if (entry) + nr_samples = entry->nr_samples; + + if (!symbol_conf.event_group && nr_samples == 0) return 0; - percent = 100.0 * symhist->addr[dl->offset] / symhist->sum; + percent = 100.0 * nr_samples / symhist->nr_samples; markup = perf_gtk__get_percent_color(percent); if (markup) @@ -48,23 +63,23 @@ static int perf_gtk__get_percent(char *buf, size_t size, struct symbol *sym, return ret; } -static int perf_gtk__get_offset(char *buf, size_t size, struct symbol *sym, - struct map *map, struct disasm_line *dl) +static int perf_gtk__get_offset(char *buf, size_t size, struct map_symbol *ms, + struct disasm_line *dl) { - u64 start = map__rip_2objdump(map, sym->start); + u64 start = map__rip_2objdump(ms->map, ms->sym->start); strcpy(buf, ""); - if (dl->offset == (s64) -1) + if (dl->al.offset == (s64) -1) return 0; - return scnprintf(buf, size, "%"PRIx64, start + dl->offset); + return scnprintf(buf, size, "%"PRIx64, start + dl->al.offset); } static int perf_gtk__get_line(char *buf, size_t size, struct disasm_line *dl) { int ret = 0; - char *line = g_markup_escape_text(dl->line, -1); + char *line = g_markup_escape_text(dl->al.line, -1); const char *markup = "<span fgcolor='gray'>"; strcpy(buf, ""); @@ -72,7 +87,7 @@ static int perf_gtk__get_line(char *buf, size_t size, struct disasm_line *dl) if (!line) return 0; - if (dl->offset != (s64) -1) + if (dl->al.offset != (s64) -1) markup = NULL; if (markup) @@ -85,10 +100,11 @@ static int perf_gtk__get_line(char *buf, size_t size, struct disasm_line *dl) return ret; } -static int perf_gtk__annotate_symbol(GtkWidget *window, struct symbol *sym, - struct map *map, struct perf_evsel *evsel, +static int perf_gtk__annotate_symbol(GtkWidget *window, struct map_symbol *ms, + struct evsel *evsel, struct hist_browser_timer *hbt __maybe_unused) { + struct symbol *sym = ms->sym; struct disasm_line *pos, *n; struct annotation *notes; GType col_types[MAX_ANN_COLS]; @@ -117,28 +133,29 @@ static int perf_gtk__annotate_symbol(GtkWidget *window, struct symbol *sym, gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); g_object_unref(GTK_TREE_MODEL(store)); - list_for_each_entry(pos, ¬es->src->source, node) { + list_for_each_entry(pos, ¬es->src->source, al.node) { GtkTreeIter iter; int ret = 0; gtk_list_store_append(store, &iter); - if (perf_evsel__is_group_event(evsel)) { - for (i = 0; i < evsel->nr_members; i++) { + if (evsel__is_group_event(evsel)) { + struct evsel *cur_evsel; + + for_each_group_evsel(cur_evsel, evsel__leader(evsel)) { ret += perf_gtk__get_percent(s + ret, sizeof(s) - ret, sym, pos, - evsel->idx + i); + cur_evsel); ret += scnprintf(s + ret, sizeof(s) - ret, " "); } } else { - ret = perf_gtk__get_percent(s, sizeof(s), sym, pos, - evsel->idx); + ret = perf_gtk__get_percent(s, sizeof(s), sym, pos, evsel); } if (ret) gtk_list_store_set(store, &iter, ANN_COL__PERCENT, s, -1); - if (perf_gtk__get_offset(s, sizeof(s), sym, map, pos)) + if (perf_gtk__get_offset(s, sizeof(s), ms, pos)) gtk_list_store_set(store, &iter, ANN_COL__OFFSET, s, -1); if (perf_gtk__get_line(s, sizeof(s), pos)) gtk_list_store_set(store, &iter, ANN_COL__LINE, s, -1); @@ -146,31 +163,40 @@ static int perf_gtk__annotate_symbol(GtkWidget *window, struct symbol *sym, gtk_container_add(GTK_CONTAINER(window), view); - list_for_each_entry_safe(pos, n, ¬es->src->source, node) { - list_del(&pos->node); + list_for_each_entry_safe(pos, n, ¬es->src->source, al.node) { + list_del_init(&pos->al.node); disasm_line__free(pos); } return 0; } -int symbol__gtk_annotate(struct symbol *sym, struct map *map, - struct perf_evsel *evsel, - struct hist_browser_timer *hbt) +static int symbol__gtk_annotate(struct map_symbol *ms, struct evsel *evsel, + struct hist_browser_timer *hbt) { + struct dso *dso = map__dso(ms->map); + struct symbol *sym = ms->sym; GtkWidget *window; GtkWidget *notebook; GtkWidget *scrolled_window; GtkWidget *tab_label; + int err; - if (map->dso->annotate_warned) + if (dso__annotate_warned(dso)) return -1; - if (symbol__annotate(sym, map, 0) < 0) { - ui__error("%s", ui_helpline__current); + err = symbol__annotate(ms, evsel, NULL); + if (err) { + char msg[BUFSIZ]; + + dso__set_annotate_warned(dso); + symbol__strerror_disassemble(ms, err, msg, sizeof(msg)); + ui__error("Couldn't annotate %s: %s\n", sym->name, msg); return -1; } + symbol__calc_percent(sym, evsel); + if (perf_gtk__is_active_context(pgctx)) { window = pgctx->main_window; notebook = pgctx->notebook; @@ -222,10 +248,17 @@ int symbol__gtk_annotate(struct symbol *sym, struct map *map, gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, tab_label); - perf_gtk__annotate_symbol(scrolled_window, sym, map, evsel, hbt); + perf_gtk__annotate_symbol(scrolled_window, ms, evsel, hbt); return 0; } +int hist_entry__gtk_annotate(struct hist_entry *he, + struct evsel *evsel, + struct hist_browser_timer *hbt) +{ + return symbol__gtk_annotate(&he->ms, evsel, hbt); +} + void perf_gtk__show_annotations(void) { GtkWidget *window; diff --git a/tools/perf/ui/gtk/browser.c b/tools/perf/ui/gtk/browser.c index c95012cdb438..d2dadf3873fb 100644 --- a/tools/perf/ui/gtk/browser.c +++ b/tools/perf/ui/gtk/browser.c @@ -1,10 +1,9 @@ -#include "../evlist.h" -#include "../cache.h" +// SPDX-License-Identifier: GPL-2.0 +#include "gtk.h" #include "../evsel.h" #include "../sort.h" #include "../hist.h" #include "../helpline.h" -#include "gtk.h" #include <signal.h> @@ -43,7 +42,7 @@ const char *perf_gtk__get_percent_color(double percent) return NULL; } -#ifdef HAVE_GTK_INFO_BAR +#ifdef HAVE_GTK_INFO_BAR_SUPPORT GtkWidget *perf_gtk__setup_info_bar(void) { GtkWidget *info_bar; diff --git a/tools/perf/ui/gtk/gtk.h b/tools/perf/ui/gtk/gtk.h index 3d96785ef155..a2b497f03fd6 100644 --- a/tools/perf/ui/gtk/gtk.h +++ b/tools/perf/ui/gtk/gtk.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_GTK_H_ #define _PERF_GTK_H_ 1 @@ -12,7 +13,7 @@ struct perf_gtk_context { GtkWidget *main_window; GtkWidget *notebook; -#ifdef HAVE_GTK_INFO_BAR +#ifdef HAVE_GTK_INFO_BAR_SUPPORT GtkWidget *info_bar; GtkWidget *message_label; #endif @@ -20,6 +21,9 @@ struct perf_gtk_context { guint statbar_ctx_id; }; +int perf_gtk__init(void); +void perf_gtk__exit(bool wait_for_ok); + extern struct perf_gtk_context *pgctx; static inline bool perf_gtk__is_active_context(struct perf_gtk_context *ctx) @@ -31,7 +35,7 @@ struct perf_gtk_context *perf_gtk__activate_context(GtkWidget *window); int perf_gtk__deactivate_context(struct perf_gtk_context **ctx); void perf_gtk__init_helpline(void); -void perf_gtk__init_progress(void); +void gtk_ui_progress__init(void); void perf_gtk__init_hpp(void); void perf_gtk__signal(int sig); @@ -39,7 +43,7 @@ void perf_gtk__resize_window(GtkWidget *window); const char *perf_gtk__get_percent_color(double percent); GtkWidget *perf_gtk__setup_statusbar(void); -#ifdef HAVE_GTK_INFO_BAR +#ifdef HAVE_GTK_INFO_BAR_SUPPORT GtkWidget *perf_gtk__setup_info_bar(void); #else static inline GtkWidget *perf_gtk__setup_info_bar(void) @@ -48,4 +52,16 @@ static inline GtkWidget *perf_gtk__setup_info_bar(void) } #endif +struct evsel; +struct evlist; +struct hist_entry; +struct hist_browser_timer; + +int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, + struct hist_browser_timer *hbt, float min_pcnt); +int hist_entry__gtk_annotate(struct hist_entry *he, + struct evsel *evsel, + struct hist_browser_timer *hbt); +void perf_gtk__show_annotations(void); + #endif /* _PERF_GTK_H_ */ diff --git a/tools/perf/ui/gtk/helpline.c b/tools/perf/ui/gtk/helpline.c index 3388cbd12186..8ea95898a5bd 100644 --- a/tools/perf/ui/gtk/helpline.c +++ b/tools/perf/ui/gtk/helpline.c @@ -1,10 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "gtk.h" #include <stdio.h> #include <string.h> +#include <linux/kernel.h> -#include "gtk.h" #include "../ui.h" #include "../helpline.h" -#include "../../util/debug.h" static void gtk_helpline_pop(void) { diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index 9708dd5fb8f3..bae21f336ae6 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -1,23 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "gtk.h" #include "../evlist.h" -#include "../cache.h" +#include "../callchain.h" #include "../evsel.h" #include "../sort.h" #include "../hist.h" #include "../helpline.h" -#include "gtk.h" +#include "../string2.h" +#include <signal.h> +#include <stdlib.h> +#include <linux/string.h> #define MAX_COLUMNS 32 -static int __percent_color_snprintf(char *buf, size_t size, double percent) +static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...) { int ret = 0; + int len; + va_list args; + double percent; const char *markup; + char *buf = hpp->buf; + size_t size = hpp->size; + + va_start(args, fmt); + len = va_arg(args, int); + percent = va_arg(args, double); + va_end(args); markup = perf_gtk__get_percent_color(percent); if (markup) ret += scnprintf(buf, size, markup); - ret += scnprintf(buf + ret, size - ret, " %6.2f%%", percent); + ret += scnprintf(buf + ret, size - ret, fmt, len, percent); if (markup) ret += scnprintf(buf + ret, size - ret, "</span>"); @@ -25,76 +40,32 @@ static int __percent_color_snprintf(char *buf, size_t size, double percent) return ret; } - -static int __hpp__color_fmt(struct perf_hpp *hpp, struct hist_entry *he, - u64 (*get_field)(struct hist_entry *)) -{ - int ret; - double percent = 0.0; - struct hists *hists = he->hists; - struct perf_evsel *evsel = hists_to_evsel(hists); - - if (hists->stats.total_period) - percent = 100.0 * get_field(he) / hists->stats.total_period; - - ret = __percent_color_snprintf(hpp->buf, hpp->size, percent); - - if (perf_evsel__is_group_event(evsel)) { - int prev_idx, idx_delta; - struct hist_entry *pair; - int nr_members = evsel->nr_members; - - prev_idx = perf_evsel__group_idx(evsel); - - list_for_each_entry(pair, &he->pairs.head, pairs.node) { - u64 period = get_field(pair); - u64 total = pair->hists->stats.total_period; - - evsel = hists_to_evsel(pair->hists); - idx_delta = perf_evsel__group_idx(evsel) - prev_idx - 1; - - while (idx_delta--) { - /* - * zero-fill group members in the middle which - * have no sample - */ - ret += __percent_color_snprintf(hpp->buf + ret, - hpp->size - ret, - 0.0); - } - - percent = 100.0 * period / total; - ret += __percent_color_snprintf(hpp->buf + ret, - hpp->size - ret, - percent); - - prev_idx = perf_evsel__group_idx(evsel); - } - - idx_delta = nr_members - prev_idx - 1; - - while (idx_delta--) { - /* - * zero-fill group members at last which have no sample - */ - ret += __percent_color_snprintf(hpp->buf + ret, - hpp->size - ret, - 0.0); - } - } - return ret; -} - #define __HPP_COLOR_PERCENT_FN(_type, _field) \ static u64 he_get_##_field(struct hist_entry *he) \ { \ return he->stat._field; \ } \ \ -static int perf_gtk__hpp_color_##_type(struct perf_hpp *hpp, \ +static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ + __percent_color_snprintf, true); \ +} + +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +static u64 he_get_acc_##_field(struct hist_entry *he) \ +{ \ + return he->stat_acc->_field; \ +} \ + \ +static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, \ struct hist_entry *he) \ { \ - return __hpp__color_fmt(hpp, he, he_get_##_field); \ + return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \ + __percent_color_snprintf, true); \ } __HPP_COLOR_PERCENT_FN(overhead, period) @@ -102,16 +73,13 @@ __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 void perf_gtk__init_hpp(void) { - perf_hpp__column_enable(PERF_HPP__OVERHEAD); - - perf_hpp__init(); - perf_hpp__format[PERF_HPP__OVERHEAD].color = perf_gtk__hpp_color_overhead; perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color = @@ -122,6 +90,204 @@ void perf_gtk__init_hpp(void) perf_gtk__hpp_color_overhead_guest_sys; perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color = perf_gtk__hpp_color_overhead_guest_us; + perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = + perf_gtk__hpp_color_overhead_acc; +} + +static void perf_gtk__add_callchain_flat(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) +{ + struct rb_node *nd; + bool has_single_node = (rb_first(root) == rb_last(root)); + + for (nd = rb_first(root); nd; nd = rb_next(nd)) { + struct callchain_node *node; + struct callchain_list *chain; + GtkTreeIter iter, new_parent; + bool need_new_parent; + + node = rb_entry(nd, struct callchain_node, rb_node); + + new_parent = *parent; + need_new_parent = !has_single_node; + + callchain_node__make_parent_list(node); + + list_for_each_entry(chain, &node->parent_val, list) { + char buf[128]; + + gtk_tree_store_append(store, &iter, &new_parent); + + callchain_node__scnprintf_value(node, buf, sizeof(buf), total); + gtk_tree_store_set(store, &iter, 0, buf, -1); + + callchain_list__sym_name(chain, buf, sizeof(buf), false); + gtk_tree_store_set(store, &iter, col, buf, -1); + + if (need_new_parent) { + /* + * Only show the top-most symbol in a callchain + * if it's not the only callchain. + */ + new_parent = iter; + need_new_parent = false; + } + } + + list_for_each_entry(chain, &node->val, list) { + char buf[128]; + + gtk_tree_store_append(store, &iter, &new_parent); + + callchain_node__scnprintf_value(node, buf, sizeof(buf), total); + gtk_tree_store_set(store, &iter, 0, buf, -1); + + callchain_list__sym_name(chain, buf, sizeof(buf), false); + gtk_tree_store_set(store, &iter, col, buf, -1); + + if (need_new_parent) { + /* + * Only show the top-most symbol in a callchain + * if it's not the only callchain. + */ + new_parent = iter; + need_new_parent = false; + } + } + } +} + +static void perf_gtk__add_callchain_folded(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) +{ + struct rb_node *nd; + + for (nd = rb_first(root); nd; nd = rb_next(nd)) { + struct callchain_node *node; + struct callchain_list *chain; + GtkTreeIter iter; + char buf[64]; + char *str, *str_alloc = NULL; + bool first = true; + + node = rb_entry(nd, struct callchain_node, rb_node); + + callchain_node__make_parent_list(node); + + list_for_each_entry(chain, &node->parent_val, list) { + char name[1024]; + + callchain_list__sym_name(chain, name, sizeof(name), false); + + if (asprintf(&str, "%s%s%s", + first ? "" : str_alloc, + first ? "" : symbol_conf.field_sep ?: "; ", + name) < 0) + return; + + first = false; + free(str_alloc); + str_alloc = str; + } + + list_for_each_entry(chain, &node->val, list) { + char name[1024]; + + callchain_list__sym_name(chain, name, sizeof(name), false); + + if (asprintf(&str, "%s%s%s", + first ? "" : str_alloc, + first ? "" : symbol_conf.field_sep ?: "; ", + name) < 0) + return; + + first = false; + free(str_alloc); + str_alloc = str; + } + + gtk_tree_store_append(store, &iter, parent); + + callchain_node__scnprintf_value(node, buf, sizeof(buf), total); + gtk_tree_store_set(store, &iter, 0, buf, -1); + + gtk_tree_store_set(store, &iter, col, str, -1); + + free(str_alloc); + } +} + +static void perf_gtk__add_callchain_graph(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) +{ + struct rb_node *nd; + bool has_single_node = (rb_first(root) == rb_last(root)); + + for (nd = rb_first(root); nd; nd = rb_next(nd)) { + struct callchain_node *node; + struct callchain_list *chain; + GtkTreeIter iter, new_parent; + bool need_new_parent; + u64 child_total; + + node = rb_entry(nd, struct callchain_node, rb_node); + + new_parent = *parent; + need_new_parent = !has_single_node && (node->val_nr > 1); + + list_for_each_entry(chain, &node->val, list) { + char buf[128]; + + gtk_tree_store_append(store, &iter, &new_parent); + + callchain_node__scnprintf_value(node, buf, sizeof(buf), total); + gtk_tree_store_set(store, &iter, 0, buf, -1); + + callchain_list__sym_name(chain, buf, sizeof(buf), false); + gtk_tree_store_set(store, &iter, col, buf, -1); + + if (need_new_parent) { + /* + * Only show the top-most symbol in a callchain + * if it's not the only callchain. + */ + new_parent = iter; + need_new_parent = false; + } + } + + if (callchain_param.mode == CHAIN_GRAPH_REL) + child_total = node->children_hit; + else + child_total = total; + + /* Now 'iter' contains info of the last callchain_list */ + perf_gtk__add_callchain_graph(&node->rb_root, store, &iter, col, + child_total); + } +} + +static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) +{ + if (callchain_param.mode == CHAIN_FLAT) + perf_gtk__add_callchain_flat(root, store, parent, col, total); + else if (callchain_param.mode == CHAIN_FOLDED) + perf_gtk__add_callchain_folded(root, store, parent, col, total); + else + perf_gtk__add_callchain_graph(root, store, parent, col, total); +} + +static void on_row_activated(GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *col __maybe_unused, + gpointer user_data __maybe_unused) +{ + bool expanded = gtk_tree_view_row_expanded(view, path); + + if (expanded) + gtk_tree_view_collapse_row(view, path); + else + gtk_tree_view_expand_row(view, path, FALSE); } static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, @@ -130,33 +296,25 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, struct perf_hpp_fmt *fmt; GType col_types[MAX_COLUMNS]; GtkCellRenderer *renderer; - struct sort_entry *se; - GtkListStore *store; + GtkTreeStore *store; struct rb_node *nd; GtkWidget *view; int col_idx; + int sym_col = -1; int nr_cols; char s[512]; struct perf_hpp hpp = { .buf = s, .size = sizeof(s), - .ptr = hists_to_evsel(hists), }; nr_cols = 0; - perf_hpp__for_each_format(fmt) + hists__for_each_format(hists, fmt) col_types[nr_cols++] = G_TYPE_STRING; - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) - continue; - - col_types[nr_cols++] = G_TYPE_STRING; - } - - store = gtk_list_store_newv(nr_cols, col_types); + store = gtk_tree_store_newv(nr_cols, col_types); view = gtk_tree_view_new(); @@ -164,74 +322,278 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, col_idx = 0; - perf_hpp__for_each_format(fmt) { - fmt->header(&hpp); + hists__for_each_format(hists, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; + + /* + * XXX no way to determine where symcol column is.. + * Just use last column for now. + */ + if (perf_hpp__is_sort_entry(fmt)) + sym_col = col_idx; gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), - -1, ltrim(s), + -1, fmt->name, renderer, "markup", col_idx++, NULL); } - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) - continue; + for (col_idx = 0; col_idx < nr_cols; col_idx++) { + GtkTreeViewColumn *column; - gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), - -1, se->se_header, - renderer, "text", - col_idx++, NULL); + column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx); + gtk_tree_view_column_set_resizable(column, TRUE); + + if (col_idx == sym_col) { + gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), + column); + } } gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); g_object_unref(GTK_TREE_MODEL(store)); - for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { + for (nd = rb_first_cached(&hists->entries); nd; nd = rb_next(nd)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); GtkTreeIter iter; - float percent = h->stat.period * 100.0 / - hists->stats.total_period; + u64 total = hists__total_period(h->hists); + float percent; if (h->filtered) continue; + percent = hist_entry__get_percent_limit(h); + if (percent < min_pcnt) + continue; + + gtk_tree_store_append(store, &iter, NULL); + + col_idx = 0; + + hists__for_each_format(hists, fmt) { + if (perf_hpp__should_skip(fmt, h->hists)) + continue; + + if (fmt->color) + fmt->color(fmt, &hpp, h); + else + fmt->entry(fmt, &hpp, h); + + gtk_tree_store_set(store, &iter, col_idx++, s, -1); + } + + if (hist_entry__has_callchains(h) && + symbol_conf.use_callchain && hists__has(hists, sym)) { + if (callchain_param.mode == CHAIN_GRAPH_REL) + total = symbol_conf.cumulate_callchain ? + h->stat_acc->period : h->stat.period; + + perf_gtk__add_callchain(&h->sorted_chain, store, &iter, + sym_col, total); + } + } + + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); + + g_signal_connect(view, "row-activated", + G_CALLBACK(on_row_activated), NULL); + gtk_container_add(GTK_CONTAINER(window), view); +} + +static void perf_gtk__add_hierarchy_entries(struct hists *hists, + struct rb_root_cached *root, + GtkTreeStore *store, + GtkTreeIter *parent, + struct perf_hpp *hpp, + float min_pcnt) +{ + int col_idx = 0; + struct rb_node *node; + struct hist_entry *he; + struct perf_hpp_fmt *fmt; + struct perf_hpp_list_node *fmt_node; + u64 total = hists__total_period(hists); + int size; + + for (node = rb_first_cached(root); node; node = rb_next(node)) { + GtkTreeIter iter; + float percent; + char *bf; + + he = rb_entry(node, struct hist_entry, rb_node); + if (he->filtered) + continue; + + percent = hist_entry__get_percent_limit(he); if (percent < min_pcnt) continue; - gtk_list_store_append(store, &iter); + gtk_tree_store_append(store, &iter, parent); col_idx = 0; - perf_hpp__for_each_format(fmt) { + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (fmt->color) + fmt->color(fmt, hpp, he); + else + fmt->entry(fmt, hpp, he); + + gtk_tree_store_set(store, &iter, col_idx++, hpp->buf, -1); + } + + bf = hpp->buf; + size = hpp->size; + perf_hpp_list__for_each_format(he->hpp_list, fmt) { + int ret; + if (fmt->color) - fmt->color(&hpp, h); + ret = fmt->color(fmt, hpp, he); else - fmt->entry(&hpp, h); + ret = fmt->entry(fmt, hpp, he); - gtk_list_store_set(store, &iter, col_idx++, s, -1); + snprintf(hpp->buf + ret, hpp->size - ret, " "); + advance_hpp(hpp, ret + 2); } - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) + gtk_tree_store_set(store, &iter, col_idx, strim(bf), -1); + + if (!he->leaf) { + hpp->buf = bf; + hpp->size = size; + + perf_gtk__add_hierarchy_entries(hists, &he->hroot_out, + store, &iter, hpp, + min_pcnt); + + if (!hist_entry__has_hierarchy_children(he, min_pcnt)) { + char buf[32]; + GtkTreeIter child; + + snprintf(buf, sizeof(buf), "no entry >= %.2f%%", + min_pcnt); + + gtk_tree_store_append(store, &child, &iter); + gtk_tree_store_set(store, &child, col_idx, buf, -1); + } + } + + if (he->leaf && hist_entry__has_callchains(he) && symbol_conf.use_callchain) { + if (callchain_param.mode == CHAIN_GRAPH_REL) + total = symbol_conf.cumulate_callchain ? + he->stat_acc->period : he->stat.period; + + perf_gtk__add_callchain(&he->sorted_chain, store, &iter, + col_idx, total); + } + } + +} + +static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists, + float min_pcnt) +{ + struct perf_hpp_fmt *fmt; + struct perf_hpp_list_node *fmt_node; + GType col_types[MAX_COLUMNS]; + GtkCellRenderer *renderer; + GtkTreeStore *store; + GtkWidget *view; + int col_idx; + int nr_cols = 0; + char s[512]; + char buf[512]; + bool first_node, first_col; + struct perf_hpp hpp = { + .buf = s, + .size = sizeof(s), + }; + + hists__for_each_format(hists, fmt) { + if (perf_hpp__is_sort_entry(fmt) || + perf_hpp__is_dynamic_entry(fmt)) + break; + + col_types[nr_cols++] = G_TYPE_STRING; + } + col_types[nr_cols++] = G_TYPE_STRING; + + store = gtk_tree_store_newv(nr_cols, col_types); + view = gtk_tree_view_new(); + renderer = gtk_cell_renderer_text_new(); + + col_idx = 0; + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), + -1, fmt->name, + renderer, "markup", + col_idx++, NULL); + } + + /* construct merged column header since sort keys share single column */ + buf[0] = '\0'; + first_node = true; + list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { + if (!first_node) + strcat(buf, " / "); + first_node = false; + + first_col = true; + perf_hpp_list__for_each_format(&fmt_node->hpp ,fmt) { + if (perf_hpp__should_skip(fmt, hists)) continue; - se->se_snprintf(h, s, ARRAY_SIZE(s), - hists__col_len(hists, se->se_width_idx)); + if (!first_col) + strcat(buf, "+"); + first_col = false; + + fmt->header(fmt, &hpp, hists, 0, NULL); + strcat(buf, strim(hpp.buf)); + } + } + + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), + -1, buf, + renderer, "markup", + col_idx++, NULL); + + for (col_idx = 0; col_idx < nr_cols; col_idx++) { + GtkTreeViewColumn *column; - gtk_list_store_set(store, &iter, col_idx++, s, -1); + column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx); + gtk_tree_view_column_set_resizable(column, TRUE); + + if (col_idx == 0) { + gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), + column); } } + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); + g_object_unref(GTK_TREE_MODEL(store)); + + perf_gtk__add_hierarchy_entries(hists, &hists->entries, store, + NULL, &hpp, min_pcnt); + + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); + + g_signal_connect(view, "row-activated", + G_CALLBACK(on_row_activated), NULL); gtk_container_add(GTK_CONTAINER(window), view); } -int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, - const char *help, - struct hist_browser_timer *hbt __maybe_unused, - float min_pcnt) +int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, + struct hist_browser_timer *hbt __maybe_unused, float min_pcnt) { - struct perf_evsel *pos; + struct evsel *pos; GtkWidget *vbox; GtkWidget *notebook; GtkWidget *info_bar; @@ -269,20 +631,20 @@ int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, gtk_container_add(GTK_CONTAINER(window), vbox); - list_for_each_entry(pos, &evlist->entries, node) { - struct hists *hists = &pos->hists; - const char *evname = perf_evsel__name(pos); + evlist__for_each_entry(evlist, pos) { + struct hists *hists = evsel__hists(pos); + const char *evname = evsel__name(pos); GtkWidget *scrolled_window; GtkWidget *tab_label; char buf[512]; size_t size = sizeof(buf); if (symbol_conf.event_group) { - if (!perf_evsel__is_group_leader(pos)) + if (!evsel__is_group_leader(pos)) continue; - if (pos->nr_members > 1) { - perf_evsel__group_desc(pos, buf, size); + if (pos->core.nr_members > 1) { + evsel__group_desc(pos, buf, size); evname = buf; } } @@ -293,7 +655,10 @@ int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - perf_gtk__show_hists(scrolled_window, hists, min_pcnt); + if (symbol_conf.report_hierarchy) + perf_gtk__show_hierarchy(scrolled_window, hists, min_pcnt); + else + perf_gtk__show_hists(scrolled_window, hists, min_pcnt); tab_label = gtk_label_new(evname); diff --git a/tools/perf/ui/gtk/progress.c b/tools/perf/ui/gtk/progress.c index 482bcf3df9b7..eea6fcde518a 100644 --- a/tools/perf/ui/gtk/progress.c +++ b/tools/perf/ui/gtk/progress.c @@ -1,20 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 #include <inttypes.h> #include "gtk.h" #include "../progress.h" -#include "util.h" static GtkWidget *dialog; static GtkWidget *progress; -static void gtk_progress_update(u64 curr, u64 total, const char *title) +static void gtk_ui_progress__update(struct ui_progress *p) { - double fraction = total ? 1.0 * curr / total : 0.0; + double fraction = p->total ? 1.0 * p->curr / p->total : 0.0; char buf[1024]; if (dialog == NULL) { GtkWidget *vbox = gtk_vbox_new(TRUE, 5); - GtkWidget *label = gtk_label_new(title); + GtkWidget *label = gtk_label_new(p->title); dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); progress = gtk_progress_bar_new(); @@ -32,7 +32,7 @@ static void gtk_progress_update(u64 curr, u64 total, const char *title) } gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction); - snprintf(buf, sizeof(buf), "%"PRIu64" / %"PRIu64, curr, total); + snprintf(buf, sizeof(buf), "%"PRIu64" / %"PRIu64, p->curr, p->total); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress), buf); /* we didn't call gtk_main yet, so do it manually */ @@ -40,7 +40,7 @@ static void gtk_progress_update(u64 curr, u64 total, const char *title) gtk_main_iteration(); } -static void gtk_progress_finish(void) +static void gtk_ui_progress__finish(void) { /* this will also destroy all of its children */ gtk_widget_destroy(dialog); @@ -48,12 +48,12 @@ static void gtk_progress_finish(void) dialog = NULL; } -static struct ui_progress gtk_progress_fns = { - .update = gtk_progress_update, - .finish = gtk_progress_finish, +static struct ui_progress_ops gtk_ui_progress__ops = { + .update = gtk_ui_progress__update, + .finish = gtk_ui_progress__finish, }; -void perf_gtk__init_progress(void) +void gtk_ui_progress__init(void) { - progress_fns = >k_progress_fns; + ui_progress__ops = >k_ui_progress__ops; } diff --git a/tools/perf/ui/gtk/setup.c b/tools/perf/ui/gtk/setup.c index 6c2dd2e423f3..f5eee4d66873 100644 --- a/tools/perf/ui/gtk/setup.c +++ b/tools/perf/ui/gtk/setup.c @@ -1,6 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 #include "gtk.h" -#include "../../util/cache.h" -#include "../../util/debug.h" +#include <linux/compiler.h> +#include "../util.h" extern struct perf_error_ops perf_gtk_eops; @@ -8,7 +9,7 @@ int perf_gtk__init(void) { perf_error__register(&perf_gtk_eops); perf_gtk__init_helpline(); - perf_gtk__init_progress(); + gtk_ui_progress__init(); perf_gtk__init_hpp(); return gtk_init_check(NULL, NULL) ? 0 : -1; diff --git a/tools/perf/ui/gtk/util.c b/tools/perf/ui/gtk/util.c index c06942a41c78..c47f5c387838 100644 --- a/tools/perf/ui/gtk/util.c +++ b/tools/perf/ui/gtk/util.c @@ -1,9 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 #include "../util.h" -#include "../../util/debug.h" #include "gtk.h" +#include <stdlib.h> #include <string.h> - +#include <linux/zalloc.h> struct perf_gtk_context *pgctx; @@ -23,8 +24,7 @@ int perf_gtk__deactivate_context(struct perf_gtk_context **ctx) if (!perf_gtk__is_active_context(*ctx)) return -1; - free(*ctx); - *ctx = NULL; + zfree(ctx); return 0; } @@ -53,7 +53,7 @@ static int perf_gtk__error(const char *format, va_list args) return 0; } -#ifdef HAVE_GTK_INFO_BAR +#ifdef HAVE_GTK_INFO_BAR_SUPPORT static int perf_gtk__warning_info_bar(const char *format, va_list args) { char *msg; @@ -105,7 +105,7 @@ static int perf_gtk__warning_statusbar(const char *format, va_list args) struct perf_error_ops perf_gtk_eops = { .error = perf_gtk__error, -#ifdef HAVE_GTK_INFO_BAR +#ifdef HAVE_GTK_INFO_BAR_SUPPORT .warning = perf_gtk__warning_info_bar, #else .warning = perf_gtk__warning_statusbar, diff --git a/tools/perf/ui/helpline.c b/tools/perf/ui/helpline.c index 700fb3cfa1c7..911182b3f5e6 100644 --- a/tools/perf/ui/helpline.c +++ b/tools/perf/ui/helpline.c @@ -1,8 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "../debug.h" #include "helpline.h" #include "ui.h" @@ -71,3 +71,13 @@ int ui_helpline__vshow(const char *fmt, va_list ap) { return helpline_fns->show(fmt, ap); } + +void ui_helpline__printf(const char *fmt, ...) +{ + va_list ap; + + ui_helpline__pop(); + va_start(ap, fmt); + ui_helpline__vpush(fmt, ap); + va_end(ap); +} diff --git a/tools/perf/ui/helpline.h b/tools/perf/ui/helpline.h index 46181f4fc07e..2165a098dee8 100644 --- a/tools/perf/ui/helpline.h +++ b/tools/perf/ui/helpline.h @@ -1,11 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_UI_HELPLINE_H_ #define _PERF_UI_HELPLINE_H_ 1 #include <stdio.h> #include <stdarg.h> -#include "../util/cache.h" - struct ui_helpline { void (*pop)(void); void (*push)(const char *msg); @@ -21,6 +20,7 @@ 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); +void ui_helpline__printf(const char *fmt, ...); int ui_helpline__vshow(const char *fmt, va_list ap); extern char ui_helpline__current[512]; diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c index 4bf91b09d62d..e58327595d37 100644 --- a/tools/perf/ui/hist.c +++ b/tools/perf/ui/hist.c @@ -1,486 +1,1037 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <errno.h> +#include <inttypes.h> #include <math.h> +#include <stdlib.h> +#include <string.h> +#include <linux/compiler.h> +#include "../util/callchain.h" +#include "../util/debug.h" #include "../util/hist.h" -#include "../util/util.h" #include "../util/sort.h" #include "../util/evsel.h" +#include "../util/evlist.h" +#include "../util/mem-events.h" +#include "../util/string2.h" +#include "../util/thread.h" +#include "../util/util.h" /* hist period print (hpp) functions */ -typedef int (*hpp_snprint_fn)(char *buf, size_t size, const char *fmt, ...); +#define hpp__call_print_fn(hpp, fn, fmt, ...) \ +({ \ + int __ret = fn(hpp, fmt, ##__VA_ARGS__); \ + advance_hpp(hpp, __ret); \ + __ret; \ +}) -static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, - u64 (*get_field)(struct hist_entry *), - const char *fmt, hpp_snprint_fn print_fn, - bool fmt_percent) +static int __hpp__fmt_print(struct perf_hpp *hpp, struct hists *hists, u64 val, + int nr_samples, const char *fmt, int len, + hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype) { - int ret; - struct hists *hists = he->hists; - struct perf_evsel *evsel = hists_to_evsel(hists); - - if (fmt_percent) { + if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY) { double percent = 0.0; + u64 total = fmtype == PERF_HPP_FMT_TYPE__PERCENT ? hists__total_period(hists) : + hists__total_latency(hists); - if (hists->stats.total_period) - percent = 100.0 * get_field(he) / - hists->stats.total_period; + if (total) + percent = 100.0 * val / total; - ret = print_fn(hpp->buf, hpp->size, fmt, percent); - } else - ret = print_fn(hpp->buf, hpp->size, fmt, get_field(he)); + return hpp__call_print_fn(hpp, print_fn, fmt, len, percent); + } - if (perf_evsel__is_group_event(evsel)) { - int prev_idx, idx_delta; - struct hist_entry *pair; - int nr_members = evsel->nr_members; + if (fmtype == PERF_HPP_FMT_TYPE__AVERAGE) { + double avg = nr_samples ? (1.0 * val / nr_samples) : 0; - prev_idx = perf_evsel__group_idx(evsel); + return hpp__call_print_fn(hpp, print_fn, fmt, len, avg); + } - list_for_each_entry(pair, &he->pairs.head, pairs.node) { - u64 period = get_field(pair); - u64 total = pair->hists->stats.total_period; + return hpp__call_print_fn(hpp, print_fn, fmt, len, val); +} - if (!total) - continue; +struct hpp_fmt_value { + struct hists *hists; + u64 val; + int samples; +}; - evsel = hists_to_evsel(pair->hists); - idx_delta = perf_evsel__group_idx(evsel) - prev_idx - 1; +static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, + hpp_field_fn get_field, const char *fmt, int len, + hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype) +{ + int ret = 0; + struct hists *hists = he->hists; + struct evsel *evsel = hists_to_evsel(hists); + struct evsel *pos; + char *buf = hpp->buf; + size_t size = hpp->size; + int i = 0, nr_members = 1; + struct hpp_fmt_value *values; + + if (evsel__is_group_event(evsel)) + nr_members = evsel->core.nr_members; + + values = calloc(nr_members, sizeof(*values)); + if (values == NULL) + return 0; - while (idx_delta--) { - /* - * zero-fill group members in the middle which - * have no sample - */ - ret += print_fn(hpp->buf + ret, hpp->size - ret, - fmt, 0); - } + values[0].hists = evsel__hists(evsel); + values[0].val = get_field(he); + values[0].samples = he->stat.nr_events; - if (fmt_percent) - ret += print_fn(hpp->buf + ret, hpp->size - ret, - fmt, 100.0 * period / total); - else - ret += print_fn(hpp->buf + ret, hpp->size - ret, - fmt, period); + if (evsel__is_group_event(evsel)) { + struct hist_entry *pair; - prev_idx = perf_evsel__group_idx(evsel); - } + for_each_group_member(pos, evsel) + values[++i].hists = evsel__hists(pos); - idx_delta = nr_members - prev_idx - 1; + list_for_each_entry(pair, &he->pairs.head, pairs.node) { + for (i = 0; i < nr_members; i++) { + if (values[i].hists != pair->hists) + continue; - while (idx_delta--) { - /* - * zero-fill group members at last which have no sample - */ - ret += print_fn(hpp->buf + ret, hpp->size - ret, - fmt, 0); + values[i].val = get_field(pair); + values[i].samples = pair->stat.nr_events; + break; + } } } + + for (i = 0; i < nr_members; i++) { + if (symbol_conf.skip_empty && + values[i].hists->stats.nr_samples == 0) + continue; + + ret += __hpp__fmt_print(hpp, values[i].hists, values[i].val, + values[i].samples, fmt, len, + print_fn, fmtype); + } + + free(values); + + /* + * Restore original buf and size as it's where caller expects + * the result will be saved. + */ + hpp->buf = buf; + hpp->size = size; + return ret; } -#define __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ -static int hpp__header_##_type(struct perf_hpp *hpp) \ -{ \ - int len = _min_width; \ - \ - if (symbol_conf.event_group) { \ - struct perf_evsel *evsel = hpp->ptr; \ - \ - len = max(len, evsel->nr_members * _unit_width); \ - } \ - return scnprintf(hpp->buf, hpp->size, "%*s", len, _str); \ -} - -#define __HPP_WIDTH_FN(_type, _min_width, _unit_width) \ -static int hpp__width_##_type(struct perf_hpp *hpp __maybe_unused) \ -{ \ - int len = _min_width; \ - \ - if (symbol_conf.event_group) { \ - struct perf_evsel *evsel = hpp->ptr; \ - \ - len = max(len, evsel->nr_members * _unit_width); \ - } \ - return len; \ -} - -#define __HPP_COLOR_PERCENT_FN(_type, _field) \ -static u64 he_get_##_field(struct hist_entry *he) \ -{ \ - return he->stat._field; \ -} \ - \ -static int hpp__color_##_type(struct perf_hpp *hpp, struct hist_entry *he) \ -{ \ - return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \ - (hpp_snprint_fn)percent_color_snprintf, true); \ -} +int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he, hpp_field_fn get_field, + const char *fmtstr, hpp_snprint_fn print_fn, + enum perf_hpp_fmt_type fmtype) +{ + int len = max(fmt->user_len ?: fmt->len, (int)strlen(fmt->name)); -#define __HPP_ENTRY_PERCENT_FN(_type, _field) \ -static int hpp__entry_##_type(struct perf_hpp *hpp, struct hist_entry *he) \ -{ \ - const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \ - return __hpp__fmt(hpp, he, he_get_##_field, fmt, \ - scnprintf, true); \ + if (symbol_conf.field_sep) { + return __hpp__fmt(hpp, he, get_field, fmtstr, 1, + print_fn, fmtype); + } + + if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY) + len -= 2; /* 2 for a space and a % sign */ + else + len -= 1; + + return __hpp__fmt(hpp, he, get_field, fmtstr, len, print_fn, fmtype); } -#define __HPP_ENTRY_RAW_FN(_type, _field) \ -static u64 he_get_raw_##_field(struct hist_entry *he) \ -{ \ - return he->stat._field; \ -} \ - \ -static int hpp__entry_##_type(struct perf_hpp *hpp, struct hist_entry *he) \ -{ \ - const char *fmt = symbol_conf.field_sep ? " %"PRIu64 : " %11"PRIu64; \ - return __hpp__fmt(hpp, he, he_get_raw_##_field, fmt, scnprintf, false); \ +int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he, hpp_field_fn get_field, + const char *fmtstr, hpp_snprint_fn print_fn, + enum perf_hpp_fmt_type fmtype) +{ + if (!symbol_conf.cumulate_callchain) { + int len = fmt->user_len ?: fmt->len; + return snprintf(hpp->buf, hpp->size, " %*s", len - 1, "N/A"); + } + + return hpp__fmt(fmt, hpp, he, get_field, fmtstr, print_fn, fmtype); } -#define HPP_PERCENT_FNS(_type, _str, _field, _min_width, _unit_width) \ -__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ -__HPP_WIDTH_FN(_type, _min_width, _unit_width) \ -__HPP_COLOR_PERCENT_FN(_type, _field) \ -__HPP_ENTRY_PERCENT_FN(_type, _field) +int hpp__fmt_mem_stat(struct perf_hpp_fmt *fmt __maybe_unused, struct perf_hpp *hpp, + struct hist_entry *he, enum mem_stat_type mst, + const char *fmtstr, hpp_snprint_fn print_fn) +{ + struct hists *hists = he->hists; + int mem_stat_idx = -1; + char *buf = hpp->buf; + size_t size = hpp->size; + u64 total = 0; + int ret = 0; + + for (int i = 0; i < hists->nr_mem_stats; i++) { + if (hists->mem_stat_types[i] == mst) { + mem_stat_idx = i; + break; + } + } + assert(mem_stat_idx != -1); -#define HPP_RAW_FNS(_type, _str, _field, _min_width, _unit_width) \ -__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ -__HPP_WIDTH_FN(_type, _min_width, _unit_width) \ -__HPP_ENTRY_RAW_FN(_type, _field) + for (int i = 0; i < MEM_STAT_LEN; i++) + total += hists->mem_stat_total[mem_stat_idx].entries[i]; + assert(total != 0); + for (int i = 0; i < MEM_STAT_LEN; i++) { + u64 val = he->mem_stat[mem_stat_idx].entries[i]; -HPP_PERCENT_FNS(overhead, "Overhead", period, 8, 8) -HPP_PERCENT_FNS(overhead_sys, "sys", period_sys, 8, 8) -HPP_PERCENT_FNS(overhead_us, "usr", period_us, 8, 8) -HPP_PERCENT_FNS(overhead_guest_sys, "guest sys", period_guest_sys, 9, 8) -HPP_PERCENT_FNS(overhead_guest_us, "guest usr", period_guest_us, 9, 8) + if (hists->mem_stat_total[mem_stat_idx].entries[i] == 0) + continue; -HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12) -HPP_RAW_FNS(period, "Period", period, 12, 12) + ret += hpp__call_print_fn(hpp, print_fn, fmtstr, 100.0 * val / total); + } + /* + * Restore original buf and size as it's where caller expects + * the result will be saved. + */ + hpp->buf = buf; + hpp->size = size; -static int hpp__header_baseline(struct perf_hpp *hpp) -{ - return scnprintf(hpp->buf, hpp->size, "Baseline"); + return ret; } -static int hpp__width_baseline(struct perf_hpp *hpp __maybe_unused) +static int field_cmp(u64 field_a, u64 field_b) { - return 8; + if (field_a > field_b) + return 1; + if (field_a < field_b) + return -1; + return 0; } -static double baseline_percent(struct hist_entry *he) +static int hist_entry__new_pair(struct hist_entry *a, struct hist_entry *b, + hpp_field_fn get_field, int nr_members, + u64 **fields_a, u64 **fields_b) { - struct hist_entry *pair = hist_entry__next_pair(he); - struct hists *pair_hists = pair ? pair->hists : NULL; - double percent = 0.0; + u64 *fa = calloc(nr_members, sizeof(*fa)), + *fb = calloc(nr_members, sizeof(*fb)); + struct hist_entry *pair; - if (pair) { - u64 total_period = pair_hists->stats.total_period; - u64 base_period = pair->stat.period; + if (!fa || !fb) + goto out_free; - percent = 100.0 * base_period / total_period; + list_for_each_entry(pair, &a->pairs.head, pairs.node) { + struct evsel *evsel = hists_to_evsel(pair->hists); + fa[evsel__group_idx(evsel)] = get_field(pair); } - return percent; + list_for_each_entry(pair, &b->pairs.head, pairs.node) { + struct evsel *evsel = hists_to_evsel(pair->hists); + fb[evsel__group_idx(evsel)] = get_field(pair); + } + + *fields_a = fa; + *fields_b = fb; + return 0; +out_free: + free(fa); + free(fb); + *fields_a = *fields_b = NULL; + return -1; } -static int hpp__color_baseline(struct perf_hpp *hpp, struct hist_entry *he) +static int __hpp__group_sort_idx(struct hist_entry *a, struct hist_entry *b, + hpp_field_fn get_field, int idx) { - double percent = baseline_percent(he); + struct evsel *evsel = hists_to_evsel(a->hists); + u64 *fields_a, *fields_b; + int cmp, nr_members, ret, i; + + cmp = field_cmp(get_field(a), get_field(b)); + if (!evsel__is_group_event(evsel)) + return cmp; + + nr_members = evsel->core.nr_members; + if (idx < 1 || idx >= nr_members) + return cmp; + + ret = hist_entry__new_pair(a, b, get_field, nr_members, &fields_a, &fields_b); + if (ret) { + ret = cmp; + goto out; + } - if (hist_entry__has_pairs(he) || symbol_conf.field_sep) - return percent_color_snprintf(hpp->buf, hpp->size, " %6.2f%%", percent); - else - return scnprintf(hpp->buf, hpp->size, " "); + ret = field_cmp(fields_a[idx], fields_b[idx]); + if (ret) + goto out; + + for (i = 1; i < nr_members; i++) { + if (i != idx) { + ret = field_cmp(fields_a[i], fields_b[i]); + if (ret) + goto out; + } + } + +out: + free(fields_a); + free(fields_b); + + return ret; } -static int hpp__entry_baseline(struct perf_hpp *hpp, struct hist_entry *he) +static int __hpp__sort(struct hist_entry *a, struct hist_entry *b, + hpp_field_fn get_field) { - double percent = baseline_percent(he); - const char *fmt = symbol_conf.field_sep ? "%.2f" : " %6.2f%%"; + s64 ret; + int i, nr_members; + struct evsel *evsel; + u64 *fields_a, *fields_b; + + if (symbol_conf.group_sort_idx && symbol_conf.event_group) { + return __hpp__group_sort_idx(a, b, get_field, + symbol_conf.group_sort_idx); + } - if (hist_entry__has_pairs(he) || symbol_conf.field_sep) - return scnprintf(hpp->buf, hpp->size, fmt, percent); - else - return scnprintf(hpp->buf, hpp->size, " "); + ret = field_cmp(get_field(a), get_field(b)); + if (ret || !symbol_conf.event_group) + return ret; + + evsel = hists_to_evsel(a->hists); + if (!evsel__is_group_event(evsel)) + return ret; + + nr_members = evsel->core.nr_members; + i = hist_entry__new_pair(a, b, get_field, nr_members, &fields_a, &fields_b); + if (i) + goto out; + + for (i = 1; i < nr_members; i++) { + ret = field_cmp(fields_a[i], fields_b[i]); + if (ret) + break; + } + +out: + free(fields_a); + free(fields_b); + + return ret; } -static int hpp__header_period_baseline(struct perf_hpp *hpp) +static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b, + hpp_field_fn get_field) { - const char *fmt = symbol_conf.field_sep ? "%s" : "%12s"; + s64 ret = 0; - return scnprintf(hpp->buf, hpp->size, fmt, "Period Base"); + if (symbol_conf.cumulate_callchain) { + /* + * Put caller above callee when they have equal period. + */ + ret = field_cmp(get_field(a), get_field(b)); + if (ret) + return ret; + + if ((a->thread == NULL ? NULL : RC_CHK_ACCESS(a->thread)) != + (b->thread == NULL ? NULL : RC_CHK_ACCESS(b->thread)) || + !hist_entry__has_callchains(a) || !symbol_conf.use_callchain) + return 0; + + ret = b->callchain->max_depth - a->callchain->max_depth; + if (callchain_param.order == ORDER_CALLER) + ret = -ret; + } + return ret; } -static int hpp__width_period_baseline(struct perf_hpp *hpp __maybe_unused) +static bool perf_hpp__is_mem_stat_entry(struct perf_hpp_fmt *fmt); + +static enum mem_stat_type hpp__mem_stat_type(struct perf_hpp_fmt *fmt) { - return 12; + if (!perf_hpp__is_mem_stat_entry(fmt)) + return -1; + + switch (fmt->idx) { + case PERF_HPP__MEM_STAT_OP: + return PERF_MEM_STAT_OP; + case PERF_HPP__MEM_STAT_CACHE: + return PERF_MEM_STAT_CACHE; + case PERF_HPP__MEM_STAT_MEMORY: + return PERF_MEM_STAT_MEMORY; + case PERF_HPP__MEM_STAT_SNOOP: + return PERF_MEM_STAT_SNOOP; + case PERF_HPP__MEM_STAT_DTLB: + return PERF_MEM_STAT_DTLB; + default: + break; + } + pr_debug("Should not reach here\n"); + return -1; } -static int hpp__entry_period_baseline(struct perf_hpp *hpp, struct hist_entry *he) +static int64_t hpp__sort_mem_stat(struct perf_hpp_fmt *fmt __maybe_unused, + struct hist_entry *a, struct hist_entry *b) { - struct hist_entry *pair = hist_entry__next_pair(he); - u64 period = pair ? pair->stat.period : 0; - const char *fmt = symbol_conf.field_sep ? "%" PRIu64 : "%12" PRIu64; - - return scnprintf(hpp->buf, hpp->size, fmt, period); + return a->stat.period - b->stat.period; } -static int hpp__header_delta(struct perf_hpp *hpp) +static int hpp__width_fn(struct perf_hpp_fmt *fmt, + struct perf_hpp *hpp __maybe_unused, + struct hists *hists) { - const char *fmt = symbol_conf.field_sep ? "%s" : "%7s"; + int len = fmt->user_len ?: fmt->len; + struct evsel *evsel = hists_to_evsel(hists); + + if (symbol_conf.event_group) { + int nr = 0; + struct evsel *pos; + + for_each_group_evsel(pos, evsel) { + if (!symbol_conf.skip_empty || + evsel__hists(pos)->stats.nr_samples) + nr++; + } + + len = max(len, nr * fmt->len); + } - return scnprintf(hpp->buf, hpp->size, fmt, "Delta"); + if (len < (int)strlen(fmt->name)) + len = strlen(fmt->name); + + return len; } -static int hpp__width_delta(struct perf_hpp *hpp __maybe_unused) +static int hpp__header_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hists *hists, int line, + int *span __maybe_unused) { - return 7; + int len = hpp__width_fn(fmt, hpp, hists); + const char *hdr = ""; + + if (line == hists->hpp_list->nr_header_lines - 1) + hdr = fmt->name; + + return scnprintf(hpp->buf, hpp->size, "%*s", len, hdr); } -static int hpp__entry_delta(struct perf_hpp *hpp, struct hist_entry *he) +static int hpp__header_mem_stat_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hists *hists, int line, + int *span __maybe_unused) { - struct hist_entry *pair = hist_entry__next_pair(he); - const char *fmt = symbol_conf.field_sep ? "%s" : "%7.7s"; - char buf[32] = " "; - double diff = 0.0; + char *buf = hpp->buf; + int ret = 0; + int len; + enum mem_stat_type mst = hpp__mem_stat_type(fmt); + int mem_stat_idx = -1; + + for (int i = 0; i < hists->nr_mem_stats; i++) { + if (hists->mem_stat_types[i] == mst) { + mem_stat_idx = i; + break; + } + } + assert(mem_stat_idx != -1); - if (pair) { - if (he->diff.computed) - diff = he->diff.period_ratio_delta; - else - diff = perf_diff__compute_delta(he, pair); - } else - diff = perf_diff__period_percent(he, he->stat.period); + if (line == 0) { + int left, right; - if (fabs(diff) >= 0.01) - scnprintf(buf, sizeof(buf), "%+4.2F%%", diff); + len = 0; + /* update fmt->len for acutally used columns only */ + for (int i = 0; i < MEM_STAT_LEN; i++) { + if (hists->mem_stat_total[mem_stat_idx].entries[i]) + len += MEM_STAT_PRINT_LEN; + } + fmt->len = len; - return scnprintf(hpp->buf, hpp->size, fmt, buf); -} + /* print header directly if single column only */ + if (len == MEM_STAT_PRINT_LEN) + return scnprintf(hpp->buf, hpp->size, "%*s", len, fmt->name); -static int hpp__header_ratio(struct perf_hpp *hpp) -{ - const char *fmt = symbol_conf.field_sep ? "%s" : "%14s"; + left = (len - strlen(fmt->name)) / 2 - 1; + right = len - left - strlen(fmt->name) - 2; - return scnprintf(hpp->buf, hpp->size, fmt, "Ratio"); + if (left < 0) + left = 0; + if (right < 0) + right = 0; + + return scnprintf(hpp->buf, hpp->size, "%.*s %s %.*s", + left, graph_dotted_line, fmt->name, right, graph_dotted_line); + } + + + len = hpp->size; + for (int i = 0; i < MEM_STAT_LEN; i++) { + int printed; + + if (hists->mem_stat_total[mem_stat_idx].entries[i] == 0) + continue; + + printed = scnprintf(buf, len, "%*s", MEM_STAT_PRINT_LEN, + mem_stat_name(mst, i)); + ret += printed; + buf += printed; + len -= printed; + } + return ret; } -static int hpp__width_ratio(struct perf_hpp *hpp __maybe_unused) +int hpp_color_scnprintf(struct perf_hpp *hpp, const char *fmt, ...) { - return 14; + va_list args; + ssize_t ssize = hpp->size; + double percent; + int ret, len; + + va_start(args, fmt); + len = va_arg(args, int); + percent = va_arg(args, double); + ret = percent_color_len_snprintf(hpp->buf, hpp->size, fmt, len, percent); + va_end(args); + + return (ret >= ssize) ? (ssize - 1) : ret; } -static int hpp__entry_ratio(struct perf_hpp *hpp, struct hist_entry *he) +static int hpp_entry_scnprintf(struct perf_hpp *hpp, const char *fmt, ...) { - struct hist_entry *pair = hist_entry__next_pair(he); - const char *fmt = symbol_conf.field_sep ? "%s" : "%14s"; - char buf[32] = " "; - double ratio = 0.0; + va_list args; + ssize_t ssize = hpp->size; + int ret; - if (pair) { - if (he->diff.computed) - ratio = he->diff.period_ratio; - else - ratio = perf_diff__compute_ratio(he, pair); - } + va_start(args, fmt); + ret = vsnprintf(hpp->buf, hpp->size, fmt, args); + va_end(args); - if (ratio > 0.0) - scnprintf(buf, sizeof(buf), "%+14.6F", ratio); + return (ret >= ssize) ? (ssize - 1) : ret; +} - return scnprintf(hpp->buf, hpp->size, fmt, buf); +#define __HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \ +static u64 he_get_##_field(struct hist_entry *he) \ +{ \ + return he->stat._field; \ +} \ + \ +static int hpp__color_##_type(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ + hpp_color_scnprintf, _fmttype); \ } -static int hpp__header_wdiff(struct perf_hpp *hpp) -{ - const char *fmt = symbol_conf.field_sep ? "%s" : "%14s"; +#define __HPP_ENTRY_PERCENT_FN(_type, _field, _fmttype) \ +static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ + hpp_entry_scnprintf, _fmttype); \ +} - return scnprintf(hpp->buf, hpp->size, fmt, "Weighted diff"); +#define __HPP_SORT_FN(_type, _field) \ +static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct hist_entry *a, struct hist_entry *b) \ +{ \ + return __hpp__sort(a, b, he_get_##_field); \ } -static int hpp__width_wdiff(struct perf_hpp *hpp __maybe_unused) -{ - return 14; +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \ +static u64 he_get_acc_##_field(struct hist_entry *he) \ +{ \ + return he->stat_acc->_field; \ +} \ + \ +static int hpp__color_##_type(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \ + hpp_color_scnprintf, _fmttype); \ } -static int hpp__entry_wdiff(struct perf_hpp *hpp, struct hist_entry *he) -{ - struct hist_entry *pair = hist_entry__next_pair(he); - const char *fmt = symbol_conf.field_sep ? "%s" : "%14s"; - char buf[32] = " "; - s64 wdiff = 0; +#define __HPP_ENTRY_ACC_PERCENT_FN(_type, _field, _fmttype) \ +static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \ + hpp_entry_scnprintf, _fmttype); \ +} - if (pair) { - if (he->diff.computed) - wdiff = he->diff.wdiff; - else - wdiff = perf_diff__compute_wdiff(he, pair); - } +#define __HPP_SORT_ACC_FN(_type, _field) \ +static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct hist_entry *a, struct hist_entry *b) \ +{ \ + return __hpp__sort_acc(a, b, he_get_acc_##_field); \ +} - if (wdiff != 0) - scnprintf(buf, sizeof(buf), "%14ld", wdiff); +#define __HPP_ENTRY_RAW_FN(_type, _field) \ +static u64 he_get_raw_##_field(struct hist_entry *he) \ +{ \ + return he->stat._field; \ +} \ + \ +static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + return hpp__fmt(fmt, hpp, he, he_get_raw_##_field, " %*"PRIu64, \ + hpp_entry_scnprintf, PERF_HPP_FMT_TYPE__RAW); \ +} + +#define __HPP_SORT_RAW_FN(_type, _field) \ +static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct hist_entry *a, struct hist_entry *b) \ +{ \ + return __hpp__sort(a, b, he_get_raw_##_field); \ +} + +#define __HPP_ENTRY_AVERAGE_FN(_type, _field) \ +static u64 he_get_##_field(struct hist_entry *he) \ +{ \ + return he->stat._field; \ +} \ + \ +static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, struct hist_entry *he) \ +{ \ + return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.1f", \ + hpp_entry_scnprintf, PERF_HPP_FMT_TYPE__AVERAGE); \ +} + +#define __HPP_SORT_AVERAGE_FN(_type, _field) \ +static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct hist_entry *a, struct hist_entry *b) \ +{ \ + return __hpp__sort(a, b, he_get_##_field); \ +} - return scnprintf(hpp->buf, hpp->size, fmt, buf); +#define __HPP_COLOR_MEM_STAT_FN(_name, _type) \ +static int hpp__color_mem_stat_##_name(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \ + " %5.1f%%", hpp_color_scnprintf); \ +} + +#define __HPP_ENTRY_MEM_STAT_FN(_name, _type) \ +static int hpp__entry_mem_stat_##_name(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \ + " %5.1f%%", hpp_entry_scnprintf); \ } -static int hpp__header_formula(struct perf_hpp *hpp) +#define HPP_PERCENT_FNS(_type, _field, _fmttype) \ +__HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \ +__HPP_ENTRY_PERCENT_FN(_type, _field, _fmttype) \ +__HPP_SORT_FN(_type, _field) + +#define HPP_PERCENT_ACC_FNS(_type, _field, _fmttype) \ +__HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \ +__HPP_ENTRY_ACC_PERCENT_FN(_type, _field, _fmttype) \ +__HPP_SORT_ACC_FN(_type, _field) + +#define HPP_RAW_FNS(_type, _field) \ +__HPP_ENTRY_RAW_FN(_type, _field) \ +__HPP_SORT_RAW_FN(_type, _field) + +#define HPP_AVERAGE_FNS(_type, _field) \ +__HPP_ENTRY_AVERAGE_FN(_type, _field) \ +__HPP_SORT_AVERAGE_FN(_type, _field) + +#define HPP_MEM_STAT_FNS(_name, _type) \ +__HPP_COLOR_MEM_STAT_FN(_name, _type) \ +__HPP_ENTRY_MEM_STAT_FN(_name, _type) + +HPP_PERCENT_FNS(overhead, period, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_FNS(latency, latency, PERF_HPP_FMT_TYPE__LATENCY) +HPP_PERCENT_FNS(overhead_sys, period_sys, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_FNS(overhead_us, period_us, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_FNS(overhead_guest_sys, period_guest_sys, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_FNS(overhead_guest_us, period_guest_us, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_ACC_FNS(overhead_acc, period, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_ACC_FNS(latency_acc, latency, PERF_HPP_FMT_TYPE__LATENCY) + +HPP_RAW_FNS(samples, nr_events) +HPP_RAW_FNS(period, period) + +HPP_AVERAGE_FNS(weight1, weight1) +HPP_AVERAGE_FNS(weight2, weight2) +HPP_AVERAGE_FNS(weight3, weight3) + +HPP_MEM_STAT_FNS(op, OP) +HPP_MEM_STAT_FNS(cache, CACHE) +HPP_MEM_STAT_FNS(memory, MEMORY) +HPP_MEM_STAT_FNS(snoop, SNOOP) +HPP_MEM_STAT_FNS(dtlb, DTLB) + +static int64_t hpp__nop_cmp(struct perf_hpp_fmt *fmt __maybe_unused, + struct hist_entry *a __maybe_unused, + struct hist_entry *b __maybe_unused) { - const char *fmt = symbol_conf.field_sep ? "%s" : "%70s"; + return 0; +} - return scnprintf(hpp->buf, hpp->size, fmt, "Formula"); +static bool perf_hpp__is_mem_stat_entry(struct perf_hpp_fmt *fmt) +{ + return fmt->sort == hpp__sort_mem_stat; } -static int hpp__width_formula(struct perf_hpp *hpp __maybe_unused) +static bool perf_hpp__is_hpp_entry(struct perf_hpp_fmt *a) { - return 70; + return a->header == hpp__header_fn; } -static int hpp__entry_formula(struct perf_hpp *hpp, struct hist_entry *he) +static bool hpp__equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) { - struct hist_entry *pair = hist_entry__next_pair(he); - const char *fmt = symbol_conf.field_sep ? "%s" : "%-70s"; - char buf[96] = " "; + if (!perf_hpp__is_hpp_entry(a) || !perf_hpp__is_hpp_entry(b)) + return false; + + return a->idx == b->idx; +} - if (pair) - perf_diff__formula(he, pair, buf, sizeof(buf)); +static bool hpp__equal_mem_stat(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) +{ + if (!perf_hpp__is_mem_stat_entry(a) || !perf_hpp__is_mem_stat_entry(b)) + return false; - return scnprintf(hpp->buf, hpp->size, fmt, buf); + return a->entry == b->entry; } -#define HPP__COLOR_PRINT_FNS(_name) \ +#define HPP__COLOR_PRINT_FNS(_name, _fn, _idx) \ + { \ + .name = _name, \ + .header = hpp__header_fn, \ + .width = hpp__width_fn, \ + .color = hpp__color_ ## _fn, \ + .entry = hpp__entry_ ## _fn, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_ ## _fn, \ + .idx = PERF_HPP__ ## _idx, \ + .equal = hpp__equal, \ + } + +#define HPP__COLOR_ACC_PRINT_FNS(_name, _fn, _idx) \ + { \ + .name = _name, \ + .header = hpp__header_fn, \ + .width = hpp__width_fn, \ + .color = hpp__color_ ## _fn, \ + .entry = hpp__entry_ ## _fn, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_ ## _fn, \ + .idx = PERF_HPP__ ## _idx, \ + .equal = hpp__equal, \ + } + +#define HPP__PRINT_FNS(_name, _fn, _idx) \ { \ - .header = hpp__header_ ## _name, \ - .width = hpp__width_ ## _name, \ - .color = hpp__color_ ## _name, \ - .entry = hpp__entry_ ## _name \ + .name = _name, \ + .header = hpp__header_fn, \ + .width = hpp__width_fn, \ + .entry = hpp__entry_ ## _fn, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_ ## _fn, \ + .idx = PERF_HPP__ ## _idx, \ + .equal = hpp__equal, \ } -#define HPP__PRINT_FNS(_name) \ +#define HPP__MEM_STAT_PRINT_FNS(_name, _fn, _type) \ { \ - .header = hpp__header_ ## _name, \ - .width = hpp__width_ ## _name, \ - .entry = hpp__entry_ ## _name \ + .name = _name, \ + .header = hpp__header_mem_stat_fn, \ + .width = hpp__width_fn, \ + .color = hpp__color_mem_stat_ ## _fn, \ + .entry = hpp__entry_mem_stat_ ## _fn, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_mem_stat, \ + .idx = PERF_HPP__MEM_STAT_ ## _type, \ + .equal = hpp__equal_mem_stat, \ } struct perf_hpp_fmt perf_hpp__format[] = { - HPP__COLOR_PRINT_FNS(baseline), - HPP__COLOR_PRINT_FNS(overhead), - HPP__COLOR_PRINT_FNS(overhead_sys), - HPP__COLOR_PRINT_FNS(overhead_us), - HPP__COLOR_PRINT_FNS(overhead_guest_sys), - HPP__COLOR_PRINT_FNS(overhead_guest_us), - HPP__PRINT_FNS(samples), - HPP__PRINT_FNS(period), - HPP__PRINT_FNS(period_baseline), - HPP__PRINT_FNS(delta), - HPP__PRINT_FNS(ratio), - HPP__PRINT_FNS(wdiff), - HPP__PRINT_FNS(formula) + HPP__COLOR_PRINT_FNS("Overhead", overhead, OVERHEAD), + HPP__COLOR_PRINT_FNS("Latency", latency, LATENCY), + HPP__COLOR_PRINT_FNS("sys", overhead_sys, OVERHEAD_SYS), + HPP__COLOR_PRINT_FNS("usr", overhead_us, OVERHEAD_US), + HPP__COLOR_PRINT_FNS("guest sys", overhead_guest_sys, OVERHEAD_GUEST_SYS), + HPP__COLOR_PRINT_FNS("guest usr", overhead_guest_us, OVERHEAD_GUEST_US), + HPP__COLOR_ACC_PRINT_FNS("Children", overhead_acc, OVERHEAD_ACC), + HPP__COLOR_ACC_PRINT_FNS("Latency", latency_acc, LATENCY_ACC), + HPP__PRINT_FNS("Samples", samples, SAMPLES), + HPP__PRINT_FNS("Period", period, PERIOD), + HPP__PRINT_FNS("Weight1", weight1, WEIGHT1), + HPP__PRINT_FNS("Weight2", weight2, WEIGHT2), + HPP__PRINT_FNS("Weight3", weight3, WEIGHT3), + HPP__MEM_STAT_PRINT_FNS("Mem Op", op, OP), + HPP__MEM_STAT_PRINT_FNS("Cache", cache, CACHE), + HPP__MEM_STAT_PRINT_FNS("Memory", memory, MEMORY), + HPP__MEM_STAT_PRINT_FNS("Snoop", snoop, SNOOP), + HPP__MEM_STAT_PRINT_FNS("D-TLB", dtlb, DTLB), }; -LIST_HEAD(perf_hpp__list); - +struct perf_hpp_list perf_hpp_list = { + .fields = LIST_HEAD_INIT(perf_hpp_list.fields), + .sorts = LIST_HEAD_INIT(perf_hpp_list.sorts), + .nr_header_lines = 1, +}; #undef HPP__COLOR_PRINT_FNS +#undef HPP__COLOR_ACC_PRINT_FNS #undef HPP__PRINT_FNS +#undef HPP__MEM_STAT_PRINT_FNS #undef HPP_PERCENT_FNS +#undef HPP_PERCENT_ACC_FNS #undef HPP_RAW_FNS +#undef HPP_AVERAGE_FNS +#undef HPP_MEM_STAT_FNS #undef __HPP_HEADER_FN #undef __HPP_WIDTH_FN #undef __HPP_COLOR_PERCENT_FN #undef __HPP_ENTRY_PERCENT_FN +#undef __HPP_COLOR_ACC_PERCENT_FN +#undef __HPP_ENTRY_ACC_PERCENT_FN #undef __HPP_ENTRY_RAW_FN +#undef __HPP_ENTRY_AVERAGE_FN +#undef __HPP_COLOR_MEM_STAT_FN +#undef __HPP_ENTRY_MEM_STAT_FN + +#undef __HPP_SORT_FN +#undef __HPP_SORT_ACC_FN +#undef __HPP_SORT_RAW_FN +#undef __HPP_SORT_AVERAGE_FN + +static void fmt_free(struct perf_hpp_fmt *fmt) +{ + /* + * At this point fmt should be completely + * unhooked, if not it's a bug. + */ + BUG_ON(!list_empty(&fmt->list)); + BUG_ON(!list_empty(&fmt->sort_list)); + + if (fmt->free) + fmt->free(fmt); +} +static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) +{ + return a->equal && a->equal(a, b); +} void perf_hpp__init(void) { + int i; + + for (i = 0; i < PERF_HPP__MAX_INDEX; i++) { + struct perf_hpp_fmt *fmt = &perf_hpp__format[i]; + + INIT_LIST_HEAD(&fmt->list); + + /* sort_list may be linked by setup_sorting() */ + if (fmt->sort_list.next == NULL) + INIT_LIST_HEAD(&fmt->sort_list); + } + + /* + * If user specified field order, no need to setup default fields. + */ + if (is_strict_order(field_order)) + return; + + /* + * Overhead and latency columns are added in setup_overhead(), + * so they are added implicitly here only if they were added + * by setup_overhead() before (have was_taken flag set). + * This is required because setup_overhead() has more complex + * logic, in particular it does not add "overhead" if user + * specified "latency" in sort order, and vise versa. + */ + if (symbol_conf.cumulate_callchain) { + /* + * Addition of fields is idempotent, so we add latency + * column twice to get desired order with simpler logic. + */ + if (symbol_conf.prefer_latency) + hpp_dimension__add_output(PERF_HPP__LATENCY_ACC, true); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_ACC, true); + if (symbol_conf.enable_latency) + hpp_dimension__add_output(PERF_HPP__LATENCY_ACC, true); + perf_hpp__format[PERF_HPP__OVERHEAD].name = "Self"; + } + + if (symbol_conf.prefer_latency) + hpp_dimension__add_output(PERF_HPP__LATENCY, true); + hpp_dimension__add_output(PERF_HPP__OVERHEAD, true); + if (symbol_conf.enable_latency) + hpp_dimension__add_output(PERF_HPP__LATENCY, true); + if (symbol_conf.show_cpu_utilization) { - perf_hpp__column_enable(PERF_HPP__OVERHEAD_SYS); - perf_hpp__column_enable(PERF_HPP__OVERHEAD_US); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_SYS, false); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_US, false); if (perf_guest) { - perf_hpp__column_enable(PERF_HPP__OVERHEAD_GUEST_SYS); - perf_hpp__column_enable(PERF_HPP__OVERHEAD_GUEST_US); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_GUEST_SYS, false); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_GUEST_US, false); } } if (symbol_conf.show_nr_samples) - perf_hpp__column_enable(PERF_HPP__SAMPLES); + hpp_dimension__add_output(PERF_HPP__SAMPLES, false); if (symbol_conf.show_total_period) - perf_hpp__column_enable(PERF_HPP__PERIOD); + hpp_dimension__add_output(PERF_HPP__PERIOD, false); } -void perf_hpp__column_register(struct perf_hpp_fmt *format) +void perf_hpp_list__column_register(struct perf_hpp_list *list, + struct perf_hpp_fmt *format) { - list_add_tail(&format->list, &perf_hpp__list); + list_add_tail(&format->list, &list->fields); } -void perf_hpp__column_enable(unsigned col) +void perf_hpp_list__register_sort_field(struct perf_hpp_list *list, + struct perf_hpp_fmt *format) { - BUG_ON(col >= PERF_HPP__MAX_INDEX); - perf_hpp__column_register(&perf_hpp__format[col]); + list_add_tail(&format->sort_list, &list->sorts); } -static inline void advance_hpp(struct perf_hpp *hpp, int inc) +void perf_hpp_list__prepend_sort_field(struct perf_hpp_list *list, + struct perf_hpp_fmt *format) { - hpp->buf += inc; - hpp->size -= inc; + list_add(&format->sort_list, &list->sorts); } -int hist_entry__period_snprintf(struct perf_hpp *hpp, struct hist_entry *he, - bool color) +static void perf_hpp__column_unregister(struct perf_hpp_fmt *format) { - const char *sep = symbol_conf.field_sep; - struct perf_hpp_fmt *fmt; - char *start = hpp->buf; - int ret; - bool first = true; + list_del_init(&format->list); + list_del_init(&format->sort_list); + fmt_free(format); +} - if (symbol_conf.exclude_other && !he->parent) - return 0; +void perf_hpp__cancel_cumulate(struct evlist *evlist) +{ + struct perf_hpp_fmt *fmt, *acc, *ovh, *acc_lat, *tmp; + struct evsel *evsel; - perf_hpp__for_each_format(fmt) { - /* - * If there's no field_sep, we still need - * to display initial ' '. - */ - if (!sep || !first) { - ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " "); - advance_hpp(hpp, ret); - } else - first = false; + if (is_strict_order(field_order)) + return; - if (color && fmt->color) - ret = fmt->color(hpp, he); - else - ret = fmt->entry(hpp, he); + ovh = &perf_hpp__format[PERF_HPP__OVERHEAD]; + acc = &perf_hpp__format[PERF_HPP__OVERHEAD_ACC]; + acc_lat = &perf_hpp__format[PERF_HPP__LATENCY_ACC]; + + perf_hpp_list__for_each_format_safe(&perf_hpp_list, fmt, tmp) { + if (fmt_equal(acc, fmt) || fmt_equal(acc_lat, fmt)) { + perf_hpp__column_unregister(fmt); + continue; + } - advance_hpp(hpp, ret); + if (fmt_equal(ovh, fmt)) + fmt->name = "Overhead"; } - return hpp->buf - start; + evlist__for_each_entry(evlist, evsel) { + struct hists *hists = evsel__hists(evsel); + struct perf_hpp_list_node *node; + + list_for_each_entry(node, &hists->hpp_formats, list) { + perf_hpp_list__for_each_format_safe(&node->hpp, fmt, tmp) { + if (fmt_equal(acc, fmt) || fmt_equal(acc_lat, fmt)) { + perf_hpp__column_unregister(fmt); + continue; + } + + if (fmt_equal(ovh, fmt)) + fmt->name = "Overhead"; + } + } + } } -int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size, - struct hists *hists) +void perf_hpp__cancel_latency(struct evlist *evlist) { - const char *sep = symbol_conf.field_sep; - struct sort_entry *se; - int ret = 0; + struct perf_hpp_fmt *fmt, *lat, *acc, *tmp; + struct evsel *evsel; + + if (is_strict_order(field_order)) + return; + if (sort_order && strstr(sort_order, "latency")) + return; + + lat = &perf_hpp__format[PERF_HPP__LATENCY]; + acc = &perf_hpp__format[PERF_HPP__LATENCY_ACC]; + + perf_hpp_list__for_each_format_safe(&perf_hpp_list, fmt, tmp) { + if (fmt_equal(lat, fmt) || fmt_equal(acc, fmt)) + perf_hpp__column_unregister(fmt); + } + + evlist__for_each_entry(evlist, evsel) { + struct hists *hists = evsel__hists(evsel); + struct perf_hpp_list_node *node; - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) + list_for_each_entry(node, &hists->hpp_formats, list) { + perf_hpp_list__for_each_format_safe(&node->hpp, fmt, tmp) { + if (fmt_equal(lat, fmt) || fmt_equal(acc, fmt)) + perf_hpp__column_unregister(fmt); + } + } + } +} + +void perf_hpp__setup_output_field(struct perf_hpp_list *list) +{ + struct perf_hpp_fmt *fmt; + + /* append sort keys to output field */ + perf_hpp_list__for_each_sort_list(list, fmt) { + struct perf_hpp_fmt *pos; + + /* skip sort-only fields ("sort_compute" in perf diff) */ + if (!fmt->entry && !fmt->color) continue; - ret += scnprintf(s + ret, size - ret, "%s", sep ?: " "); - ret += se->se_snprintf(he, s + ret, size - ret, - hists__col_len(hists, se->se_width_idx)); + perf_hpp_list__for_each_format(list, pos) { + if (fmt_equal(fmt, pos)) + goto next; + } + + perf_hpp__column_register(fmt); +next: + continue; } +} - return ret; +void perf_hpp__append_sort_keys(struct perf_hpp_list *list) +{ + struct perf_hpp_fmt *fmt; + + /* append output fields to sort keys */ + perf_hpp_list__for_each_format(list, fmt) { + struct perf_hpp_fmt *pos; + + perf_hpp_list__for_each_sort_list(list, pos) { + if (fmt_equal(fmt, pos)) + goto next; + } + + perf_hpp__register_sort_field(fmt); +next: + continue; + } +} + + +void perf_hpp__reset_output_field(struct perf_hpp_list *list) +{ + struct perf_hpp_fmt *fmt, *tmp; + + /* reset output fields */ + perf_hpp_list__for_each_format_safe(list, fmt, tmp) + perf_hpp__column_unregister(fmt); + + /* reset sort keys */ + perf_hpp_list__for_each_sort_list_safe(list, fmt, tmp) + perf_hpp__column_unregister(fmt); } /* @@ -489,25 +1040,234 @@ int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size, unsigned int hists__sort_list_width(struct hists *hists) { struct perf_hpp_fmt *fmt; - struct sort_entry *se; - int i = 0, ret = 0; - struct perf_hpp dummy_hpp = { - .ptr = hists_to_evsel(hists), - }; - - perf_hpp__for_each_format(fmt) { - if (i) + int ret = 0; + bool first = true; + struct perf_hpp dummy_hpp; + + hists__for_each_format(hists, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; + + if (first) + first = false; + else ret += 2; - ret += fmt->width(&dummy_hpp); + ret += fmt->width(fmt, &dummy_hpp, hists); } - list_for_each_entry(se, &hist_entry__sort_list, list) - if (!se->elide) - ret += 2 + hists__col_len(hists, se->se_width_idx); - - if (verbose) /* Addr + origin */ + if (verbose > 0 && hists__has(hists, sym)) /* Addr + origin */ ret += 3 + BITS_PER_LONG / 4; return ret; } + +unsigned int hists__overhead_width(struct hists *hists) +{ + struct perf_hpp_fmt *fmt; + int ret = 0; + bool first = true; + struct perf_hpp dummy_hpp; + + hists__for_each_format(hists, fmt) { + if (perf_hpp__is_sort_entry(fmt) || perf_hpp__is_dynamic_entry(fmt)) + break; + + if (first) + first = false; + else + ret += 2; + + ret += fmt->width(fmt, &dummy_hpp, hists); + } + + return ret; +} + +void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists) +{ + if (perf_hpp__is_sort_entry(fmt)) + return perf_hpp__reset_sort_width(fmt, hists); + + if (perf_hpp__is_dynamic_entry(fmt)) + return; + + BUG_ON(fmt->idx >= PERF_HPP__MAX_INDEX); + + switch (fmt->idx) { + case PERF_HPP__OVERHEAD: + case PERF_HPP__LATENCY: + case PERF_HPP__OVERHEAD_SYS: + case PERF_HPP__OVERHEAD_US: + case PERF_HPP__OVERHEAD_ACC: + fmt->len = 8; + break; + + case PERF_HPP__OVERHEAD_GUEST_SYS: + case PERF_HPP__OVERHEAD_GUEST_US: + fmt->len = 9; + break; + + case PERF_HPP__SAMPLES: + case PERF_HPP__PERIOD: + fmt->len = 12; + break; + + case PERF_HPP__WEIGHT1: + case PERF_HPP__WEIGHT2: + case PERF_HPP__WEIGHT3: + fmt->len = 8; + break; + + case PERF_HPP__MEM_STAT_OP: + case PERF_HPP__MEM_STAT_CACHE: + case PERF_HPP__MEM_STAT_MEMORY: + case PERF_HPP__MEM_STAT_SNOOP: + case PERF_HPP__MEM_STAT_DTLB: + fmt->len = MEM_STAT_LEN * MEM_STAT_PRINT_LEN; + break; + + default: + break; + } +} + +void hists__reset_column_width(struct hists *hists) +{ + struct perf_hpp_fmt *fmt; + struct perf_hpp_list_node *node; + + hists__for_each_format(hists, fmt) + perf_hpp__reset_width(fmt, hists); + + /* hierarchy entries have their own hpp list */ + list_for_each_entry(node, &hists->hpp_formats, list) { + perf_hpp_list__for_each_format(&node->hpp, fmt) + perf_hpp__reset_width(fmt, hists); + } +} + +void perf_hpp__set_user_width(const char *width_list_str) +{ + struct perf_hpp_fmt *fmt; + const char *ptr = width_list_str; + + perf_hpp_list__for_each_format(&perf_hpp_list, fmt) { + char *p; + + int len = strtol(ptr, &p, 10); + fmt->user_len = len; + + if (*p == ',') + ptr = p + 1; + else + break; + } +} + +static int add_hierarchy_fmt(struct hists *hists, struct perf_hpp_fmt *fmt) +{ + struct perf_hpp_list_node *node = NULL; + struct perf_hpp_fmt *fmt_copy; + bool found = false; + bool skip = perf_hpp__should_skip(fmt, hists); + + list_for_each_entry(node, &hists->hpp_formats, list) { + if (node->level == fmt->level) { + found = true; + break; + } + } + + if (!found) { + node = malloc(sizeof(*node)); + if (node == NULL) + return -1; + + node->skip = skip; + node->level = fmt->level; + perf_hpp_list__init(&node->hpp); + + hists->nr_hpp_node++; + list_add_tail(&node->list, &hists->hpp_formats); + } + + fmt_copy = perf_hpp_fmt__dup(fmt); + if (fmt_copy == NULL) + return -1; + + if (!skip) + node->skip = false; + + list_add_tail(&fmt_copy->list, &node->hpp.fields); + list_add_tail(&fmt_copy->sort_list, &node->hpp.sorts); + + return 0; +} + +int perf_hpp__setup_hists_formats(struct perf_hpp_list *list, + struct evlist *evlist) +{ + struct evsel *evsel; + struct perf_hpp_fmt *fmt; + struct hists *hists; + int ret; + + if (!symbol_conf.report_hierarchy) + return 0; + + evlist__for_each_entry(evlist, evsel) { + hists = evsel__hists(evsel); + + perf_hpp_list__for_each_sort_list(list, fmt) { + if (perf_hpp__is_dynamic_entry(fmt) && + !perf_hpp__defined_dynamic_entry(fmt, hists)) + continue; + + ret = add_hierarchy_fmt(hists, fmt); + if (ret < 0) + return ret; + } + } + + return 0; +} + +int perf_hpp__alloc_mem_stats(struct perf_hpp_list *list, struct evlist *evlist) +{ + struct perf_hpp_fmt *fmt; + struct evsel *evsel; + enum mem_stat_type mst[16]; + unsigned nr_mem_stats = 0; + + perf_hpp_list__for_each_format(list, fmt) { + if (!perf_hpp__is_mem_stat_entry(fmt)) + continue; + + assert(nr_mem_stats < ARRAY_SIZE(mst)); + mst[nr_mem_stats++] = hpp__mem_stat_type(fmt); + } + + if (nr_mem_stats == 0) + return 0; + + list->nr_header_lines = 2; + + evlist__for_each_entry(evlist, evsel) { + struct hists *hists = evsel__hists(evsel); + + hists->mem_stat_types = calloc(nr_mem_stats, + sizeof(*hists->mem_stat_types)); + if (hists->mem_stat_types == NULL) + return -ENOMEM; + + hists->mem_stat_total = calloc(nr_mem_stats, + sizeof(*hists->mem_stat_total)); + if (hists->mem_stat_total == NULL) + return -ENOMEM; + + memcpy(hists->mem_stat_types, mst, nr_mem_stats * sizeof(*mst)); + hists->nr_mem_stats = nr_mem_stats; + } + return 0; +} diff --git a/tools/perf/ui/keysyms.c b/tools/perf/ui/keysyms.c new file mode 100644 index 000000000000..b64564b07f2f --- /dev/null +++ b/tools/perf/ui/keysyms.c @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include "keysyms.h" +#include <linux/ctype.h> +#include <linux/kernel.h> + +const char *key_name(int key, char *bf, size_t size) +{ + if (isprint(key)) { + scnprintf(bf, size, "%c", key); + } else if (key < 32) { + scnprintf(bf, size, "Ctrl+%c", key + '@'); + } else { + const char *name = NULL; + + switch (key) { + case K_DOWN: name = "Down"; break; + case K_END: name = "End"; break; + case K_ENTER: name = "Enter"; break; + case K_ESC: name = "ESC"; break; + case K_F1: name = "F1"; break; + case K_HOME: name = "Home"; break; + case K_LEFT: name = "Left"; break; + case K_PGDN: name = "PgDown"; break; + case K_PGUP: name = "PgUp"; break; + case K_RIGHT: name = "Right"; break; + case K_TAB: name = "Tab"; break; + case K_UNTAB: name = "Untab"; break; + case K_UP: name = "Up"; break; + case K_BKSPC: name = "Backspace"; break; + case K_DEL: name = "Del"; break; + default: + if (key >= SL_KEY_F(1) && key <= SL_KEY_F(63)) + scnprintf(bf, size, "F%d", key - SL_KEY_F(0)); + else + scnprintf(bf, size, "Unknown (%d)", key); + } + + if (name) + scnprintf(bf, size, "%s", name); + } + + return bf; +} diff --git a/tools/perf/ui/keysyms.h b/tools/perf/ui/keysyms.h index 65092d576b4e..969060edc362 100644 --- a/tools/perf/ui/keysyms.h +++ b/tools/perf/ui/keysyms.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_KEYSYMS_H_ #define _PERF_KEYSYMS_H_ 1 @@ -24,5 +25,8 @@ #define K_ERROR -2 #define K_RESIZE -3 #define K_SWITCH_INPUT_DATA -4 +#define K_RELOAD -5 + +const char *key_name(int key, char *bf, size_t size); #endif /* _PERF_KEYSYMS_H_ */ diff --git a/tools/perf/ui/libslang.h b/tools/perf/ui/libslang.h index 4d54b6450f5b..6722561e0458 100644 --- a/tools/perf/ui/libslang.h +++ b/tools/perf/ui/libslang.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_UI_SLANG_H_ #define _PERF_UI_SLANG_H_ 1 /* @@ -9,20 +10,12 @@ #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 +/* Enable future slang's corrected function prototypes. */ +#define ENABLE_SLFUTURE_CONST 1 +#define ENABLE_SLFUTURE_VOID 1 + +#include <slang.h> #define SL_KEY_UNTAB 0x1000 diff --git a/tools/perf/ui/progress.c b/tools/perf/ui/progress.c index 3ec695607a4d..99d60223c74b 100644 --- a/tools/perf/ui/progress.c +++ b/tools/perf/ui/progress.c @@ -1,26 +1,47 @@ -#include "../cache.h" +// SPDX-License-Identifier: GPL-2.0 +#include <linux/kernel.h> #include "progress.h" -static void nop_progress_update(u64 curr __maybe_unused, - u64 total __maybe_unused, - const char *title __maybe_unused) +static void null_progress__update(struct ui_progress *p __maybe_unused) { } -static struct ui_progress default_progress_fns = +static struct ui_progress_ops null_progress__ops = { - .update = nop_progress_update, + .update = null_progress__update, }; -struct ui_progress *progress_fns = &default_progress_fns; +struct ui_progress_ops *ui_progress__ops = &null_progress__ops; -void ui_progress__update(u64 curr, u64 total, const char *title) +void ui_progress__update(struct ui_progress *p, u64 adv) { - return progress_fns->update(curr, total, title); + u64 last = p->curr; + + p->curr += adv; + + if (p->curr >= p->next) { + u64 nr = DIV_ROUND_UP(p->curr - last, p->step); + + p->next += nr * p->step; + ui_progress__ops->update(p); + } +} + +void __ui_progress__init(struct ui_progress *p, u64 total, + const char *title, bool size) +{ + p->curr = 0; + p->next = p->step = total / 16 ?: 1; + p->total = total; + p->title = title; + p->size = size; + + if (ui_progress__ops->init) + ui_progress__ops->init(p); } void ui_progress__finish(void) { - if (progress_fns->finish) - progress_fns->finish(); + if (ui_progress__ops->finish) + ui_progress__ops->finish(); } diff --git a/tools/perf/ui/progress.h b/tools/perf/ui/progress.h index 257cc224f9cf..4f52c37b2f09 100644 --- a/tools/perf/ui/progress.h +++ b/tools/perf/ui/progress.h @@ -1,18 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_UI_PROGRESS_H_ #define _PERF_UI_PROGRESS_H_ 1 -#include <../types.h> +#include <linux/types.h> + +void ui_progress__finish(void); struct ui_progress { - void (*update)(u64, u64, const char *); - void (*finish)(void); + const char *title; + u64 curr, next, step, total; + bool size; }; -extern struct ui_progress *progress_fns; +void __ui_progress__init(struct ui_progress *p, u64 total, + const char *title, bool size); -void ui_progress__init(void); +#define ui_progress__init(p, total, title) \ + __ui_progress__init(p, total, title, false) -void ui_progress__update(u64 curr, u64 total, const char *title); -void ui_progress__finish(void); +#define ui_progress__init_size(p, total, title) \ + __ui_progress__init(p, total, title, true) + +void ui_progress__update(struct ui_progress *p, u64 adv); + +struct ui_progress_ops { + void (*init)(struct ui_progress *p); + void (*update)(struct ui_progress *p); + void (*finish)(void); +}; + +extern struct ui_progress_ops *ui_progress__ops; #endif diff --git a/tools/perf/ui/setup.c b/tools/perf/ui/setup.c index ae6a789cb0f6..ff800047e697 100644 --- a/tools/perf/ui/setup.c +++ b/tools/perf/ui/setup.c @@ -1,13 +1,82 @@ -#include <pthread.h> +// SPDX-License-Identifier: GPL-2.0 +#include <dlfcn.h> +#include <signal.h> +#include <unistd.h> -#include "../util/cache.h" +#include <subcmd/pager.h> #include "../util/debug.h" #include "../util/hist.h" +#include "ui.h" -pthread_mutex_t ui__lock = PTHREAD_MUTEX_INITIALIZER; +struct mutex ui__lock; +void *perf_gtk_handle; +int use_browser = -1; + +#define PERF_GTK_DSO "libperf-gtk.so" + +#ifdef HAVE_GTK2_SUPPORT + +static int setup_gtk_browser(void) +{ + int (*perf_ui_init)(void); + + if (perf_gtk_handle) + return 0; + + perf_gtk_handle = dlopen(PERF_GTK_DSO, RTLD_LAZY); + if (perf_gtk_handle == NULL) { + char buf[PATH_MAX]; + scnprintf(buf, sizeof(buf), "%s/%s", LIBDIR, PERF_GTK_DSO); + perf_gtk_handle = dlopen(buf, RTLD_LAZY); + } + if (perf_gtk_handle == NULL) + return -1; + + perf_ui_init = dlsym(perf_gtk_handle, "perf_gtk__init"); + if (perf_ui_init == NULL) + goto out_close; + + if (perf_ui_init() == 0) + return 0; + +out_close: + dlclose(perf_gtk_handle); + return -1; +} + +static void exit_gtk_browser(bool wait_for_ok) +{ + void (*perf_ui_exit)(bool); + + if (perf_gtk_handle == NULL) + return; + + perf_ui_exit = dlsym(perf_gtk_handle, "perf_gtk__exit"); + if (perf_ui_exit == NULL) + goto out_close; + + perf_ui_exit(wait_for_ok); + +out_close: + dlclose(perf_gtk_handle); + + perf_gtk_handle = NULL; +} +#else +static inline int setup_gtk_browser(void) { return -1; } +static inline void exit_gtk_browser(bool wait_for_ok __maybe_unused) {} +#endif + +int stdio__config_color(const struct option *opt __maybe_unused, + const char *mode, int unset __maybe_unused) +{ + perf_use_color_default = perf_config_colorbool("color.ui", mode, -1); + return 0; +} void setup_browser(bool fallback_to_pager) { + mutex_init(&ui__lock); if (use_browser < 2 && (!isatty(1) || dump_trace)) use_browser = 0; @@ -17,11 +86,14 @@ void setup_browser(bool fallback_to_pager) switch (use_browser) { case 2: - if (perf_gtk__init() == 0) + if (setup_gtk_browser() == 0) break; + printf("GTK browser requested but could not find %s\n", + PERF_GTK_DSO); + sleep(1); + use_browser = 1; /* fall through */ case 1: - use_browser = 1; if (ui__init() == 0) break; /* fall through */ @@ -29,9 +101,6 @@ void setup_browser(bool fallback_to_pager) use_browser = 0; if (fallback_to_pager) setup_pager(); - - perf_hpp__column_enable(PERF_HPP__OVERHEAD); - perf_hpp__init(); break; } } @@ -40,7 +109,7 @@ void exit_browser(bool wait_for_ok) { switch (use_browser) { case 2: - perf_gtk__exit(wait_for_ok); + exit_gtk_browser(wait_for_ok); break; case 1: @@ -50,4 +119,23 @@ void exit_browser(bool wait_for_ok) default: break; } + mutex_destroy(&ui__lock); +} + +void pthread__block_sigwinch(void) +{ + sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGWINCH); + pthread_sigmask(SIG_BLOCK, &set, NULL); +} + +void pthread__unblock_sigwinch(void) +{ + sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGWINCH); + pthread_sigmask(SIG_UNBLOCK, &set, NULL); } diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index ae7a75432249..8c4c8925df2c 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -1,16 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <limits.h> #include <stdio.h> +#include <stdlib.h> +#include <linux/string.h> -#include "../../util/util.h" +#include "../../util/callchain.h" +#include "../../util/debug.h" +#include "../../util/event.h" #include "../../util/hist.h" +#include "../../util/map.h" +#include "../../util/maps.h" +#include "../../util/symbol.h" #include "../../util/sort.h" #include "../../util/evsel.h" - +#include "../../util/srcline.h" +#include "../../util/string2.h" +#include "../../util/thread.h" +#include "../../util/block-info.h" +#include <linux/ctype.h> +#include <linux/zalloc.h> static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) { int i; int ret = fprintf(fp, " "); + if (left_margin > USHRT_MAX) + left_margin = USHRT_MAX; + for (i = 0; i < left_margin; i++) ret += fprintf(fp, " "); @@ -34,13 +51,16 @@ static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, return ret; } -static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, +static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_node *node, + struct callchain_list *chain, int depth, int depth_mask, int period, - u64 total_samples, u64 hits, - int left_margin) + u64 total_samples, int left_margin) { int i; size_t ret = 0; + char bf[1024], *alloc_str = NULL; + char buf[64]; + const char *str; ret += callchain__fprintf_left_margin(fp, left_margin); for (i = 0; i < depth; i++) { @@ -49,17 +69,28 @@ static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, else ret += fprintf(fp, " "); if (!period && i == depth - 1) { - double percent; - - percent = hits * 100.0 / total_samples; - ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent); + ret += fprintf(fp, "--"); + ret += callchain_node__fprintf_value(node, fp, total_samples); + ret += fprintf(fp, "--"); } else ret += fprintf(fp, "%s", " "); } - if (chain->ms.sym) - ret += fprintf(fp, "%s\n", chain->ms.sym->name); - else - ret += fprintf(fp, "0x%0" PRIx64 "\n", chain->ip); + + str = callchain_list__sym_name(chain, bf, sizeof(bf), false); + + if (symbol_conf.show_branchflag_count) { + callchain_list_counts__printf_value(chain, NULL, + buf, sizeof(buf)); + + if (asprintf(&alloc_str, "%s%s", str, buf) < 0) + str = "Not enough memory!"; + else + str = alloc_str; + } + + fputs(str, fp); + fputc('\n', fp); + free(alloc_str); return ret; } @@ -84,13 +115,14 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, int depth_mask, int left_margin) { struct rb_node *node, *next; - struct callchain_node *child; + struct callchain_node *child = NULL; struct callchain_list *chain; int new_depth_mask = depth_mask; u64 remaining; size_t ret = 0; int i; uint entries_printed = 0; + int cumul_count = 0; remaining = total_samples; @@ -102,6 +134,7 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, child = rb_entry(node, struct callchain_node, rb_node); cumul = callchain_cumul_hits(child); remaining -= cumul; + cumul_count += callchain_cumul_counts(child); /* * The depth mask manages the output of pipes that show @@ -122,10 +155,9 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, left_margin); i = 0; list_for_each_entry(chain, &child->val, list) { - ret += ipchain__fprintf_graph(fp, chain, depth, + ret += ipchain__fprintf_graph(fp, child, chain, depth, new_depth_mask, i++, total_samples, - cumul, left_margin); } @@ -145,21 +177,50 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, if (callchain_param.mode == CHAIN_GRAPH_REL && remaining && remaining != total_samples) { + struct callchain_node rem_node = { + .hit = remaining, + }; if (!rem_sq_bracket) return ret; + if (callchain_param.value == CCVAL_COUNT && child && child->parent) { + rem_node.count = child->parent->children_count - cumul_count; + if (rem_node.count <= 0) + return ret; + } + new_depth_mask &= ~(1 << (depth - 1)); - ret += ipchain__fprintf_graph(fp, &rem_hits, depth, + ret += ipchain__fprintf_graph(fp, &rem_node, &rem_hits, depth, new_depth_mask, 0, total_samples, - remaining, left_margin); + left_margin); } return ret; } +/* + * If have one single callchain root, don't bother printing + * its percentage (100 % in fractal mode and the same percentage + * than the hist in graph mode). This also avoid one level of column. + * + * However when percent-limit applied, it's possible that single callchain + * node have different (non-100% in fractal mode) percentage. + */ +static bool need_percent_display(struct rb_node *node, u64 parent_samples) +{ + struct callchain_node *cnode; + + if (rb_next(node)) + return true; + + cnode = rb_entry(node, struct callchain_node, rb_node); + return callchain_cumul_hits(cnode) != parent_samples; +} + static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, - u64 total_samples, int left_margin) + u64 total_samples, u64 parent_samples, + int left_margin) { struct callchain_node *cnode; struct callchain_list *chain; @@ -168,14 +229,10 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, struct rb_node *node; int i = 0; int ret = 0; + char bf[1024]; - /* - * If have one single callchain root, don't bother printing - * its percentage (100 % in fractal mode and the same percentage - * than the hist in graph mode). This also avoid one level of column. - */ node = rb_first(root); - if (node && !rb_next(node)) { + if (node && !need_percent_display(node, parent_samples)) { cnode = rb_entry(node, struct callchain_node, rb_node); list_for_each_entry(chain, &cnode->val, list) { /* @@ -183,8 +240,10 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, * the symbol. No need to print it otherwise it appears as * displayed twice. */ - if (!i++ && sort__first_dimension == SORT_SYM) + if (!i++ && field_order == NULL && + sort_order && strstarts(sort_order, "sym")) continue; + if (!printed) { ret += callchain__fprintf_left_margin(fp, left_margin); ret += fprintf(fp, "|\n"); @@ -195,10 +254,15 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, } else ret += callchain__fprintf_left_margin(fp, left_margin); - if (chain->ms.sym) - ret += fprintf(fp, " %s\n", chain->ms.sym->name); - else - ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); + ret += fprintf(fp, "%s", + callchain_list__sym_name(chain, bf, + sizeof(bf), + false)); + + if (symbol_conf.show_branchflag_count) + ret += callchain_list_counts__printf_value( + chain, fp, NULL, 0); + ret += fprintf(fp, "\n"); if (++entries_printed == callchain_param.print_limit) break; @@ -206,55 +270,56 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, root = &cnode->rb_root; } + if (callchain_param.mode == CHAIN_GRAPH_REL) + total_samples = parent_samples; + ret += __callchain__fprintf_graph(fp, root, total_samples, 1, 1, left_margin); - ret += fprintf(fp, "\n"); + if (ret) { + /* do not add a blank line if it printed nothing */ + ret += fprintf(fp, "\n"); + } return ret; } -static size_t __callchain__fprintf_flat(FILE *fp, - struct callchain_node *self, +static size_t __callchain__fprintf_flat(FILE *fp, struct callchain_node *node, u64 total_samples) { struct callchain_list *chain; size_t ret = 0; + char bf[1024]; - if (!self) + if (!node) return 0; - ret += __callchain__fprintf_flat(fp, self->parent, total_samples); + ret += __callchain__fprintf_flat(fp, node->parent, total_samples); - list_for_each_entry(chain, &self->val, list) { + list_for_each_entry(chain, &node->val, list) { if (chain->ip >= PERF_CONTEXT_MAX) continue; - if (chain->ms.sym) - ret += fprintf(fp, " %s\n", chain->ms.sym->name); - else - ret += fprintf(fp, " %p\n", - (void *)(long)chain->ip); + ret += fprintf(fp, " %s\n", callchain_list__sym_name(chain, + bf, sizeof(bf), false)); } return ret; } -static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *self, +static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *tree, u64 total_samples) { size_t ret = 0; u32 entries_printed = 0; - struct rb_node *rb_node; struct callchain_node *chain; + struct rb_node *rb_node = rb_first(tree); - rb_node = rb_first(self); while (rb_node) { - double percent; - chain = rb_entry(rb_node, struct callchain_node, rb_node); - percent = chain->hit * 100.0 / total_samples; - ret = percent_color_fprintf(fp, " %6.2f%%\n", percent); + ret += fprintf(fp, " "); + ret += callchain_node__fprintf_value(chain, fp, total_samples); + ret += fprintf(fp, "\n"); ret += __callchain__fprintf_flat(fp, chain, total_samples); ret += fprintf(fp, "\n"); if (++entries_printed == callchain_param.print_limit) @@ -266,22 +331,81 @@ static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *self, return ret; } +static size_t __callchain__fprintf_folded(FILE *fp, struct callchain_node *node) +{ + const char *sep = symbol_conf.field_sep ?: ";"; + struct callchain_list *chain; + size_t ret = 0; + char bf[1024]; + bool first; + + if (!node) + return 0; + + ret += __callchain__fprintf_folded(fp, node->parent); + + first = (ret == 0); + list_for_each_entry(chain, &node->val, list) { + if (chain->ip >= PERF_CONTEXT_MAX) + continue; + ret += fprintf(fp, "%s%s", first ? "" : sep, + callchain_list__sym_name(chain, + bf, sizeof(bf), false)); + first = false; + } + + return ret; +} + +static size_t callchain__fprintf_folded(FILE *fp, struct rb_root *tree, + u64 total_samples) +{ + size_t ret = 0; + u32 entries_printed = 0; + struct callchain_node *chain; + struct rb_node *rb_node = rb_first(tree); + + while (rb_node) { + + chain = rb_entry(rb_node, struct callchain_node, rb_node); + + ret += callchain_node__fprintf_value(chain, fp, total_samples); + ret += fprintf(fp, " "); + ret += __callchain__fprintf_folded(fp, chain); + ret += fprintf(fp, "\n"); + if (++entries_printed == callchain_param.print_limit) + break; + + rb_node = rb_next(rb_node); + } + + return ret; +} + static size_t hist_entry_callchain__fprintf(struct hist_entry *he, u64 total_samples, int left_margin, FILE *fp) { + u64 parent_samples = he->stat.period; + + if (symbol_conf.cumulate_callchain) + parent_samples = he->stat_acc->period; + switch (callchain_param.mode) { case CHAIN_GRAPH_REL: - return callchain__fprintf_graph(fp, &he->sorted_chain, he->stat.period, - left_margin); + return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, + parent_samples, left_margin); break; case CHAIN_GRAPH_ABS: return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, - left_margin); + parent_samples, left_margin); break; case CHAIN_FLAT: return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples); break; + case CHAIN_FOLDED: + return callchain__fprintf_folded(fp, &he->sorted_chain, total_samples); + break; case CHAIN_NONE: break; default: @@ -291,177 +415,501 @@ static size_t hist_entry_callchain__fprintf(struct hist_entry *he, return 0; } -static size_t hist_entry__callchain_fprintf(struct hist_entry *he, - struct hists *hists, - FILE *fp) +int __hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp, + struct perf_hpp_list *hpp_list) { - int left_margin = 0; - u64 total_period = hists->stats.total_period; + const char *sep = symbol_conf.field_sep; + struct perf_hpp_fmt *fmt; + char *start = hpp->buf; + int ret; + bool first = true; + + if (symbol_conf.exclude_other && !he->parent) + return 0; + + perf_hpp_list__for_each_format(hpp_list, fmt) { + if (perf_hpp__should_skip(fmt, he->hists)) + continue; + + /* + * If there's no field_sep, we still need + * to display initial ' '. + */ + if (!sep || !first) { + ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " "); + advance_hpp(hpp, ret); + } else + first = false; + + if (perf_hpp__use_color() && fmt->color) + ret = fmt->color(fmt, hpp, he); + else + ret = fmt->entry(fmt, hpp, he); + + ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret); + advance_hpp(hpp, ret); + } + + return hpp->buf - start; +} + +static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp) +{ + return __hist_entry__snprintf(he, hpp, he->hists->hpp_list); +} + +static int hist_entry__hierarchy_fprintf(struct hist_entry *he, + struct perf_hpp *hpp, + struct hists *hists, + FILE *fp) +{ + const char *sep = symbol_conf.field_sep; + struct perf_hpp_fmt *fmt; + struct perf_hpp_list_node *fmt_node; + char *buf = hpp->buf; + size_t size = hpp->size; + int ret, printed = 0; + bool first = true; + + if (symbol_conf.exclude_other && !he->parent) + return 0; + + ret = scnprintf(hpp->buf, hpp->size, "%*s", he->depth * HIERARCHY_INDENT, ""); + advance_hpp(hpp, ret); + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + /* + * If there's no field_sep, we still need + * to display initial ' '. + */ + if (!sep || !first) { + ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " "); + advance_hpp(hpp, ret); + } else + first = false; + + if (perf_hpp__use_color() && fmt->color) + ret = fmt->color(fmt, hpp, he); + else + ret = fmt->entry(fmt, hpp, he); + + ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret); + advance_hpp(hpp, ret); + } + + if (!sep) + ret = scnprintf(hpp->buf, hpp->size, "%*s", + (hists->nr_hpp_node - 2) * HIERARCHY_INDENT, ""); + advance_hpp(hpp, ret); + + printed += fprintf(fp, "%s", buf); + + perf_hpp_list__for_each_format(he->hpp_list, fmt) { + hpp->buf = buf; + hpp->size = size; + + /* + * No need to call hist_entry__snprintf_alignment() since this + * fmt is always the last column in the hierarchy mode. + */ + if (perf_hpp__use_color() && fmt->color) + fmt->color(fmt, hpp, he); + else + fmt->entry(fmt, hpp, he); + + /* + * dynamic entries are right-aligned but we want left-aligned + * in the hierarchy mode + */ + printed += fprintf(fp, "%s%s", sep ?: " ", skip_spaces(buf)); + } + printed += putc('\n', fp); + + if (he->leaf && hist_entry__has_callchains(he) && symbol_conf.use_callchain) { + u64 total = hists__total_period(hists); - if (sort__first_dimension == SORT_COMM) { - struct sort_entry *se = list_first_entry(&hist_entry__sort_list, - typeof(*se), list); - left_margin = hists__col_len(hists, se->se_width_idx); - left_margin -= thread__comm_len(he->thread); + printed += hist_entry_callchain__fprintf(he, total, 0, fp); + goto out; } - return hist_entry_callchain__fprintf(he, total_period, left_margin, fp); +out: + return printed; +} + +static int hist_entry__block_fprintf(struct hist_entry *he, + char *bf, size_t size, + FILE *fp) +{ + struct block_hist *bh = container_of(he, struct block_hist, he); + int ret = 0; + + for (unsigned int i = 0; i < bh->block_hists.nr_entries; i++) { + struct perf_hpp hpp = { + .buf = bf, + .size = size, + .skip = false, + }; + + bh->block_idx = i; + hist_entry__snprintf(he, &hpp); + + if (!hpp.skip) + ret += fprintf(fp, "%s\n", bf); + } + + return ret; +} + +static int hist_entry__individual_block_fprintf(struct hist_entry *he, + char *bf, size_t size, + FILE *fp) +{ + int ret = 0; + + struct perf_hpp hpp = { + .buf = bf, + .size = size, + .skip = false, + }; + + hist_entry__snprintf(he, &hpp); + if (!hpp.skip) + ret += fprintf(fp, "%s\n", bf); + + return ret; } static int hist_entry__fprintf(struct hist_entry *he, size_t size, - struct hists *hists, FILE *fp) + char *bf, size_t bfsz, FILE *fp, + bool ignore_callchains) { - char bf[512]; int ret; + int callchain_ret = 0; struct perf_hpp hpp = { .buf = bf, .size = size, }; - bool color = !symbol_conf.field_sep; + struct hists *hists = he->hists; + u64 total_period = hists->stats.total_period; + + if (size == 0 || size > bfsz) + size = hpp.size = bfsz; - if (size == 0 || size > sizeof(bf)) - size = hpp.size = sizeof(bf); + if (symbol_conf.report_hierarchy) + return hist_entry__hierarchy_fprintf(he, &hpp, hists, fp); - ret = hist_entry__period_snprintf(&hpp, he, color); - hist_entry__sort_snprintf(he, bf + ret, size - ret, hists); + if (symbol_conf.report_block) + return hist_entry__block_fprintf(he, bf, size, fp); + + if (symbol_conf.report_individual_block) + return hist_entry__individual_block_fprintf(he, bf, size, fp); + + hist_entry__snprintf(he, &hpp); ret = fprintf(fp, "%s\n", bf); - if (symbol_conf.use_callchain) - ret += hist_entry__callchain_fprintf(he, hists, fp); + if (hist_entry__has_callchains(he) && !ignore_callchains) + callchain_ret = hist_entry_callchain__fprintf(he, total_period, + 0, fp); + + ret += callchain_ret; return ret; } -size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, - int max_cols, float min_pcnt, FILE *fp) +static int print_hierarchy_indent(const char *sep, int indent, + const char *line, FILE *fp) +{ + int width; + + if (sep != NULL || indent < 2) + return 0; + + width = (indent - 2) * HIERARCHY_INDENT; + + return fprintf(fp, "%-*.*s", width, width, line); +} + +static int hists__fprintf_hierarchy_headers(struct hists *hists, + struct perf_hpp *hpp, FILE *fp) { + bool first_node, first_col; + int indent; + int depth; + unsigned width = 0; + unsigned header_width = 0; struct perf_hpp_fmt *fmt; - struct sort_entry *se; - struct rb_node *nd; - size_t ret = 0; - unsigned int width; + struct perf_hpp_list_node *fmt_node; + struct perf_hpp_list *hpp_list = hists->hpp_list; const char *sep = symbol_conf.field_sep; - const char *col_width = symbol_conf.col_width_list_str; - int nr_rows = 0; - char bf[96]; - struct perf_hpp dummy_hpp = { - .buf = bf, - .size = sizeof(bf), - .ptr = hists_to_evsel(hists), - }; - bool first = true; - init_rem_hits(); + indent = hists->nr_hpp_node; + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + + for (int line = 0; line < hpp_list->nr_header_lines; line++) { + /* first # is displayed one level up */ + if (line) + fprintf(fp, "# "); + + /* preserve max indent depth for column headers */ + print_hierarchy_indent(sep, indent, " ", fp); + + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + fmt->header(fmt, hpp, hists, line, NULL); + fprintf(fp, "%s%s", hpp->buf, sep ?: " "); + } + + if (line < hpp_list->nr_header_lines - 1) + goto next_line; + + /* combine sort headers with ' / ' */ + first_node = true; + list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { + if (!first_node) + header_width += fprintf(fp, " / "); + first_node = false; + + first_col = true; + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; + + if (!first_col) + header_width += fprintf(fp, "+"); + first_col = false; - if (!show_header) - goto print_entries; + fmt->header(fmt, hpp, hists, line, NULL); + + header_width += fprintf(fp, "%s", strim(hpp->buf)); + } + } + +next_line: + fprintf(fp, "\n"); + } fprintf(fp, "# "); - perf_hpp__for_each_format(fmt) { - if (!first) + /* preserve max indent depth for initial dots */ + print_hierarchy_indent(sep, indent, dots, fp); + + /* the first hpp_list_node is for overhead columns */ + fmt_node = list_first_entry(&hists->hpp_formats, + struct perf_hpp_list_node, list); + + first_col = true; + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (!first_col) + fprintf(fp, "%s", sep ?: ".."); + first_col = false; + + width = fmt->width(fmt, hpp, hists); + fprintf(fp, "%.*s", width, dots); + } + + depth = 0; + list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { + first_col = true; + width = depth * HIERARCHY_INDENT; + + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; + + if (!first_col) + width++; /* for '+' sign between column header */ + first_col = false; + + width += fmt->width(fmt, hpp, hists); + } + + if (width > header_width) + header_width = width; + + depth++; + } + + fprintf(fp, "%s%-.*s", sep ?: " ", header_width, dots); + + fprintf(fp, "\n#\n"); + + return 2; +} + +static void fprintf_line(struct hists *hists, struct perf_hpp *hpp, + int line, FILE *fp) +{ + struct perf_hpp_fmt *fmt; + const char *sep = symbol_conf.field_sep; + bool first = true; + int span = 0; + + hists__for_each_format(hists, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; + + if (!first && !span) fprintf(fp, "%s", sep ?: " "); else first = false; - fmt->header(&dummy_hpp); - fprintf(fp, "%s", bf); - } + fmt->header(fmt, hpp, hists, line, &span); - list_for_each_entry(se, &hist_entry__sort_list, list) { - if (se->elide) - continue; - if (sep) { - fprintf(fp, "%c%s", *sep, se->se_header); - continue; - } - width = strlen(se->se_header); - if (symbol_conf.col_width_list_str) { - if (col_width) { - hists__set_col_len(hists, se->se_width_idx, - atoi(col_width)); - col_width = strchr(col_width, ','); - if (col_width) - ++col_width; - } - } - if (!hists__new_col_len(hists, se->se_width_idx, width)) - width = hists__col_len(hists, se->se_width_idx); - fprintf(fp, " %*s", width, se->se_header); + if (!span) + fprintf(fp, "%s", hpp->buf); } +} - fprintf(fp, "\n"); - if (max_rows && ++nr_rows >= max_rows) - goto out; +static int +hists__fprintf_standard_headers(struct hists *hists, + struct perf_hpp *hpp, + FILE *fp) +{ + struct perf_hpp_list *hpp_list = hists->hpp_list; + struct perf_hpp_fmt *fmt; + unsigned int width; + const char *sep = symbol_conf.field_sep; + bool first = true; + int line; + + for (line = 0; line < hpp_list->nr_header_lines; line++) { + /* first # is displayed one level up */ + if (line) + fprintf(fp, "# "); + fprintf_line(hists, hpp, line, fp); + fprintf(fp, "\n"); + } if (sep) - goto print_entries; + return hpp_list->nr_header_lines; first = true; fprintf(fp, "# "); - perf_hpp__for_each_format(fmt) { + hists__for_each_format(hists, fmt) { unsigned int i; + if (perf_hpp__should_skip(fmt, hists)) + continue; + if (!first) fprintf(fp, "%s", sep ?: " "); else first = false; - width = fmt->width(&dummy_hpp); + width = fmt->width(fmt, hpp, hists); for (i = 0; i < width; i++) fprintf(fp, "."); } - list_for_each_entry(se, &hist_entry__sort_list, list) { - unsigned int i; + fprintf(fp, "\n"); + fprintf(fp, "#\n"); + return hpp_list->nr_header_lines + 2; +} - if (se->elide) - continue; +int hists__fprintf_headers(struct hists *hists, FILE *fp) +{ + char bf[1024]; + struct perf_hpp dummy_hpp = { + .buf = bf, + .size = sizeof(bf), + }; - fprintf(fp, " "); - width = hists__col_len(hists, se->se_width_idx); - if (width == 0) - width = strlen(se->se_header); - for (i = 0; i < width; i++) - fprintf(fp, "."); - } + fprintf(fp, "# "); - fprintf(fp, "\n"); - if (max_rows && ++nr_rows >= max_rows) + if (symbol_conf.report_hierarchy) + return hists__fprintf_hierarchy_headers(hists, &dummy_hpp, fp); + else + return hists__fprintf_standard_headers(hists, &dummy_hpp, fp); + +} + +size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, + int max_cols, float min_pcnt, FILE *fp, + bool ignore_callchains) +{ + struct rb_node *nd; + size_t ret = 0; + const char *sep = symbol_conf.field_sep; + int nr_rows = 0; + size_t linesz; + char *line = NULL; + unsigned indent; + + init_rem_hits(); + + hists__reset_column_width(hists); + + if (symbol_conf.col_width_list_str) + perf_hpp__set_user_width(symbol_conf.col_width_list_str); + + if (show_header) + nr_rows += hists__fprintf_headers(hists, fp); + + if (max_rows && nr_rows >= max_rows) goto out; - fprintf(fp, "#\n"); - if (max_rows && ++nr_rows >= max_rows) + linesz = hists__sort_list_width(hists) + 3 + 1; + linesz += perf_hpp__color_overhead(); + line = malloc(linesz); + if (line == NULL) { + ret = -1; goto out; + } + + indent = hists__overhead_width(hists) + 4; -print_entries: - for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { + for (nd = rb_first_cached(&hists->entries); nd; + nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD)) { struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); - float percent = h->stat.period * 100.0 / - hists->stats.total_period; + float percent; if (h->filtered) continue; + if (symbol_conf.report_individual_block) + percent = block_info__total_cycles_percent(h); + else + percent = hist_entry__get_percent_limit(h); + if (percent < min_pcnt) continue; - ret += hist_entry__fprintf(h, max_cols, hists, fp); + ret += hist_entry__fprintf(h, max_cols, line, linesz, fp, ignore_callchains); if (max_rows && ++nr_rows >= max_rows) - goto out; + break; + + /* + * If all children are filtered out or percent-limited, + * display "no entry >= x.xx%" message. + */ + if (!h->leaf && !hist_entry__has_hierarchy_children(h, min_pcnt)) { + int depth = hists->nr_hpp_node + h->depth + 1; + + print_hierarchy_indent(sep, depth, " ", fp); + fprintf(fp, "%*sno entry >= %.2f%%\n", indent, "", min_pcnt); + + if (max_rows && ++nr_rows >= max_rows) + break; + } if (h->ms.map == NULL && verbose > 1) { - __map_groups__fprintf_maps(&h->thread->mg, - MAP__FUNCTION, verbose, fp); + maps__fprintf(thread__maps(h->thread), fp); fprintf(fp, "%.10s end\n", graph_dotted_line); } } + + free(line); out: - free(rem_sq_bracket); + zfree(&rem_sq_bracket); return ret; } @@ -470,19 +918,25 @@ size_t events_stats__fprintf(struct events_stats *stats, FILE *fp) { int i; size_t ret = 0; + u32 total = stats->nr_events[0]; for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) { const char *name; - if (stats->nr_events[i] == 0) - continue; - name = perf_event__name(i); if (!strcmp(name, "UNKNOWN")) continue; + if (symbol_conf.skip_empty && !stats->nr_events[i]) + continue; - ret += fprintf(fp, "%16s events: %10d\n", name, - stats->nr_events[i]); + if (i && total) { + ret += fprintf(fp, "%20s events: %10d (%4.1f%%)\n", + name, stats->nr_events[i], + 100.0 * stats->nr_events[i] / total); + } else { + ret += fprintf(fp, "%20s events: %10d\n", + name, stats->nr_events[i]); + } } return ret; diff --git a/tools/perf/ui/tui/Build b/tools/perf/ui/tui/Build new file mode 100644 index 000000000000..2ac058ad1a61 --- /dev/null +++ b/tools/perf/ui/tui/Build @@ -0,0 +1,4 @@ +perf-ui-y += setup.o +perf-ui-y += util.o +perf-ui-y += helpline.o +perf-ui-y += progress.o diff --git a/tools/perf/ui/tui/helpline.c b/tools/perf/ui/tui/helpline.c index 1c8b9afd5d6e..b39451314f43 100644 --- a/tools/perf/ui/tui/helpline.c +++ b/tools/perf/ui/tui/helpline.c @@ -1,14 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <pthread.h> +#include <linux/kernel.h> +#include <linux/string.h> -#include "../../util/debug.h" #include "../helpline.h" #include "../ui.h" #include "../libslang.h" char ui_helpline__last_msg[1024]; +bool tui_helpline__set; static void tui_helpline__pop(void) { @@ -20,9 +22,9 @@ static void tui_helpline__push(const char *msg) SLsmg_gotorc(SLtt_Screen_Rows - 1, 0); SLsmg_set_color(0); - SLsmg_write_nstring((char *)msg, SLtt_Screen_Cols); + SLsmg_write_nstring(msg, SLtt_Screen_Cols); SLsmg_refresh(); - strncpy(ui_helpline__current, msg, sz)[sz - 1] = '\0'; + strlcpy(ui_helpline__current, msg, sz); } static int tui_helpline__show(const char *format, va_list ap) @@ -30,17 +32,19 @@ static int tui_helpline__show(const char *format, va_list ap) int ret; static int backlog; - pthread_mutex_lock(&ui__lock); + mutex_lock(&ui__lock); ret = vscnprintf(ui_helpline__last_msg + backlog, sizeof(ui_helpline__last_msg) - backlog, format, ap); backlog += ret; + tui_helpline__set = true; + if (ui_helpline__last_msg[backlog - 1] == '\n') { ui_helpline__puts(ui_helpline__last_msg); SLsmg_refresh(); backlog = 0; } - pthread_mutex_unlock(&ui__lock); + mutex_unlock(&ui__lock); return ret; } diff --git a/tools/perf/ui/tui/progress.c b/tools/perf/ui/tui/progress.c index 6c2184d53cbf..71b6c8d9474f 100644 --- a/tools/perf/ui/tui/progress.c +++ b/tools/perf/ui/tui/progress.c @@ -1,11 +1,33 @@ -#include "../cache.h" +// SPDX-License-Identifier: GPL-2.0 +#include <linux/kernel.h> #include "../progress.h" #include "../libslang.h" #include "../ui.h" +#include "tui.h" +#include "units.h" #include "../browser.h" -static void tui_progress__update(u64 curr, u64 total, const char *title) +static void __tui_progress__init(struct ui_progress *p) { + p->next = p->step = p->total / (SLtt_Screen_Cols - 2) ?: 1; +} + +static int get_title(struct ui_progress *p, char *buf, size_t size) +{ + char buf_cur[20]; + char buf_tot[20]; + int ret; + + ret = unit_number__scnprintf(buf_cur, sizeof(buf_cur), p->curr); + ret += unit_number__scnprintf(buf_tot, sizeof(buf_tot), p->total); + + return ret + scnprintf(buf, size, "%s [%s/%s]", + p->title, buf_cur, buf_tot); +} + +static void tui_progress__update(struct ui_progress *p) +{ + char buf[100], *title = (char *) p->title; int bar, y; /* * FIXME: We should have a per UI backend way of showing progress, @@ -14,29 +36,52 @@ static void tui_progress__update(u64 curr, u64 total, const char *title) if (use_browser <= 0) return; - if (total == 0) + if (p->total == 0) return; - ui__refresh_dimensions(true); - pthread_mutex_lock(&ui__lock); + if (p->size) { + get_title(p, buf, sizeof(buf)); + title = buf; + } + + ui__refresh_dimensions(false); + mutex_lock(&ui__lock); y = SLtt_Screen_Rows / 2 - 2; SLsmg_set_color(0); SLsmg_draw_box(y, 0, 3, SLtt_Screen_Cols); SLsmg_gotorc(y++, 1); - SLsmg_write_string((char *)title); + SLsmg_write_string(title); + SLsmg_fill_region(y, 1, 1, SLtt_Screen_Cols - 2, ' '); SLsmg_set_color(HE_COLORSET_SELECTED); - bar = ((SLtt_Screen_Cols - 2) * curr) / total; + bar = ((SLtt_Screen_Cols - 2) * p->curr) / p->total; SLsmg_fill_region(y, 1, 1, bar, ' '); SLsmg_refresh(); - pthread_mutex_unlock(&ui__lock); + mutex_unlock(&ui__lock); } -static struct ui_progress tui_progress_fns = +static void tui_progress__finish(void) { - .update = tui_progress__update, + int y; + + if (use_browser <= 0) + return; + + ui__refresh_dimensions(false); + mutex_lock(&ui__lock); + y = SLtt_Screen_Rows / 2 - 2; + SLsmg_set_color(0); + SLsmg_fill_region(y, 0, 3, SLtt_Screen_Cols, ' '); + SLsmg_refresh(); + mutex_unlock(&ui__lock); +} + +static struct ui_progress_ops tui_progress__ops = { + .init = __tui_progress__init, + .update = tui_progress__update, + .finish = tui_progress__finish, }; -void ui_progress__init(void) +void tui_progress__init(void) { - progress_fns = &tui_progress_fns; + ui_progress__ops = &tui_progress__ops; } diff --git a/tools/perf/ui/tui/setup.c b/tools/perf/ui/tui/setup.c index b9401482d110..022534eed68c 100644 --- a/tools/perf/ui/tui/setup.c +++ b/tools/perf/ui/tui/setup.c @@ -1,7 +1,15 @@ +#include <errno.h> #include <signal.h> #include <stdbool.h> - -#include "../../util/cache.h" +#include <stdlib.h> +#include <termios.h> +#include <unistd.h> +#include <linux/kernel.h> +#ifdef HAVE_BACKTRACE_SUPPORT +#include <execinfo.h> +#endif + +#include "../../util/color.h" #include "../../util/debug.h" #include "../browser.h" #include "../helpline.h" @@ -9,10 +17,12 @@ #include "../util.h" #include "../libslang.h" #include "../keysyms.h" +#include "tui.h" static volatile int ui__need_resize; extern struct perf_error_ops perf_tui_eops; +extern bool tui_helpline__set; extern void hist_browser__init_hpp(void); @@ -20,10 +30,10 @@ void ui__refresh_dimensions(bool force) { if (force || ui__need_resize) { ui__need_resize = 0; - pthread_mutex_lock(&ui__lock); + mutex_lock(&ui__lock); SLtt_get_screen_size(); SLsmg_reinit_smg(); - pthread_mutex_unlock(&ui__lock); + mutex_unlock(&ui__lock); } } @@ -87,6 +97,25 @@ int ui__getch(int delay_secs) return SLkp_getkey(); } +#ifdef HAVE_BACKTRACE_SUPPORT +static void ui__signal_backtrace(int sig) +{ + void *stackdump[32]; + size_t size; + + ui__exit(false); + psignal(sig, "perf"); + + printf("-------- backtrace --------\n"); + size = backtrace(stackdump, ARRAY_SIZE(stackdump)); + __dump_stack(stdout, stackdump, size); + + exit(0); +} +#else +# define ui__signal_backtrace ui__signal +#endif + static void ui__signal(int sig) { ui__exit(false); @@ -94,6 +123,23 @@ static void ui__signal(int sig) exit(0); } +static void ui__sigcont(int sig) +{ + static struct termios tty; + + if (sig == SIGTSTP) { + while (tcgetattr(SLang_TT_Read_FD, &tty) == -1 && errno == EINTR) + ; + while (write(SLang_TT_Read_FD, PERF_COLOR_RESET, sizeof(PERF_COLOR_RESET) - 1) == -1 && errno == EINTR) + ; + raise(SIGSTOP); + } else { + while (tcsetattr(SLang_TT_Read_FD, TCSADRAIN, &tty) == -1 && errno == EINTR) + ; + raise(SIGWINCH); + } +} + int ui__init(void) { int err; @@ -105,9 +151,10 @@ int ui__init(void) err = SLsmg_init_smg(); if (err < 0) goto out; - err = SLang_init_tty(0, 0, 0); + err = SLang_init_tty(-1, 0, 0); if (err < 0) goto out; + SLtty_set_suspend_state(true); err = SLkp_init(); if (err < 0) { @@ -115,20 +162,22 @@ int ui__init(void) goto out; } - SLkp_define_keysym((char *)"^(kB)", SL_KEY_UNTAB); - - ui_helpline__init(); - ui_browser__init(); - ui_progress__init(); + SLkp_define_keysym("^(kB)", SL_KEY_UNTAB); - signal(SIGSEGV, ui__signal); - signal(SIGFPE, ui__signal); + signal(SIGSEGV, ui__signal_backtrace); + signal(SIGFPE, ui__signal_backtrace); signal(SIGINT, ui__signal); signal(SIGQUIT, ui__signal); signal(SIGTERM, ui__signal); + signal(SIGTSTP, ui__sigcont); + signal(SIGCONT, ui__sigcont); perf_error__register(&perf_tui_eops); + ui_helpline__init(); + ui_browser__init(); + tui_progress__init(); + hist_browser__init_hpp(); out: return err; @@ -136,15 +185,17 @@ out: void ui__exit(bool wait_for_ok) { - if (wait_for_ok) + if (wait_for_ok && tui_helpline__set) ui__question_window("Fatal Error", ui_helpline__last_msg, "Press any key...", 0); SLtt_set_cursor_visibility(1); - SLsmg_refresh(); - SLsmg_reset_smg(); + if (mutex_trylock(&ui__lock)) { + SLsmg_refresh(); + SLsmg_reset_smg(); + mutex_unlock(&ui__lock); + } SLang_reset_tty(); - perf_error__unregister(&perf_tui_eops); } diff --git a/tools/perf/ui/tui/tui.h b/tools/perf/ui/tui/tui.h new file mode 100644 index 000000000000..8de06f634c56 --- /dev/null +++ b/tools/perf/ui/tui/tui.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _PERF_TUI_H_ +#define _PERF_TUI_H_ 1 + +void tui_progress__init(void); + +#endif /* _PERF_TUI_H_ */ diff --git a/tools/perf/ui/tui/util.c b/tools/perf/ui/tui/util.c index 092902e30cee..e4d322ce0b54 100644 --- a/tools/perf/ui/tui/util.c +++ b/tools/perf/ui/tui/util.c @@ -1,11 +1,10 @@ -#include "../../util/util.h" +// SPDX-License-Identifier: GPL-2.0 #include <signal.h> #include <stdbool.h> #include <string.h> +#include <stdlib.h> #include <sys/ttydefaults.h> -#include "../../util/cache.h" -#include "../../util/debug.h" #include "../browser.h" #include "../keysyms.h" #include "../helpline.h" @@ -21,10 +20,10 @@ static void ui_browser__argv_write(struct ui_browser *browser, ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : HE_COLORSET_NORMAL); - slsmg_write_nstring(*arg, browser->width); + ui_browser__write_nstring(browser, *arg, browser->width); } -static int popup_menu__run(struct ui_browser *menu) +static int popup_menu__run(struct ui_browser *menu, int *keyp) { int key; @@ -46,6 +45,11 @@ static int popup_menu__run(struct ui_browser *menu) key = -1; break; default: + if (keyp) { + *keyp = key; + key = menu->nr_entries; + break; + } continue; } @@ -56,7 +60,7 @@ static int popup_menu__run(struct ui_browser *menu) return key; } -int ui__popup_menu(int argc, char * const argv[]) +int ui__popup_menu(int argc, char * const argv[], int *keyp) { struct ui_browser menu = { .entries = (void *)argv, @@ -65,8 +69,7 @@ int ui__popup_menu(int argc, char * const argv[]) .write = ui_browser__argv_write, .nr_entries = argc, }; - - return popup_menu__run(&menu); + return popup_menu__run(&menu, keyp); } int ui_browser__input_window(const char *title, const char *text, char *input, @@ -92,6 +95,8 @@ int ui_browser__input_window(const char *title, const char *text, char *input, t = sep + 1; } + mutex_lock(&ui__lock); + max_len += 2; nr_lines += 8; y = SLtt_Screen_Rows / 2 - nr_lines / 2; @@ -101,7 +106,7 @@ int ui_browser__input_window(const char *title, const char *text, char *input, SLsmg_draw_box(y, x++, nr_lines, max_len); if (title) { SLsmg_gotorc(y, x + 1); - SLsmg_write_string((char *)title); + SLsmg_write_string(title); } SLsmg_gotorc(++y, x); nr_lines -= 7; @@ -112,21 +117,27 @@ int ui_browser__input_window(const char *title, const char *text, char *input, len = 5; while (len--) { SLsmg_gotorc(y + len - 1, x); - SLsmg_write_nstring((char *)" ", max_len); + SLsmg_write_nstring(" ", max_len); } SLsmg_draw_box(y++, x + 1, 3, max_len - 2); SLsmg_gotorc(y + 3, x); - SLsmg_write_nstring((char *)exit_msg, max_len); + SLsmg_write_nstring(exit_msg, max_len); SLsmg_refresh(); + mutex_unlock(&ui__lock); + x += 2; len = 0; key = ui__getch(delay_secs); while (key != K_TIMER && key != K_ENTER && key != K_ESC) { + mutex_lock(&ui__lock); + if (key == K_BKSPC) { - if (len == 0) + if (len == 0) { + mutex_unlock(&ui__lock); goto next_key; + } SLsmg_gotorc(y, x + --len); SLsmg_write_char(' '); } else { @@ -136,6 +147,8 @@ int ui_browser__input_window(const char *title, const char *text, char *input, } SLsmg_refresh(); + mutex_unlock(&ui__lock); + /* XXX more graceful overflow handling needed */ if (len == sizeof(buf) - 1) { ui_helpline__push("maximum size of symbol name reached!"); @@ -151,8 +164,7 @@ next_key: return key; } -int ui__question_window(const char *title, const char *text, - const char *exit_msg, int delay_secs) +void __ui__info_window(const char *title, const char *text, const char *exit_msg) { int x, y; int max_len = 0, nr_lines = 0; @@ -175,7 +187,9 @@ int ui__question_window(const char *title, const char *text, } max_len += 2; - nr_lines += 4; + nr_lines += 2; + if (exit_msg) + nr_lines += 2; y = SLtt_Screen_Rows / 2 - nr_lines / 2, x = SLtt_Screen_Cols / 2 - max_len / 2; @@ -183,18 +197,37 @@ int ui__question_window(const char *title, const char *text, SLsmg_draw_box(y, x++, nr_lines, max_len); if (title) { SLsmg_gotorc(y, x + 1); - SLsmg_write_string((char *)title); + SLsmg_write_string(title); } SLsmg_gotorc(++y, x); - nr_lines -= 2; + if (exit_msg) + nr_lines -= 2; max_len -= 2; SLsmg_write_wrapped_string((unsigned char *)text, y, x, nr_lines, max_len, 1); - SLsmg_gotorc(y + nr_lines - 2, x); - SLsmg_write_nstring((char *)" ", max_len); - SLsmg_gotorc(y + nr_lines - 1, x); - SLsmg_write_nstring((char *)exit_msg, max_len); + if (exit_msg) { + SLsmg_gotorc(y + nr_lines - 2, x); + SLsmg_write_nstring(" ", max_len); + SLsmg_gotorc(y + nr_lines - 1, x); + SLsmg_write_nstring(exit_msg, max_len); + } +} + +void ui__info_window(const char *title, const char *text) +{ + mutex_lock(&ui__lock); + __ui__info_window(title, text, NULL); + SLsmg_refresh(); + mutex_unlock(&ui__lock); +} + +int ui__question_window(const char *title, const char *text, + const char *exit_msg, int delay_secs) +{ + mutex_lock(&ui__lock); + __ui__info_window(title, text, exit_msg); SLsmg_refresh(); + mutex_unlock(&ui__lock); return ui__getch(delay_secs); } @@ -215,9 +248,7 @@ static int __ui__warning(const char *title, const char *format, va_list args) if (vasprintf(&s, format, args) > 0) { int key; - pthread_mutex_lock(&ui__lock); key = ui__question_window(title, s, "Press any key...", 0); - pthread_mutex_unlock(&ui__lock); free(s); return key; } diff --git a/tools/perf/ui/ui.h b/tools/perf/ui/ui.h index 70cb0d4eb8aa..d8e911205582 100644 --- a/tools/perf/ui/ui.h +++ b/tools/perf/ui/ui.h @@ -1,18 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_UI_H_ #define _PERF_UI_H_ 1 -#include <pthread.h> +#include "../util/mutex.h" #include <stdbool.h> #include <linux/compiler.h> -extern pthread_mutex_t ui__lock; +extern struct mutex ui__lock; +extern void *perf_gtk_handle; extern int use_browser; void setup_browser(bool fallback_to_pager); void exit_browser(bool wait_for_ok); -#ifdef SLANG_SUPPORT +#ifdef HAVE_SLANG_SUPPORT int ui__init(void); void ui__exit(bool wait_for_ok); #else @@ -23,17 +25,13 @@ static inline int ui__init(void) static inline void ui__exit(bool wait_for_ok __maybe_unused) {} #endif -#ifdef GTK2_SUPPORT -int perf_gtk__init(void); -void perf_gtk__exit(bool wait_for_ok); -#else -static inline int perf_gtk__init(void) -{ - return -1; -} -static inline void perf_gtk__exit(bool wait_for_ok __maybe_unused) {} -#endif - void ui__refresh_dimensions(bool force); +struct option; + +int stdio__config_color(const struct option *opt, const char *mode, int unset); + +void pthread__block_sigwinch(void); +void pthread__unblock_sigwinch(void); + #endif /* _PERF_UI_H_ */ diff --git a/tools/perf/ui/util.c b/tools/perf/ui/util.c index e3e0a963d03a..1d38ddf01b60 100644 --- a/tools/perf/ui/util.c +++ b/tools/perf/ui/util.c @@ -1,6 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 #include "util.h" -#include "../debug.h" - +#include "../util/debug.h" +#include <stdio.h> /* * Default error logging functions @@ -14,6 +15,9 @@ static int perf_stdio__error(const char *format, va_list args) static int perf_stdio__warning(const char *format, va_list args) { + if (quiet) + return 0; + fprintf(stderr, "Warning:\n"); vfprintf(stderr, format, args); return 0; @@ -44,6 +48,8 @@ int ui__warning(const char *format, ...) { int ret; va_list args; + if (quiet) + return 0; va_start(args, format); ret = perf_eops->warning(format, args); diff --git a/tools/perf/ui/util.h b/tools/perf/ui/util.h index 361f08c52d37..e30cea807564 100644 --- a/tools/perf/ui/util.h +++ b/tools/perf/ui/util.h @@ -1,12 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_UI_UTIL_H_ #define _PERF_UI_UTIL_H_ 1 #include <stdarg.h> int ui__getch(int delay_secs); -int ui__popup_menu(int argc, char * const argv[]); +int ui__popup_menu(int argc, char * const argv[], int *keyp); int ui__help_window(const char *text); int ui__dialog_yesno(const char *msg); +void __ui__info_window(const char *title, const char *text, const char *exit_msg); +void ui__info_window(const char *title, const char *text); int ui__question_window(const char *title, const char *text, const char *exit_msg, int delay_secs); |
