| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * C2C function browser - TUI front end for function-level sharing analysis | |
| */ | |
| #include <errno.h> | |
| #include <inttypes.h> | |
| #include <stdlib.h> | |
| #include <sys/ttydefaults.h> | |
| #include <linux/rbtree.h> | |
| #include <linux/zalloc.h> | |
| #include "../browser.h" | |
| #include "../keysyms.h" | |
| #include "../libslang.h" | |
| #include "../ui.h" | |
| #include "../../util/c2c.h" | |
| #include "../../util/debug.h" | |
| #include "../../util/hist.h" | |
| #include "../../util/symbol.h" | |
| #include "hists.h" | |
| struct c2c_function_browser { | |
| struct hist_browser hb; | |
| unsigned int (*orig_refresh)(struct ui_browser *browser); | |
| int (*browse_cacheline)(struct hist_entry *he); | |
| }; | |
| /* | |
| * Count visible entries in @root, descending only through visible, unfolded | |
| * parents. Match hists__filter_entries(), which drives generic browser | |
| * navigation, so the count cannot include rows the browser skips. | |
| */ | |
| static u64 | |
| c2c_function__nr_visible_rows(struct rb_root_cached *root, float min_pcnt) | |
| { | |
| struct rb_node *nd; | |
| u64 rows = 0; | |
| for (nd = rb_first_cached(root); nd; nd = rb_next(nd)) { | |
| struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); | |
| /* | |
| * The generic refresh folds filtered parents and therefore hides | |
| * their subtree. A percentage-rejected parent is merely skipped; | |
| * if it is unfolded, qualifying descendants are still rendered. | |
| */ | |
| if (he->filtered) | |
| continue; | |
| if (hist_entry__get_percent_limit(he) >= min_pcnt) | |
| rows++; | |
| if (he->has_children && he->unfolded) | |
| rows += c2c_function__nr_visible_rows(&he->hroot_out, | |
| min_pcnt); | |
| } | |
| return rows; | |
| } | |
| static void | |
| c2c_function_browser__update_nr_entries(struct c2c_function_browser *browser) | |
| { | |
| u64 nr_entries; | |
| nr_entries = c2c_function__nr_visible_rows(&browser->hb.hists->entries, | |
| browser->hb.min_pcnt); | |
| browser->hb.nr_non_filtered_entries = nr_entries; | |
| browser->hb.b.nr_entries = nr_entries; | |
| } | |
| static unsigned int c2c_function_browser__refresh(struct ui_browser *ui_browser) | |
| { | |
| struct hist_browser *hist_browser = container_of(ui_browser, struct hist_browser, b); | |
| struct c2c_function_browser *browser; | |
| browser = container_of(hist_browser, struct c2c_function_browser, hb); | |
| c2c_function_browser__update_nr_entries(browser); | |
| return browser->orig_refresh(ui_browser); | |
| } | |
| static int c2c_function_browser__title(struct hist_browser *browser, | |
| char *bf, size_t size) | |
| { | |
| scnprintf(bf, size, | |
| "Shared Data Functions Table (%" PRIu64 " entries, sorted on Cycles %%)", | |
| browser->hists->nr_non_filtered_entries); | |
| return 0; | |
| } | |
| static struct c2c_function_browser * | |
| c2c_function_browser__new(struct hists *hists, | |
| int (*browse_cacheline)(struct hist_entry *he)) | |
| { | |
| struct c2c_function_browser *browser; | |
| if (!hists) | |
| return NULL; | |
| browser = zalloc(sizeof(*browser)); | |
| if (!browser) | |
| return NULL; | |
| hist_browser__init(&browser->hb, hists); | |
| browser->orig_refresh = browser->hb.b.refresh; | |
| browser->hb.b.refresh = c2c_function_browser__refresh; | |
| browser->browse_cacheline = browse_cacheline; | |
| browser->hb.title = c2c_function_browser__title; | |
| browser->hb.c2c_filter = true; | |
| browser->hb.show_headers = true; | |
| /* Keep title line count consistent with forcing headers on. */ | |
| browser->hb.b.extra_title_lines = hists->hpp_list->nr_header_lines; | |
| browser->hb.min_pcnt = 0.0; | |
| return browser; | |
| } | |
| static void c2c_function_browser__delete(struct c2c_function_browser *browser) | |
| { | |
| free(browser); | |
| } | |
| static int | |
| c2c_browser__browse_cacheline(struct c2c_function_browser *browser, | |
| struct hist_entry *he_selection) | |
| { | |
| struct hist_entry *he = c2c_function__find_cacheline(he_selection); | |
| return he ? browser->browse_cacheline(he) : -1; | |
| } | |
| int perf_c2c__browse_function_view(struct c2c_function_view_args *args) | |
| { | |
| struct c2c_function_browser *browser; | |
| struct hists *hists; | |
| bool saved_use_callchain = symbol_conf.use_callchain; | |
| int key, ret; | |
| static const char help[] = | |
| " d Display details for the selected level-3 cacheline\n" | |
| " e/+ Expand/collapse the selected entry\n" | |
| " TAB/ESC/q/^C Return to the cacheline view\n"; | |
| if (!args || !args->cl_hists || !args->browse_cacheline) | |
| return -EINVAL; | |
| /* | |
| * Function view does not display callchains; cacheline detail temporarily | |
| * restores them. | |
| */ | |
| symbol_conf.use_callchain = false; | |
| ret = c2c_function__build(args->cl_hists, args->cl_sort, | |
| args->symbol_full, &hists); | |
| if (ret) { | |
| if (ret == -EOPNOTSUPP) | |
| ui__warning("The function view requires iaddr in --coalesce.\n"); | |
| else | |
| ui__error("Failed to build function view hierarchy (ret=%d)\n", ret); | |
| goto out; | |
| } | |
| browser = c2c_function_browser__new(hists, args->browse_cacheline); | |
| if (!browser) { | |
| ret = -ENOMEM; | |
| goto out_reset; | |
| } | |
| /* Reset abort key so we can receive Ctrl-C as a key. */ | |
| SLang_reset_tty(); | |
| SLang_init_tty(0, 0, 0); | |
| SLtty_set_suspend_state(true); | |
| while (1) { | |
| c2c_function_browser__update_nr_entries(browser); | |
| key = hist_browser__run(&browser->hb, "? - help", true, 0); | |
| switch (key) { | |
| case 'q': | |
| case K_TAB: | |
| case K_ESC: | |
| case CTRL('c'): | |
| goto browser_done; | |
| case 'd': | |
| /* Cacheline detail honors the user's callchain setting. */ | |
| symbol_conf.use_callchain = saved_use_callchain; | |
| c2c_browser__browse_cacheline(browser, browser->hb.he_selection); | |
| /* | |
| * Preserve any toggle made in the detail view, then | |
| * re-disable callchain for the function view. | |
| */ | |
| saved_use_callchain = symbol_conf.use_callchain; | |
| symbol_conf.use_callchain = false; | |
| break; | |
| case '?': | |
| ui_browser__help_window(&browser->hb.b, help); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| browser_done: | |
| c2c_function_browser__delete(browser); | |
| out_reset: | |
| c2c_function__reset(); | |
| out: | |
| symbol_conf.use_callchain = saved_use_callchain; | |
| return ret; | |
| } |