diff options
Diffstat (limited to 'tools/perf/perf.c')
| -rw-r--r-- | tools/perf/perf.c | 428 | 
1 files changed, 428 insertions, 0 deletions
diff --git a/tools/perf/perf.c b/tools/perf/perf.c new file mode 100644 index 00000000000..4eb72593370 --- /dev/null +++ b/tools/perf/perf.c @@ -0,0 +1,428 @@ +/* + * perf.c + * + * Performance analysis utility. + * + * This is the main hub from which the sub-commands (perf stat, + * perf top, perf record, perf report, etc.) are started. + */ +#include "builtin.h" + +#include "util/exec_cmd.h" +#include "util/cache.h" +#include "util/quote.h" +#include "util/run-command.h" + +const char perf_usage_string[] = +	"perf [--version] [--help] COMMAND [ARGS]"; + +const char perf_more_info_string[] = +	"See 'perf help COMMAND' for more information on a specific command."; + +static int use_pager = -1; +struct pager_config { +	const char *cmd; +	int val; +}; + +static int pager_command_config(const char *var, const char *value, void *data) +{ +	struct pager_config *c = data; +	if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) +		c->val = perf_config_bool(var, value); +	return 0; +} + +/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */ +int check_pager_config(const char *cmd) +{ +	struct pager_config c; +	c.cmd = cmd; +	c.val = -1; +	perf_config(pager_command_config, &c); +	return c.val; +} + +static void commit_pager_choice(void) { +	switch (use_pager) { +	case 0: +		setenv("PERF_PAGER", "cat", 1); +		break; +	case 1: +		/* setup_pager(); */ +		break; +	default: +		break; +	} +} + +static int handle_options(const char*** argv, int* argc, int* envchanged) +{ +	int handled = 0; + +	while (*argc > 0) { +		const char *cmd = (*argv)[0]; +		if (cmd[0] != '-') +			break; + +		/* +		 * For legacy reasons, the "version" and "help" +		 * commands can be written with "--" prepended +		 * to make them look like flags. +		 */ +		if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version")) +			break; + +		/* +		 * Check remaining flags. +		 */ +		if (!prefixcmp(cmd, "--exec-path")) { +			cmd += 11; +			if (*cmd == '=') +				perf_set_argv_exec_path(cmd + 1); +			else { +				puts(perf_exec_path()); +				exit(0); +			} +		} else if (!strcmp(cmd, "--html-path")) { +			puts(system_path(PERF_HTML_PATH)); +			exit(0); +		} else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { +			use_pager = 1; +		} else if (!strcmp(cmd, "--no-pager")) { +			use_pager = 0; +			if (envchanged) +				*envchanged = 1; +		} else if (!strcmp(cmd, "--perf-dir")) { +			if (*argc < 2) { +				fprintf(stderr, "No directory given for --perf-dir.\n" ); +				usage(perf_usage_string); +			} +			setenv(PERF_DIR_ENVIRONMENT, (*argv)[1], 1); +			if (envchanged) +				*envchanged = 1; +			(*argv)++; +			(*argc)--; +			handled++; +		} else if (!prefixcmp(cmd, "--perf-dir=")) { +			setenv(PERF_DIR_ENVIRONMENT, cmd + 10, 1); +			if (envchanged) +				*envchanged = 1; +		} else if (!strcmp(cmd, "--work-tree")) { +			if (*argc < 2) { +				fprintf(stderr, "No directory given for --work-tree.\n" ); +				usage(perf_usage_string); +			} +			setenv(PERF_WORK_TREE_ENVIRONMENT, (*argv)[1], 1); +			if (envchanged) +				*envchanged = 1; +			(*argv)++; +			(*argc)--; +		} else if (!prefixcmp(cmd, "--work-tree=")) { +			setenv(PERF_WORK_TREE_ENVIRONMENT, cmd + 12, 1); +			if (envchanged) +				*envchanged = 1; +		} else { +			fprintf(stderr, "Unknown option: %s\n", cmd); +			usage(perf_usage_string); +		} + +		(*argv)++; +		(*argc)--; +		handled++; +	} +	return handled; +} + +static int handle_alias(int *argcp, const char ***argv) +{ +	int envchanged = 0, ret = 0, saved_errno = errno; +	int count, option_count; +	const char** new_argv; +	const char *alias_command; +	char *alias_string; + +	alias_command = (*argv)[0]; +	alias_string = alias_lookup(alias_command); +	if (alias_string) { +		if (alias_string[0] == '!') { +			if (*argcp > 1) { +				struct strbuf buf; + +				strbuf_init(&buf, PATH_MAX); +				strbuf_addstr(&buf, alias_string); +				sq_quote_argv(&buf, (*argv) + 1, PATH_MAX); +				free(alias_string); +				alias_string = buf.buf; +			} +			ret = system(alias_string + 1); +			if (ret >= 0 && WIFEXITED(ret) && +			    WEXITSTATUS(ret) != 127) +				exit(WEXITSTATUS(ret)); +			die("Failed to run '%s' when expanding alias '%s'", +			    alias_string + 1, alias_command); +		} +		count = split_cmdline(alias_string, &new_argv); +		if (count < 0) +			die("Bad alias.%s string", alias_command); +		option_count = handle_options(&new_argv, &count, &envchanged); +		if (envchanged) +			die("alias '%s' changes environment variables\n" +				 "You can use '!perf' in the alias to do this.", +				 alias_command); +		memmove(new_argv - option_count, new_argv, +				count * sizeof(char *)); +		new_argv -= option_count; + +		if (count < 1) +			die("empty alias for %s", alias_command); + +		if (!strcmp(alias_command, new_argv[0])) +			die("recursive alias: %s", alias_command); + +		new_argv = realloc(new_argv, sizeof(char*) * +				    (count + *argcp + 1)); +		/* insert after command name */ +		memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); +		new_argv[count+*argcp] = NULL; + +		*argv = new_argv; +		*argcp += count - 1; + +		ret = 1; +	} + +	errno = saved_errno; + +	return ret; +} + +const char perf_version_string[] = PERF_VERSION; + +#define RUN_SETUP	(1<<0) +#define USE_PAGER	(1<<1) +/* + * require working tree to be present -- anything uses this needs + * RUN_SETUP for reading from the configuration file. + */ +#define NEED_WORK_TREE	(1<<2) + +struct cmd_struct { +	const char *cmd; +	int (*fn)(int, const char **, const char *); +	int option; +}; + +static int run_builtin(struct cmd_struct *p, int argc, const char **argv) +{ +	int status; +	struct stat st; +	const char *prefix; + +	prefix = NULL; +	if (p->option & RUN_SETUP) +		prefix = NULL; /* setup_perf_directory(); */ + +	if (use_pager == -1 && p->option & RUN_SETUP) +		use_pager = check_pager_config(p->cmd); +	if (use_pager == -1 && p->option & USE_PAGER) +		use_pager = 1; +	commit_pager_choice(); + +	if (p->option & NEED_WORK_TREE) +		/* setup_work_tree() */; + +	status = p->fn(argc, argv, prefix); +	if (status) +		return status & 0xff; + +	/* Somebody closed stdout? */ +	if (fstat(fileno(stdout), &st)) +		return 0; +	/* Ignore write errors for pipes and sockets.. */ +	if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) +		return 0; + +	/* Check for ENOSPC and EIO errors.. */ +	if (fflush(stdout)) +		die("write failure on standard output: %s", strerror(errno)); +	if (ferror(stdout)) +		die("unknown write failure on standard output"); +	if (fclose(stdout)) +		die("close failed on standard output: %s", strerror(errno)); +	return 0; +} + +static void handle_internal_command(int argc, const char **argv) +{ +	const char *cmd = argv[0]; +	static struct cmd_struct commands[] = { +		{ "help", cmd_help, 0 }, +		{ "list", cmd_list, 0 }, +		{ "record", cmd_record, 0 }, +		{ "report", cmd_report, 0 }, +		{ "stat", cmd_stat, 0 }, +		{ "top", cmd_top, 0 }, +		{ "annotate", cmd_annotate, 0 }, +		{ "version", cmd_version, 0 }, +	}; +	int i; +	static const char ext[] = STRIP_EXTENSION; + +	if (sizeof(ext) > 1) { +		i = strlen(argv[0]) - strlen(ext); +		if (i > 0 && !strcmp(argv[0] + i, ext)) { +			char *argv0 = strdup(argv[0]); +			argv[0] = cmd = argv0; +			argv0[i] = '\0'; +		} +	} + +	/* Turn "perf cmd --help" into "perf help cmd" */ +	if (argc > 1 && !strcmp(argv[1], "--help")) { +		argv[1] = argv[0]; +		argv[0] = cmd = "help"; +	} + +	for (i = 0; i < ARRAY_SIZE(commands); i++) { +		struct cmd_struct *p = commands+i; +		if (strcmp(p->cmd, cmd)) +			continue; +		exit(run_builtin(p, argc, argv)); +	} +} + +static void execv_dashed_external(const char **argv) +{ +	struct strbuf cmd = STRBUF_INIT; +	const char *tmp; +	int status; + +	strbuf_addf(&cmd, "perf-%s", argv[0]); + +	/* +	 * argv[0] must be the perf command, but the argv array +	 * belongs to the caller, and may be reused in +	 * subsequent loop iterations. Save argv[0] and +	 * restore it on error. +	 */ +	tmp = argv[0]; +	argv[0] = cmd.buf; + +	/* +	 * if we fail because the command is not found, it is +	 * OK to return. Otherwise, we just pass along the status code. +	 */ +	status = run_command_v_opt(argv, 0); +	if (status != -ERR_RUN_COMMAND_EXEC) { +		if (IS_RUN_COMMAND_ERR(status)) +			die("unable to run '%s'", argv[0]); +		exit(-status); +	} +	errno = ENOENT; /* as if we called execvp */ + +	argv[0] = tmp; + +	strbuf_release(&cmd); +} + +static int run_argv(int *argcp, const char ***argv) +{ +	int done_alias = 0; + +	while (1) { +		/* See if it's an internal command */ +		handle_internal_command(*argcp, *argv); + +		/* .. then try the external ones */ +		execv_dashed_external(*argv); + +		/* It could be an alias -- this works around the insanity +		 * of overriding "perf log" with "perf show" by having +		 * alias.log = show +		 */ +		if (done_alias || !handle_alias(argcp, argv)) +			break; +		done_alias = 1; +	} + +	return done_alias; +} + + +int main(int argc, const char **argv) +{ +	const char *cmd; + +	cmd = perf_extract_argv0_path(argv[0]); +	if (!cmd) +		cmd = "perf-help"; + +	/* +	 * "perf-xxxx" is the same as "perf xxxx", but we obviously: +	 * +	 *  - cannot take flags in between the "perf" and the "xxxx". +	 *  - cannot execute it externally (since it would just do +	 *    the same thing over again) +	 * +	 * So we just directly call the internal command handler, and +	 * die if that one cannot handle it. +	 */ +	if (!prefixcmp(cmd, "perf-")) { +		cmd += 5; +		argv[0] = cmd; +		handle_internal_command(argc, argv); +		die("cannot handle %s internally", cmd); +	} + +	/* Look for flags.. */ +	argv++; +	argc--; +	handle_options(&argv, &argc, NULL); +	commit_pager_choice(); +	if (argc > 0) { +		if (!prefixcmp(argv[0], "--")) +			argv[0] += 2; +	} else { +		/* The user didn't specify a command; give them help */ +		printf("\n usage: %s\n\n", perf_usage_string); +		list_common_cmds_help(); +		printf("\n %s\n\n", perf_more_info_string); +		exit(1); +	} +	cmd = argv[0]; + +	/* +	 * We use PATH to find perf commands, but we prepend some higher +	 * precidence paths: the "--exec-path" option, the PERF_EXEC_PATH +	 * environment, and the $(perfexecdir) from the Makefile at build +	 * time. +	 */ +	setup_path(); + +	while (1) { +		static int done_help = 0; +		static int was_alias = 0; + +		was_alias = run_argv(&argc, &argv); +		if (errno != ENOENT) +			break; + +		if (was_alias) { +			fprintf(stderr, "Expansion of alias '%s' failed; " +				"'%s' is not a perf-command\n", +				cmd, argv[0]); +			exit(1); +		} +		if (!done_help) { +			cmd = argv[0] = help_unknown_cmd(cmd); +			done_help = 1; +		} else +			break; +	} + +	fprintf(stderr, "Failed to run command '%s': %s\n", +		cmd, strerror(errno)); + +	return 1; +}  |