diff options
Diffstat (limited to 'tools/perf/ui/browsers')
| -rw-r--r-- | tools/perf/ui/browsers/Build | 17 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/annotate-data.c | 614 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/annotate.c | 1391 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/header.c | 10 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/hists.c | 1356 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/hists.h | 7 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/map.c | 15 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/map.h | 1 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/res_sample.c | 96 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/scripts.c | 452 |
10 files changed, 2604 insertions, 1355 deletions
diff --git a/tools/perf/ui/browsers/Build b/tools/perf/ui/browsers/Build index de223f5bed58..a07489e44765 100644 --- a/tools/perf/ui/browsers/Build +++ b/tools/perf/ui/browsers/Build @@ -1,10 +1,7 @@ -libperf-y += annotate.o -libperf-y += hists.o -libperf-y += map.o -libperf-y += scripts.o -libperf-y += header.o - -CFLAGS_annotate.o += -DENABLE_SLFUTURE_CONST -CFLAGS_hists.o += -DENABLE_SLFUTURE_CONST -CFLAGS_map.o += -DENABLE_SLFUTURE_CONST -CFLAGS_scripts.o += -DENABLE_SLFUTURE_CONST +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 27f41f28dcb4..36aca8d6d003 100644 --- a/tools/perf/ui/browsers/annotate.c +++ b/tools/perf/ui/browsers/annotate.c @@ -1,363 +1,307 @@ -#include "../../util/util.h" +// SPDX-License-Identifier: GPL-2.0 #include "../browser.h" #include "../helpline.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 "../../util/config.h" +#include "../../util/evlist.h" +#include "../../util/thread.h" #include <inttypes.h> -#include <pthread.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 disasm_line_samples { - double percent; - u64 nr; -}; - -#define IPC_WIDTH 6 -#define CYCLES_WIDTH 6 +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. */ - struct disasm_line_samples samples[1]; -}; - -static struct annotate_browser_opt { - bool hide_src_code, - use_offset, - jump_arrows, - show_linenr, - show_nr_jumps, - show_total_period; -} annotate_browser__opts = { - .use_offset = true, - .jump_arrows = true, + struct hist_entry *he; + struct debuginfo *dbg; + struct evsel *evsel; + struct hashmap *type_hash; + bool searching_backwards; + char search_bf[128]; }; -struct arch; +/* 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; - struct arch *arch; - int nr_events; - u64 start; - int nr_asm_entries; - int nr_entries; - int max_jump_sources; - int nr_jumps; - bool searching_backwards; - bool have_cycles; - 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 = annotate_browser__jumps_percent_color(browser, nr, current); - return ui_browser__set_color(&browser->b, color); + int color = ui_browser__jumps_percent_color(browser, nr, current); + return ui_browser__set_color(browser, color); } -static int annotate_browser__pcnt_width(struct annotate_browser *ab) +static int annotate_browser__set_color(void *browser, int color) { - int w = 7 * ab->nr_events; - - if (ab->have_cycles) - w += IPC_WIDTH + CYCLES_WIDTH; - return w; + return ui_browser__set_color(browser, color); } -static void annotate_browser__write(struct ui_browser *browser, void *entry, int row) +static void annotate_browser__write_graph(void *browser, int graph) { - 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 = annotate_browser__pcnt_width(ab); - double percent_max = 0.0; - char bf[256]; - bool show_title = false; - - for (i = 0; i < ab->nr_events; i++) { - if (bdl->samples[i].percent > percent_max) - percent_max = bdl->samples[i].percent; - } - - if ((row == 0) && (dl->offset == -1 || percent_max == 0.0)) { - if (ab->have_cycles) { - if (dl->ipc == 0.0 && dl->cycles == 0) - show_title = true; - } else - show_title = true; - } + ui_browser__write_graph(browser, graph); +} - if (dl->offset != -1 && percent_max != 0.0) { - for (i = 0; i < ab->nr_events; i++) { - ui_browser__set_percent_color(browser, - bdl->samples[i].percent, - current_entry); - if (annotate_browser__opts.show_total_period) { - ui_browser__printf(browser, "%6" PRIu64 " ", - bdl->samples[i].nr); - } else { - ui_browser__printf(browser, "%6.2f ", - bdl->samples[i].percent); - } - } - } else { - ui_browser__set_percent_color(browser, 0, current_entry); +static void annotate_browser__set_percent_color(void *browser, double percent, bool current) +{ + ui_browser__set_percent_color(browser, percent, current); +} - if (!show_title) - ui_browser__write_nstring(browser, " ", 7 * ab->nr_events); - else - ui_browser__printf(browser, "%*s", 7, "Percent"); - } - if (ab->have_cycles) { - if (dl->ipc) - ui_browser__printf(browser, "%*.2f ", IPC_WIDTH - 1, dl->ipc); - else if (!show_title) - ui_browser__write_nstring(browser, " ", IPC_WIDTH); - else - ui_browser__printf(browser, "%*s ", IPC_WIDTH - 1, "IPC"); +static void annotate_browser__printf(void *browser, const char *fmt, ...) +{ + va_list args; - if (dl->cycles) - ui_browser__printf(browser, "%*" PRIu64 " ", - CYCLES_WIDTH - 1, dl->cycles); - else if (!show_title) - ui_browser__write_nstring(browser, " ", CYCLES_WIDTH); - else - ui_browser__printf(browser, "%*s ", CYCLES_WIDTH - 1, "Cycle"); - } + va_start(args, fmt); + ui_browser__vprintf(browser, fmt, args); + va_end(args); +} - SLsmg_write_char(' '); +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 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) - ui_browser__write_nstring(browser, " ", width - pcnt_width); - else if (dl->offset == -1) { - if (dl->line_nr && annotate_browser__opts.show_linenr) - printed = scnprintf(bf, sizeof(bf), "%-*d ", - ab->addr_width + 1, dl->line_nr); - else - printed = scnprintf(bf, sizeof(bf), "%*s ", - ab->addr_width, " "); - ui_browser__write_nstring(browser, bf, printed); - ui_browser__write_nstring(browser, 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); - ui_browser__write_nstring(browser, 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); - ui_browser__write_nstring(browser, bf, printed); - if (change_color) - ui_browser__set_color(browser, color); - if (dl->ins.ops && dl->ins.ops->scnprintf) { - if (ins__is_jump(&dl->ins)) { - bool fwd = dl->ops.target.offset > 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 if (ins__is_ret(&dl->ins)) { - ui_browser__write_graph(browser, SLSMG_LARROW_CHAR); - SLsmg_write_char(' '); - } else { - ui_browser__write_nstring(browser, " ", 2); - } - } else { - ui_browser__write_nstring(browser, " ", 2); - } + 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); - ui_browser__write_nstring(browser, 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.ops || !ins__is_jump(&dl->ins) - || !disasm_line__has_offset(dl) - || dl->ops.target.offset < 0 - || dl->ops.target.offset >= (s64)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 = annotate_browser__pcnt_width(ab); + 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; } + width = annotation__cycles_width(notes); + ui_browser__set_color(browser, HE_COLORSET_JUMP_ARROWS); - __ui_browser__line_arrow(browser, pcnt_width + 2 + ab->addr_width, + __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 = annotate_browser__pcnt_width(ab); + int pcnt_width = annotation__pcnt_width(notes); - 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->samples[i].percent == b->samples[i].percent) + for (i = 0; i < a->data_nr; i++) { + if (a->data[i].percent[percent_type] == b->data[i].percent[percent_type]) continue; - return a->samples[i].percent < b->samples[i].percent; + 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; @@ -366,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; @@ -382,162 +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); - list_for_each_entry(pos, ¬es->src->source, node) { - struct browser_disasm_line *bpos = disasm_line__browser(pos); - const char *path = NULL; + symbol__calc_percent(sym, evsel); + + 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 < browser->nr_events; i++) { - u64 nr_samples; + for (i = 0; i < pos->al.data_nr; i++) { + double percent; - bpos->samples[i].percent = disasm__calc_percent(notes, - evsel->idx + i, - pos->offset, - next ? next->offset : len, - &path, &nr_samples); - bpos->samples[i].nr = nr_samples; + percent = annotation_data__percent(&pos->al.data[i], + annotate_opts.percent_type); - if (max_percent < bpos->samples[i].percent) - max_percent = bpos->samples[i].percent; + if (max_percent < percent) + max_percent = percent; } - if (max_percent < 0.01 && pos->ipc == 0) { - 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 disasm_line *dl; - struct browser_disasm_line *bdl; + 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 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_opts.annotate_src) + annotate_opts.annotate_src = true; + + /* + * 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_browser__opts.hide_src_code) { - if (bdl->idx_asm < offset) - offset = bdl->idx; + if (annotate_opts.hide_src_code) { + if (al->idx_asm < offset) + offset = al->idx; - browser->b.nr_entries = browser->nr_entries; - annotate_browser__opts.hide_src_code = false; + 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); + } } -#define SYM_TITLE_MAX_SIZE (PATH_MAX + 64) +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) + size_t sz, int percent_type) { - return snprintf(title, sz, "%s %s", sym->name, map->dso->long_name); + 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 map_symbol *ms = browser->b.priv, target_ms; + struct disasm_line *dl = disasm_line(browser->selection); struct annotation *notes; - struct addr_map_symbol target = { - .map = ms->map, - .addr = map__objdump_2mem(ms->map, dl->ops.target.addr), - }; - char title[SYM_TITLE_MAX_SIZE]; - if (!ins__is_call(&dl->ins)) - return false; - - if (map_groups__find_ams(&target) || - map__rip_2objdump(target.map, target.map->map_ip(target.map, - target.addr)) != - dl->ops.target.addr) { + if (!dl->ops.target.sym) { ui_helpline__puts("The called function was not found."); return true; } - notes = symbol__annotation(target.sym); - pthread_mutex_lock(¬es->lock); + notes = symbol__annotation(dl->ops.target.sym); + annotation__lock(notes); - if (notes->src == NULL && symbol__alloc_hist(target.sym) < 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.sym->name); + dl->ops.target.sym->name); return true; } - pthread_mutex_unlock(¬es->lock); - symbol__tui_annotate(target.sym, target.map, evsel, hbt); - sym_title(ms->sym, ms->map, title, sizeof(title)); - ui_browser__show_title(&browser->b, title); + 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; } @@ -545,31 +620,36 @@ 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)) return false; + 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) { @@ -577,29 +657,27 @@ static bool annotate_browser__jump(struct annotate_browser *browser) 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; @@ -607,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; @@ -646,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; } @@ -708,37 +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); + } +} + +static int annotate__scnprintf_title(struct hists *hists, char *bf, size_t size) +{ + int printed = hists__scnprintf_title(hists, bf, size); - browser->addr_width = browser->target_width; + if (!annotate_opts.hide_src_code_on_title) { + printed += scnprintf(bf + printed, size - printed, " [source: %s]", + annotate_opts.hide_src_code ? "OFF" : "On"); + } - if (annotate_browser__opts.show_nr_jumps) - browser->addr_width += browser->jumps_width + 1; + 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; - char title[SYM_TITLE_MAX_SIZE]; - sym_title(sym, ms->map, title, sizeof(title)); - if (ui_browser__show(&browser->b, title, 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; @@ -746,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); @@ -765,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) { @@ -789,47 +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" "ENTER Go to target\n" - "ESC Exit\n" - "H Cycle thru hottest instructions\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 Toggle total period 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 string backwards\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_browser__opts.show_linenr = - !annotate_browser__opts.show_linenr; - break; + 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)) { @@ -855,36 +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.ops) + else if (!dl->ins.ops) goto show_sup_ins; - else if (ins__is_ret(&browser->selection->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 function call/return & jump/branch instructions."); } continue; + } + case 'P': + map_symbol__annotation_dump(ms, evsel, browser->he); + continue; case 't': - annotate_browser__opts.show_total_period = - !annotate_browser__opts.show_total_period; - annotate_browser__update_addr_width(browser); + 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; } @@ -893,298 +1140,130 @@ show_sup_ins: } out: ui_browser__hide(&browser->b); + free(br_cntr_text); return key; } -int map_symbol__tui_annotate(struct map_symbol *ms, struct perf_evsel *evsel, - struct hist_browser_timer *hbt) -{ - /* Set default value for show_total_period. */ - annotate_browser__opts.show_total_period = - symbol_conf.show_total_period; - - return symbol__tui_annotate(ms->sym, ms->map, evsel, hbt); -} - -int hist_entry__tui_annotate(struct hist_entry *he, struct perf_evsel *evsel, - struct hist_browser_timer *hbt) +int hist_entry__tui_annotate(struct hist_entry *he, struct evsel *evsel, + struct hist_browser_timer *hbt, u64 al_addr) { /* 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); - return map_symbol__tui_annotate(&he->ms, evsel, hbt); -} - - -static unsigned count_insn(struct annotate_browser *browser, u64 start, u64 end) -{ - unsigned n_insn = 0; - u64 offset; - - for (offset = start; offset <= end; offset++) { - if (browser->offsets[offset]) - n_insn++; - } - return n_insn; + return __hist_entry__tui_annotate(he, &he->ms, evsel, hbt, al_addr); } -static void count_and_fill(struct annotate_browser *browser, u64 start, u64 end, - struct cyc_hist *ch) +int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms, + struct evsel *evsel, + struct hist_browser_timer *hbt, u64 al_addr) { - unsigned n_insn; - u64 offset; - - n_insn = count_insn(browser, start, end); - if (n_insn && ch->num && ch->cycles) { - float ipc = n_insn / ((double)ch->cycles / (double)ch->num); - - /* Hide data when there are too many overlaps. */ - if (ch->reset >= 0x7fff || ch->reset >= ch->num / 2) - return; - - for (offset = start; offset <= end; offset++) { - struct disasm_line *dl = browser->offsets[offset]; - - if (dl) - dl->ipc = ipc; - } - } -} - -/* - * This should probably be in util/annotate.c to share with the tty - * annotate, but right now we need the per byte offsets arrays, - * which are only here. - */ -static void annotate__compute_ipc(struct annotate_browser *browser, size_t size, - struct symbol *sym) -{ - u64 offset; - struct annotation *notes = symbol__annotation(sym); - - if (!notes->src || !notes->src->cycles_hist) - return; - - pthread_mutex_lock(¬es->lock); - for (offset = 0; offset < size; ++offset) { - struct cyc_hist *ch; - - ch = ¬es->src->cycles_hist[offset]; - if (ch && ch->cycles) { - struct disasm_line *dl; - - if (ch->have_start) - count_and_fill(browser, ch->start, offset, ch); - dl = browser->offsets[offset]; - if (dl && ch->num_aggr) - dl->cycles = ch->cycles_aggr / ch->num_aggr; - browser->have_cycles = true; - } - } - pthread_mutex_unlock(¬es->lock); -} - -static void annotate_browser__mark_jump_targets(struct annotate_browser *browser, - size_t size) -{ - u64 offset; - struct map_symbol *ms = browser->b.priv; struct symbol *sym = ms->sym; - - /* PLT symbols contain external offsets */ - if (strstr(sym->name, "@plt")) - return; - - for (offset = 0; offset < size; ++offset) { - struct disasm_line *dl = browser->offsets[offset], *dlt; - struct browser_disasm_line *bdlt; - - if (!disasm_line__is_valid_jump(dl, sym)) - continue; - - dlt = browser->offsets[dl->ops.target.offset]; - /* - * FIXME: Oops, no jump target? Buggy disassembler? Or do we - * have to adjust to the previous offset? - */ - if (dlt == NULL) - continue; - - bdlt = disasm_line__browser(dlt); - if (++bdlt->jump_sources > browser->max_jump_sources) - browser->max_jump_sources = bdlt->jump_sources; - - ++browser->nr_jumps; - } -} - -static inline int width_jumps(int n) -{ - if (n >= 100) - return 5; - if (n / 10) - return 2; - return 1; -} - -int symbol__tui_annotate(struct symbol *sym, struct map *map, - struct perf_evsel *evsel, - struct hist_browser_timer *hbt) -{ - struct disasm_line *pos, *n; - struct annotation *notes; - size_t size; - struct map_symbol ms = { - .map = map, - .sym = sym, - }; + struct 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, }; + struct dso *dso; int ret = -1, err; - int nr_pcnt = 1; - size_t sizeof_bdl = sizeof(struct browser_disasm_line); + 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(struct disasm_line_samples) * - (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; + } } - err = symbol__disassemble(sym, map, perf_evsel__env_arch(evsel), - sizeof_bdl, &browser.arch); - if (err) { - char msg[BUFSIZ]; - symbol__strerror_disassemble(sym, map, err, msg, sizeof(msg)); - ui__error("Couldn't annotate %s:\n%s", sym->name, 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); + + browser.he = &annotate_he; } ui_helpline__push("Press ESC to exit"); - notes = symbol__annotation(sym); - browser.start = map__rip_2objdump(map, sym->start); - - list_for_each_entry(pos, ¬es->src->source, node) { - struct browser_disasm_line *bpos; - size_t line_len = strlen(pos->line); - - if (browser.b.width < line_len) - browser.b.width = line_len; - bpos = disasm_line__browser(pos); - bpos->idx = browser.nr_entries++; - if (pos->offset != -1) { - bpos->idx_asm = browser.nr_asm_entries++; - /* - * FIXME: short term bandaid to cope with assembly - * routines that comes with labels in the same column - * as the address in objdump, sigh. - * - * E.g. copy_user_generic_unrolled - */ - if (pos->offset < (s64)size) - browser.offsets[pos->offset] = pos; - } else - bpos->idx_asm = -1; + if (annotate_opts.code_with_type) { + browser.dbg = dso__debuginfo(dso); + browser.type_hash = hashmap__new(type_hash, type_equal, /*ctx=*/NULL); } - annotate_browser__mark_jump_targets(&browser, size); - annotate__compute_ipc(&browser, size, sym); - - 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_linenr), - ANNOTATE_CFG(show_nr_jumps), - ANNOTATE_CFG(show_total_period), - ANNOTATE_CFG(use_offset), -}; - -#undef ANNOTATE_CFG + ret = annotate_browser__run(&browser, evsel, hbt); -static int annotate_config__cmp(const void *name, const void *cfgp) -{ - const struct annotate_config *cfg = cfgp; + debuginfo__delete(browser.dbg); - return strcmp(name, cfg->name); -} + if (!IS_ERR_OR_NULL(browser.type_hash)) { + struct hashmap_entry *cur; + size_t bkt; -static int annotate__config(const char *var, const char *value, - void *data __maybe_unused) -{ - struct annotate_config *cfg; - const char *name; - - if (prefixcmp(var, "annotate.") != 0) - return 0; + hashmap__for_each_entry(browser.type_hash, cur, bkt) + zfree(&cur->pvalue); + hashmap__free(browser.type_hash); + } - name = var + 9; - cfg = bsearch(name, annotate__configs, ARRAY_SIZE(annotate__configs), - sizeof(struct annotate_config), annotate_config__cmp); + if (not_annotated && !notes->src->tried_source) + annotated_source__purge(notes->src); - if (cfg == NULL) - ui__warning("%s variable unknown, ignoring...", var); - else - *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 index e2c9390ff4c5..5b5ca32e3eef 100644 --- a/tools/perf/ui/browsers/header.c +++ b/tools/perf/ui/browsers/header.c @@ -1,5 +1,4 @@ -#include "util/cache.h" -#include "util/debug.h" +// SPDX-License-Identifier: GPL-2.0 #include "ui/browser.h" #include "ui/keysyms.h" #include "ui/ui.h" @@ -34,7 +33,7 @@ static int list_menu__run(struct ui_browser *menu) { int key; unsigned long offset; - const char help[] = + static const char help[] = "h/?/F1 Show this window\n" "UP/DOWN/PGUP\n" "PGDN/SPACE\n" @@ -70,6 +69,7 @@ static int list_menu__run(struct ui_browser *menu) key = -1; break; default: + ui_browser__warn_unhandled_hotkey(menu, key, 0, ", use 'h'/'?'/F1 to see actions"); continue; } @@ -93,16 +93,14 @@ static int ui__list_menu(int argc, char * const argv[]) return list_menu__run(&menu); } -int tui__header_window(struct perf_env *env) +int tui__header_window(struct perf_session *session) { int i, argc = 0; char **argv; - struct perf_session *session; char *ptr, *pos; size_t size; FILE *fp = open_memstream(&ptr, &size); - session = container_of(env, struct perf_session, header.env); perf_header__fprintf_info(session, fp, true); fclose(fp); diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index 69f4570bd4f9..08fecbe28a52 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 #include <dirent.h> #include <errno.h> #include <inttypes.h> @@ -5,16 +6,30 @@ #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/util.h" #include "../../util/top.h" #include "../../util/thread.h" +#include "../../util/block-info.h" +#include "../../util/util.h" #include "../../arch/common.h" #include "../browsers/hists.h" @@ -23,16 +38,17 @@ #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" -#include "sane_ctype.h" +#include <linux/ctype.h> extern void hist_browser__init_hpp(void); -static int perf_evsel_browser_title(struct hist_browser *browser, - char *bf, size_t size); +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, @@ -49,7 +65,7 @@ static int hist_browser__get_folding(struct hist_browser *browser) struct hists *hists = browser->hists; int unfolded_rows = 0; - for (nd = rb_first(&hists->entries); + 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 = @@ -61,6 +77,15 @@ static int hist_browser__get_folding(struct hist_browser *browser) 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; @@ -81,13 +106,19 @@ 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 header_offset, index_row; + u16 index_row; + + if (!hb->show_headers) { + browser->rows += browser->extra_title_lines; + browser->extra_title_lines = 0; + return; + } - header_offset = hb->show_headers ? hpp_list->nr_header_lines : 0; - browser->rows = browser->height - header_offset; + 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 - * visibe because we now show the header line(s). + * visible because we now show the header line(s). */ index_row = browser->index - browser->top_idx; if (index_row >= browser->rows) @@ -107,17 +138,6 @@ static void hist_browser__refresh_dimensions(struct ui_browser *browser) * changeset. */ ui_browser__refresh_dimensions(browser); - hist_browser__update_rows(hb); -} - -static void hist_browser__gotorc(struct hist_browser *browser, int row, int column) -{ - struct hists *hists = browser->hists; - struct perf_hpp_list *hpp_list = hists->hpp_list; - u16 header_offset; - - header_offset = browser->show_headers ? hpp_list->nr_header_lines : 0; - ui_browser__gotorc(&browser->b, row + header_offset, column); } static void hist_browser__reset(struct hist_browser *browser) @@ -154,60 +174,9 @@ static void callchain_list__set_folding(struct callchain_list *cl, bool unfold) cl->unfolded = unfold ? cl->has_children : false; } -static struct inline_node *inline_node__create(struct map *map, u64 ip) -{ - struct dso *dso; - struct inline_node *node; - - if (map == NULL) - return NULL; - - dso = map->dso; - if (dso == NULL) - return NULL; - - if (dso->kernel != DSO_TYPE_USER) - return NULL; - - node = dso__parse_addr_inlines(dso, - map__rip_2objdump(map, ip)); - - return node; -} - -static int inline__count_rows(struct inline_node *node) -{ - struct inline_list *ilist; - int i = 0; - - if (node == NULL) - return 0; - - list_for_each_entry(ilist, &node->val, list) { - if ((ilist->filename != NULL) || (ilist->funcname != NULL)) - i++; - } - - return i; -} - -static int callchain_list__inline_rows(struct callchain_list *chain) -{ - struct inline_node *node; - int rows; - - node = inline_node__create(chain->ms.map, chain->ip); - if (node == NULL) - return 0; - - rows = inline__count_rows(node); - inline_node__delete(node); - return rows; -} - static int callchain_node__count_rows_rb_tree(struct callchain_node *node) { - int n = 0, inline_rows; + int n = 0; struct rb_node *nd; for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) { @@ -218,12 +187,6 @@ static int callchain_node__count_rows_rb_tree(struct callchain_node *node) list_for_each_entry(chain, &child->val, list) { ++n; - if (symbol_conf.inline_name) { - inline_rows = - callchain_list__inline_rows(chain); - n += inline_rows; - } - /* We need this because we may not have children */ folded_sign = callchain_list__folded(chain); if (folded_sign == '+') @@ -275,7 +238,7 @@ static int callchain_node__count_rows(struct callchain_node *node) { struct callchain_list *chain; bool unfolded = false; - int n = 0, inline_rows; + int n = 0; if (callchain_param.mode == CHAIN_FLAT) return callchain_node__count_flat_rows(node); @@ -284,10 +247,6 @@ static int callchain_node__count_rows(struct callchain_node *node) list_for_each_entry(chain, &node->val, list) { ++n; - if (symbol_conf.inline_name) { - inline_rows = callchain_list__inline_rows(chain); - n += inline_rows; - } unfolded = chain->unfolded; } @@ -324,7 +283,7 @@ static int hierarchy_count_rows(struct hist_browser *hb, struct hist_entry *he, if (he->has_no_entry) return 1; - node = rb_first(&he->hroot_out); + node = rb_first_cached(&he->hroot_out); while (node) { float percent; @@ -429,23 +388,56 @@ static void hist_entry__init_have_children(struct hist_entry *he) he->has_children = !RB_EMPTY_ROOT(&he->sorted_chain); callchain__init_have_children(&he->sorted_chain); } else { - he->has_children = !RB_EMPTY_ROOT(&he->hroot_out); + he->has_children = !RB_EMPTY_ROOT(&he->hroot_out.rb_root); } he->init_have_children = true; } -static void hist_entry_init_inline_node(struct hist_entry *he) +static bool hist_browser__selection_has_children(struct hist_browser *browser) { - if (he->inline_node) - return; + struct hist_entry *he = browser->he_selection; + struct map_symbol *ms = browser->selection; - he->inline_node = inline_node__create(he->ms.map, he->ip); + if (!he || !ms) + return false; - if (he->inline_node == NULL) - return; + 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; - he->has_children = true; + 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) @@ -479,12 +471,8 @@ static bool hist_browser__toggle_fold(struct hist_browser *browser) if (he->unfolded) { if (he->leaf) - if (he->inline_node) - he->nr_rows = inline__count_rows( - he->inline_node); - else - he->nr_rows = callchain__count_rows( - &he->sorted_chain); + he->nr_rows = callchain__count_rows( + &he->sorted_chain); else he->nr_rows = hierarchy_count_rows(browser, he, false); @@ -582,7 +570,7 @@ static int hierarchy_set_folding(struct hist_browser *hb, struct hist_entry *he, struct hist_entry *child; int n = 0; - for (nd = rb_first(&he->hroot_out); nd; nd = rb_next(nd)) { + 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) @@ -592,8 +580,8 @@ static int hierarchy_set_folding(struct hist_browser *hb, struct hist_entry *he, return n; } -static void __hist_entry__set_folding(struct hist_entry *he, - struct hist_browser *hb, bool unfold) +static void hist_entry__set_folding(struct hist_entry *he, + struct hist_browser *hb, bool unfold) { hist_entry__init_have_children(he); he->unfolded = unfold ? he->has_children : false; @@ -611,36 +599,14 @@ static void __hist_entry__set_folding(struct hist_entry *he, he->nr_rows = 0; } -static void hist_entry__set_folding(struct hist_entry *he, - struct hist_browser *browser, bool unfold) -{ - double percent; - - percent = hist_entry__get_percent_limit(he); - if (he->filtered || percent < browser->min_pcnt) - return; - - __hist_entry__set_folding(he, browser, unfold); - - 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) { struct rb_node *nd; struct hist_entry *he; + double percent; - nd = rb_first(&browser->hists->entries); + nd = rb_first_cached(&browser->hists->entries); while (nd) { he = rb_entry(nd, struct hist_entry, rb_node); @@ -648,6 +614,21 @@ __hist_browser__set_folding(struct hist_browser *browser, bool unfold) nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD); 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; } } @@ -667,8 +648,10 @@ static void hist_browser__set_folding_selected(struct hist_browser *browser, boo if (!browser->he_selection) return; - hist_entry__set_folding(browser->he_selection, browser, unfold); - browser->b.nr_entries = hist_browser__nr_entries(browser); + if (unfold == browser->he_selection->unfolded) + return; + + hist_browser__toggle_fold(browser); } static void ui_browser__warn_lost_events(struct ui_browser *browser) @@ -685,9 +668,82 @@ static int hist_browser__title(struct hist_browser *browser, char *bf, size_t si return browser->title ? browser->title(browser, bf, size) : 0; } -int hist_browser__run(struct hist_browser *browser, const char *help) +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; @@ -700,73 +756,14 @@ int hist_browser__run(struct hist_browser *browser, const char *help) 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 (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 (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); - } - - hist_browser__title(browser, title, sizeof(title)); - ui_browser__show_title(&browser->b, title); - continue; - } - case 'D': { /* Debug */ - static int seq; - struct hist_entry *h = rb_entry(browser->b.top, - struct hist_entry, rb_node); - ui_helpline__pop(); - ui_helpline__fpush("%d: nr_ent=(%d,%d), rows=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", - seq++, browser->b.nr_entries, - browser->hists->nr_entries, - browser->b.rows, - browser->b.index, - browser->b.top_idx, - h->row_offset, h->nr_rows); - } - break; - case 'C': - /* Collapse the whole world. */ - hist_browser__set_folding(browser, false); - break; - case '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': - /* Expand the selected entry. */ - hist_browser__set_folding_selected(browser, true); - break; - case 'H': - browser->show_headers = !browser->show_headers; - hist_browser__update_rows(browser); + if (hist_browser__handle_hotkey(browser, warn_lost_event, title, sizeof(title), key)) break; - case K_ENTER: - if (hist_browser__toggle_fold(browser)) - break; - /* fall thru */ - default: - goto out; - } } out: ui_browser__hide(&browser->b); @@ -808,7 +805,7 @@ static void hist_browser__show_callchain_entry(struct hist_browser *browser, } ui_browser__set_color(&browser->b, color); - hist_browser__gotorc(browser, row, 0); + 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 : ' '); @@ -844,71 +841,6 @@ static bool hist_browser__check_dump_full(struct hist_browser *browser __maybe_u #define LEVEL_OFFSET_STEP 3 -static int hist_browser__show_inline(struct hist_browser *browser, - struct inline_node *node, - unsigned short row, - int offset) -{ - struct inline_list *ilist; - char buf[1024]; - int color, width, first_row; - - first_row = row; - width = browser->b.width - (LEVEL_OFFSET_STEP + 2); - list_for_each_entry(ilist, &node->val, list) { - if ((ilist->filename != NULL) || (ilist->funcname != NULL)) { - color = HE_COLORSET_NORMAL; - if (ui_browser__is_current_entry(&browser->b, row)) - color = HE_COLORSET_SELECTED; - - if (callchain_param.key == CCKEY_ADDRESS || - callchain_param.key == CCKEY_SRCLINE) { - if (ilist->filename != NULL) - scnprintf(buf, sizeof(buf), - "%s:%d (inline)", - ilist->filename, - ilist->line_nr); - else - scnprintf(buf, sizeof(buf), "??"); - } else if (ilist->funcname != NULL) - scnprintf(buf, sizeof(buf), "%s (inline)", - ilist->funcname); - else if (ilist->filename != NULL) - scnprintf(buf, sizeof(buf), - "%s:%d (inline)", - ilist->filename, - ilist->line_nr); - else - scnprintf(buf, sizeof(buf), "??"); - - ui_browser__set_color(&browser->b, color); - hist_browser__gotorc(browser, row, 0); - ui_browser__write_nstring(&browser->b, " ", - LEVEL_OFFSET_STEP + offset); - ui_browser__write_nstring(&browser->b, buf, width); - row++; - } - } - - return row - first_row; -} - -static size_t show_inline_list(struct hist_browser *browser, struct map *map, - u64 ip, int row, int offset) -{ - struct inline_node *node; - int ret; - - node = inline_node__create(map, ip); - if (node == NULL) - return 0; - - ret = hist_browser__show_inline(browser, node, row, offset); - - inline_node__delete(node); - return ret; -} - static int hist_browser__show_callchain_list(struct hist_browser *browser, struct callchain_node *node, struct callchain_list *chain, @@ -920,7 +852,7 @@ static int hist_browser__show_callchain_list(struct hist_browser *browser, char bf[1024], *alloc_str; char buf[64], *alloc_str2; const char *str; - int inline_rows = 0, ret = 1; + int ret = 1; if (arg->row_offset != 0) { arg->row_offset--; @@ -934,12 +866,8 @@ static int hist_browser__show_callchain_list(struct hist_browser *browser, browser->show_dso); if (symbol_conf.show_branchflag_count) { - if (need_percent) - callchain_list_counts__printf_value(node, chain, NULL, - buf, sizeof(buf)); - else - callchain_list_counts__printf_value(NULL, chain, NULL, - buf, sizeof(buf)); + callchain_list_counts__printf_value(chain, NULL, + buf, sizeof(buf)); if (asprintf(&alloc_str2, "%s%s", str, buf) < 0) str = "Not enough memory!"; @@ -961,12 +889,7 @@ static int hist_browser__show_callchain_list(struct hist_browser *browser, free(alloc_str); free(alloc_str2); - if (symbol_conf.inline_name) { - inline_rows = show_inline_list(browser, chain->ms.map, - chain->ip, row + 1, offset); - } - - return ret + inline_rows; + return ret; } static bool check_percent_display(struct rb_node *node, u64 parent_total) @@ -1303,7 +1226,7 @@ int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...) return ret; } -#define __HPP_COLOR_PERCENT_FN(_type, _field) \ +#define __HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \ static u64 __hpp_get_##_field(struct hist_entry *he) \ { \ return he->stat._field; \ @@ -1315,10 +1238,10 @@ hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ struct hist_entry *he) \ { \ return hpp__fmt(fmt, hpp, he, __hpp_get_##_field, " %*.2f%%", \ - __hpp__slsmg_color_printf, true); \ + __hpp__slsmg_color_printf, _fmttype); \ } -#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \ static u64 __hpp_get_acc_##_field(struct hist_entry *he) \ { \ return he->stat_acc->_field; \ @@ -1339,23 +1262,44 @@ hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt, \ return ret; \ } \ return hpp__fmt(fmt, hpp, he, __hpp_get_acc_##_field, \ - " %*.2f%%", __hpp__slsmg_color_printf, true); \ + " %*.2f%%", __hpp__slsmg_color_printf, \ + _fmttype); \ } -__HPP_COLOR_PERCENT_FN(overhead, period) -__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys) -__HPP_COLOR_PERCENT_FN(overhead_us, period_us) -__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys) -__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) -__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period) +#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__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 = @@ -1366,6 +1310,20 @@ void hist_browser__init_hpp(void) 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, @@ -1376,6 +1334,7 @@ static int hist_browser__show_entry(struct hist_browser *browser, 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; @@ -1385,17 +1344,11 @@ 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 (symbol_conf.inline_name && - (!entry->has_children)) { - hist_entry_init_inline_node(entry); - folded_sign = hist_entry__folded(entry); - } - if (row_offset == 0) { struct hpp_arg arg = { .b = &browser->b, @@ -1404,7 +1357,7 @@ static int hist_browser__show_entry(struct hist_browser *browser, }; int column = 0; - hist_browser__gotorc(browser, row, 0); + ui_browser__gotorc(&browser->b, row, 0); hists__for_each_format(browser->hists, fmt) { char s[2048]; @@ -1427,8 +1380,7 @@ static int hist_browser__show_entry(struct hist_browser *browser, } if (first) { - if (symbol_conf.use_callchain || - symbol_conf.inline_name) { + if (use_callchain) { ui_browser__printf(&browser->b, "%c ", folded_sign); width -= 2; } @@ -1470,15 +1422,11 @@ static int hist_browser__show_entry(struct hist_browser *browser, .is_current_entry = current_entry, }; - if (entry->inline_node) - printed += hist_browser__show_inline(browser, - entry->inline_node, row, 0); - else - printed += hist_browser__show_callchain(browser, - entry, 1, row, - hist_browser__show_callchain_entry, - &arg, - hist_browser__check_output_full); + printed += hist_browser__show_callchain(browser, + entry, 1, row, + hist_browser__show_callchain_entry, + &arg, + hist_browser__check_output_full); } return printed; @@ -1518,7 +1466,7 @@ static int hist_browser__show_hierarchy_entry(struct hist_browser *browser, goto show_callchain; } - hist_browser__gotorc(browser, row, 0); + ui_browser__gotorc(&browser->b, row, 0); if (current_entry && browser->b.navkeypressed) ui_browser__set_color(&browser->b, HE_COLORSET_SELECTED); @@ -1618,7 +1566,7 @@ static int hist_browser__show_hierarchy_entry(struct hist_browser *browser, int i = 0; width -= fmt->entry(fmt, &hpp, entry); - ui_browser__printf(&browser->b, "%s", ltrim(s)); + ui_browser__printf(&browser->b, "%s", skip_spaces(s)); while (isspace(s[i++])) width++; @@ -1667,7 +1615,7 @@ static int hist_browser__show_no_entry(struct hist_browser *browser, browser->selection = NULL; } - hist_browser__gotorc(browser, row, 0); + ui_browser__gotorc(&browser->b, row, 0); if (current_entry && browser->b.navkeypressed) ui_browser__set_color(&browser->b, HE_COLORSET_SELECTED); @@ -1739,7 +1687,7 @@ hists_browser__scnprintf_headers(struct hist_browser *browser, char *buf, int column = 0; int span = 0; - if (symbol_conf.use_callchain) { + if (hists__has_callchains(hists) && symbol_conf.use_callchain) { ret = scnprintf(buf, size, " "); if (advance_hpp_check(&dummy_hpp, ret)) return ret; @@ -1764,7 +1712,8 @@ hists_browser__scnprintf_headers(struct hist_browser *browser, char *buf, return ret; } -static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *browser, char *buf, size_t size) +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 = { @@ -1790,7 +1739,7 @@ static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *brows if (column++ < browser->b.horiz_scroll) continue; - ret = fmt->header(fmt, &dummy_hpp, hists, 0, NULL); + ret = fmt->header(fmt, &dummy_hpp, hists, line, NULL); if (advance_hpp_check(&dummy_hpp, ret)) break; @@ -1801,6 +1750,9 @@ static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *brows 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, ""); @@ -1831,10 +1783,10 @@ static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *brows } first_col = false; - ret = fmt->header(fmt, &dummy_hpp, hists, 0, NULL); + ret = fmt->header(fmt, &dummy_hpp, hists, line, NULL); dummy_hpp.buf[ret] = '\0'; - start = trim(dummy_hpp.buf); + start = strim(dummy_hpp.buf); ret = strlen(start); if (start != dummy_hpp.buf) @@ -1850,14 +1802,18 @@ static int hists_browser__scnprintf_hierarchy_headers(struct hist_browser *brows 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; - hists_browser__scnprintf_hierarchy_headers(browser, headers, - sizeof(headers)); + for (line = 0; line < hpp_list->nr_header_lines; line++) { + hists_browser__scnprintf_hierarchy_headers(browser, headers, + sizeof(headers), line); - ui_browser__gotorc(&browser->b, 0, 0); - ui_browser__set_color(&browser->b, HE_COLORSET_ROOT); - ui_browser__write_nstring(&browser->b, headers, browser->b.width + 1); + 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) @@ -1873,7 +1829,7 @@ static void hists_browser__headers(struct hist_browser *browser) hists_browser__scnprintf_headers(browser, headers, sizeof(headers), line); - ui_browser__gotorc(&browser->b, line, 0); + 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); } @@ -1893,24 +1849,18 @@ static void ui_browser__hists_init_top(struct ui_browser *browser) 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); } } static unsigned int hist_browser__refresh(struct ui_browser *browser) { unsigned row = 0; - u16 header_offset = 0; struct rb_node *nd; struct hist_browser *hb = container_of(browser, struct hist_browser, b); - struct hists *hists = hb->hists; - - if (hb->show_headers) { - struct perf_hpp_list *hpp_list = hists->hpp_list; + if (hb->show_headers) hist_browser__show_headers(hb); - header_offset = hpp_list->nr_header_lines; - } ui_browser__hists_init_top(browser); hb->he_selection = NULL; @@ -1926,7 +1876,11 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser) continue; } - percent = hist_entry__get_percent_limit(h); + 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; @@ -1948,7 +1902,7 @@ static unsigned int hist_browser__refresh(struct ui_browser *browser) break; } - return row + header_offset; + return row; } static struct rb_node *hists__filter_entries(struct rb_node *nd, @@ -2149,7 +2103,7 @@ static int hist_browser__fprintf_entry(struct hist_browser *browser, bool first = true; int ret; - if (symbol_conf.use_callchain) { + if (hist_entry__has_callchains(he) && symbol_conf.use_callchain) { folded_sign = hist_entry__folded(he); printed += fprintf(fp, "%c ", folded_sign); } @@ -2224,7 +2178,8 @@ static int hist_browser__fprintf_hierarchy_entry(struct hist_browser *browser, advance_hpp(&hpp, ret); } - printed += fprintf(fp, "%s\n", rtrim(s)); + strim(s); + printed += fprintf(fp, "%s\n", s); if (he->leaf && folded_sign == '-') { printed += hist_browser__fprintf_callchain(browser, he, fp, @@ -2303,6 +2258,7 @@ void hist_browser__init(struct hist_browser *browser, 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; @@ -2334,7 +2290,7 @@ struct hist_browser *hist_browser__new(struct hists *hists) } static struct hist_browser * -perf_evsel_browser__new(struct perf_evsel *evsel, +perf_evsel_browser__new(struct evsel *evsel, struct hist_browser_timer *hbt, struct perf_env *env) { @@ -2343,7 +2299,7 @@ perf_evsel_browser__new(struct perf_evsel *evsel, if (browser) { browser->hbt = hbt; browser->env = env; - browser->title = perf_evsel_browser_title; + browser->title = hists_browser__scnprintf_title; } return browser; } @@ -2363,92 +2319,40 @@ static struct thread *hist_browser__selected_thread(struct hist_browser *browser return browser->he_selection->thread; } +static struct res_sample *hist_browser__selected_res_sample(struct hist_browser *browser) +{ + return browser->he_selection ? browser->he_selection->res_samples : NULL; +} + /* Check whether the browser is for 'top' or 'report' */ static inline bool is_report_browser(void *timer) { return timer == NULL; } -static int perf_evsel_browser_title(struct hist_browser *browser, - char *bf, size_t size) +static int hists_browser__scnprintf_title(struct hist_browser *browser, char *bf, size_t size) { struct hist_browser_timer *hbt = browser->hbt; - struct hists *hists = browser->hists; - char unit; - int printed; - const struct dso *dso = hists->dso_filter; - const struct thread *thread = hists->thread_filter; - int socket_id = hists->socket_filter; - unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE]; - u64 nr_events = hists->stats.total_period; - struct perf_evsel *evsel = hists_to_evsel(hists); - const char *ev_name = perf_evsel__name(evsel); - char buf[512]; - size_t buflen = sizeof(buf); - char ref[30] = " show reference callgraph, "; - bool enable_ref = false; - - if (symbol_conf.filter_relative) { - nr_samples = hists->stats.nr_non_filtered_samples; - nr_events = hists->stats.total_non_filtered_period; - } - - if (perf_evsel__is_group_event(evsel)) { - struct perf_evsel *pos; - - perf_evsel__group_desc(evsel, buf, buflen); - ev_name = buf; - - for_each_group_member(pos, evsel) { - struct hists *pos_hists = evsel__hists(pos); - - if (symbol_conf.filter_relative) { - nr_samples += pos_hists->stats.nr_non_filtered_samples; - nr_events += pos_hists->stats.total_non_filtered_period; - } else { - nr_samples += pos_hists->stats.nr_events[PERF_RECORD_SAMPLE]; - nr_events += pos_hists->stats.total_period; - } - } - } - - if (symbol_conf.show_ref_callgraph && - strstr(ev_name, "call-graph=no")) - enable_ref = true; - nr_samples = convert_unit(nr_samples, &unit); - printed = scnprintf(bf, size, - "Samples: %lu%c of event '%s',%sEvent count (approx.): %" PRIu64, - nr_samples, unit, ev_name, enable_ref ? ref : " ", nr_events); + 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) { - if (hists__has(hists, thread)) { - printed += scnprintf(bf + printed, size - printed, - ", Thread: %s(%d)", - (thread->comm_set ? thread__comm_str(thread) : ""), - thread->tid); - } else { - printed += scnprintf(bf + printed, size - printed, - ", Thread: %s", - (thread->comm_set ? thread__comm_str(thread) : "")); - } - } - if (dso) printed += scnprintf(bf + printed, size - printed, - ", DSO: %s", dso->short_name); - if (socket_id > -1) + " lost: %" PRIu64 "/%" PRIu64, + top->lost, top->lost_total); + printed += scnprintf(bf + printed, size - printed, - ", Processor Socket: %d", socket_id); - if (!is_report_browser(hbt)) { - struct perf_top *top = hbt->arg; + " 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; } @@ -2530,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) { @@ -2550,31 +2454,38 @@ close_file_and_continue: } 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; - int (*fn)(struct hist_browser *browser, struct popup_action *act); }; static int do_annotate(struct hist_browser *browser, struct popup_action *act) { - struct perf_evsel *evsel; + struct evsel *evsel; struct annotation *notes; struct hist_entry *he; int err; - if (!objdump_path && perf_env__lookup_objdump(browser->env)) + 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; - evsel = hists_to_evsel(browser->hists); - err = map_symbol__tui_annotate(&act->ms, evsel, browser->hbt); + 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 @@ -2588,24 +2499,77 @@ do_annotate(struct hist_browser *browser, struct popup_action *act) 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 hist_browser *browser __maybe_unused, - struct popup_action *act, char **optstr, - struct map *map, struct symbol *sym) +add_annotate_opt(struct popup_action *act, char **optstr, + struct map_symbol *ms, + u64 addr) { - if (sym == NULL || map->dso->annotate_warned) + struct dso *dso; + + if (!ms->map || (dso = map__dso(ms->map)) == NULL || dso__annotate_warned(dso)) return 0; - if (asprintf(optstr, "Annotate %s", sym->name) < 0) + if (!ms->sym) + ms->sym = symbol__new_unresolved(addr, ms->map); + + if (ms->sym == NULL || symbol__annotation(ms->sym)->src == NULL) return 0; - act->ms.map = map; - act->ms.sym = sym; + 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; @@ -2620,13 +2584,15 @@ do_zoom_thread(struct hist_browser *browser, struct popup_action *act) 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\"", - thread->comm_set ? thread__comm_str(thread) : "", - thread->tid); + comm_set_str, thread__tid(thread)); } else { ui_helpline__fpush("To zoom out press ESC or ENTER + \"Zoom out of %s thread\"", - thread->comm_set ? thread__comm_str(thread) : ""); + comm_set_str); } browser->hists->thread_filter = thread__get(thread); @@ -2644,20 +2610,19 @@ 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", - browser->hists->thread_filter ? "out of" : "into", - thread->comm_set ? thread__comm_str(thread) : "", - thread->tid); + in_out, comm_set_str, thread__tid(thread)); } else { - ret = asprintf(optstr, "Zoom %s %s thread", - browser->hists->thread_filter ? "out of" : "into", - thread->comm_set ? thread__comm_str(thread) : ""); + ret = asprintf(optstr, "Zoom %s %s thread", in_out, comm_set_str); } if (ret < 0) return 0; @@ -2667,11 +2632,8 @@ add_thread_opt(struct hist_browser *browser, struct popup_action *act, return 1; } -static int -do_zoom_dso(struct hist_browser *browser, struct popup_action *act) +static int hists_browser__zoom_map(struct hist_browser *browser, struct map *map) { - struct map *map = act->ms.map; - if (!hists__has(browser->hists, dso) || map == NULL) return 0; @@ -2681,9 +2643,10 @@ do_zoom_dso(struct hist_browser *browser, struct popup_action *act) 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" : map->dso->short_name); - browser->hists->dso_filter = map->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); } @@ -2694,15 +2657,21 @@ do_zoom_dso(struct hist_browser *browser, struct popup_action *act) } 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", + 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" : map->dso->short_name) < 0) + __map__is_kernel(map) ? "the Kernel" : dso__short_name(map__dso(map))) < 0) return 0; act->ms.map = map; @@ -2710,6 +2679,28 @@ add_dso_opt(struct hist_browser *browser, struct popup_action *act, 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) @@ -2734,39 +2725,77 @@ add_map_opt(struct hist_browser *browser, } static int -do_run_script(struct hist_browser *browser __maybe_unused, +do_run_script(struct hist_browser *browser, struct popup_action *act) { - char script_opt[64]; - memset(script_opt, 0, sizeof(script_opt)); + 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) { - scnprintf(script_opt, sizeof(script_opt), " -c %s ", + n = scnprintf(script_opt, len, " -c %s ", thread__comm_str(act->thread)); } else if (act->ms.sym) { - scnprintf(script_opt, sizeof(script_opt), " -S %s ", + n = scnprintf(script_opt, len, " -S %s ", act->ms.sym->name); } - script_browse(script_opt); + 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 -add_script_opt(struct hist_browser *browser __maybe_unused, - struct popup_action *act, char **optstr, - struct thread *thread, struct symbol *sym) +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]", - thread__comm_str(thread)) < 0) + 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]", - sym->name) < 0) + 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") < 0) + if (asprintf(optstr, "Run scripts for all samples%s", tstr) < 0) return 0; } @@ -2777,6 +2806,53 @@ add_script_opt(struct hist_browser *browser __maybe_unused, } 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) { @@ -2862,7 +2938,7 @@ add_socket_opt(struct hist_browser *browser, struct popup_action *act, 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); if (hb->min_pcnt == 0 && !symbol_conf.report_hierarchy) { hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries; @@ -2882,7 +2958,7 @@ static void hist_browser__update_percent_limit(struct hist_browser *hb, double percent) { struct hist_entry *he; - struct rb_node *nd = rb_first(&hb->hists->entries); + struct rb_node *nd = rb_first_cached(&hb->hists->entries); u64 total = hists__total_period(hb->hists); u64 min_callchain_hits = total * (percent / 100); @@ -2896,7 +2972,7 @@ static void hist_browser__update_percent_limit(struct hist_browser *hb, he->nr_rows = 0; } - if (!he->leaf || !symbol_conf.use_callchain) + if (!he->leaf || !hist_entry__has_callchains(he) || !symbol_conf.use_callchain) goto next; if (callchain_param.mode == CHAIN_GRAPH_REL) { @@ -2920,54 +2996,55 @@ next: } } -static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, - const char *helpline, - bool left_exits, - struct hist_browser_timer *hbt, - float min_pcnt, - struct perf_env *env) +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; + 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 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\n\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 */ - const char report_help[] = HIST_BROWSER_HELP_COMMON + 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"; - const char top_help[] = HIST_BROWSER_HELP_COMMON + "/ 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" @@ -2981,6 +3058,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, /* 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; @@ -2998,15 +3076,19 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, if (symbol_conf.col_width_list_str) perf_hpp__set_user_width(symbol_conf.col_width_list_str); + if (!is_report_browser(hbt)) + browser->b.no_samples_msg = "Collecting samples..."; + while (1) { struct thread *thread = NULL; struct map *map = NULL; - int choice = 0; + int choice; int socked_id = -1; - nr_options = 0; - - key = hist_browser__run(browser, helpline); + 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); @@ -3023,6 +3105,31 @@ 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 (!hists__has(hists, sym)) { ui_browser__warning(&browser->b, delay_secs * 2, @@ -3031,13 +3138,45 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, 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; + } + + 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; + } - actions->ms.map = browser->selection->map; - actions->ms.sym = browser->selection->sym; do_annotate(browser, actions); continue; case 'P': @@ -3047,6 +3186,11 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, 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': verbose = (verbose + 1) % 4; browser->show_dso = verbose > 0; @@ -3089,7 +3233,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, case 'i': /* env->arch is NULL for live-mode (i.e. perf top) */ if (env->arch) - tui__header_window(env); + tui__header_window(evsel__session(evsel)); continue; case 'F': symbol_conf.filter_relative ^= 1; @@ -3148,15 +3292,16 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, continue; } + 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. - * Ditto for thread below. */ 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); @@ -3170,7 +3315,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, if (!is_report_browser(hbt)) { struct perf_top *top = hbt->arg; - perf_evlist__toggle_enable(top->evlist); + evlist__toggle_enable(top->evlist); /* * No need to refresh, resort/decay histogram * entries if we are not collecting samples: @@ -3187,6 +3332,8 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, /* 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; } @@ -3194,34 +3341,37 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, goto skip_annotation; if (sort__mode == SORT_MODE__BRANCH) { - bi = browser->he_selection->branch_info; + + if (browser->he_selection) + bi = browser->he_selection->branch_info; if (bi == NULL) goto skip_annotation; - nr_options += add_annotate_opt(browser, - &actions[nr_options], + nr_options += add_annotate_opt(&actions[nr_options], &options[nr_options], - bi->from.map, - bi->from.sym); - if (bi->to.sym != bi->from.sym) - nr_options += add_annotate_opt(browser, - &actions[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.map, - bi->to.sym); - } else { - nr_options += add_annotate_opt(browser, - &actions[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->map, - browser->selection->sym); + browser->selection, + browser->he_selection->ip); } 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 ? @@ -3258,6 +3408,18 @@ skip_annotation: } 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: @@ -3267,10 +3429,13 @@ skip_scripting: do { struct popup_action *act; - choice = ui__popup_menu(nr_options, options); - if (choice == -1 || choice >= nr_options) + choice = ui__popup_menu(nr_options, options, &key); + if (choice == -1) break; + if (choice == nr_options) + goto do_hotkey; + act = &actions[choice]; key = act->fn(browser, act); } while (key == 1); @@ -3286,9 +3451,9 @@ out: 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_env *env; @@ -3297,13 +3462,13 @@ struct perf_evsel_menu { 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 = 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; @@ -3311,14 +3476,14 @@ 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) { struct hists *pos_hists = evsel__hists(pos); - nr_events += pos_hists->stats.nr_events[PERF_RECORD_SAMPLE]; + nr_events += pos_hists->stats.nr_samples; } } @@ -3327,7 +3492,7 @@ static void perf_evsel_menu__write(struct ui_browser *browser, unit, unit == ' ' ? "" : " ", ev_name); ui_browser__printf(browser, "%s", bf); - nr_events = 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) @@ -3344,12 +3509,13 @@ static void perf_evsel_menu__write(struct ui_browser *browser, 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; + struct evlist *evlist = menu->b.priv; + struct evsel *pos; const char *title = "Available samples"; int delay_secs = hbt ? hbt->refresh : 0; int key; @@ -3363,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; } @@ -3376,32 +3545,32 @@ 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); - key = perf_evsel__hists_browse(pos, nr_events, help, - 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 = perf_evlist__first(evlist); + if (pos->core.node.next == &evlist->core.entries) + pos = evlist__first(evlist); else - pos = perf_evsel__next(pos); + pos = evsel__next(pos); goto browse_hists; case K_UNTAB: - if (pos->node.prev == &evlist->entries) - pos = perf_evlist__last(evlist); + if (pos->core.node.prev == &evlist->core.entries) + pos = evlist__last(evlist); else - pos = perf_evsel__prev(pos); + pos = evsel__prev(pos); goto browse_hists; case K_SWITCH_INPUT_DATA: + case K_RELOAD: case 'q': case CTRL('c'): goto out; @@ -3420,6 +3589,7 @@ browse_hists: case CTRL('c'): goto out; default: + ui_browser__warn_unhandled_hotkey(&menu->b, key, delay_secs, NULL); continue; } } @@ -3432,24 +3602,22 @@ out: 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_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, @@ -3464,38 +3632,54 @@ static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, ui_helpline__push("Press ESC to exit"); evlist__for_each_entry(evlist, pos) { - const char *ev_name = perf_evsel__name(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); } -int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help, - struct hist_browser_timer *hbt, - float min_pcnt, - struct perf_env *env) +static bool evlist__single_entry(struct evlist *evlist) { - int nr_entries = evlist->nr_entries; + int nr_entries = evlist->core.nr_entries; + + if (nr_entries == 1) + return true; -single_entry: - if (nr_entries == 1) { - struct perf_evsel *first = perf_evlist__first(evlist); + if (nr_entries == 2) { + struct evsel *last = evlist__last(evlist); - return perf_evsel__hists_browse(first, nr_entries, help, - false, hbt, min_pcnt, - env); + if (evsel__is_dummy_event(last)) + return true; + } + + return false; +} + +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->core.nr_entries; + + if (evlist__single_entry(evlist)) { +single_entry: { + struct evsel *first = evlist__first(evlist); + + 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; evlist__for_each_entry(evlist, pos) { - if (perf_evsel__is_group_leader(pos)) + if (evsel__is_group_leader(pos)) nr_entries++; } @@ -3503,6 +3687,96 @@ single_entry: 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 index 23d6acb84800..de46f6c56b0e 100644 --- a/tools/perf/ui/browsers/hists.h +++ b/tools/perf/ui/browsers/hists.h @@ -1,8 +1,11 @@ +/* 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; @@ -11,6 +14,7 @@ struct hist_browser { 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; @@ -27,7 +31,8 @@ struct hist_browser { 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); +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 ffa5addf631d..c61ba3174a24 100644 --- a/tools/perf/ui/browsers/map.c +++ b/tools/perf/ui/browsers/map.c @@ -1,17 +1,20 @@ +// 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 "sane_ctype.h" +#include <linux/ctype.h> struct map_browser { struct ui_browser b; @@ -73,7 +76,7 @@ static int map_browser__run(struct map_browser *browser) { int key; - if (ui_browser__show(&browser->b, browser->map->dso->long_name, + 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; @@ -85,8 +88,10 @@ static int map_browser__run(struct map_browser *browser) case '/': 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': @@ -103,7 +108,7 @@ int map__browse(struct map *map) { struct map_browser mb = { .b = { - .entries = &map->dso->symbols[map->type], + .entries = dso__symbols(map__dso(map)), .refresh = ui_browser__rb_tree_refresh, .seek = ui_browser__rb_tree_seek, .write = map_browser__write, diff --git a/tools/perf/ui/browsers/map.h b/tools/perf/ui/browsers/map.h index 2d58e4b3eb6f..0ed7dbb3a373 100644 --- a/tools/perf/ui/browsers/map.h +++ b/tools/perf/ui/browsers/map.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef _PERF_UI_MAP_BROWSER_H_ #define _PERF_UI_MAP_BROWSER_H_ 1 struct map; 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 ad6b6ee3770e..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,149 +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; + } + } + } - ui_browser__write_nstring(browser, sline->line, browser->width); + fclose(fp); + return 0; } -static int script_browser__run(struct perf_script_browser *browser) +/* + * 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(&browser->b, browser->script_name, - "Press 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(&browser->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(&browser->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; - 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; } |
