diff options
Diffstat (limited to 'tools/perf/ui/browsers/annotate-data.c')
-rw-r--r-- | tools/perf/ui/browsers/annotate-data.c | 613 |
1 files changed, 613 insertions, 0 deletions
diff --git a/tools/perf/ui/browsers/annotate-data.c b/tools/perf/ui/browsers/annotate-data.c new file mode 100644 index 000000000000..cd562a8822b7 --- /dev/null +++ b/tools/perf/ui/browsers/annotate-data.c @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <inttypes.h> +#include <string.h> +#include <linux/zalloc.h> +#include <sys/ttydefaults.h> + +#include "ui/browser.h" +#include "ui/helpline.h" +#include "ui/keysyms.h" +#include "ui/ui.h" +#include "util/annotate.h" +#include "util/annotate-data.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/sort.h" + +#define FOLDED_SIGN '+' +#define UNFOLD_SIGN '-' +#define NOCHLD_SIGN ' ' + +struct browser_entry { + struct list_head node; + struct annotated_member *data; + struct type_hist_entry *hists; + struct browser_entry *parent; + struct list_head children; + int indent; /*indentation level, starts from 0 */ + int nr_entries; /* # of visible entries: self + descendents */ + bool folded; /* only can be false when it has children */ +}; + +struct annotated_data_browser { + struct ui_browser b; + struct list_head entries; + struct browser_entry *curr; + int nr_events; +}; + +static struct annotated_data_browser *get_browser(struct ui_browser *uib) +{ + return container_of(uib, struct annotated_data_browser, b); +} + +static void update_hist_entry(struct type_hist_entry *dst, + struct type_hist_entry *src) +{ + dst->nr_samples += src->nr_samples; + dst->period += src->period; +} + +static int get_member_overhead(struct annotated_data_type *adt, + struct browser_entry *entry, + struct evsel *leader) +{ + struct annotated_member *member = entry->data; + int i, k; + + for (i = 0; i < member->size; i++) { + struct type_hist *h; + struct evsel *evsel; + int offset = member->offset + i; + + k = 0; + for_each_group_evsel(evsel, leader) { + if (symbol_conf.skip_empty && + evsel__hists(evsel)->stats.nr_samples == 0) + continue; + + h = adt->histograms[evsel->core.idx]; + update_hist_entry(&entry->hists[k++], &h->addr[offset]); + } + } + return 0; +} + +static int add_child_entries(struct annotated_data_browser *browser, + struct browser_entry *parent, + struct annotated_data_type *adt, + struct annotated_member *member, + struct evsel *evsel, int indent) +{ + struct annotated_member *pos; + struct browser_entry *entry; + struct list_head *parent_list; + + entry = zalloc(sizeof(*entry)); + if (entry == NULL) + return -1; + + entry->hists = calloc(browser->nr_events, sizeof(*entry->hists)); + if (entry->hists == NULL) { + free(entry); + return -1; + } + + entry->data = member; + entry->parent = parent; + entry->indent = indent; + if (get_member_overhead(adt, entry, evsel) < 0) { + free(entry); + return -1; + } + + INIT_LIST_HEAD(&entry->children); + if (parent) + parent_list = &parent->children; + else + parent_list = &browser->entries; + + list_add_tail(&entry->node, parent_list); + + list_for_each_entry(pos, &member->children, node) { + int nr = add_child_entries(browser, entry, adt, pos, evsel, + indent + 1); + if (nr < 0) + return nr; + } + + /* add an entry for the closing bracket ("}") */ + if (!list_empty(&member->children)) { + struct browser_entry *bracket; + + bracket = zalloc(sizeof(*bracket)); + if (bracket == NULL) + return -1; + + bracket->indent = indent; + bracket->parent = entry; + bracket->folded = true; + bracket->nr_entries = 1; + + INIT_LIST_HEAD(&bracket->children); + list_add_tail(&bracket->node, &entry->children); + } + + /* fold child entries by default */ + entry->folded = true; + entry->nr_entries = 1; + return 0; +} + +static u32 count_visible_entries(struct annotated_data_browser *browser) +{ + int nr = 0; + struct browser_entry *entry; + + list_for_each_entry(entry, &browser->entries, node) + nr += entry->nr_entries; + + return nr; +} + +static int annotated_data_browser__collect_entries(struct annotated_data_browser *browser) +{ + struct hist_entry *he = browser->b.priv; + struct annotated_data_type *adt = he->mem_type; + struct evsel *evsel = hists_to_evsel(he->hists); + + INIT_LIST_HEAD(&browser->entries); + + add_child_entries(browser, /*parent=*/NULL, adt, &adt->self, evsel, + /*indent=*/0); + + browser->b.entries = &browser->entries; + browser->b.nr_entries = count_visible_entries(browser); + return 0; +} + +static void annotated_data_browser__delete_entries(struct annotated_data_browser *browser) +{ + struct browser_entry *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &browser->entries, node) { + list_del_init(&pos->node); + zfree(&pos->hists); + free(pos); + } +} + +static struct browser_entry *get_first_child(struct browser_entry *entry) +{ + if (list_empty(&entry->children)) + return NULL; + + return list_first_entry(&entry->children, struct browser_entry, node); +} + +static struct browser_entry *get_last_child(struct browser_entry *entry) +{ + if (list_empty(&entry->children)) + return NULL; + + return list_last_entry(&entry->children, struct browser_entry, node); +} + +static bool is_first_child(struct browser_entry *entry) +{ + /* This will be checked in a different way */ + if (entry->parent == NULL) + return false; + + return get_first_child(entry->parent) == entry; +} + +static bool is_last_child(struct browser_entry *entry) +{ + /* This will be checked in a different way */ + if (entry->parent == NULL) + return false; + + return get_last_child(entry->parent) == entry; +} + +static struct browser_entry *browser__prev_entry(struct ui_browser *uib, + struct browser_entry *entry) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *first; + + first = list_first_entry(&browser->entries, struct browser_entry, node); + + while (entry != first) { + if (is_first_child(entry)) + entry = entry->parent; + else { + entry = list_prev_entry(entry, node); + while (!entry->folded) + entry = get_last_child(entry); + } + + if (!uib->filter || !uib->filter(uib, &entry->node)) + return entry; + } + return first; +} + +static struct browser_entry *browser__next_entry(struct ui_browser *uib, + struct browser_entry *entry) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *last; + + last = list_last_entry(&browser->entries, struct browser_entry, node); + while (!last->folded) + last = get_last_child(last); + + while (entry != last) { + if (!entry->folded) + entry = get_first_child(entry); + else { + while (is_last_child(entry)) + entry = entry->parent; + + entry = list_next_entry(entry, node); + } + + if (!uib->filter || !uib->filter(uib, &entry->node)) + return entry; + } + return last; +} + +static void browser__seek(struct ui_browser *uib, off_t offset, int whence) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *entry; + + if (uib->nr_entries == 0) + return; + + switch (whence) { + case SEEK_SET: + entry = list_first_entry(&browser->entries, typeof(*entry), node); + if (uib->filter && uib->filter(uib, &entry->node)) + entry = browser__next_entry(uib, entry); + break; + case SEEK_CUR: + entry = list_entry(uib->top, typeof(*entry), node); + break; + case SEEK_END: + entry = list_last_entry(&browser->entries, typeof(*entry), node); + while (!entry->folded) + entry = get_last_child(entry); + if (uib->filter && uib->filter(uib, &entry->node)) + entry = browser__prev_entry(uib, entry); + break; + default: + return; + } + + assert(entry != NULL); + + if (offset > 0) { + while (offset-- != 0) + entry = browser__next_entry(uib, entry); + } else { + while (offset++ != 0) + entry = browser__prev_entry(uib, entry); + } + + uib->top = &entry->node; +} + +static unsigned int browser__refresh(struct ui_browser *uib) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *entry, *next; + int row = 0; + + if (uib->top == NULL || uib->top == uib->entries) + browser__seek(uib, SEEK_SET, 0); + + entry = list_entry(uib->top, typeof(*entry), node); + + while (true) { + if (!uib->filter || !uib->filter(uib, &entry->node)) { + ui_browser__gotorc(uib, row, 0); + uib->write(uib, entry, row); + if (uib->top_idx + row == uib->index) + browser->curr = entry; + if (++row == uib->rows) + break; + } + next = browser__next_entry(uib, entry); + if (next == entry) + break; + + entry = next; + } + + return row; +} + +static int browser__show(struct ui_browser *uib) +{ + struct hist_entry *he = uib->priv; + struct annotated_data_type *adt = he->mem_type; + struct annotated_data_browser *browser = get_browser(uib); + const char *help = "Press 'h' for help on key bindings"; + char title[256]; + + snprintf(title, sizeof(title), "Annotate type: '%s' (%d samples)", + adt->self.type_name, he->stat.nr_events); + + if (ui_browser__show(uib, title, help) < 0) + return -1; + + /* second line header */ + ui_browser__gotorc_title(uib, 0, 0); + ui_browser__set_color(uib, HE_COLORSET_ROOT); + + if (symbol_conf.show_total_period) + strcpy(title, "Period"); + else if (symbol_conf.show_nr_samples) + strcpy(title, "Samples"); + else + strcpy(title, "Percent"); + + ui_browser__printf(uib, "%*s %10s %10s %10s %s", + 2 + 11 * (browser->nr_events - 1), "", + title, "Offset", "Size", "Field"); + ui_browser__write_nstring(uib, "", uib->width); + return 0; +} + +static void browser__write_overhead(struct ui_browser *uib, + struct type_hist *total, + struct type_hist_entry *hist, int row) +{ + u64 period = hist->period; + double percent = total->period ? (100.0 * period / total->period) : 0; + bool current = ui_browser__is_current_entry(uib, row); + int nr_samples = 0; + + ui_browser__set_percent_color(uib, percent, current); + + if (symbol_conf.show_total_period) + ui_browser__printf(uib, " %10" PRIu64, period); + else if (symbol_conf.show_nr_samples) + ui_browser__printf(uib, " %10d", nr_samples); + else + ui_browser__printf(uib, " %10.2f", percent); + + ui_browser__set_percent_color(uib, 0, current); +} + +static void browser__write(struct ui_browser *uib, void *entry, int row) +{ + struct annotated_data_browser *browser = get_browser(uib); + struct browser_entry *be = entry; + struct annotated_member *member = be->data; + struct hist_entry *he = uib->priv; + struct annotated_data_type *adt = he->mem_type; + struct evsel *leader = hists_to_evsel(he->hists); + struct evsel *evsel; + int idx = 0; + bool current = ui_browser__is_current_entry(uib, row); + + if (member == NULL) { + /* print the closing bracket */ + ui_browser__set_percent_color(uib, 0, current); + ui_browser__printf(uib, "%c ", NOCHLD_SIGN); + ui_browser__write_nstring(uib, "", 11 * browser->nr_events); + ui_browser__printf(uib, " %10s %10s %*s};", + "", "", be->indent * 4, ""); + ui_browser__write_nstring(uib, "", uib->width); + return; + } + + ui_browser__set_percent_color(uib, 0, current); + + if (!list_empty(&be->children)) + ui_browser__printf(uib, "%c ", be->folded ? FOLDED_SIGN : UNFOLD_SIGN); + else + ui_browser__printf(uib, "%c ", NOCHLD_SIGN); + + /* print the number */ + for_each_group_evsel(evsel, leader) { + struct type_hist *h = adt->histograms[evsel->core.idx]; + + if (symbol_conf.skip_empty && + evsel__hists(evsel)->stats.nr_samples == 0) + continue; + + browser__write_overhead(uib, h, &be->hists[idx++], row); + } + + /* print type info */ + if (be->indent == 0 && !member->var_name) { + ui_browser__printf(uib, " %#10x %#10x %s%s", + member->offset, member->size, + member->type_name, + list_empty(&member->children) || be->folded? ";" : " {"); + } else { + ui_browser__printf(uib, " %#10x %#10x %*s%s\t%s%s", + member->offset, member->size, + be->indent * 4, "", member->type_name, + member->var_name ?: "", + list_empty(&member->children) || be->folded ? ";" : " {"); + } + /* fill the rest */ + ui_browser__write_nstring(uib, "", uib->width); +} + +static void annotated_data_browser__fold(struct annotated_data_browser *browser, + struct browser_entry *entry, + bool recursive) +{ + struct browser_entry *child; + + if (list_empty(&entry->children)) + return; + if (entry->folded && !recursive) + return; + + if (recursive) { + list_for_each_entry(child, &entry->children, node) + annotated_data_browser__fold(browser, child, true); + } + + entry->nr_entries = 1; + entry->folded = true; +} + +static void annotated_data_browser__unfold(struct annotated_data_browser *browser, + struct browser_entry *entry, + bool recursive) +{ + struct browser_entry *child; + int nr_entries; + + if (list_empty(&entry->children)) + return; + if (!entry->folded && !recursive) + return; + + nr_entries = 1; /* for self */ + list_for_each_entry(child, &entry->children, node) { + if (recursive) + annotated_data_browser__unfold(browser, child, true); + + nr_entries += child->nr_entries; + } + + entry->nr_entries = nr_entries; + entry->folded = false; +} + +static void annotated_data_browser__toggle_fold(struct annotated_data_browser *browser, + bool recursive) +{ + struct browser_entry *curr = browser->curr; + struct browser_entry *parent; + + parent = curr->parent; + while (parent) { + parent->nr_entries -= curr->nr_entries; + parent = parent->parent; + } + browser->b.nr_entries -= curr->nr_entries; + + if (curr->folded) + annotated_data_browser__unfold(browser, curr, recursive); + else + annotated_data_browser__fold(browser, curr, recursive); + + parent = curr->parent; + while (parent) { + parent->nr_entries += curr->nr_entries; + parent = parent->parent; + } + browser->b.nr_entries += curr->nr_entries; + + assert(browser->b.nr_entries == count_visible_entries(browser)); +} + +static int annotated_data_browser__run(struct annotated_data_browser *browser, + struct evsel *evsel __maybe_unused, + struct hist_browser_timer *hbt) +{ + int delay_secs = hbt ? hbt->refresh : 0; + int key; + + if (browser__show(&browser->b) < 0) + return -1; + + while (1) { + key = ui_browser__run(&browser->b, delay_secs); + + switch (key) { + case K_TIMER: + if (hbt) + hbt->timer(hbt->arg); + continue; + case K_F1: + case 'h': + ui_browser__help_window(&browser->b, + "UP/DOWN/PGUP\n" + "PGDN/SPACE Navigate\n" + "</> Move to prev/next symbol\n" + "e Expand/Collapse current entry\n" + "E Expand/Collapse all children of the current\n" + "q/ESC/CTRL+C Exit\n\n"); + continue; + case 'e': + annotated_data_browser__toggle_fold(browser, + /*recursive=*/false); + break; + case 'E': + annotated_data_browser__toggle_fold(browser, + /*recursive=*/true); + break; + case K_LEFT: + case '<': + case '>': + case K_ESC: + case 'q': + case CTRL('c'): + goto out; + default: + continue; + } + } +out: + ui_browser__hide(&browser->b); + return key; +} + +int hist_entry__annotate_data_tui(struct hist_entry *he, struct evsel *evsel, + struct hist_browser_timer *hbt) +{ + struct annotated_data_browser browser = { + .b = { + .refresh = browser__refresh, + .seek = browser__seek, + .write = browser__write, + .priv = he, + .extra_title_lines = 1, + }, + .nr_events = 1, + }; + int ret; + + ui_helpline__push("Press ESC to exit"); + + if (evsel__is_group_event(evsel)) { + struct evsel *pos; + int nr = 0; + + for_each_group_evsel(pos, evsel) { + if (!symbol_conf.skip_empty || + evsel__hists(pos)->stats.nr_samples) + nr++; + } + browser.nr_events = nr; + } + + ret = annotated_data_browser__collect_entries(&browser); + if (ret < 0) + goto out; + + /* To get the top and current entry */ + browser__refresh(&browser.b); + /* Show the first-level child entries by default */ + annotated_data_browser__toggle_fold(&browser, /*recursive=*/false); + + ret = annotated_data_browser__run(&browser, evsel, hbt); + +out: + annotated_data_browser__delete_entries(&browser); + + return ret; +} |