xref: /linux/tools/perf/ui/browsers/scripts.c (revision 7685b334d1e4927cc73b62c65293ba65748d9c52)
1 // SPDX-License-Identifier: GPL-2.0
2 #include "../../util/util.h" // perf_exe()
3 #include "../util.h"
4 #include "../../util/evlist.h"
5 #include "../../util/hist.h"
6 #include "../../util/debug.h"
7 #include "../../util/session.h"
8 #include "../../util/symbol.h"
9 #include "../browser.h"
10 #include "../libslang.h"
11 #include "config.h"
12 #include <linux/err.h>
13 #include <linux/string.h>
14 #include <linux/zalloc.h>
15 #include <subcmd/exec-cmd.h>
16 #include <stdlib.h>
17 
18 #define SCRIPT_NAMELEN	128
19 #define SCRIPT_MAX_NO	64
20 /*
21  * Usually the full path for a script is:
22  *	/home/username/libexec/perf-core/scripts/python/xxx.py
23  *	/home/username/libexec/perf-core/scripts/perl/xxx.pl
24  * So 256 should be long enough to contain the full path.
25  */
26 #define SCRIPT_FULLPATH_LEN	256
27 
28 struct script_config {
29 	const char **names;
30 	char **paths;
31 	int index;
32 	const char *perf;
33 	char extra_format[256];
34 };
35 
36 void attr_to_script(char *extra_format, struct perf_event_attr *attr)
37 {
38 	extra_format[0] = 0;
39 	if (attr->read_format & PERF_FORMAT_GROUP)
40 		strcat(extra_format, " -F +metric");
41 	if (attr->sample_type & PERF_SAMPLE_BRANCH_STACK)
42 		strcat(extra_format, " -F +brstackinsn --xed");
43 	if (attr->sample_type & PERF_SAMPLE_REGS_INTR)
44 		strcat(extra_format, " -F +iregs");
45 	if (attr->sample_type & PERF_SAMPLE_REGS_USER)
46 		strcat(extra_format, " -F +uregs");
47 	if (attr->sample_type & PERF_SAMPLE_PHYS_ADDR)
48 		strcat(extra_format, " -F +phys_addr");
49 }
50 
51 static int add_script_option(const char *name, const char *opt,
52 			     struct script_config *c)
53 {
54 	c->names[c->index] = name;
55 	if (asprintf(&c->paths[c->index],
56 		     "%s script %s -F +metric %s %s",
57 		     c->perf, opt, symbol_conf.inline_name ? " --inline" : "",
58 		     c->extra_format) < 0)
59 		return -1;
60 	c->index++;
61 	return 0;
62 }
63 
64 static int scripts_config(const char *var, const char *value, void *data)
65 {
66 	struct script_config *c = data;
67 
68 	if (!strstarts(var, "scripts."))
69 		return -1;
70 	if (c->index >= SCRIPT_MAX_NO)
71 		return -1;
72 	c->names[c->index] = strdup(var + 7);
73 	if (!c->names[c->index])
74 		return -1;
75 	if (asprintf(&c->paths[c->index], "%s %s", value,
76 		     c->extra_format) < 0)
77 		return -1;
78 	c->index++;
79 	return 0;
80 }
81 
82 /*
83  * Some scripts specify the required events in their "xxx-record" file,
84  * this function will check if the events in perf.data match those
85  * mentioned in the "xxx-record".
86  *
87  * Fixme: All existing "xxx-record" are all in good formats "-e event ",
88  * which is covered well now. And new parsing code should be added to
89  * cover the future complex formats like event groups etc.
90  */
91 static int check_ev_match(int dir_fd, const char *scriptname, struct perf_session *session)
92 {
93 	char line[BUFSIZ];
94 	FILE *fp;
95 
96 	{
97 		char filename[FILENAME_MAX + 5];
98 		int fd;
99 
100 		scnprintf(filename, sizeof(filename), "bin/%s-record", scriptname);
101 		fd = openat(dir_fd, filename, O_RDONLY);
102 		if (fd == -1)
103 			return -1;
104 		fp = fdopen(fd, "r");
105 		if (!fp)
106 			return -1;
107 	}
108 
109 	while (fgets(line, sizeof(line), fp)) {
110 		char *p = skip_spaces(line);
111 
112 		if (*p == '#')
113 			continue;
114 
115 		while (strlen(p)) {
116 			int match, len;
117 			struct evsel *pos;
118 			char evname[128];
119 
120 			p = strstr(p, "-e");
121 			if (!p)
122 				break;
123 
124 			p += 2;
125 			p = skip_spaces(p);
126 			len = strcspn(p, " \t");
127 			if (!len)
128 				break;
129 
130 			snprintf(evname, len + 1, "%s", p);
131 
132 			match = 0;
133 			evlist__for_each_entry(session->evlist, pos) {
134 				if (evsel__name_is(pos, evname)) {
135 					match = 1;
136 					break;
137 				}
138 			}
139 
140 			if (!match) {
141 				fclose(fp);
142 				return -1;
143 			}
144 		}
145 	}
146 
147 	fclose(fp);
148 	return 0;
149 }
150 
151 /*
152  * Return -1 if none is found, otherwise the actual scripts number.
153  *
154  * Currently the only user of this function is the script browser, which
155  * will list all statically runnable scripts, select one, execute it and
156  * show the output in a perf browser.
157  */
158 static int find_scripts(char **scripts_array, char **scripts_path_array, int num,
159 		 int pathlen)
160 {
161 	struct dirent *script_dirent, *lang_dirent;
162 	int scripts_dir_fd, lang_dir_fd;
163 	DIR *scripts_dir, *lang_dir;
164 	struct perf_session *session;
165 	struct perf_data data = {
166 		.path = input_name,
167 		.mode = PERF_DATA_MODE_READ,
168 	};
169 	char *temp;
170 	int i = 0;
171 	const char *exec_path = get_argv_exec_path();
172 
173 	session = perf_session__new(&data, NULL);
174 	if (IS_ERR(session))
175 		return PTR_ERR(session);
176 
177 	{
178 		char scripts_path[PATH_MAX];
179 
180 		snprintf(scripts_path, sizeof(scripts_path), "%s/scripts", exec_path);
181 		scripts_dir_fd = open(scripts_path, O_DIRECTORY);
182 		pr_err("Failed to open directory '%s'", scripts_path);
183 		if (scripts_dir_fd == -1) {
184 			perf_session__delete(session);
185 			return -1;
186 		}
187 	}
188 	scripts_dir = fdopendir(scripts_dir_fd);
189 	if (!scripts_dir) {
190 		close(scripts_dir_fd);
191 		perf_session__delete(session);
192 		return -1;
193 	}
194 
195 	while ((lang_dirent = readdir(scripts_dir)) != NULL) {
196 		if (lang_dirent->d_type != DT_DIR &&
197 		    (lang_dirent->d_type == DT_UNKNOWN &&
198 		     !is_directory_at(scripts_dir_fd, lang_dirent->d_name)))
199 			continue;
200 		if (!strcmp(lang_dirent->d_name, ".") || !strcmp(lang_dirent->d_name, ".."))
201 			continue;
202 
203 #ifndef HAVE_LIBPERL_SUPPORT
204 		if (strstr(lang_dirent->d_name, "perl"))
205 			continue;
206 #endif
207 #ifndef HAVE_LIBPYTHON_SUPPORT
208 		if (strstr(lang_dirent->d_name, "python"))
209 			continue;
210 #endif
211 
212 		lang_dir_fd = openat(scripts_dir_fd, lang_dirent->d_name, O_DIRECTORY);
213 		if (lang_dir_fd == -1)
214 			continue;
215 		lang_dir = fdopendir(lang_dir_fd);
216 		if (!lang_dir) {
217 			close(lang_dir_fd);
218 			continue;
219 		}
220 		while ((script_dirent = readdir(lang_dir)) != NULL) {
221 			if (script_dirent->d_type == DT_DIR)
222 				continue;
223 			if (script_dirent->d_type == DT_UNKNOWN &&
224 			    is_directory_at(lang_dir_fd, script_dirent->d_name))
225 				continue;
226 			/* Skip those real time scripts: xxxtop.p[yl] */
227 			if (strstr(script_dirent->d_name, "top."))
228 				continue;
229 			if (i >= num)
230 				break;
231 			scnprintf(scripts_path_array[i], pathlen, "%s/scripts/%s/%s",
232 				exec_path,
233 				lang_dirent->d_name,
234 				script_dirent->d_name);
235 			temp = strchr(script_dirent->d_name, '.');
236 			snprintf(scripts_array[i],
237 				(temp - script_dirent->d_name) + 1,
238 				"%s", script_dirent->d_name);
239 
240 			if (check_ev_match(lang_dir_fd, scripts_array[i], session))
241 				continue;
242 
243 			i++;
244 		}
245 		closedir(lang_dir);
246 	}
247 
248 	closedir(scripts_dir);
249 	perf_session__delete(session);
250 	return i;
251 }
252 
253 /*
254  * When success, will copy the full path of the selected script
255  * into  the buffer pointed by script_name, and return 0.
256  * Return -1 on failure.
257  */
258 static int list_scripts(char *script_name, bool *custom,
259 			struct evsel *evsel)
260 {
261 	char *buf, *paths[SCRIPT_MAX_NO], *names[SCRIPT_MAX_NO];
262 	int i, num, choice;
263 	int ret = 0;
264 	int max_std, custom_perf;
265 	char pbuf[256];
266 	const char *perf = perf_exe(pbuf, sizeof pbuf);
267 	struct script_config scriptc = {
268 		.names = (const char **)names,
269 		.paths = paths,
270 		.perf = perf
271 	};
272 
273 	script_name[0] = 0;
274 
275 	/* Preset the script name to SCRIPT_NAMELEN */
276 	buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN));
277 	if (!buf)
278 		return -1;
279 
280 	if (evsel)
281 		attr_to_script(scriptc.extra_format, &evsel->core.attr);
282 	add_script_option("Show individual samples", "", &scriptc);
283 	add_script_option("Show individual samples with assembler", "-F +disasm",
284 			  &scriptc);
285 	add_script_option("Show individual samples with source", "-F +srcline,+srccode",
286 			  &scriptc);
287 	perf_config(scripts_config, &scriptc);
288 	custom_perf = scriptc.index;
289 	add_script_option("Show samples with custom perf script arguments", "", &scriptc);
290 	i = scriptc.index;
291 	max_std = i;
292 
293 	for (; i < SCRIPT_MAX_NO; i++) {
294 		names[i] = buf + (i - max_std) * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN);
295 		paths[i] = names[i] + SCRIPT_NAMELEN;
296 	}
297 
298 	num = find_scripts(names + max_std, paths + max_std, SCRIPT_MAX_NO - max_std,
299 			SCRIPT_FULLPATH_LEN);
300 	if (num < 0)
301 		num = 0;
302 	choice = ui__popup_menu(num + max_std, (char * const *)names, NULL);
303 	if (choice < 0) {
304 		ret = -1;
305 		goto out;
306 	}
307 	if (choice == custom_perf) {
308 		char script_args[50];
309 		int key = ui_browser__input_window("perf script command",
310 				"Enter perf script command line (without perf script prefix)",
311 				script_args, "", 0);
312 		if (key != K_ENTER) {
313 			ret = -1;
314 			goto out;
315 		}
316 		sprintf(script_name, "%s script %s", perf, script_args);
317 	} else if (choice < num + max_std) {
318 		strcpy(script_name, paths[choice]);
319 	}
320 	*custom = choice >= max_std;
321 
322 out:
323 	free(buf);
324 	for (i = 0; i < max_std; i++)
325 		zfree(&paths[i]);
326 	return ret;
327 }
328 
329 void run_script(char *cmd)
330 {
331 	pr_debug("Running %s\n", cmd);
332 	SLang_reset_tty();
333 	if (system(cmd) < 0)
334 		pr_warning("Cannot run %s\n", cmd);
335 	/*
336 	 * SLang doesn't seem to reset the whole terminal, so be more
337 	 * forceful to get back to the original state.
338 	 */
339 	printf("\033[c\033[H\033[J");
340 	fflush(stdout);
341 	SLang_init_tty(0, 0, 0);
342 	SLtty_set_suspend_state(true);
343 	SLsmg_refresh();
344 }
345 
346 int script_browse(const char *script_opt, struct evsel *evsel)
347 {
348 	char *cmd, script_name[SCRIPT_FULLPATH_LEN];
349 	bool custom = false;
350 
351 	memset(script_name, 0, SCRIPT_FULLPATH_LEN);
352 	if (list_scripts(script_name, &custom, evsel))
353 		return -1;
354 
355 	if (asprintf(&cmd, "%s%s %s %s%s 2>&1 | less",
356 			custom ? "perf script -s " : "",
357 			script_name,
358 			script_opt ? script_opt : "",
359 			input_name ? "-i " : "",
360 			input_name ? input_name : "") < 0)
361 		return -1;
362 
363 	run_script(cmd);
364 	free(cmd);
365 
366 	return 0;
367 }
368