File Index
Files and directories
Sign in
FILE

scripts.c

File preview with syntax highlighting.

linux-6.19.14/tools/perf/ui/browsers/scripts.c C
  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
 28struct script_config {
 29	const char **names;
 30	char **paths;
 31	int index;
 32	const char *perf;
 33	char extra_format[256];
 34};
 35
 36void 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
 51static 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
 64static 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 */
 91static 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[NAME_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 */
158static 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 */
258static 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
322out:
323	free(buf);
324	for (i = 0; i < max_std; i++)
325		zfree(&paths[i]);
326	return ret;
327}
328
329void 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
346int 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}
~/ Read-only file preview.