diff options
Diffstat (limited to 'tools/perf/builtin-record.c')
| -rw-r--r-- | tools/perf/builtin-record.c | 267 | 
1 files changed, 220 insertions, 47 deletions
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 515510ecc76a..f3679c44d3f3 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -29,10 +29,12 @@  #include "util/data.h"  #include "util/perf_regs.h"  #include "util/auxtrace.h" +#include "util/tsc.h"  #include "util/parse-branch-options.h"  #include "util/parse-regs-options.h"  #include "util/llvm-utils.h"  #include "util/bpf-loader.h" +#include "util/trigger.h"  #include "asm/bug.h"  #include <unistd.h> @@ -55,6 +57,8 @@ struct record {  	bool			no_buildid_cache;  	bool			no_buildid_cache_set;  	bool			buildid_all; +	bool			timestamp_filename; +	bool			switch_output;  	unsigned long long	samples;  }; @@ -124,9 +128,10 @@ out:  static volatile int done;  static volatile int signr = -1;  static volatile int child_finished; -static volatile int auxtrace_snapshot_enabled; -static volatile int auxtrace_snapshot_err; +  static volatile int auxtrace_record__snapshot_started; +static DEFINE_TRIGGER(auxtrace_snapshot_trigger); +static DEFINE_TRIGGER(switch_output_trigger);  static void sig_handler(int sig)  { @@ -244,11 +249,12 @@ static void record__read_auxtrace_snapshot(struct record *rec)  {  	pr_debug("Recording AUX area tracing snapshot\n");  	if (record__auxtrace_read_snapshot_all(rec) < 0) { -		auxtrace_snapshot_err = -1; +		trigger_error(&auxtrace_snapshot_trigger);  	} else { -		auxtrace_snapshot_err = auxtrace_record__snapshot_finish(rec->itr); -		if (!auxtrace_snapshot_err) -			auxtrace_snapshot_enabled = 1; +		if (auxtrace_record__snapshot_finish(rec->itr)) +			trigger_error(&auxtrace_snapshot_trigger); +		else +			trigger_ready(&auxtrace_snapshot_trigger);  	}  } @@ -283,7 +289,7 @@ static int record__open(struct record *rec)  	struct record_opts *opts = &rec->opts;  	int rc = 0; -	perf_evlist__config(evlist, opts); +	perf_evlist__config(evlist, opts, &callchain_param);  	evlist__for_each(evlist, pos) {  try_again: @@ -494,6 +500,73 @@ record__finish_output(struct record *rec)  	return;  } +static int record__synthesize_workload(struct record *rec) +{ +	struct { +		struct thread_map map; +		struct thread_map_data map_data; +	} thread_map; + +	thread_map.map.nr = 1; +	thread_map.map.map[0].pid = rec->evlist->workload.pid; +	thread_map.map.map[0].comm = NULL; +	return perf_event__synthesize_thread_map(&rec->tool, &thread_map.map, +						 process_synthesized_event, +						 &rec->session->machines.host, +						 rec->opts.sample_address, +						 rec->opts.proc_map_timeout); +} + +static int record__synthesize(struct record *rec); + +static int +record__switch_output(struct record *rec, bool at_exit) +{ +	struct perf_data_file *file = &rec->file; +	int fd, err; + +	/* Same Size:      "2015122520103046"*/ +	char timestamp[] = "InvalidTimestamp"; + +	rec->samples = 0; +	record__finish_output(rec); +	err = fetch_current_timestamp(timestamp, sizeof(timestamp)); +	if (err) { +		pr_err("Failed to get current timestamp\n"); +		return -EINVAL; +	} + +	fd = perf_data_file__switch(file, timestamp, +				    rec->session->header.data_offset, +				    at_exit); +	if (fd >= 0 && !at_exit) { +		rec->bytes_written = 0; +		rec->session->header.data_size = 0; +	} + +	if (!quiet) +		fprintf(stderr, "[ perf record: Dump %s.%s ]\n", +			file->path, timestamp); + +	/* Output tracking events */ +	if (!at_exit) { +		record__synthesize(rec); + +		/* +		 * In 'perf record --switch-output' without -a, +		 * record__synthesize() in record__switch_output() won't +		 * generate tracking events because there's no thread_map +		 * in evlist. Which causes newly created perf.data doesn't +		 * contain map and comm information. +		 * Create a fake thread_map and directly call +		 * perf_event__synthesize_thread_map() for those events. +		 */ +		if (target__none(&rec->opts.target)) +			record__synthesize_workload(rec); +	} +	return fd; +} +  static volatile int workload_exec_errno;  /* @@ -512,6 +585,15 @@ static void workload_exec_failed_signal(int signo __maybe_unused,  static void snapshot_sig_handler(int sig); +int __weak +perf_event__synth_time_conv(const struct perf_event_mmap_page *pc __maybe_unused, +			    struct perf_tool *tool __maybe_unused, +			    perf_event__handler_t process __maybe_unused, +			    struct machine *machine __maybe_unused) +{ +	return 0; +} +  static int record__synthesize(struct record *rec)  {  	struct perf_session *session = rec->session; @@ -549,6 +631,11 @@ static int record__synthesize(struct record *rec)  		}  	} +	err = perf_event__synth_time_conv(rec->evlist->mmap[0].base, tool, +					  process_synthesized_event, machine); +	if (err) +		goto out; +  	if (rec->opts.full_auxtrace) {  		err = perf_event__synthesize_auxtrace_info(rec->itr, tool,  					session, process_synthesized_event); @@ -600,10 +687,16 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)  	signal(SIGCHLD, sig_handler);  	signal(SIGINT, sig_handler);  	signal(SIGTERM, sig_handler); -	if (rec->opts.auxtrace_snapshot_mode) + +	if (rec->opts.auxtrace_snapshot_mode || rec->switch_output) {  		signal(SIGUSR2, snapshot_sig_handler); -	else +		if (rec->opts.auxtrace_snapshot_mode) +			trigger_on(&auxtrace_snapshot_trigger); +		if (rec->switch_output) +			trigger_on(&switch_output_trigger); +	} else {  		signal(SIGUSR2, SIG_IGN); +	}  	session = perf_session__new(file, false, tool);  	if (session == NULL) { @@ -729,27 +822,45 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)  		perf_evlist__enable(rec->evlist);  	} -	auxtrace_snapshot_enabled = 1; +	trigger_ready(&auxtrace_snapshot_trigger); +	trigger_ready(&switch_output_trigger);  	for (;;) {  		unsigned long long hits = rec->samples;  		if (record__mmap_read_all(rec) < 0) { -			auxtrace_snapshot_enabled = 0; +			trigger_error(&auxtrace_snapshot_trigger); +			trigger_error(&switch_output_trigger);  			err = -1;  			goto out_child;  		}  		if (auxtrace_record__snapshot_started) {  			auxtrace_record__snapshot_started = 0; -			if (!auxtrace_snapshot_err) +			if (!trigger_is_error(&auxtrace_snapshot_trigger))  				record__read_auxtrace_snapshot(rec); -			if (auxtrace_snapshot_err) { +			if (trigger_is_error(&auxtrace_snapshot_trigger)) {  				pr_err("AUX area tracing snapshot failed\n");  				err = -1;  				goto out_child;  			}  		} +		if (trigger_is_hit(&switch_output_trigger)) { +			trigger_ready(&switch_output_trigger); + +			if (!quiet) +				fprintf(stderr, "[ perf record: dump data: Woken up %ld times ]\n", +					waking); +			waking = 0; +			fd = record__switch_output(rec, false); +			if (fd < 0) { +				pr_err("Failed to switch to new file\n"); +				trigger_error(&switch_output_trigger); +				err = fd; +				goto out_child; +			} +		} +  		if (hits == rec->samples) {  			if (done || draining)  				break; @@ -772,12 +883,13 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)  		 * disable events in this case.  		 */  		if (done && !disabled && !target__none(&opts->target)) { -			auxtrace_snapshot_enabled = 0; +			trigger_off(&auxtrace_snapshot_trigger);  			perf_evlist__disable(rec->evlist);  			disabled = true;  		}  	} -	auxtrace_snapshot_enabled = 0; +	trigger_off(&auxtrace_snapshot_trigger); +	trigger_off(&switch_output_trigger);  	if (forks && workload_exec_errno) {  		char msg[STRERR_BUFSIZE]; @@ -811,11 +923,22 @@ out_child:  	/* this will be recalculated during process_buildids() */  	rec->samples = 0; -	if (!err) -		record__finish_output(rec); +	if (!err) { +		if (!rec->timestamp_filename) { +			record__finish_output(rec); +		} else { +			fd = record__switch_output(rec, true); +			if (fd < 0) { +				status = fd; +				goto out_delete_session; +			} +		} +	}  	if (!err && !quiet) {  		char samples[128]; +		const char *postfix = rec->timestamp_filename ? +					".<timestamp>" : "";  		if (rec->samples && !rec->opts.full_auxtrace)  			scnprintf(samples, sizeof(samples), @@ -823,9 +946,9 @@ out_child:  		else  			samples[0] = '\0'; -		fprintf(stderr,	"[ perf record: Captured and wrote %.3f MB %s%s ]\n", +		fprintf(stderr,	"[ perf record: Captured and wrote %.3f MB %s%s%s ]\n",  			perf_data_file__size(file) / 1024.0 / 1024.0, -			file->path, samples); +			file->path, postfix, samples);  	}  out_delete_session: @@ -833,58 +956,61 @@ out_delete_session:  	return status;  } -static void callchain_debug(void) +static void callchain_debug(struct callchain_param *callchain)  {  	static const char *str[CALLCHAIN_MAX] = { "NONE", "FP", "DWARF", "LBR" }; -	pr_debug("callchain: type %s\n", str[callchain_param.record_mode]); +	pr_debug("callchain: type %s\n", str[callchain->record_mode]); -	if (callchain_param.record_mode == CALLCHAIN_DWARF) +	if (callchain->record_mode == CALLCHAIN_DWARF)  		pr_debug("callchain: stack dump size %d\n", -			 callchain_param.dump_size); +			 callchain->dump_size);  } -int record_parse_callchain_opt(const struct option *opt, -			       const char *arg, -			       int unset) +int record_opts__parse_callchain(struct record_opts *record, +				 struct callchain_param *callchain, +				 const char *arg, bool unset)  {  	int ret; -	struct record_opts *record = (struct record_opts *)opt->value; - -	record->callgraph_set = true; -	callchain_param.enabled = !unset; +	callchain->enabled = !unset;  	/* --no-call-graph */  	if (unset) { -		callchain_param.record_mode = CALLCHAIN_NONE; +		callchain->record_mode = CALLCHAIN_NONE;  		pr_debug("callchain: disabled\n");  		return 0;  	} -	ret = parse_callchain_record_opt(arg, &callchain_param); +	ret = parse_callchain_record_opt(arg, callchain);  	if (!ret) {  		/* Enable data address sampling for DWARF unwind. */ -		if (callchain_param.record_mode == CALLCHAIN_DWARF) +		if (callchain->record_mode == CALLCHAIN_DWARF)  			record->sample_address = true; -		callchain_debug(); +		callchain_debug(callchain);  	}  	return ret;  } +int record_parse_callchain_opt(const struct option *opt, +			       const char *arg, +			       int unset) +{ +	return record_opts__parse_callchain(opt->value, &callchain_param, arg, unset); +} +  int record_callchain_opt(const struct option *opt,  			 const char *arg __maybe_unused,  			 int unset __maybe_unused)  { -	struct record_opts *record = (struct record_opts *)opt->value; +	struct callchain_param *callchain = opt->value; -	record->callgraph_set = true; -	callchain_param.enabled = true; +	callchain->enabled = true; -	if (callchain_param.record_mode == CALLCHAIN_NONE) -		callchain_param.record_mode = CALLCHAIN_FP; +	if (callchain->record_mode == CALLCHAIN_NONE) +		callchain->record_mode = CALLCHAIN_FP; -	callchain_debug(); +	callchain_debug(callchain);  	return 0;  } @@ -1122,7 +1248,7 @@ struct option __record_options[] = {  		     record__parse_mmap_pages),  	OPT_BOOLEAN(0, "group", &record.opts.group,  		    "put the counters into a counter group"), -	OPT_CALLBACK_NOOPT('g', NULL, &record.opts, +	OPT_CALLBACK_NOOPT('g', NULL, &callchain_param,  			   NULL, "enables call-graph recording" ,  			   &record_callchain_opt),  	OPT_CALLBACK(0, "call-graph", &record.opts, @@ -1195,6 +1321,10 @@ struct option __record_options[] = {  		   "file", "vmlinux pathname"),  	OPT_BOOLEAN(0, "buildid-all", &record.buildid_all,  		    "Record build-id of all DSOs regardless of hits"), +	OPT_BOOLEAN(0, "timestamp-filename", &record.timestamp_filename, +		    "append timestamp to output filename"), +	OPT_BOOLEAN(0, "switch-output", &record.switch_output, +		    "Switch output when receive SIGUSR2"),  	OPT_END()  }; @@ -1250,6 +1380,9 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)  		return -EINVAL;  	} +	if (rec->switch_output) +		rec->timestamp_filename = true; +  	if (!rec->itr) {  		rec->itr = auxtrace_record__init(rec->evlist, &err);  		if (err) @@ -1261,6 +1394,14 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)  	if (err)  		return err; +	err = bpf__setup_stdout(rec->evlist); +	if (err) { +		bpf__strerror_setup_stdout(rec->evlist, err, errbuf, sizeof(errbuf)); +		pr_err("ERROR: Setup BPF stdout failed: %s\n", +			 errbuf); +		return err; +	} +  	err = -ENOMEM;  	symbol__init(NULL); @@ -1275,8 +1416,36 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)  "If some relocation was applied (e.g. kexec) symbols may be misresolved\n"  "even with a suitable vmlinux or kallsyms file.\n\n"); -	if (rec->no_buildid_cache || rec->no_buildid) +	if (rec->no_buildid_cache || rec->no_buildid) {  		disable_buildid_cache(); +	} else if (rec->switch_output) { +		/* +		 * In 'perf record --switch-output', disable buildid +		 * generation by default to reduce data file switching +		 * overhead. Still generate buildid if they are required +		 * explicitly using +		 * +		 *  perf record --signal-trigger --no-no-buildid \ +		 *              --no-no-buildid-cache +		 * +		 * Following code equals to: +		 * +		 * if ((rec->no_buildid || !rec->no_buildid_set) && +		 *     (rec->no_buildid_cache || !rec->no_buildid_cache_set)) +		 *         disable_buildid_cache(); +		 */ +		bool disable = true; + +		if (rec->no_buildid_set && !rec->no_buildid) +			disable = false; +		if (rec->no_buildid_cache_set && !rec->no_buildid_cache) +			disable = false; +		if (disable) { +			rec->no_buildid = true; +			rec->no_buildid_cache = true; +			disable_buildid_cache(); +		} +	}  	if (rec->evlist->nr_entries == 0 &&  	    perf_evlist__add_default(rec->evlist) < 0) { @@ -1335,9 +1504,13 @@ out_symbol_exit:  static void snapshot_sig_handler(int sig __maybe_unused)  { -	if (!auxtrace_snapshot_enabled) -		return; -	auxtrace_snapshot_enabled = 0; -	auxtrace_snapshot_err = auxtrace_record__snapshot_start(record.itr); -	auxtrace_record__snapshot_started = 1; +	if (trigger_is_ready(&auxtrace_snapshot_trigger)) { +		trigger_hit(&auxtrace_snapshot_trigger); +		auxtrace_record__snapshot_started = 1; +		if (auxtrace_record__snapshot_start(record.itr)) +			trigger_error(&auxtrace_snapshot_trigger); +	} + +	if (trigger_is_ready(&switch_output_trigger)) +		trigger_hit(&switch_output_trigger);  }  |