Skip to content
Snippets Groups Projects
builtin-script.c 37.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • {
    	char *script_root, *str;
    
    	script_root = strdup(script_dirent->d_name);
    	if (!script_root)
    		return NULL;
    
    	str = (char *)ends_with(script_root, suffix);
    	if (!str) {
    		free(script_root);
    		return NULL;
    	}
    
    	*str = '\0';
    	return script_root;
    }
    
    
    static int list_available_scripts(const struct option *opt __maybe_unused,
    				  const char *s __maybe_unused,
    				  int unset __maybe_unused)
    
    {
    	struct dirent *script_next, *lang_next, script_dirent, lang_dirent;
    	char scripts_path[MAXPATHLEN];
    	DIR *scripts_dir, *lang_dir;
    	char script_path[MAXPATHLEN];
    	char lang_path[MAXPATHLEN];
    	struct script_desc *desc;
    	char first_half[BUFSIZ];
    	char *script_root;
    
    	snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path());
    
    	scripts_dir = opendir(scripts_path);
    	if (!scripts_dir)
    		return -1;
    
    
    	for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) {
    
    		snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path,
    			 lang_dirent.d_name);
    		lang_dir = opendir(lang_path);
    		if (!lang_dir)
    			continue;
    
    
    		for_each_script(lang_path, lang_dir, script_dirent, script_next) {
    
    			script_root = get_script_root(&script_dirent, REPORT_SUFFIX);
    			if (script_root) {
    
    				desc = script_desc__findnew(script_root);
    				snprintf(script_path, MAXPATHLEN, "%s/%s",
    					 lang_path, script_dirent.d_name);
    				read_script_info(desc, script_path);
    
    			}
    		}
    	}
    
    	fprintf(stdout, "List of available trace scripts:\n");
    	list_for_each_entry(desc, &script_descs, node) {
    		sprintf(first_half, "%s %s", desc->name,
    			desc->args ? desc->args : "");
    		fprintf(stdout, "  %-36s %s\n", first_half,
    			desc->half_liner ? desc->half_liner : "");
    	}
    
    	exit(0);
    }
    
    
    /*
     * Some scripts specify the required events in their "xxx-record" file,
     * this function will check if the events in perf.data match those
     * mentioned in the "xxx-record".
     *
     * Fixme: All existing "xxx-record" are all in good formats "-e event ",
     * which is covered well now. And new parsing code should be added to
     * cover the future complexing formats like event groups etc.
     */
    static int check_ev_match(char *dir_name, char *scriptname,
    			struct perf_session *session)
    {
    	char filename[MAXPATHLEN], evname[128];
    	char line[BUFSIZ], *p;
    	struct perf_evsel *pos;
    	int match, len;
    	FILE *fp;
    
    	sprintf(filename, "%s/bin/%s-record", dir_name, scriptname);
    
    	fp = fopen(filename, "r");
    	if (!fp)
    		return -1;
    
    	while (fgets(line, sizeof(line), fp)) {
    		p = ltrim(line);
    		if (*p == '#')
    			continue;
    
    		while (strlen(p)) {
    			p = strstr(p, "-e");
    			if (!p)
    				break;
    
    			p += 2;
    			p = ltrim(p);
    			len = strcspn(p, " \t");
    			if (!len)
    				break;
    
    			snprintf(evname, len + 1, "%s", p);
    
    			match = 0;
    			list_for_each_entry(pos,
    					&session->evlist->entries, node) {
    				if (!strcmp(perf_evsel__name(pos), evname)) {
    					match = 1;
    					break;
    				}
    			}
    
    			if (!match) {
    				fclose(fp);
    				return -1;
    			}
    		}
    	}
    
    	fclose(fp);
    	return 0;
    }
    
    
    /*
     * Return -1 if none is found, otherwise the actual scripts number.
     *
     * Currently the only user of this function is the script browser, which
     * will list all statically runnable scripts, select one, execute it and
     * show the output in a perf browser.
     */
    int find_scripts(char **scripts_array, char **scripts_path_array)
    {
    	struct dirent *script_next, *lang_next, script_dirent, lang_dirent;
    
    	char scripts_path[MAXPATHLEN], lang_path[MAXPATHLEN];
    
    	DIR *scripts_dir, *lang_dir;
    
    	struct perf_session *session;
    
    	struct perf_data_file file = {
    		.path = input_name,
    		.mode = PERF_DATA_MODE_READ,
    	};
    
    	session = perf_session__new(&file, false, NULL);
    
    	snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path());
    
    	scripts_dir = opendir(scripts_path);
    
    	if (!scripts_dir) {
    		perf_session__delete(session);
    
    
    	for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) {
    		snprintf(lang_path, MAXPATHLEN, "%s/%s", scripts_path,
    			 lang_dirent.d_name);
    #ifdef NO_LIBPERL
    		if (strstr(lang_path, "perl"))
    			continue;
    #endif
    #ifdef NO_LIBPYTHON
    		if (strstr(lang_path, "python"))
    			continue;
    #endif
    
    		lang_dir = opendir(lang_path);
    		if (!lang_dir)
    			continue;
    
    		for_each_script(lang_path, lang_dir, script_dirent, script_next) {
    			/* Skip those real time scripts: xxxtop.p[yl] */
    			if (strstr(script_dirent.d_name, "top."))
    				continue;
    			sprintf(scripts_path_array[i], "%s/%s", lang_path,
    				script_dirent.d_name);
    			temp = strchr(script_dirent.d_name, '.');
    			snprintf(scripts_array[i],
    				(temp - script_dirent.d_name) + 1,
    				"%s", script_dirent.d_name);
    
    
    			if (check_ev_match(lang_path,
    					scripts_array[i], session))
    				continue;
    
    
    		closedir(lang_dir);
    
    	closedir(scripts_dir);
    	perf_session__delete(session);
    
    static char *get_script_path(const char *script_root, const char *suffix)
    {
    	struct dirent *script_next, *lang_next, script_dirent, lang_dirent;
    	char scripts_path[MAXPATHLEN];
    	char script_path[MAXPATHLEN];
    	DIR *scripts_dir, *lang_dir;
    	char lang_path[MAXPATHLEN];
    
    
    	snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path());
    
    	scripts_dir = opendir(scripts_path);
    	if (!scripts_dir)
    		return NULL;
    
    
    	for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) {
    
    		snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path,
    			 lang_dirent.d_name);
    		lang_dir = opendir(lang_path);
    		if (!lang_dir)
    			continue;
    
    
    		for_each_script(lang_path, lang_dir, script_dirent, script_next) {
    
    			__script_root = get_script_root(&script_dirent, suffix);
    			if (__script_root && !strcmp(script_root, __script_root)) {
    				free(__script_root);
    
    				closedir(lang_dir);
    				closedir(scripts_dir);
    
    				snprintf(script_path, MAXPATHLEN, "%s/%s",
    					 lang_path, script_dirent.d_name);
    
    		closedir(lang_dir);
    
    	closedir(scripts_dir);
    
    static bool is_top_script(const char *script_path)
    {
    
    	return ends_with(script_path, "top") == NULL ? false : true;
    
    }
    
    static int has_required_arg(char *script_path)
    {
    	struct script_desc *desc;
    	int n_args = 0;
    	char *p;
    
    	desc = script_desc__new(NULL);
    
    	if (read_script_info(desc, script_path))
    		goto out;
    
    	if (!desc->args)
    		goto out;
    
    	for (p = desc->args; *p; p++)
    		if (*p == '<')
    			n_args++;
    out:
    	script_desc__delete(desc);
    
    	return n_args;
    }
    
    
    static int have_cmd(int argc, const char **argv)
    {
    	char **__argv = malloc(sizeof(const char *) * argc);
    
    	if (!__argv) {
    		pr_err("malloc failed\n");
    		return -1;
    	}
    
    	memcpy(__argv, argv, sizeof(const char *) * argc);
    	argc = parse_options(argc, (const char **)__argv, record_options,
    			     NULL, PARSE_OPT_STOP_AT_NON_OPTION);
    	free(__argv);
    
    	system_wide = (argc == 0);
    
    	return 0;
    }
    
    int cmd_script(int argc, const char **argv, const char *prefix __maybe_unused)
    {
    	bool show_full_info = false;
    	char *rec_script_path = NULL;
    	char *rep_script_path = NULL;
    	struct perf_session *session;
    	char *script_path = NULL;
    	const char **__argv;
    	int i, j, err;
    
    	struct perf_script script = {
    		.tool = {
    			.sample		 = process_sample_event,
    			.mmap		 = perf_event__process_mmap,
    			.mmap2		 = perf_event__process_mmap2,
    			.comm		 = perf_event__process_comm,
    			.exit		 = perf_event__process_exit,
    			.fork		 = perf_event__process_fork,
    
    			.tracing_data	 = perf_event__process_tracing_data,
    			.build_id	 = perf_event__process_build_id,
    			.ordered_samples = true,
    			.ordering_requires_timestamps = true,
    		},
    	};
    
    	const struct option options[] = {
    
    	OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
    		    "dump raw trace in ASCII"),
    
    		 "be more verbose (show symbol address, etc)"),
    
    	OPT_BOOLEAN('L', "Latency", &latency_format,
    
    		    "show latency attributes (irqs/preemption disabled, etc)"),
    
    	OPT_CALLBACK_NOOPT('l', "list", NULL, NULL, "list available scripts",
    			   list_available_scripts),
    
    	OPT_CALLBACK('s', "script", NULL, "name",
    		     "script file name (lang:script name, script name, or *)",
    		     parse_scriptname),
    	OPT_STRING('g', "gen-script", &generate_script_lang, "lang",
    
    		   "generate perf-script.xx script in specified language"),
    
    	OPT_STRING('i', "input", &input_name, "file", "input file name"),
    
    	OPT_BOOLEAN('d', "debug-mode", &debug_mode,
    		   "do various checks like samples ordering and lost events"),
    
    	OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
    		   "file", "vmlinux pathname"),
    	OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name,
    		   "file", "kallsyms pathname"),
    	OPT_BOOLEAN('G', "hide-call-graph", &no_callchain,
    		    "When printing symbols do not display call chain"),
    	OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
    		    "Look for files with symbols relative to this directory"),
    
    	OPT_CALLBACK('f', "fields", NULL, "str",
    
    		     "comma separated output fields prepend with 'type:'. "
    		     "Valid types: hw,sw,trace,raw. "
    		     "Fields: comm,tid,pid,time,cpu,event,trace,ip,sym,dso,"
    
    		     "addr,symoff", parse_output_fields),
    
    	OPT_BOOLEAN('a', "all-cpus", &system_wide,
    
    		    "system-wide collection from all CPUs"),
    
    	OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
    		   "only consider these symbols"),
    
    	OPT_STRING('C', "cpu", &cpu_list, "cpu", "list of cpus to profile"),
    
    	OPT_STRING('c', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
    		   "only display events for these comms"),
    
    	OPT_BOOLEAN('I', "show-info", &show_full_info,
    		    "display extended information from perf.data file"),
    
    	OPT_BOOLEAN('\0', "show-kernel-path", &symbol_conf.show_kernel_path,
    		    "Show the path of [kernel.kallsyms]"),
    
    	};
    	const char * const script_usage[] = {
    		"perf script [<options>]",
    		"perf script [<options>] record <script> [<record-options>] <command>",
    		"perf script [<options>] report <script> [script-args]",
    		"perf script [<options>] <script> [<record-options>] <command>",
    		"perf script [<options>] <top-script> [script-args]",
    		NULL
    	};
    
    	struct perf_data_file file = {
    		.mode = PERF_DATA_MODE_READ,
    	};
    
    	argc = parse_options(argc, argv, options, script_usage,
    
    			     PARSE_OPT_STOP_AT_NON_OPTION);
    
    
    	if (argc > 1 && !strncmp(argv[0], "rec", strlen("rec"))) {
    		rec_script_path = get_script_path(argv[1], RECORD_SUFFIX);
    		if (!rec_script_path)
    			return cmd_record(argc, argv, NULL);
    
    	if (argc > 1 && !strncmp(argv[0], "rep", strlen("rep"))) {
    		rep_script_path = get_script_path(argv[1], REPORT_SUFFIX);
    		if (!rep_script_path) {
    
    				"Please specify a valid report script"
    
    				"(see 'perf script -l' for listing)\n");
    
    	/* make sure PERF_EXEC_PATH is set for scripts */
    	perf_set_argv_exec_path(perf_exec_path());
    
    
    	if (argc && !script_name && !rec_script_path && !rep_script_path) {
    
    		rec_script_path = get_script_path(argv[0], RECORD_SUFFIX);
    		rep_script_path = get_script_path(argv[0], REPORT_SUFFIX);
    
    		if (!rec_script_path && !rep_script_path) {
    			fprintf(stderr, " Couldn't find script %s\n\n See perf"
    
    				" script -l for available scripts.\n", argv[0]);
    			usage_with_options(script_usage, options);
    
    		if (is_top_script(argv[0])) {
    			rep_args = argc - 1;
    		} else {
    			int rec_args;
    
    			rep_args = has_required_arg(rep_script_path);
    			rec_args = (argc - 1) - rep_args;
    			if (rec_args < 0) {
    				fprintf(stderr, " %s script requires options."
    
    					"\n\n See perf script -l for available "
    
    					"scripts and options.\n", argv[0]);
    
    				usage_with_options(script_usage, options);
    
    		}
    
    		if (pipe(live_pipe) < 0) {
    			perror("failed to create pipe");
    
    			return -1;
    
    		}
    
    		pid = fork();
    		if (pid < 0) {
    			perror("failed to fork");
    
    			return -1;
    
    			if (is_top_script(argv[0])) {
    				system_wide = true;
    			} else if (!system_wide) {
    
    				if (have_cmd(argc - rep_args, &argv[rep_args]) != 0) {
    					err = -1;
    					goto out;
    				}
    
    
    			__argv = malloc((argc + 6) * sizeof(const char *));
    
    			if (!__argv) {
    				pr_err("malloc failed\n");
    				err = -ENOMEM;
    				goto out;
    			}
    
    			__argv[j++] = "/bin/sh";
    			__argv[j++] = rec_script_path;
    			if (system_wide)
    				__argv[j++] = "-a";
    			__argv[j++] = "-q";
    			__argv[j++] = "-o";
    			__argv[j++] = "-";
    			for (i = rep_args + 1; i < argc; i++)
    				__argv[j++] = argv[i];
    			__argv[j++] = NULL;
    
    		__argv = malloc((argc + 4) * sizeof(const char *));
    
    		if (!__argv) {
    			pr_err("malloc failed\n");
    			err = -ENOMEM;
    			goto out;
    		}
    
    
    		j = 0;
    		__argv[j++] = "/bin/sh";
    		__argv[j++] = rep_script_path;
    		for (i = 1; i < rep_args + 1; i++)
    			__argv[j++] = argv[i];
    		__argv[j++] = "-i";
    		__argv[j++] = "-";
    		__argv[j++] = NULL;
    
    	if (rec_script_path)
    		script_path = rec_script_path;
    	if (rep_script_path)
    		script_path = rep_script_path;
    
    	if (script_path) {
    		j = 0;
    
    		if (!rec_script_path)
    			system_wide = false;
    
    		else if (!system_wide) {
    			if (have_cmd(argc - 1, &argv[1]) != 0) {
    				err = -1;
    				goto out;
    			}
    		}
    
    		__argv = malloc((argc + 2) * sizeof(const char *));
    
    		if (!__argv) {
    			pr_err("malloc failed\n");
    			err = -ENOMEM;
    			goto out;
    		}
    
    
    		__argv[j++] = "/bin/sh";
    		__argv[j++] = script_path;
    		if (system_wide)
    			__argv[j++] = "-a";
    
    		for (i = 2; i < argc; i++)
    
    			__argv[j++] = argv[i];
    		__argv[j++] = NULL;
    
    
    		execvp("/bin/sh", (char **)__argv);
    
    	if (symbol__init() < 0)
    		return -1;
    
    	if (!script_name)
    		setup_pager();
    
    	session = perf_session__new(&file, false, &script.tool);
    
    	script.session = session;
    
    
    	if (cpu_list) {
    		if (perf_session__cpu_bitmap(session, cpu_list, cpu_bitmap))
    			return -1;
    	}
    
    
    	if (!script_name && !generate_script_lang)
    		perf_session__fprintf_info(session, stdout, show_full_info);
    
    	if (!no_callchain)
    
    		symbol_conf.use_callchain = true;
    	else
    		symbol_conf.use_callchain = false;
    
    
    	if (generate_script_lang) {
    		struct stat perf_stat;
    
    		if (output_set_by_user()) {
    
    			fprintf(stderr,
    				"custom fields not supported for generated scripts");
    			return -1;
    		}
    
    		input = open(file.path, O_RDONLY);	/* input_name */
    
    		if (input < 0) {
    			perror("failed to open file");
    
    			return -1;
    
    		}
    
    		err = fstat(input, &perf_stat);
    		if (err < 0) {
    			perror("failed to stat file");
    
    			return -1;
    
    		}
    
    		if (!perf_stat.st_size) {
    			fprintf(stderr, "zero-sized file, nothing to do!\n");
    
    			return 0;
    
    		}
    
    		scripting_ops = script_spec__lookup(generate_script_lang);
    		if (!scripting_ops) {
    			fprintf(stderr, "invalid language specifier");
    			return -1;
    		}
    
    
    		err = scripting_ops->generate_script(session->pevent,
    						     "perf-script");
    
    		goto out;
    	}
    
    	if (script_name) {
    
    		err = scripting_ops->start_script(script_name, argc, argv);
    
    		if (err)
    			goto out;
    
    		pr_debug("perf script started with script %s\n\n", script_name);
    
    
    	err = perf_session__check_output_opt(session);
    	if (err < 0)
    		goto out;
    
    
    	err = __cmd_script(&script);
    
    	cleanup_scripting();
    out:
    	return err;