diff options
Diffstat (limited to 'tools/perf/ui')
| -rw-r--r-- | tools/perf/ui/Build | 19 | ||||
| -rw-r--r-- | tools/perf/ui/browser.c | 10 | ||||
| -rw-r--r-- | tools/perf/ui/browser.h | 5 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/Build | 14 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/annotate-data.c | 377 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/annotate.c | 367 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/header.c | 5 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/hists.c | 170 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/map.c | 4 | ||||
| -rw-r--r-- | tools/perf/ui/browsers/scripts.c | 177 | ||||
| -rw-r--r-- | tools/perf/ui/gtk/annotate.c | 21 | ||||
| -rw-r--r-- | tools/perf/ui/hist.c | 565 | ||||
| -rw-r--r-- | tools/perf/ui/keysyms.c | 44 | ||||
| -rw-r--r-- | tools/perf/ui/keysyms.h | 2 | ||||
| -rw-r--r-- | tools/perf/ui/libslang.h | 4 | ||||
| -rw-r--r-- | tools/perf/ui/stdio/hist.c | 70 | ||||
| -rw-r--r-- | tools/perf/ui/tui/Build | 8 | ||||
| -rw-r--r-- | tools/perf/ui/tui/setup.c | 2 |
18 files changed, 1527 insertions, 337 deletions
diff --git a/tools/perf/ui/Build b/tools/perf/ui/Build index 6b6d7143a37b..6005f813c9e3 100644 --- a/tools/perf/ui/Build +++ b/tools/perf/ui/Build @@ -1,12 +1,13 @@ -perf-y += setup.o -perf-y += helpline.o -perf-y += progress.o -perf-y += util.o -perf-y += hist.o -perf-y += stdio/hist.o +perf-ui-y += setup.o +perf-ui-y += helpline.o +perf-ui-y += progress.o +perf-ui-y += util.o +perf-ui-y += hist.o +perf-ui-y += stdio/hist.o CFLAGS_setup.o += -DLIBDIR="BUILD_STR($(LIBDIR))" -perf-$(CONFIG_SLANG) += browser.o -perf-$(CONFIG_SLANG) += browsers/ -perf-$(CONFIG_SLANG) += tui/ +perf-ui-$(CONFIG_SLANG) += browser.o +perf-ui-$(CONFIG_SLANG) += keysyms.o +perf-ui-$(CONFIG_SLANG) += browsers/ +perf-ui-$(CONFIG_SLANG) += tui/ diff --git a/tools/perf/ui/browser.c b/tools/perf/ui/browser.c index 19503e838738..dc88427b4ae5 100644 --- a/tools/perf/ui/browser.c +++ b/tools/perf/ui/browser.c @@ -233,6 +233,14 @@ int ui_browser__warning(struct ui_browser *browser, int timeout, return key; } +int ui_browser__warn_unhandled_hotkey(struct ui_browser *browser, int key, int timeout, const char *help) +{ + char kname[32]; + + key_name(key, kname, sizeof(kname)); + return ui_browser__warning(browser, timeout, "\n'%s' key not associated%s!\n", kname, help ?: ""); +} + int ui_browser__help_window(struct ui_browser *browser, const char *text) { int key; @@ -451,6 +459,8 @@ int ui_browser__run(struct ui_browser *browser, int delay_secs) goto out; if (browser->horiz_scroll != 0) --browser->horiz_scroll; + else + goto out; break; case K_PGDN: case ' ': diff --git a/tools/perf/ui/browser.h b/tools/perf/ui/browser.h index 6e98d5f8f71c..9d4404f9b87f 100644 --- a/tools/perf/ui/browser.h +++ b/tools/perf/ui/browser.h @@ -66,12 +66,13 @@ void __ui_browser__vline(struct ui_browser *browser, unsigned int column, int ui_browser__warning(struct ui_browser *browser, int timeout, const char *format, ...); +int ui_browser__warn_unhandled_hotkey(struct ui_browser *browser, int key, int timeout, const char *help); int ui_browser__help_window(struct ui_browser *browser, const char *text); bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text); int ui_browser__input_window(const char *title, const char *text, char *input, const char *exit_msg, int delay_sec); -struct perf_env; -int tui__header_window(struct perf_env *env); +struct perf_session; +int tui__header_window(struct perf_session *session); void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence); unsigned int ui_browser__argv_refresh(struct ui_browser *browser); diff --git a/tools/perf/ui/browsers/Build b/tools/perf/ui/browsers/Build index 2608b5da3167..a07489e44765 100644 --- a/tools/perf/ui/browsers/Build +++ b/tools/perf/ui/browsers/Build @@ -1,7 +1,7 @@ -perf-y += annotate.o -perf-y += annotate-data.o -perf-y += hists.o -perf-y += map.o -perf-y += scripts.o -perf-y += header.o -perf-y += res_sample.o +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 index 8d6bf08d371d..aa8c89fe2e82 100644 --- a/tools/perf/ui/browsers/annotate-data.c +++ b/tools/perf/ui/browsers/annotate-data.c @@ -14,17 +14,26 @@ #include "util/evlist.h" #include "util/sort.h" -struct annotated_data_browser { - struct ui_browser b; - struct list_head entries; - int nr_events; -}; +#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; - int indent; + 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) @@ -51,23 +60,28 @@ static int get_member_overhead(struct annotated_data_type *adt, 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]; - k = evsel__group_idx(evsel); - update_hist_entry(&entry->hists[k], &h->addr[offset]); + 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; - int nr_entries = 0; + struct list_head *parent_list; entry = zalloc(sizeof(*entry)); if (entry == NULL) @@ -80,36 +94,60 @@ static int add_child_entries(struct annotated_data_browser *browser, } entry->data = member; + entry->parent = parent; entry->indent = indent; if (get_member_overhead(adt, entry, evsel) < 0) { free(entry); return -1; } - list_add_tail(&entry->node, &browser->entries); - nr_entries++; + INIT_LIST_HEAD(&entry->children); + if (parent) + parent_list = &parent->children; + else + parent_list = &browser->entries; - list_for_each_entry(pos, &member->children, node) { - int nr = add_child_entries(browser, adt, pos, evsel, indent + 1); + 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; - - nr_entries += nr; } /* add an entry for the closing bracket ("}") */ if (!list_empty(&member->children)) { - entry = zalloc(sizeof(*entry)); - if (entry == NULL) + struct browser_entry *bracket; + + bracket = zalloc(sizeof(*bracket)); + if (bracket == NULL) return -1; - entry->indent = indent; - list_add_tail(&entry->node, &browser->entries); - nr_entries++; + 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); } - return nr_entries; + /* 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) @@ -119,9 +157,12 @@ static int annotated_data_browser__collect_entries(struct annotated_data_browser 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 = add_child_entries(browser, adt, &adt->self, - evsel, /*indent=*/0); + browser->b.nr_entries = count_visible_entries(browser); return 0; } @@ -136,9 +177,158 @@ static void annotated_data_browser__delete_entries(struct annotated_data_browser } } +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) { - return ui_browser__list_head_refresh(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) @@ -167,7 +357,7 @@ static int browser__show(struct ui_browser *uib) strcpy(title, "Percent"); ui_browser__printf(uib, "%*s %10s %10s %10s %s", - 11 * (browser->nr_events - 1), "", + 2 + 11 * (browser->nr_events - 1), "", title, "Offset", "Size", "Field"); ui_browser__write_nstring(uib, "", uib->width); return 0; @@ -203,12 +393,13 @@ static void browser__write(struct ui_browser *uib, void *entry, int row) 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) { - bool current = ui_browser__is_current_entry(uib, row); - /* 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, ""); @@ -216,31 +407,113 @@ static void browser__write(struct ui_browser *uib, void *entry, int row) 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]; - int idx = evsel__group_idx(evsel); - browser__write_overhead(uib, h, &be->hists[idx], row); + 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, " %10d %10d %s%s", + ui_browser__printf(uib, " %#10x %#10x %s%s", member->offset, member->size, member->type_name, - list_empty(&member->children) ? ";" : " {"); + list_empty(&member->children) || be->folded? ";" : " {"); } else { - ui_browser__printf(uib, " %10d %10d %*s%s\t%s%s", + 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) ? ";" : " {"); + 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) @@ -265,8 +538,18 @@ static int annotated_data_browser__run(struct annotated_data_browser *browser, "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 '>': @@ -275,6 +558,7 @@ static int annotated_data_browser__run(struct annotated_data_browser *browser, case CTRL('c'): goto out; default: + ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs, ", use 'h'/F1 to see actions"); continue; } } @@ -289,7 +573,7 @@ int hist_entry__annotate_data_tui(struct hist_entry *he, struct evsel *evsel, struct annotated_data_browser browser = { .b = { .refresh = browser__refresh, - .seek = ui_browser__list_head_seek, + .seek = browser__seek, .write = browser__write, .priv = he, .extra_title_lines = 1, @@ -300,13 +584,30 @@ int hist_entry__annotate_data_tui(struct hist_entry *he, struct evsel *evsel, ui_helpline__push("Press ESC to exit"); - if (evsel__is_group_event(evsel)) - browser.nr_events = evsel->core.nr_members; + 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) - ret = annotated_data_browser__run(&browser, evsel, hbt); + 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 ea986430241e..36aca8d6d003 100644 --- a/tools/perf/ui/browsers/annotate.c +++ b/tools/perf/ui/browsers/annotate.c @@ -4,7 +4,9 @@ #include "../ui.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" @@ -12,7 +14,9 @@ #include "../../util/symbol.h" #include "../../util/evsel.h" #include "../../util/evlist.h" +#include "../../util/thread.h" #include <inttypes.h> +#include <linux/err.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/zalloc.h> @@ -27,10 +31,31 @@ struct annotate_browser { struct rb_node *curr_hot; struct annotation_line *selection; struct arch *arch; + /* + * perf top can delete hist_entry anytime. Callers should make sure + * its lifetime. + */ + struct hist_entry *he; + struct debuginfo *dbg; + struct evsel *evsel; + struct hashmap *type_hash; bool searching_backwards; char search_bf[128]; }; +/* A copy of target hist_entry for perf top. */ +static struct hist_entry annotate_he; + +static size_t type_hash(long key, void *ctx __maybe_unused) +{ + return key; +} + +static bool type_equal(long key1, long key2, void *ctx __maybe_unused) +{ + return key1 == key2; +} + static inline struct annotation *browser__annotation(struct ui_browser *browser) { struct map_symbol *ms = browser->priv; @@ -107,12 +132,21 @@ static void annotate_browser__write(struct ui_browser *browser, void *entry, int .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) ops.width += 1; - annotation_line__write(al, notes, &ops); + if (!IS_ERR_OR_NULL(ab->type_hash)) + apd.type_hash = ab->type_hash; + + annotation_line__write(al, notes, &ops, &apd); if (ops.current_entry) ab->selection = al; @@ -156,6 +190,7 @@ static void annotate_browser__draw_current_jump(struct ui_browser *browser) struct symbol *sym = ms->sym; 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; @@ -205,13 +240,13 @@ static void annotate_browser__draw_current_jump(struct ui_browser *browser) ui_browser__set_color(browser, HE_COLORSET_JUMP_ARROWS); __ui_browser__line_arrow(browser, - pcnt_width + 2 + notes->src->widths.addr + width, + 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, + pcnt_width + 3 + notes->src->widths.addr + width + cntr_width, from - diff, diff, to > from); } } @@ -344,6 +379,23 @@ static void annotate_browser__calc_percent(struct annotate_browser *browser, browser->curr_hot = rb_last(&browser->entries); } +static struct annotation_line *annotate_browser__find_new_asm_line( + struct annotate_browser *browser, + int idx_asm) +{ + struct annotation_line *al; + struct list_head *head = browser->b.entries; + + /* find an annotation line in the new list with the same idx_asm */ + list_for_each_entry(al, head, node) { + if (al->idx_asm == idx_asm) + return al; + } + + /* There are no asm lines */ + return NULL; +} + static struct annotation_line *annotate_browser__find_next_asm_line( struct annotate_browser *browser, struct annotation_line *al) @@ -367,7 +419,31 @@ static struct annotation_line *annotate_browser__find_next_asm_line( return NULL; } -static bool annotate_browser__toggle_source(struct annotate_browser *browser) +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; @@ -376,6 +452,39 @@ static bool annotate_browser__toggle_source(struct annotate_browser *browser) browser->b.seek(&browser->b, offset, SEEK_CUR); 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_opts.hide_src_code) { if (al->idx_asm < offset) offset = al->idx; @@ -405,6 +514,9 @@ static bool annotate_browser__toggle_source(struct annotate_browser *browser) browser->b.index = al->idx_asm; } + if (annotate_opts.hide_src_code_on_title) + annotate_opts.hide_src_code_on_title = false; + return true; } @@ -437,9 +549,24 @@ static void ui_browser__init_asm_mode(struct ui_browser *browser) static int sym_title(struct symbol *sym, struct map *map, char *title, size_t sz, int percent_type) { - return snprintf(title, sz, "%s %s [Percent: %s]", sym->name, + return snprintf(title, sz, "%s %s [Percent: %s] %s", sym->name, dso__long_name(map__dso(map)), - percent_type_str(percent_type)); + 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); } /* @@ -458,7 +585,6 @@ static bool annotate_browser__callq(struct annotate_browser *browser, struct map_symbol *ms = browser->b.priv, target_ms; struct disasm_line *dl = disasm_line(browser->selection); struct annotation *notes; - char title[SYM_TITLE_MAX_SIZE]; if (!dl->ops.target.sym) { ui_helpline__puts("The called function was not found."); @@ -479,9 +605,14 @@ static bool annotate_browser__callq(struct annotate_browser *browser, target_ms.map = ms->map; target_ms.sym = dl->ops.target.sym; annotation__unlock(notes); - symbol__tui_annotate(&target_ms, evsel, hbt); - sym_title(ms->sym, ms->map, title, sizeof(title), annotate_opts.percent_type); - ui_browser__show_title(&browser->b, title); + __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; } @@ -653,20 +784,12 @@ bool annotate_browser__continue_search_reverse(struct annotate_browser *browser, return __annotate_browser__search_reverse(browser); } -static int annotate_browser__show(struct ui_browser *browser, char *title, const char *help) +static int annotate_browser__show(struct annotate_browser *browser, char *title, const char *help) { - struct map_symbol *ms = browser->priv; - struct symbol *sym = ms->sym; - char symbol_dso[SYM_TITLE_MAX_SIZE]; - - if (ui_browser__show(browser, title, help) < 0) + if (ui_browser__show(&browser->b, title, help) < 0) return -1; - sym_title(sym, ms->map, symbol_dso, sizeof(symbol_dso), annotate_opts.percent_type); - - ui_browser__gotorc_title(browser, 0, 0); - ui_browser__set_color(browser, HE_COLORSET_ROOT); - ui_browser__write_nstring(browser, symbol_dso, browser->width + 1); + annotate_browser__show_function_title(browser); return 0; } @@ -703,6 +826,54 @@ switch_percent_type(struct annotation_options *opts, bool base) } } +static int annotate__scnprintf_title(struct hists *hists, char *bf, size_t size) +{ + int printed = hists__scnprintf_title(hists, bf, size); + + if (!annotate_opts.hide_src_code_on_title) { + printed += scnprintf(bf + printed, size - printed, " [source: %s]", + annotate_opts.hide_src_code ? "OFF" : "On"); + } + + return printed; +} + +static void annotate_browser__debuginfo_warning(struct annotate_browser *browser) +{ + struct map_symbol *ms = browser->b.priv; + struct dso *dso = map__dso(ms->map); + + if (browser->dbg == NULL && annotate_opts.code_with_type && + !dso__debuginfo_warned(dso)) { + ui__warning("DWARF debuginfo not found.\n\n" + "Data-type in this DSO will not be displayed.\n" + "Please make sure to have debug information."); + dso__set_debuginfo_warned(dso); + } +} + +static s64 annotate_browser__curr_hot_offset(struct annotate_browser *browser) +{ + struct annotation_line *al = NULL; + + if (browser->curr_hot) + al = rb_entry(browser->curr_hot, struct annotation_line, rb_node); + + return al ? al->offset : 0; +} + +static void annotate_browser__symbol_annotate_error(struct annotate_browser *browser, int err) +{ + struct map_symbol *ms = browser->b.priv; + struct symbol *sym = ms->sym; + struct dso *dso = map__dso(ms->map); + char msg[BUFSIZ]; + + dso__set_annotate_warned(dso); + symbol__strerror_disassemble(ms, err, msg, sizeof(msg)); + ui__error("Couldn't annotate %s:\n%s", sym->name, msg); +} + static int annotate_browser__run(struct annotate_browser *browser, struct evsel *evsel, struct hist_browser_timer *hbt) @@ -714,15 +885,21 @@ static int annotate_browser__run(struct annotate_browser *browser, 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; - hists__scnprintf_title(hists, title, sizeof(title)); - if (annotate_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; @@ -730,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); @@ -750,9 +931,9 @@ static int annotate_browser__run(struct annotate_browser *browser, hbt->timer(hbt->arg); if (delay_secs != 0) { - symbol__annotate_decay_histogram(sym, evsel->core.idx); - hists__scnprintf_title(hists, title, sizeof(title)); - annotate_browser__show(&browser->b, title, help); + symbol__annotate_decay_histogram(sym, evsel); + annotate__scnprintf_title(hists, title, sizeof(title)); + annotate_browser__show(browser, title, help); } continue; case K_TAB: @@ -796,12 +977,14 @@ static int annotate_browser__run(struct annotate_browser *browser, "r Run available scripts\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"); + "f Toggle showing offsets to full address\n" + "T Toggle data type display\n"); continue; case 'r': script_browse(NULL, NULL); - annotate_browser__show(&browser->b, title, help); + annotate_browser__show(browser, title, help); continue; case 'k': annotate_opts.show_linenr = !annotate_opts.show_linenr; @@ -812,10 +995,24 @@ static int annotate_browser__run(struct annotate_browser *browser, 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_opts.use_offset = !annotate_opts.use_offset; annotation__update_column_widths(notes); @@ -879,7 +1076,7 @@ show_sup_ins: continue; } case 'P': - map_symbol__annotation_dump(ms, evsel); + map_symbol__annotation_dump(ms, evsel, browser->he); continue; case 't': if (symbol_conf.show_total_period) { @@ -901,12 +1098,31 @@ show_sup_ins: case 'p': case 'b': switch_percent_type(&annotate_opts, key == 'b'); - hists__scnprintf_title(hists, title, sizeof(title)); - annotate_browser__show(&browser->b, title, help); + 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 '>': @@ -915,6 +1131,7 @@ show_sup_ins: case CTRL('c'): goto out; default: + ui_browser__warn_unhandled_hotkey(&browser->b, key, delay_secs, ", use 'h'/F1 to see actions"); continue; } @@ -923,28 +1140,24 @@ 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 evsel *evsel, - struct hist_browser_timer *hbt) -{ - return symbol__tui_annotate(ms, evsel, hbt); -} - int hist_entry__tui_annotate(struct hist_entry *he, struct evsel *evsel, - struct hist_browser_timer *hbt) + 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); + return __hist_entry__tui_annotate(he, &he->ms, evsel, hbt, al_addr); } -int symbol__tui_annotate(struct map_symbol *ms, struct evsel *evsel, - struct hist_browser_timer *hbt) +int __hist_entry__tui_annotate(struct hist_entry *he, struct map_symbol *ms, + struct evsel *evsel, + struct hist_browser_timer *hbt, u64 al_addr) { struct symbol *sym = ms->sym; struct annotation *notes = symbol__annotation(sym); @@ -958,6 +1171,8 @@ int symbol__tui_annotate(struct map_symbol *ms, struct evsel *evsel, .priv = ms, .use_navkeypressed = true, }, + .he = he, + .evsel = evsel, }; struct dso *dso; int ret = -1, err; @@ -973,28 +1188,82 @@ int symbol__tui_annotate(struct map_symbol *ms, struct evsel *evsel, if (not_annotated || !sym->annotate2) { err = symbol__annotate2(ms, evsel, &browser.arch); if (err) { - 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); + annotate_browser__symbol_annotate_error(&browser, err); + return -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; } } + /* 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"); + if (annotate_opts.code_with_type) { + browser.dbg = dso__debuginfo(dso); + browser.type_hash = hashmap__new(type_hash, type_equal, /*ctx=*/NULL); + } + 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.entries = ¬es->src->source; browser.b.width += 18; /* Percentage */ if (annotate_opts.hide_src_code) ui_browser__init_asm_mode(&browser.b); + /* + * 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); + + browser.selection = al; + } + ret = annotate_browser__run(&browser, evsel, hbt); - if(not_annotated) + debuginfo__delete(browser.dbg); + + if (!IS_ERR_OR_NULL(browser.type_hash)) { + struct hashmap_entry *cur; + size_t bkt; + + hashmap__for_each_entry(browser.type_hash, cur, bkt) + zfree(&cur->pvalue); + hashmap__free(browser.type_hash); + } + + if (not_annotated && !notes->src->tried_source) annotated_source__purge(notes->src); + if (hbt != NULL && he != &annotate_he) { + thread__zput(annotate_he.thread); + map_symbol__exit(&annotate_he.ms); + } + return ret; } diff --git a/tools/perf/ui/browsers/header.c b/tools/perf/ui/browsers/header.c index 57e6e4332f74..5b5ca32e3eef 100644 --- a/tools/perf/ui/browsers/header.c +++ b/tools/perf/ui/browsers/header.c @@ -69,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; } @@ -92,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 b7219df51236..08fecbe28a52 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -1226,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; \ @@ -1238,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; \ @@ -1262,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 = @@ -1289,6 +1310,18 @@ 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(); } @@ -1679,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 = { @@ -1705,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; @@ -1716,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, ""); @@ -1746,7 +1783,7 @@ 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 = strim(dummy_hpp.buf); @@ -1765,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_title(&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) @@ -2415,7 +2456,6 @@ close_file_and_continue: struct popup_action { unsigned long time; struct thread *thread; - struct evsel *evsel; int (*fn)(struct hist_browser *browser, struct popup_action *act); struct map_symbol ms; int socket; @@ -2444,8 +2484,8 @@ do_annotate(struct hist_browser *browser, struct popup_action *act) else evsel = hists_to_evsel(browser->hists); - err = map_symbol__tui_annotate(&act->ms, evsel, browser->hbt); 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 @@ -2482,8 +2522,7 @@ static struct symbol *symbol__new_unresolved(u64 addr, struct map *map) } static int -add_annotate_opt(struct hist_browser *browser __maybe_unused, - struct popup_action *act, char **optstr, +add_annotate_opt(struct popup_action *act, char **optstr, struct map_symbol *ms, u64 addr) { @@ -2507,18 +2546,17 @@ add_annotate_opt(struct hist_browser *browser __maybe_unused, } static int -do_annotate_type(struct hist_browser *browser, struct popup_action *act) +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, act->evsel, browser->hbt); + 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 hist_browser *browser, - struct popup_action *act, char **optstr, +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) @@ -2527,7 +2565,6 @@ add_annotate_type_opt(struct hist_browser *browser, if (asprintf(optstr, "Annotate type %s", he->mem_type->self.type_name) < 0) return 0; - act->evsel = hists_to_evsel(browser->hists); act->fn = do_annotate_type; return 1; } @@ -2688,7 +2725,7 @@ 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; @@ -2727,27 +2764,26 @@ do_run_script(struct hist_browser *browser __maybe_unused, n += snprintf(script_opt + n, len - n, " --time %s,%s", start, end); } - script_browse(script_opt, act->evsel); + script_browse(script_opt, hists_to_evsel(browser->hists)); free(script_opt); return 0; } static int -do_res_sample_script(struct hist_browser *browser __maybe_unused, +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, act->evsel, act->rstype); + 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 hist_browser *browser __maybe_unused, - struct popup_action *act, char **optstr, +add_script_opt_2(struct popup_action *act, char **optstr, struct thread *thread, struct symbol *sym, - struct evsel *evsel, const char *tstr) + const char *tstr) { if (thread) { @@ -2765,7 +2801,6 @@ add_script_opt_2(struct hist_browser *browser __maybe_unused, act->thread = thread; act->ms.sym = sym; - act->evsel = evsel; act->fn = do_run_script; return 1; } @@ -2773,13 +2808,12 @@ add_script_opt_2(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, - struct evsel *evsel) + struct thread *thread, struct symbol *sym) { int n, j; struct hist_entry *he; - n = add_script_opt_2(browser, act, optstr, thread, sym, evsel, ""); + n = add_script_opt_2(act, optstr, thread, sym, ""); he = hist_browser__selected_entry(browser); if (sort_order && strstr(sort_order, "time")) { @@ -2793,8 +2827,7 @@ add_script_opt(struct hist_browser *browser, j += sprintf(tstr + j, "-"); timestamp__scnprintf_usec(he->time + symbol_conf.time_quantum, tstr + j, sizeof tstr - j); - n += add_script_opt_2(browser, act, optstr, thread, sym, - evsel, tstr); + n += add_script_opt_2(act, optstr, thread, sym, tstr); act->time = he->time; } return n; @@ -2804,7 +2837,6 @@ static int add_res_sample_opt(struct hist_browser *browser __maybe_unused, struct popup_action *act, char **optstr, struct res_sample *res_sample, - struct evsel *evsel, enum rstype type) { if (!res_sample) @@ -2816,7 +2848,6 @@ add_res_sample_opt(struct hist_browser *browser __maybe_unused, return 0; act->fn = do_res_sample_script; - act->evsel = evsel; act->rstype = type; return 1; } @@ -3202,7 +3233,7 @@ do_hotkey: // key came straight from options ui__popup_menu() 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; @@ -3267,10 +3298,10 @@ do_hotkey: // key came straight from options ui__popup_menu() /* * 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); @@ -3301,6 +3332,8 @@ do_hotkey: // key came straight from options ui__popup_menu() /* 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; } @@ -3315,27 +3348,23 @@ do_hotkey: // key came straight from options ui__popup_menu() 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.ms, bi->from.al_addr); if (bi->to.ms.sym != bi->from.ms.sym) - nr_options += add_annotate_opt(browser, - &actions[nr_options], + nr_options += add_annotate_opt(&actions[nr_options], &options[nr_options], &bi->to.ms, bi->to.al_addr); } else if (browser->he_selection) { - nr_options += add_annotate_opt(browser, - &actions[nr_options], + nr_options += add_annotate_opt(&actions[nr_options], &options[nr_options], browser->selection, browser->he_selection->ip); } skip_annotation: - nr_options += add_annotate_type_opt(browser, - &actions[nr_options], + nr_options += add_annotate_type_opt(&actions[nr_options], &options[nr_options], browser->he_selection); nr_options += add_thread_opt(browser, &actions[nr_options], @@ -3359,7 +3388,7 @@ skip_annotation: nr_options += add_script_opt(browser, &actions[nr_options], &options[nr_options], - thread, NULL, evsel); + thread, NULL); } /* * Note that browser->selection != NULL @@ -3374,24 +3403,23 @@ skip_annotation: nr_options += add_script_opt(browser, &actions[nr_options], &options[nr_options], - NULL, browser->selection->sym, - evsel); + NULL, browser->selection->sym); } } nr_options += add_script_opt(browser, &actions[nr_options], - &options[nr_options], NULL, NULL, evsel); + &options[nr_options], NULL, NULL); nr_options += add_res_sample_opt(browser, &actions[nr_options], &options[nr_options], hist_browser__selected_res_sample(browser), - evsel, A_NORMAL); + A_NORMAL); nr_options += add_res_sample_opt(browser, &actions[nr_options], &options[nr_options], hist_browser__selected_res_sample(browser), - evsel, A_ASM); + A_ASM); nr_options += add_res_sample_opt(browser, &actions[nr_options], &options[nr_options], hist_browser__selected_res_sample(browser), - evsel, A_SOURCE); + A_SOURCE); nr_options += add_switch_opt(browser, &actions[nr_options], &options[nr_options]); skip_scripting: @@ -3561,6 +3589,7 @@ browse_hists: case CTRL('c'): goto out; default: + ui_browser__warn_unhandled_hotkey(&menu->b, key, delay_secs, NULL); continue; } } @@ -3684,8 +3713,10 @@ int block_hists_tui_browse(struct block_hist *bh, struct evsel *evsel, struct hist_browser *browser; int key = -1; struct popup_action action; + char *br_cntr_text = NULL; static const char help[] = - " q Quit \n"; + " q/ESC Quit \n" + " B Branch counter abbr list (Optional)\n"; browser = hist_browser__new(hists); if (!browser) @@ -3703,11 +3734,15 @@ int block_hists_tui_browse(struct block_hist *bh, struct evsel *evsel, 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); @@ -3723,12 +3758,25 @@ int block_hists_tui_browse(struct block_hist *bh, struct evsel *evsel, 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: - break; + 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/map.c b/tools/perf/ui/browsers/map.c index fba55175a935..c61ba3174a24 100644 --- a/tools/perf/ui/browsers/map.c +++ b/tools/perf/ui/browsers/map.c @@ -88,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': diff --git a/tools/perf/ui/browsers/scripts.c b/tools/perf/ui/browsers/scripts.c index e437d7889de6..1e8c2c2f952d 100644 --- a/tools/perf/ui/browsers/scripts.c +++ b/tools/perf/ui/browsers/scripts.c @@ -1,16 +1,18 @@ // SPDX-License-Identifier: GPL-2.0 -#include "../../builtin.h" -#include "../../perf.h" #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 "../libslang.h" #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 @@ -78,6 +80,177 @@ static int scripts_config(const char *var, const char *value, void *data) } /* + * 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) +{ + 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; + } + + 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; + } + } + } + + fclose(fp); + return 0; +} + +/* + * 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) +{ + 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 ((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); + } + + closedir(scripts_dir); + perf_session__delete(session); + return i; +} + +/* * 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. diff --git a/tools/perf/ui/gtk/annotate.c b/tools/perf/ui/gtk/annotate.c index 93ce3d47e47e..8920e298420a 100644 --- a/tools/perf/ui/gtk/annotate.c +++ b/tools/perf/ui/gtk/annotate.c @@ -3,6 +3,7 @@ #include "util/sort.h" #include "util/debug.h" #include "util/annotate.h" +#include "util/evlist.h" #include "util/evsel.h" #include "util/map.h" #include "util/dso.h" @@ -26,7 +27,7 @@ static const char *const col_names[] = { }; static int perf_gtk__get_percent(char *buf, size_t size, struct symbol *sym, - struct disasm_line *dl, int evidx) + struct disasm_line *dl, const struct evsel *evsel) { struct annotation *notes; struct sym_hist *symhist; @@ -42,8 +43,8 @@ static int perf_gtk__get_percent(char *buf, size_t size, struct symbol *sym, return 0; notes = symbol__annotation(sym); - symhist = annotation__histogram(notes, evidx); - entry = annotated_source__hist_entry(notes->src, evidx, dl->al.offset); + symhist = annotation__histogram(notes, evsel); + entry = annotated_source__hist_entry(notes->src, evsel, dl->al.offset); if (entry) nr_samples = entry->nr_samples; @@ -139,16 +140,17 @@ static int perf_gtk__annotate_symbol(GtkWidget *window, struct map_symbol *ms, gtk_list_store_append(store, &iter); if (evsel__is_group_event(evsel)) { - for (i = 0; i < evsel->core.nr_members; i++) { + struct evsel *cur_evsel; + + for_each_group_evsel(cur_evsel, evsel__leader(evsel)) { ret += perf_gtk__get_percent(s + ret, sizeof(s) - ret, sym, pos, - evsel->core.idx + i); + cur_evsel); ret += scnprintf(s + ret, sizeof(s) - ret, " "); } } else { - ret = perf_gtk__get_percent(s, sizeof(s), sym, pos, - evsel->core.idx); + ret = perf_gtk__get_percent(s, sizeof(s), sym, pos, evsel); } if (ret) @@ -180,13 +182,14 @@ static int symbol__gtk_annotate(struct map_symbol *ms, struct evsel *evsel, GtkWidget *tab_label; int err; - if (dso->annotate_warned) + if (dso__annotate_warned(dso)) return -1; err = symbol__annotate(ms, evsel, NULL); if (err) { char msg[BUFSIZ]; - dso->annotate_warned = true; + + dso__set_annotate_warned(dso); symbol__strerror_disassemble(ms, err, msg, sizeof(msg)); ui__error("Couldn't annotate %s: %s\n", sym->name, msg); return -1; diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c index 685ba2a54fd8..e58327595d37 100644 --- a/tools/perf/ui/hist.c +++ b/tools/perf/ui/hist.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include <errno.h> #include <inttypes.h> #include <math.h> #include <stdlib.h> @@ -11,6 +12,8 @@ #include "../util/sort.h" #include "../util/evsel.h" #include "../util/evlist.h" +#include "../util/mem-events.h" +#include "../util/string2.h" #include "../util/thread.h" #include "../util/util.h" @@ -23,99 +26,90 @@ __ret; \ }) +static int __hpp__fmt_print(struct perf_hpp *hpp, struct hists *hists, u64 val, + int nr_samples, const char *fmt, int len, + hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype) +{ + if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY) { + double percent = 0.0; + u64 total = fmtype == PERF_HPP_FMT_TYPE__PERCENT ? hists__total_period(hists) : + hists__total_latency(hists); + + if (total) + percent = 100.0 * val / total; + + return hpp__call_print_fn(hpp, print_fn, fmt, len, percent); + } + + if (fmtype == PERF_HPP_FMT_TYPE__AVERAGE) { + double avg = nr_samples ? (1.0 * val / nr_samples) : 0; + + return hpp__call_print_fn(hpp, print_fn, fmt, len, avg); + } + + return hpp__call_print_fn(hpp, print_fn, fmt, len, val); +} + +struct hpp_fmt_value { + struct hists *hists; + u64 val; + int samples; +}; + static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, hpp_field_fn get_field, const char *fmt, int len, hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype) { - int ret; + int ret = 0; struct hists *hists = he->hists; struct evsel *evsel = hists_to_evsel(hists); + struct evsel *pos; char *buf = hpp->buf; size_t size = hpp->size; + int i = 0, nr_members = 1; + struct hpp_fmt_value *values; - if (fmtype == PERF_HPP_FMT_TYPE__PERCENT) { - double percent = 0.0; - u64 total = hists__total_period(hists); - - if (total) - percent = 100.0 * get_field(he) / total; - - ret = hpp__call_print_fn(hpp, print_fn, fmt, len, percent); - } else if (fmtype == PERF_HPP_FMT_TYPE__AVERAGE) { - double average = 0; + if (evsel__is_group_event(evsel)) + nr_members = evsel->core.nr_members; - if (he->stat.nr_events) - average = 1.0 * get_field(he) / he->stat.nr_events; + values = calloc(nr_members, sizeof(*values)); + if (values == NULL) + return 0; - ret = hpp__call_print_fn(hpp, print_fn, fmt, len, average); - } else { - ret = hpp__call_print_fn(hpp, print_fn, fmt, len, get_field(he)); - } + values[0].hists = evsel__hists(evsel); + values[0].val = get_field(he); + values[0].samples = he->stat.nr_events; if (evsel__is_group_event(evsel)) { - int prev_idx, idx_delta; struct hist_entry *pair; - int nr_members = evsel->core.nr_members; - prev_idx = evsel__group_idx(evsel); + for_each_group_member(pos, evsel) + values[++i].hists = evsel__hists(pos); list_for_each_entry(pair, &he->pairs.head, pairs.node) { - u64 period = get_field(pair); - u64 total = hists__total_period(pair->hists); - int nr_samples = pair->stat.nr_events; + for (i = 0; i < nr_members; i++) { + if (values[i].hists != pair->hists) + continue; - if (!total) - continue; - - evsel = hists_to_evsel(pair->hists); - idx_delta = evsel__group_idx(evsel) - prev_idx - 1; - - while (idx_delta--) { - /* - * zero-fill group members in the middle which - * have no sample - */ - if (fmtype != PERF_HPP_FMT_TYPE__RAW) { - ret += hpp__call_print_fn(hpp, print_fn, - fmt, len, 0.0); - } else { - ret += hpp__call_print_fn(hpp, print_fn, - fmt, len, 0ULL); - } - } - - if (fmtype == PERF_HPP_FMT_TYPE__PERCENT) { - ret += hpp__call_print_fn(hpp, print_fn, fmt, len, - 100.0 * period / total); - } else if (fmtype == PERF_HPP_FMT_TYPE__AVERAGE) { - double avg = nr_samples ? (period / nr_samples) : 0; - - ret += hpp__call_print_fn(hpp, print_fn, fmt, - len, avg); - } else { - ret += hpp__call_print_fn(hpp, print_fn, fmt, - len, period); + values[i].val = get_field(pair); + values[i].samples = pair->stat.nr_events; + break; } - - prev_idx = evsel__group_idx(evsel); } + } - idx_delta = nr_members - prev_idx - 1; - - while (idx_delta--) { - /* - * zero-fill group members at last which have no sample - */ - if (fmtype != PERF_HPP_FMT_TYPE__RAW) { - ret += hpp__call_print_fn(hpp, print_fn, - fmt, len, 0.0); - } else { - ret += hpp__call_print_fn(hpp, print_fn, - fmt, len, 0ULL); - } - } + for (i = 0; i < nr_members; i++) { + if (symbol_conf.skip_empty && + values[i].hists->stats.nr_samples == 0) + continue; + + ret += __hpp__fmt_print(hpp, values[i].hists, values[i].val, + values[i].samples, fmt, len, + print_fn, fmtype); } + free(values); + /* * Restore original buf and size as it's where caller expects * the result will be saved. @@ -131,14 +125,14 @@ int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, const char *fmtstr, hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype) { - int len = fmt->user_len ?: fmt->len; + int len = max(fmt->user_len ?: fmt->len, (int)strlen(fmt->name)); if (symbol_conf.field_sep) { return __hpp__fmt(hpp, he, get_field, fmtstr, 1, print_fn, fmtype); } - if (fmtype == PERF_HPP_FMT_TYPE__PERCENT) + if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY) len -= 2; /* 2 for a space and a % sign */ else len -= 1; @@ -159,6 +153,48 @@ int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, return hpp__fmt(fmt, hpp, he, get_field, fmtstr, print_fn, fmtype); } +int hpp__fmt_mem_stat(struct perf_hpp_fmt *fmt __maybe_unused, struct perf_hpp *hpp, + struct hist_entry *he, enum mem_stat_type mst, + const char *fmtstr, hpp_snprint_fn print_fn) +{ + struct hists *hists = he->hists; + int mem_stat_idx = -1; + char *buf = hpp->buf; + size_t size = hpp->size; + u64 total = 0; + int ret = 0; + + for (int i = 0; i < hists->nr_mem_stats; i++) { + if (hists->mem_stat_types[i] == mst) { + mem_stat_idx = i; + break; + } + } + assert(mem_stat_idx != -1); + + for (int i = 0; i < MEM_STAT_LEN; i++) + total += hists->mem_stat_total[mem_stat_idx].entries[i]; + assert(total != 0); + + for (int i = 0; i < MEM_STAT_LEN; i++) { + u64 val = he->mem_stat[mem_stat_idx].entries[i]; + + if (hists->mem_stat_total[mem_stat_idx].entries[i] == 0) + continue; + + ret += hpp__call_print_fn(hpp, print_fn, fmtstr, 100.0 * val / total); + } + + /* + * Restore original buf and size as it's where caller expects + * the result will be saved. + */ + hpp->buf = buf; + hpp->size = size; + + return ret; +} + static int field_cmp(u64 field_a, u64 field_b) { if (field_a > field_b) @@ -303,6 +339,37 @@ static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b, return ret; } +static bool perf_hpp__is_mem_stat_entry(struct perf_hpp_fmt *fmt); + +static enum mem_stat_type hpp__mem_stat_type(struct perf_hpp_fmt *fmt) +{ + if (!perf_hpp__is_mem_stat_entry(fmt)) + return -1; + + switch (fmt->idx) { + case PERF_HPP__MEM_STAT_OP: + return PERF_MEM_STAT_OP; + case PERF_HPP__MEM_STAT_CACHE: + return PERF_MEM_STAT_CACHE; + case PERF_HPP__MEM_STAT_MEMORY: + return PERF_MEM_STAT_MEMORY; + case PERF_HPP__MEM_STAT_SNOOP: + return PERF_MEM_STAT_SNOOP; + case PERF_HPP__MEM_STAT_DTLB: + return PERF_MEM_STAT_DTLB; + default: + break; + } + pr_debug("Should not reach here\n"); + return -1; +} + +static int64_t hpp__sort_mem_stat(struct perf_hpp_fmt *fmt __maybe_unused, + struct hist_entry *a, struct hist_entry *b) +{ + return a->stat.period - b->stat.period; +} + static int hpp__width_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp __maybe_unused, struct hists *hists) @@ -310,8 +377,18 @@ static int hpp__width_fn(struct perf_hpp_fmt *fmt, int len = fmt->user_len ?: fmt->len; struct evsel *evsel = hists_to_evsel(hists); - if (symbol_conf.event_group) - len = max(len, evsel->core.nr_members * fmt->len); + if (symbol_conf.event_group) { + int nr = 0; + struct evsel *pos; + + for_each_group_evsel(pos, evsel) { + if (!symbol_conf.skip_empty || + evsel__hists(pos)->stats.nr_samples) + nr++; + } + + len = max(len, nr * fmt->len); + } if (len < (int)strlen(fmt->name)) len = strlen(fmt->name); @@ -320,11 +397,78 @@ static int hpp__width_fn(struct perf_hpp_fmt *fmt, } static int hpp__header_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, - struct hists *hists, int line __maybe_unused, + struct hists *hists, int line, int *span __maybe_unused) { int len = hpp__width_fn(fmt, hpp, hists); - return scnprintf(hpp->buf, hpp->size, "%*s", len, fmt->name); + const char *hdr = ""; + + if (line == hists->hpp_list->nr_header_lines - 1) + hdr = fmt->name; + + return scnprintf(hpp->buf, hpp->size, "%*s", len, hdr); +} + +static int hpp__header_mem_stat_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hists *hists, int line, + int *span __maybe_unused) +{ + char *buf = hpp->buf; + int ret = 0; + int len; + enum mem_stat_type mst = hpp__mem_stat_type(fmt); + int mem_stat_idx = -1; + + for (int i = 0; i < hists->nr_mem_stats; i++) { + if (hists->mem_stat_types[i] == mst) { + mem_stat_idx = i; + break; + } + } + assert(mem_stat_idx != -1); + + if (line == 0) { + int left, right; + + len = 0; + /* update fmt->len for acutally used columns only */ + for (int i = 0; i < MEM_STAT_LEN; i++) { + if (hists->mem_stat_total[mem_stat_idx].entries[i]) + len += MEM_STAT_PRINT_LEN; + } + fmt->len = len; + + /* print header directly if single column only */ + if (len == MEM_STAT_PRINT_LEN) + return scnprintf(hpp->buf, hpp->size, "%*s", len, fmt->name); + + left = (len - strlen(fmt->name)) / 2 - 1; + right = len - left - strlen(fmt->name) - 2; + + if (left < 0) + left = 0; + if (right < 0) + right = 0; + + return scnprintf(hpp->buf, hpp->size, "%.*s %s %.*s", + left, graph_dotted_line, fmt->name, right, graph_dotted_line); + } + + + len = hpp->size; + for (int i = 0; i < MEM_STAT_LEN; i++) { + int printed; + + if (hists->mem_stat_total[mem_stat_idx].entries[i] == 0) + continue; + + printed = scnprintf(buf, len, "%*s", MEM_STAT_PRINT_LEN, + mem_stat_name(mst, i)); + ret += printed; + buf += printed; + len -= printed; + } + return ret; } int hpp_color_scnprintf(struct perf_hpp *hpp, const char *fmt, ...) @@ -356,7 +500,7 @@ static int hpp_entry_scnprintf(struct perf_hpp *hpp, const char *fmt, ...) return (ret >= ssize) ? (ssize - 1) : ret; } -#define __HPP_COLOR_PERCENT_FN(_type, _field) \ +#define __HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \ static u64 he_get_##_field(struct hist_entry *he) \ { \ return he->stat._field; \ @@ -366,15 +510,15 @@ static int hpp__color_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ - hpp_color_scnprintf, PERF_HPP_FMT_TYPE__PERCENT); \ + hpp_color_scnprintf, _fmttype); \ } -#define __HPP_ENTRY_PERCENT_FN(_type, _field) \ +#define __HPP_ENTRY_PERCENT_FN(_type, _field, _fmttype) \ static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ return hpp__fmt(fmt, hpp, he, he_get_##_field, " %*.2f%%", \ - hpp_entry_scnprintf, PERF_HPP_FMT_TYPE__PERCENT); \ + hpp_entry_scnprintf, _fmttype); \ } #define __HPP_SORT_FN(_type, _field) \ @@ -384,7 +528,7 @@ static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ return __hpp__sort(a, b, he_get_##_field); \ } -#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ +#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \ static u64 he_get_acc_##_field(struct hist_entry *he) \ { \ return he->stat_acc->_field; \ @@ -394,15 +538,15 @@ static int hpp__color_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \ - hpp_color_scnprintf, PERF_HPP_FMT_TYPE__PERCENT); \ + hpp_color_scnprintf, _fmttype); \ } -#define __HPP_ENTRY_ACC_PERCENT_FN(_type, _field) \ +#define __HPP_ENTRY_ACC_PERCENT_FN(_type, _field, _fmttype) \ static int hpp__entry_##_type(struct perf_hpp_fmt *fmt, \ struct perf_hpp *hpp, struct hist_entry *he) \ { \ return hpp__fmt_acc(fmt, hpp, he, he_get_acc_##_field, " %*.2f%%", \ - hpp_entry_scnprintf, PERF_HPP_FMT_TYPE__PERCENT); \ + hpp_entry_scnprintf, _fmttype); \ } #define __HPP_SORT_ACC_FN(_type, _field) \ @@ -452,15 +596,32 @@ static int64_t hpp__sort_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ return __hpp__sort(a, b, he_get_##_field); \ } +#define __HPP_COLOR_MEM_STAT_FN(_name, _type) \ +static int hpp__color_mem_stat_##_name(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \ + " %5.1f%%", hpp_color_scnprintf); \ +} + +#define __HPP_ENTRY_MEM_STAT_FN(_name, _type) \ +static int hpp__entry_mem_stat_##_name(struct perf_hpp_fmt *fmt, \ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ +{ \ + return hpp__fmt_mem_stat(fmt, hpp, he, PERF_MEM_STAT_##_type, \ + " %5.1f%%", hpp_entry_scnprintf); \ +} -#define HPP_PERCENT_FNS(_type, _field) \ -__HPP_COLOR_PERCENT_FN(_type, _field) \ -__HPP_ENTRY_PERCENT_FN(_type, _field) \ +#define HPP_PERCENT_FNS(_type, _field, _fmttype) \ +__HPP_COLOR_PERCENT_FN(_type, _field, _fmttype) \ +__HPP_ENTRY_PERCENT_FN(_type, _field, _fmttype) \ __HPP_SORT_FN(_type, _field) -#define HPP_PERCENT_ACC_FNS(_type, _field) \ -__HPP_COLOR_ACC_PERCENT_FN(_type, _field) \ -__HPP_ENTRY_ACC_PERCENT_FN(_type, _field) \ +#define HPP_PERCENT_ACC_FNS(_type, _field, _fmttype) \ +__HPP_COLOR_ACC_PERCENT_FN(_type, _field, _fmttype) \ +__HPP_ENTRY_ACC_PERCENT_FN(_type, _field, _fmttype) \ __HPP_SORT_ACC_FN(_type, _field) #define HPP_RAW_FNS(_type, _field) \ @@ -471,12 +632,18 @@ __HPP_SORT_RAW_FN(_type, _field) __HPP_ENTRY_AVERAGE_FN(_type, _field) \ __HPP_SORT_AVERAGE_FN(_type, _field) -HPP_PERCENT_FNS(overhead, period) -HPP_PERCENT_FNS(overhead_sys, period_sys) -HPP_PERCENT_FNS(overhead_us, period_us) -HPP_PERCENT_FNS(overhead_guest_sys, period_guest_sys) -HPP_PERCENT_FNS(overhead_guest_us, period_guest_us) -HPP_PERCENT_ACC_FNS(overhead_acc, period) +#define HPP_MEM_STAT_FNS(_name, _type) \ +__HPP_COLOR_MEM_STAT_FN(_name, _type) \ +__HPP_ENTRY_MEM_STAT_FN(_name, _type) + +HPP_PERCENT_FNS(overhead, period, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_FNS(latency, latency, PERF_HPP_FMT_TYPE__LATENCY) +HPP_PERCENT_FNS(overhead_sys, period_sys, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_FNS(overhead_us, period_us, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_FNS(overhead_guest_sys, period_guest_sys, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_FNS(overhead_guest_us, period_guest_us, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_ACC_FNS(overhead_acc, period, PERF_HPP_FMT_TYPE__PERCENT) +HPP_PERCENT_ACC_FNS(latency_acc, latency, PERF_HPP_FMT_TYPE__LATENCY) HPP_RAW_FNS(samples, nr_events) HPP_RAW_FNS(period, period) @@ -485,6 +652,12 @@ HPP_AVERAGE_FNS(weight1, weight1) HPP_AVERAGE_FNS(weight2, weight2) HPP_AVERAGE_FNS(weight3, weight3) +HPP_MEM_STAT_FNS(op, OP) +HPP_MEM_STAT_FNS(cache, CACHE) +HPP_MEM_STAT_FNS(memory, MEMORY) +HPP_MEM_STAT_FNS(snoop, SNOOP) +HPP_MEM_STAT_FNS(dtlb, DTLB) + static int64_t hpp__nop_cmp(struct perf_hpp_fmt *fmt __maybe_unused, struct hist_entry *a __maybe_unused, struct hist_entry *b __maybe_unused) @@ -492,6 +665,11 @@ static int64_t hpp__nop_cmp(struct perf_hpp_fmt *fmt __maybe_unused, return 0; } +static bool perf_hpp__is_mem_stat_entry(struct perf_hpp_fmt *fmt) +{ + return fmt->sort == hpp__sort_mem_stat; +} + static bool perf_hpp__is_hpp_entry(struct perf_hpp_fmt *a) { return a->header == hpp__header_fn; @@ -505,6 +683,14 @@ static bool hpp__equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) return a->idx == b->idx; } +static bool hpp__equal_mem_stat(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) +{ + if (!perf_hpp__is_mem_stat_entry(a) || !perf_hpp__is_mem_stat_entry(b)) + return false; + + return a->entry == b->entry; +} + #define HPP__COLOR_PRINT_FNS(_name, _fn, _idx) \ { \ .name = _name, \ @@ -546,18 +732,39 @@ static bool hpp__equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) .equal = hpp__equal, \ } +#define HPP__MEM_STAT_PRINT_FNS(_name, _fn, _type) \ + { \ + .name = _name, \ + .header = hpp__header_mem_stat_fn, \ + .width = hpp__width_fn, \ + .color = hpp__color_mem_stat_ ## _fn, \ + .entry = hpp__entry_mem_stat_ ## _fn, \ + .cmp = hpp__nop_cmp, \ + .collapse = hpp__nop_cmp, \ + .sort = hpp__sort_mem_stat, \ + .idx = PERF_HPP__MEM_STAT_ ## _type, \ + .equal = hpp__equal_mem_stat, \ + } + struct perf_hpp_fmt perf_hpp__format[] = { HPP__COLOR_PRINT_FNS("Overhead", overhead, OVERHEAD), + HPP__COLOR_PRINT_FNS("Latency", latency, LATENCY), HPP__COLOR_PRINT_FNS("sys", overhead_sys, OVERHEAD_SYS), HPP__COLOR_PRINT_FNS("usr", overhead_us, OVERHEAD_US), HPP__COLOR_PRINT_FNS("guest sys", overhead_guest_sys, OVERHEAD_GUEST_SYS), HPP__COLOR_PRINT_FNS("guest usr", overhead_guest_us, OVERHEAD_GUEST_US), HPP__COLOR_ACC_PRINT_FNS("Children", overhead_acc, OVERHEAD_ACC), + HPP__COLOR_ACC_PRINT_FNS("Latency", latency_acc, LATENCY_ACC), HPP__PRINT_FNS("Samples", samples, SAMPLES), HPP__PRINT_FNS("Period", period, PERIOD), HPP__PRINT_FNS("Weight1", weight1, WEIGHT1), HPP__PRINT_FNS("Weight2", weight2, WEIGHT2), HPP__PRINT_FNS("Weight3", weight3, WEIGHT3), + HPP__MEM_STAT_PRINT_FNS("Mem Op", op, OP), + HPP__MEM_STAT_PRINT_FNS("Cache", cache, CACHE), + HPP__MEM_STAT_PRINT_FNS("Memory", memory, MEMORY), + HPP__MEM_STAT_PRINT_FNS("Snoop", snoop, SNOOP), + HPP__MEM_STAT_PRINT_FNS("D-TLB", dtlb, DTLB), }; struct perf_hpp_list perf_hpp_list = { @@ -569,11 +776,13 @@ struct perf_hpp_list perf_hpp_list = { #undef HPP__COLOR_PRINT_FNS #undef HPP__COLOR_ACC_PRINT_FNS #undef HPP__PRINT_FNS +#undef HPP__MEM_STAT_PRINT_FNS #undef HPP_PERCENT_FNS #undef HPP_PERCENT_ACC_FNS #undef HPP_RAW_FNS #undef HPP_AVERAGE_FNS +#undef HPP_MEM_STAT_FNS #undef __HPP_HEADER_FN #undef __HPP_WIDTH_FN @@ -583,6 +792,9 @@ struct perf_hpp_list perf_hpp_list = { #undef __HPP_ENTRY_ACC_PERCENT_FN #undef __HPP_ENTRY_RAW_FN #undef __HPP_ENTRY_AVERAGE_FN +#undef __HPP_COLOR_MEM_STAT_FN +#undef __HPP_ENTRY_MEM_STAT_FN + #undef __HPP_SORT_FN #undef __HPP_SORT_ACC_FN #undef __HPP_SORT_RAW_FN @@ -601,6 +813,11 @@ static void fmt_free(struct perf_hpp_fmt *fmt) fmt->free(fmt); } +static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) +{ + return a->equal && a->equal(a, b); +} + void perf_hpp__init(void) { int i; @@ -621,28 +838,48 @@ void perf_hpp__init(void) if (is_strict_order(field_order)) return; + /* + * Overhead and latency columns are added in setup_overhead(), + * so they are added implicitly here only if they were added + * by setup_overhead() before (have was_taken flag set). + * This is required because setup_overhead() has more complex + * logic, in particular it does not add "overhead" if user + * specified "latency" in sort order, and vise versa. + */ if (symbol_conf.cumulate_callchain) { - hpp_dimension__add_output(PERF_HPP__OVERHEAD_ACC); + /* + * Addition of fields is idempotent, so we add latency + * column twice to get desired order with simpler logic. + */ + if (symbol_conf.prefer_latency) + hpp_dimension__add_output(PERF_HPP__LATENCY_ACC, true); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_ACC, true); + if (symbol_conf.enable_latency) + hpp_dimension__add_output(PERF_HPP__LATENCY_ACC, true); perf_hpp__format[PERF_HPP__OVERHEAD].name = "Self"; } - hpp_dimension__add_output(PERF_HPP__OVERHEAD); + if (symbol_conf.prefer_latency) + hpp_dimension__add_output(PERF_HPP__LATENCY, true); + hpp_dimension__add_output(PERF_HPP__OVERHEAD, true); + if (symbol_conf.enable_latency) + hpp_dimension__add_output(PERF_HPP__LATENCY, true); if (symbol_conf.show_cpu_utilization) { - hpp_dimension__add_output(PERF_HPP__OVERHEAD_SYS); - hpp_dimension__add_output(PERF_HPP__OVERHEAD_US); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_SYS, false); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_US, false); if (perf_guest) { - hpp_dimension__add_output(PERF_HPP__OVERHEAD_GUEST_SYS); - hpp_dimension__add_output(PERF_HPP__OVERHEAD_GUEST_US); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_GUEST_SYS, false); + hpp_dimension__add_output(PERF_HPP__OVERHEAD_GUEST_US, false); } } if (symbol_conf.show_nr_samples) - hpp_dimension__add_output(PERF_HPP__SAMPLES); + hpp_dimension__add_output(PERF_HPP__SAMPLES, false); if (symbol_conf.show_total_period) - hpp_dimension__add_output(PERF_HPP__PERIOD); + hpp_dimension__add_output(PERF_HPP__PERIOD, false); } void perf_hpp_list__column_register(struct perf_hpp_list *list, @@ -666,33 +903,79 @@ void perf_hpp_list__prepend_sort_field(struct perf_hpp_list *list, static void perf_hpp__column_unregister(struct perf_hpp_fmt *format) { list_del_init(&format->list); + list_del_init(&format->sort_list); fmt_free(format); } -void perf_hpp__cancel_cumulate(void) +void perf_hpp__cancel_cumulate(struct evlist *evlist) { - struct perf_hpp_fmt *fmt, *acc, *ovh, *tmp; + struct perf_hpp_fmt *fmt, *acc, *ovh, *acc_lat, *tmp; + struct evsel *evsel; if (is_strict_order(field_order)) return; ovh = &perf_hpp__format[PERF_HPP__OVERHEAD]; acc = &perf_hpp__format[PERF_HPP__OVERHEAD_ACC]; + acc_lat = &perf_hpp__format[PERF_HPP__LATENCY_ACC]; perf_hpp_list__for_each_format_safe(&perf_hpp_list, fmt, tmp) { - if (acc->equal(acc, fmt)) { + if (fmt_equal(acc, fmt) || fmt_equal(acc_lat, fmt)) { perf_hpp__column_unregister(fmt); continue; } - if (ovh->equal(ovh, fmt)) + if (fmt_equal(ovh, fmt)) fmt->name = "Overhead"; } + + evlist__for_each_entry(evlist, evsel) { + struct hists *hists = evsel__hists(evsel); + struct perf_hpp_list_node *node; + + list_for_each_entry(node, &hists->hpp_formats, list) { + perf_hpp_list__for_each_format_safe(&node->hpp, fmt, tmp) { + if (fmt_equal(acc, fmt) || fmt_equal(acc_lat, fmt)) { + perf_hpp__column_unregister(fmt); + continue; + } + + if (fmt_equal(ovh, fmt)) + fmt->name = "Overhead"; + } + } + } } -static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) +void perf_hpp__cancel_latency(struct evlist *evlist) { - return a->equal && a->equal(a, b); + struct perf_hpp_fmt *fmt, *lat, *acc, *tmp; + struct evsel *evsel; + + if (is_strict_order(field_order)) + return; + if (sort_order && strstr(sort_order, "latency")) + return; + + lat = &perf_hpp__format[PERF_HPP__LATENCY]; + acc = &perf_hpp__format[PERF_HPP__LATENCY_ACC]; + + perf_hpp_list__for_each_format_safe(&perf_hpp_list, fmt, tmp) { + if (fmt_equal(lat, fmt) || fmt_equal(acc, fmt)) + perf_hpp__column_unregister(fmt); + } + + evlist__for_each_entry(evlist, evsel) { + struct hists *hists = evsel__hists(evsel); + struct perf_hpp_list_node *node; + + list_for_each_entry(node, &hists->hpp_formats, list) { + perf_hpp_list__for_each_format_safe(&node->hpp, fmt, tmp) { + if (fmt_equal(lat, fmt) || fmt_equal(acc, fmt)) + perf_hpp__column_unregister(fmt); + } + } + } } void perf_hpp__setup_output_field(struct perf_hpp_list *list) @@ -743,18 +1026,12 @@ void perf_hpp__reset_output_field(struct perf_hpp_list *list) struct perf_hpp_fmt *fmt, *tmp; /* reset output fields */ - perf_hpp_list__for_each_format_safe(list, fmt, tmp) { - list_del_init(&fmt->list); - list_del_init(&fmt->sort_list); - fmt_free(fmt); - } + perf_hpp_list__for_each_format_safe(list, fmt, tmp) + perf_hpp__column_unregister(fmt); /* reset sort keys */ - perf_hpp_list__for_each_sort_list_safe(list, fmt, tmp) { - list_del_init(&fmt->list); - list_del_init(&fmt->sort_list); - fmt_free(fmt); - } + perf_hpp_list__for_each_sort_list_safe(list, fmt, tmp) + perf_hpp__column_unregister(fmt); } /* @@ -819,6 +1096,7 @@ void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists) switch (fmt->idx) { case PERF_HPP__OVERHEAD: + case PERF_HPP__LATENCY: case PERF_HPP__OVERHEAD_SYS: case PERF_HPP__OVERHEAD_US: case PERF_HPP__OVERHEAD_ACC: @@ -841,6 +1119,14 @@ void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists) fmt->len = 8; break; + case PERF_HPP__MEM_STAT_OP: + case PERF_HPP__MEM_STAT_CACHE: + case PERF_HPP__MEM_STAT_MEMORY: + case PERF_HPP__MEM_STAT_SNOOP: + case PERF_HPP__MEM_STAT_DTLB: + fmt->len = MEM_STAT_LEN * MEM_STAT_PRINT_LEN; + break; + default: break; } @@ -946,3 +1232,42 @@ int perf_hpp__setup_hists_formats(struct perf_hpp_list *list, return 0; } + +int perf_hpp__alloc_mem_stats(struct perf_hpp_list *list, struct evlist *evlist) +{ + struct perf_hpp_fmt *fmt; + struct evsel *evsel; + enum mem_stat_type mst[16]; + unsigned nr_mem_stats = 0; + + perf_hpp_list__for_each_format(list, fmt) { + if (!perf_hpp__is_mem_stat_entry(fmt)) + continue; + + assert(nr_mem_stats < ARRAY_SIZE(mst)); + mst[nr_mem_stats++] = hpp__mem_stat_type(fmt); + } + + if (nr_mem_stats == 0) + return 0; + + list->nr_header_lines = 2; + + evlist__for_each_entry(evlist, evsel) { + struct hists *hists = evsel__hists(evsel); + + hists->mem_stat_types = calloc(nr_mem_stats, + sizeof(*hists->mem_stat_types)); + if (hists->mem_stat_types == NULL) + return -ENOMEM; + + hists->mem_stat_total = calloc(nr_mem_stats, + sizeof(*hists->mem_stat_total)); + if (hists->mem_stat_total == NULL) + return -ENOMEM; + + memcpy(hists->mem_stat_types, mst, nr_mem_stats * sizeof(*mst)); + hists->nr_mem_stats = nr_mem_stats; + } + return 0; +} diff --git a/tools/perf/ui/keysyms.c b/tools/perf/ui/keysyms.c new file mode 100644 index 000000000000..b64564b07f2f --- /dev/null +++ b/tools/perf/ui/keysyms.c @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include "keysyms.h" +#include <linux/ctype.h> +#include <linux/kernel.h> + +const char *key_name(int key, char *bf, size_t size) +{ + if (isprint(key)) { + scnprintf(bf, size, "%c", key); + } else if (key < 32) { + scnprintf(bf, size, "Ctrl+%c", key + '@'); + } else { + const char *name = NULL; + + switch (key) { + case K_DOWN: name = "Down"; break; + case K_END: name = "End"; break; + case K_ENTER: name = "Enter"; break; + case K_ESC: name = "ESC"; break; + case K_F1: name = "F1"; break; + case K_HOME: name = "Home"; break; + case K_LEFT: name = "Left"; break; + case K_PGDN: name = "PgDown"; break; + case K_PGUP: name = "PgUp"; break; + case K_RIGHT: name = "Right"; break; + case K_TAB: name = "Tab"; break; + case K_UNTAB: name = "Untab"; break; + case K_UP: name = "Up"; break; + case K_BKSPC: name = "Backspace"; break; + case K_DEL: name = "Del"; break; + default: + if (key >= SL_KEY_F(1) && key <= SL_KEY_F(63)) + scnprintf(bf, size, "F%d", key - SL_KEY_F(0)); + else + scnprintf(bf, size, "Unknown (%d)", key); + } + + if (name) + scnprintf(bf, size, "%s", name); + } + + return bf; +} diff --git a/tools/perf/ui/keysyms.h b/tools/perf/ui/keysyms.h index 04cc4e5c031f..969060edc362 100644 --- a/tools/perf/ui/keysyms.h +++ b/tools/perf/ui/keysyms.h @@ -27,4 +27,6 @@ #define K_SWITCH_INPUT_DATA -4 #define K_RELOAD -5 +const char *key_name(int key, char *bf, size_t size); + #endif /* _PERF_KEYSYMS_H_ */ diff --git a/tools/perf/ui/libslang.h b/tools/perf/ui/libslang.h index 1dff3020e9d5..6722561e0458 100644 --- a/tools/perf/ui/libslang.h +++ b/tools/perf/ui/libslang.h @@ -15,11 +15,7 @@ #define ENABLE_SLFUTURE_CONST 1 #define ENABLE_SLFUTURE_VOID 1 -#ifdef HAVE_SLANG_INCLUDE_SUBDIR -#include <slang/slang.h> -#else #include <slang.h> -#endif #define SL_KEY_UNTAB 0x1000 diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index b849caace398..8c4c8925df2c 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <linux/string.h> @@ -24,6 +25,9 @@ static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) int i; int ret = fprintf(fp, " "); + if (left_margin > USHRT_MAX) + left_margin = USHRT_MAX; + for (i = 0; i < left_margin; i++) ret += fprintf(fp, " "); @@ -639,45 +643,58 @@ static int hists__fprintf_hierarchy_headers(struct hists *hists, unsigned header_width = 0; struct perf_hpp_fmt *fmt; struct perf_hpp_list_node *fmt_node; + struct perf_hpp_list *hpp_list = hists->hpp_list; const char *sep = symbol_conf.field_sep; indent = hists->nr_hpp_node; - /* preserve max indent depth for column headers */ - print_hierarchy_indent(sep, indent, " ", fp); - /* the first hpp_list_node is for overhead columns */ fmt_node = list_first_entry(&hists->hpp_formats, struct perf_hpp_list_node, list); - perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { - fmt->header(fmt, hpp, hists, 0, NULL); - fprintf(fp, "%s%s", hpp->buf, sep ?: " "); - } + for (int line = 0; line < hpp_list->nr_header_lines; line++) { + /* first # is displayed one level up */ + if (line) + fprintf(fp, "# "); - /* combine sort headers with ' / ' */ - first_node = true; - list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { - if (!first_node) - header_width += fprintf(fp, " / "); - first_node = false; + /* preserve max indent depth for column headers */ + print_hierarchy_indent(sep, indent, " ", fp); - first_col = true; perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { - if (perf_hpp__should_skip(fmt, hists)) - continue; + fmt->header(fmt, hpp, hists, line, NULL); + fprintf(fp, "%s%s", hpp->buf, sep ?: " "); + } - if (!first_col) - header_width += fprintf(fp, "+"); - first_col = false; + if (line < hpp_list->nr_header_lines - 1) + goto next_line; + + /* combine sort headers with ' / ' */ + first_node = true; + list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { + if (!first_node) + header_width += fprintf(fp, " / "); + first_node = false; - fmt->header(fmt, hpp, hists, 0, NULL); + first_col = true; + perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { + if (perf_hpp__should_skip(fmt, hists)) + continue; - header_width += fprintf(fp, "%s", strim(hpp->buf)); + if (!first_col) + header_width += fprintf(fp, "+"); + first_col = false; + + fmt->header(fmt, hpp, hists, line, NULL); + + header_width += fprintf(fp, "%s", strim(hpp->buf)); + } } + +next_line: + fprintf(fp, "\n"); } - fprintf(fp, "\n# "); + fprintf(fp, "# "); /* preserve max indent depth for initial dots */ print_hierarchy_indent(sep, indent, dots, fp); @@ -897,8 +914,7 @@ out: return ret; } -size_t events_stats__fprintf(struct events_stats *stats, FILE *fp, - bool skip_empty) +size_t events_stats__fprintf(struct events_stats *stats, FILE *fp) { int i; size_t ret = 0; @@ -910,15 +926,15 @@ size_t events_stats__fprintf(struct events_stats *stats, FILE *fp, name = perf_event__name(i); if (!strcmp(name, "UNKNOWN")) continue; - if (skip_empty && !stats->nr_events[i]) + if (symbol_conf.skip_empty && !stats->nr_events[i]) continue; if (i && total) { - ret += fprintf(fp, "%16s events: %10d (%4.1f%%)\n", + ret += fprintf(fp, "%20s events: %10d (%4.1f%%)\n", name, stats->nr_events[i], 100.0 * stats->nr_events[i] / total); } else { - ret += fprintf(fp, "%16s events: %10d\n", + ret += fprintf(fp, "%20s events: %10d\n", name, stats->nr_events[i]); } } diff --git a/tools/perf/ui/tui/Build b/tools/perf/ui/tui/Build index f916df33a1a7..2ac058ad1a61 100644 --- a/tools/perf/ui/tui/Build +++ b/tools/perf/ui/tui/Build @@ -1,4 +1,4 @@ -perf-y += setup.o -perf-y += util.o -perf-y += helpline.o -perf-y += progress.o +perf-ui-y += setup.o +perf-ui-y += util.o +perf-ui-y += helpline.o +perf-ui-y += progress.o diff --git a/tools/perf/ui/tui/setup.c b/tools/perf/ui/tui/setup.c index 16c6eff4d241..022534eed68c 100644 --- a/tools/perf/ui/tui/setup.c +++ b/tools/perf/ui/tui/setup.c @@ -108,7 +108,7 @@ static void ui__signal_backtrace(int sig) printf("-------- backtrace --------\n"); size = backtrace(stackdump, ARRAY_SIZE(stackdump)); - backtrace_symbols_fd(stackdump, size, STDOUT_FILENO); + __dump_stack(stdout, stackdump, size); exit(0); } |
