// SPDX-License-Identifier: GPL-2.0 /* * C2C function browser - TUI front end for function-level sharing analysis */ #include #include #include #include #include #include #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; }