xref: /linux/tools/perf/ui/browsers/c2c-function.c (revision 473f6c8f437b049f8ec015d57cd59bb983b1d85c)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * C2C function browser - TUI front end for function-level sharing analysis
4  */
5 
6 #include <errno.h>
7 #include <inttypes.h>
8 #include <stdlib.h>
9 #include <sys/ttydefaults.h>
10 #include <linux/rbtree.h>
11 #include <linux/zalloc.h>
12 
13 #include "../browser.h"
14 #include "../keysyms.h"
15 #include "../libslang.h"
16 #include "../ui.h"
17 #include "../../util/c2c.h"
18 #include "../../util/debug.h"
19 #include "../../util/hist.h"
20 #include "../../util/symbol.h"
21 #include "hists.h"
22 
23 struct c2c_function_browser {
24 	struct hist_browser	hb;
25 	unsigned int		(*orig_refresh)(struct ui_browser *browser);
26 	int			(*browse_cacheline)(struct hist_entry *he);
27 };
28 
29 /*
30  * Count visible entries in @root, descending only through visible, unfolded
31  * parents. Match hists__filter_entries(), which drives generic browser
32  * navigation, so the count cannot include rows the browser skips.
33  */
34 static u64
35 c2c_function__nr_visible_rows(struct rb_root_cached *root, float min_pcnt)
36 {
37 	struct rb_node *nd;
38 	u64 rows = 0;
39 
40 	for (nd = rb_first_cached(root); nd; nd = rb_next(nd)) {
41 		struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
42 
43 		/*
44 		 * The generic refresh folds filtered parents and therefore hides
45 		 * their subtree. A percentage-rejected parent is merely skipped;
46 		 * if it is unfolded, qualifying descendants are still rendered.
47 		 */
48 		if (he->filtered)
49 			continue;
50 
51 		if (hist_entry__get_percent_limit(he) >= min_pcnt)
52 			rows++;
53 		if (he->has_children && he->unfolded)
54 			rows += c2c_function__nr_visible_rows(&he->hroot_out,
55 							     min_pcnt);
56 	}
57 	return rows;
58 }
59 
60 static void
61 c2c_function_browser__update_nr_entries(struct c2c_function_browser *browser)
62 {
63 	u64 nr_entries;
64 
65 	nr_entries = c2c_function__nr_visible_rows(&browser->hb.hists->entries,
66 						   browser->hb.min_pcnt);
67 	browser->hb.nr_non_filtered_entries = nr_entries;
68 	browser->hb.b.nr_entries = nr_entries;
69 }
70 
71 static unsigned int c2c_function_browser__refresh(struct ui_browser *ui_browser)
72 {
73 	struct hist_browser *hist_browser = container_of(ui_browser, struct hist_browser, b);
74 	struct c2c_function_browser *browser;
75 
76 	browser = container_of(hist_browser, struct c2c_function_browser, hb);
77 	c2c_function_browser__update_nr_entries(browser);
78 	return browser->orig_refresh(ui_browser);
79 }
80 
81 static int c2c_function_browser__title(struct hist_browser *browser,
82 				       char *bf, size_t size)
83 {
84 	scnprintf(bf, size,
85 		  "Shared Data Functions Table     (%" PRIu64 " entries, sorted on Cycles %%)",
86 		  browser->hists->nr_non_filtered_entries);
87 	return 0;
88 }
89 
90 static struct c2c_function_browser *
91 c2c_function_browser__new(struct hists *hists,
92 			  int (*browse_cacheline)(struct hist_entry *he))
93 {
94 	struct c2c_function_browser *browser;
95 
96 	if (!hists)
97 		return NULL;
98 
99 	browser = zalloc(sizeof(*browser));
100 	if (!browser)
101 		return NULL;
102 
103 	hist_browser__init(&browser->hb, hists);
104 	browser->orig_refresh = browser->hb.b.refresh;
105 	browser->hb.b.refresh = c2c_function_browser__refresh;
106 	browser->browse_cacheline = browse_cacheline;
107 
108 	browser->hb.title = c2c_function_browser__title;
109 	browser->hb.c2c_filter = true;
110 	browser->hb.show_headers = true;
111 	/* Keep title line count consistent with forcing headers on. */
112 	browser->hb.b.extra_title_lines = hists->hpp_list->nr_header_lines;
113 	browser->hb.min_pcnt = 0.0;
114 
115 	return browser;
116 }
117 
118 static void c2c_function_browser__delete(struct c2c_function_browser *browser)
119 {
120 	free(browser);
121 }
122 
123 static int
124 c2c_browser__browse_cacheline(struct c2c_function_browser *browser,
125 			      struct hist_entry *he_selection)
126 {
127 	struct hist_entry *he = c2c_function__find_cacheline(he_selection);
128 
129 	return he ? browser->browse_cacheline(he) : -1;
130 }
131 
132 int perf_c2c__browse_function_view(struct c2c_function_view_args *args)
133 {
134 	struct c2c_function_browser *browser;
135 	struct hists *hists;
136 	bool saved_use_callchain = symbol_conf.use_callchain;
137 	int key, ret;
138 	static const char help[] =
139 	" d             Display details for the selected level-3 cacheline\n"
140 	" e/+           Expand/collapse the selected entry\n"
141 	" TAB/ESC/q/^C  Return to the cacheline view\n";
142 
143 	if (!args || !args->cl_hists || !args->browse_cacheline)
144 		return -EINVAL;
145 
146 	/*
147 	 * Function view does not display callchains; cacheline detail temporarily
148 	 * restores them.
149 	 */
150 	symbol_conf.use_callchain = false;
151 
152 	ret = c2c_function__build(args->cl_hists, args->cl_sort,
153 				  args->symbol_full, &hists);
154 	if (ret) {
155 		if (ret == -EOPNOTSUPP)
156 			ui__warning("The function view requires iaddr in --coalesce.\n");
157 		else
158 			ui__error("Failed to build function view hierarchy (ret=%d)\n", ret);
159 		goto out;
160 	}
161 
162 	browser = c2c_function_browser__new(hists, args->browse_cacheline);
163 	if (!browser) {
164 		ret = -ENOMEM;
165 		goto out_reset;
166 	}
167 
168 	/* Reset abort key so we can receive Ctrl-C as a key. */
169 	SLang_reset_tty();
170 	SLang_init_tty(0, 0, 0);
171 	SLtty_set_suspend_state(true);
172 
173 	while (1) {
174 		c2c_function_browser__update_nr_entries(browser);
175 		key = hist_browser__run(&browser->hb, "? - help", true, 0);
176 
177 		switch (key) {
178 		case 'q':
179 		case K_TAB:
180 		case K_ESC:
181 		case CTRL('c'):
182 			goto browser_done;
183 		case 'd':
184 			/* Cacheline detail honors the user's callchain setting. */
185 			symbol_conf.use_callchain = saved_use_callchain;
186 			c2c_browser__browse_cacheline(browser, browser->hb.he_selection);
187 			/*
188 			 * Preserve any toggle made in the detail view, then
189 			 * re-disable callchain for the function view.
190 			 */
191 			saved_use_callchain = symbol_conf.use_callchain;
192 			symbol_conf.use_callchain = false;
193 			break;
194 		case '?':
195 			ui_browser__help_window(&browser->hb.b, help);
196 			break;
197 		default:
198 			break;
199 		}
200 	}
201 
202 browser_done:
203 	c2c_function_browser__delete(browser);
204 out_reset:
205 	c2c_function__reset();
206 out:
207 	symbol_conf.use_callchain = saved_use_callchain;
208 	return ret;
209 }
210