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