summaryrefslogtreecommitdiff
path: root/tools/perf/ui/browsers/annotate-data.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/ui/browsers/annotate-data.c')
-rw-r--r--tools/perf/ui/browsers/annotate-data.c613
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;
+}