diff options
Diffstat (limited to 'tools/perf/builtin-test.c')
| -rw-r--r-- | tools/perf/builtin-test.c | 545 | 
1 files changed, 522 insertions, 23 deletions
diff --git a/tools/perf/builtin-test.c b/tools/perf/builtin-test.c index 831d1baeac3..2b9a7f497a2 100644 --- a/tools/perf/builtin-test.c +++ b/tools/perf/builtin-test.c @@ -7,6 +7,7 @@  #include "util/cache.h"  #include "util/debug.h" +#include "util/debugfs.h"  #include "util/evlist.h"  #include "util/parse-options.h"  #include "util/parse-events.h" @@ -14,8 +15,6 @@  #include "util/thread_map.h"  #include "../../include/linux/hw_breakpoint.h" -static long page_size; -  static int vmlinux_matches_kallsyms_filter(struct map *map __used, struct symbol *sym)  {  	bool *visited = symbol__priv(sym); @@ -31,6 +30,7 @@ static int test__vmlinux_matches_kallsyms(void)  	struct map *kallsyms_map, *vmlinux_map;  	struct machine kallsyms, vmlinux;  	enum map_type type = MAP__FUNCTION; +	long page_size = sysconf(_SC_PAGE_SIZE);  	struct ref_reloc_sym ref_reloc_sym = { .name = "_stext", };  	/* @@ -247,7 +247,7 @@ static int trace_event__id(const char *evname)  	if (asprintf(&filename,  		     "%s/syscalls/%s/id", -		     debugfs_path, evname) < 0) +		     tracing_events_path, evname) < 0)  		return -1;  	fd = open(filename, O_RDONLY); @@ -603,7 +603,7 @@ out_free_threads:  #define TEST_ASSERT_VAL(text, cond) \  do { \ -	if (!cond) { \ +	if (!(cond)) { \  		pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \  		return -1; \  	} \ @@ -759,6 +759,103 @@ static int test__checkevent_breakpoint_w(struct perf_evlist *evlist)  	return 0;  } +static int test__checkevent_tracepoint_modifier(struct perf_evlist *evlist) +{ +	struct perf_evsel *evsel = list_entry(evlist->entries.next, +					      struct perf_evsel, node); + +	TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); +	TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); +	TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); +	TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + +	return test__checkevent_tracepoint(evlist); +} + +static int +test__checkevent_tracepoint_multi_modifier(struct perf_evlist *evlist) +{ +	struct perf_evsel *evsel; + +	TEST_ASSERT_VAL("wrong number of entries", evlist->nr_entries > 1); + +	list_for_each_entry(evsel, &evlist->entries, node) { +		TEST_ASSERT_VAL("wrong exclude_user", +				!evsel->attr.exclude_user); +		TEST_ASSERT_VAL("wrong exclude_kernel", +				evsel->attr.exclude_kernel); +		TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); +		TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); +	} + +	return test__checkevent_tracepoint_multi(evlist); +} + +static int test__checkevent_raw_modifier(struct perf_evlist *evlist) +{ +	struct perf_evsel *evsel = list_entry(evlist->entries.next, +					      struct perf_evsel, node); + +	TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); +	TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); +	TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); +	TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + +	return test__checkevent_raw(evlist); +} + +static int test__checkevent_numeric_modifier(struct perf_evlist *evlist) +{ +	struct perf_evsel *evsel = list_entry(evlist->entries.next, +					      struct perf_evsel, node); + +	TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); +	TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); +	TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); +	TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + +	return test__checkevent_numeric(evlist); +} + +static int test__checkevent_symbolic_name_modifier(struct perf_evlist *evlist) +{ +	struct perf_evsel *evsel = list_entry(evlist->entries.next, +					      struct perf_evsel, node); + +	TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); +	TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); +	TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); +	TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + +	return test__checkevent_symbolic_name(evlist); +} + +static int test__checkevent_symbolic_alias_modifier(struct perf_evlist *evlist) +{ +	struct perf_evsel *evsel = list_entry(evlist->entries.next, +					      struct perf_evsel, node); + +	TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); +	TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); +	TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); +	TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + +	return test__checkevent_symbolic_alias(evlist); +} + +static int test__checkevent_genhw_modifier(struct perf_evlist *evlist) +{ +	struct perf_evsel *evsel = list_entry(evlist->entries.next, +					      struct perf_evsel, node); + +	TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); +	TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); +	TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); +	TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + +	return test__checkevent_genhw(evlist); +} +  static struct test__event_st {  	const char *name;  	__u32 type; @@ -808,6 +905,34 @@ static struct test__event_st {  		.name  = "mem:0:w",  		.check = test__checkevent_breakpoint_w,  	}, +	{ +		.name  = "syscalls:sys_enter_open:k", +		.check = test__checkevent_tracepoint_modifier, +	}, +	{ +		.name  = "syscalls:*:u", +		.check = test__checkevent_tracepoint_multi_modifier, +	}, +	{ +		.name  = "r1:kp", +		.check = test__checkevent_raw_modifier, +	}, +	{ +		.name  = "1:1:hp", +		.check = test__checkevent_numeric_modifier, +	}, +	{ +		.name  = "instructions:h", +		.check = test__checkevent_symbolic_name_modifier, +	}, +	{ +		.name  = "faults:u", +		.check = test__checkevent_symbolic_alias_modifier, +	}, +	{ +		.name  = "L1-dcache-load-miss:kp", +		.check = test__checkevent_genhw_modifier, +	},  };  #define TEST__EVENTS_CNT (sizeof(test__events) / sizeof(struct test__event_st)) @@ -841,6 +966,336 @@ static int test__parse_events(void)  	return ret;  } + +static int sched__get_first_possible_cpu(pid_t pid, cpu_set_t **maskp, +					 size_t *sizep) +{ +	cpu_set_t *mask; +	size_t size; +	int i, cpu = -1, nrcpus = 1024; +realloc: +	mask = CPU_ALLOC(nrcpus); +	size = CPU_ALLOC_SIZE(nrcpus); +	CPU_ZERO_S(size, mask); + +	if (sched_getaffinity(pid, size, mask) == -1) { +		CPU_FREE(mask); +		if (errno == EINVAL && nrcpus < (1024 << 8)) { +			nrcpus = nrcpus << 2; +			goto realloc; +		} +		perror("sched_getaffinity"); +			return -1; +	} + +	for (i = 0; i < nrcpus; i++) { +		if (CPU_ISSET_S(i, size, mask)) { +			if (cpu == -1) { +				cpu = i; +				*maskp = mask; +				*sizep = size; +			} else +				CPU_CLR_S(i, size, mask); +		} +	} + +	if (cpu == -1) +		CPU_FREE(mask); + +	return cpu; +} + +static int test__PERF_RECORD(void) +{ +	struct perf_record_opts opts = { +		.target_pid = -1, +		.target_tid = -1, +		.no_delay   = true, +		.freq	    = 10, +		.mmap_pages = 256, +		.sample_id_all_avail = true, +	}; +	cpu_set_t *cpu_mask = NULL; +	size_t cpu_mask_size = 0; +	struct perf_evlist *evlist = perf_evlist__new(NULL, NULL); +	struct perf_evsel *evsel; +	struct perf_sample sample; +	const char *cmd = "sleep"; +	const char *argv[] = { cmd, "1", NULL, }; +	char *bname; +	u64 sample_type, prev_time = 0; +	bool found_cmd_mmap = false, +	     found_libc_mmap = false, +	     found_vdso_mmap = false, +	     found_ld_mmap = false; +	int err = -1, errs = 0, i, wakeups = 0, sample_size; +	u32 cpu; +	int total_events = 0, nr_events[PERF_RECORD_MAX] = { 0, }; + +	if (evlist == NULL || argv == NULL) { +		pr_debug("Not enough memory to create evlist\n"); +		goto out; +	} + +	/* +	 * We need at least one evsel in the evlist, use the default +	 * one: "cycles". +	 */ +	err = perf_evlist__add_default(evlist); +	if (err < 0) { +		pr_debug("Not enough memory to create evsel\n"); +		goto out_delete_evlist; +	} + +	/* +	 * Create maps of threads and cpus to monitor. In this case +	 * we start with all threads and cpus (-1, -1) but then in +	 * perf_evlist__prepare_workload we'll fill in the only thread +	 * we're monitoring, the one forked there. +	 */ +	err = perf_evlist__create_maps(evlist, opts.target_pid, +				       opts.target_tid, opts.cpu_list); +	if (err < 0) { +		pr_debug("Not enough memory to create thread/cpu maps\n"); +		goto out_delete_evlist; +	} + +	/* +	 * Prepare the workload in argv[] to run, it'll fork it, and then wait +	 * for perf_evlist__start_workload() to exec it. This is done this way +	 * so that we have time to open the evlist (calling sys_perf_event_open +	 * on all the fds) and then mmap them. +	 */ +	err = perf_evlist__prepare_workload(evlist, &opts, argv); +	if (err < 0) { +		pr_debug("Couldn't run the workload!\n"); +		goto out_delete_evlist; +	} + +	/* +	 * Config the evsels, setting attr->comm on the first one, etc. +	 */ +	evsel = list_entry(evlist->entries.next, struct perf_evsel, node); +	evsel->attr.sample_type |= PERF_SAMPLE_CPU; +	evsel->attr.sample_type |= PERF_SAMPLE_TID; +	evsel->attr.sample_type |= PERF_SAMPLE_TIME; +	perf_evlist__config_attrs(evlist, &opts); + +	err = sched__get_first_possible_cpu(evlist->workload.pid, &cpu_mask, +					    &cpu_mask_size); +	if (err < 0) { +		pr_debug("sched__get_first_possible_cpu: %s\n", strerror(errno)); +		goto out_delete_evlist; +	} + +	cpu = err; + +	/* +	 * So that we can check perf_sample.cpu on all the samples. +	 */ +	if (sched_setaffinity(evlist->workload.pid, cpu_mask_size, cpu_mask) < 0) { +		pr_debug("sched_setaffinity: %s\n", strerror(errno)); +		goto out_free_cpu_mask; +	} + +	/* +	 * Call sys_perf_event_open on all the fds on all the evsels, +	 * grouping them if asked to. +	 */ +	err = perf_evlist__open(evlist, opts.group); +	if (err < 0) { +		pr_debug("perf_evlist__open: %s\n", strerror(errno)); +		goto out_delete_evlist; +	} + +	/* +	 * mmap the first fd on a given CPU and ask for events for the other +	 * fds in the same CPU to be injected in the same mmap ring buffer +	 * (using ioctl(PERF_EVENT_IOC_SET_OUTPUT)). +	 */ +	err = perf_evlist__mmap(evlist, opts.mmap_pages, false); +	if (err < 0) { +		pr_debug("perf_evlist__mmap: %s\n", strerror(errno)); +		goto out_delete_evlist; +	} + +	/* +	 * We'll need these two to parse the PERF_SAMPLE_* fields in each +	 * event. +	 */ +	sample_type = perf_evlist__sample_type(evlist); +	sample_size = __perf_evsel__sample_size(sample_type); + +	/* +	 * Now that all is properly set up, enable the events, they will +	 * count just on workload.pid, which will start... +	 */ +	perf_evlist__enable(evlist); + +	/* +	 * Now! +	 */ +	perf_evlist__start_workload(evlist); + +	while (1) { +		int before = total_events; + +		for (i = 0; i < evlist->nr_mmaps; i++) { +			union perf_event *event; + +			while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { +				const u32 type = event->header.type; +				const char *name = perf_event__name(type); + +				++total_events; +				if (type < PERF_RECORD_MAX) +					nr_events[type]++; + +				err = perf_event__parse_sample(event, sample_type, +							       sample_size, true, +							       &sample, false); +				if (err < 0) { +					if (verbose) +						perf_event__fprintf(event, stderr); +					pr_debug("Couldn't parse sample\n"); +					goto out_err; +				} + +				if (verbose) { +					pr_info("%" PRIu64" %d ", sample.time, sample.cpu); +					perf_event__fprintf(event, stderr); +				} + +				if (prev_time > sample.time) { +					pr_debug("%s going backwards in time, prev=%" PRIu64 ", curr=%" PRIu64 "\n", +						 name, prev_time, sample.time); +					++errs; +				} + +				prev_time = sample.time; + +				if (sample.cpu != cpu) { +					pr_debug("%s with unexpected cpu, expected %d, got %d\n", +						 name, cpu, sample.cpu); +					++errs; +				} + +				if ((pid_t)sample.pid != evlist->workload.pid) { +					pr_debug("%s with unexpected pid, expected %d, got %d\n", +						 name, evlist->workload.pid, sample.pid); +					++errs; +				} + +				if ((pid_t)sample.tid != evlist->workload.pid) { +					pr_debug("%s with unexpected tid, expected %d, got %d\n", +						 name, evlist->workload.pid, sample.tid); +					++errs; +				} + +				if ((type == PERF_RECORD_COMM || +				     type == PERF_RECORD_MMAP || +				     type == PERF_RECORD_FORK || +				     type == PERF_RECORD_EXIT) && +				     (pid_t)event->comm.pid != evlist->workload.pid) { +					pr_debug("%s with unexpected pid/tid\n", name); +					++errs; +				} + +				if ((type == PERF_RECORD_COMM || +				     type == PERF_RECORD_MMAP) && +				     event->comm.pid != event->comm.tid) { +					pr_debug("%s with different pid/tid!\n", name); +					++errs; +				} + +				switch (type) { +				case PERF_RECORD_COMM: +					if (strcmp(event->comm.comm, cmd)) { +						pr_debug("%s with unexpected comm!\n", name); +						++errs; +					} +					break; +				case PERF_RECORD_EXIT: +					goto found_exit; +				case PERF_RECORD_MMAP: +					bname = strrchr(event->mmap.filename, '/'); +					if (bname != NULL) { +						if (!found_cmd_mmap) +							found_cmd_mmap = !strcmp(bname + 1, cmd); +						if (!found_libc_mmap) +							found_libc_mmap = !strncmp(bname + 1, "libc", 4); +						if (!found_ld_mmap) +							found_ld_mmap = !strncmp(bname + 1, "ld", 2); +					} else if (!found_vdso_mmap) +						found_vdso_mmap = !strcmp(event->mmap.filename, "[vdso]"); +					break; + +				case PERF_RECORD_SAMPLE: +					/* Just ignore samples for now */ +					break; +				default: +					pr_debug("Unexpected perf_event->header.type %d!\n", +						 type); +					++errs; +				} +			} +		} + +		/* +		 * We don't use poll here because at least at 3.1 times the +		 * PERF_RECORD_{!SAMPLE} events don't honour +		 * perf_event_attr.wakeup_events, just PERF_EVENT_SAMPLE does. +		 */ +		if (total_events == before && false) +			poll(evlist->pollfd, evlist->nr_fds, -1); + +		sleep(1); +		if (++wakeups > 5) { +			pr_debug("No PERF_RECORD_EXIT event!\n"); +			break; +		} +	} + +found_exit: +	if (nr_events[PERF_RECORD_COMM] > 1) { +		pr_debug("Excessive number of PERF_RECORD_COMM events!\n"); +		++errs; +	} + +	if (nr_events[PERF_RECORD_COMM] == 0) { +		pr_debug("Missing PERF_RECORD_COMM for %s!\n", cmd); +		++errs; +	} + +	if (!found_cmd_mmap) { +		pr_debug("PERF_RECORD_MMAP for %s missing!\n", cmd); +		++errs; +	} + +	if (!found_libc_mmap) { +		pr_debug("PERF_RECORD_MMAP for %s missing!\n", "libc"); +		++errs; +	} + +	if (!found_ld_mmap) { +		pr_debug("PERF_RECORD_MMAP for %s missing!\n", "ld"); +		++errs; +	} + +	if (!found_vdso_mmap) { +		pr_debug("PERF_RECORD_MMAP for %s missing!\n", "[vdso]"); +		++errs; +	} +out_err: +	perf_evlist__munmap(evlist); +out_free_cpu_mask: +	CPU_FREE(cpu_mask); +out_delete_evlist: +	perf_evlist__delete(evlist); +out: +	return (err < 0 || errs > 0) ? -1 : 0; +} +  static struct test {  	const char *desc;  	int (*func)(void); @@ -866,45 +1321,89 @@ static struct test {  		.func = test__parse_events,  	},  	{ +		.desc = "Validate PERF_RECORD_* events & perf_sample fields", +		.func = test__PERF_RECORD, +	}, +	{  		.func = NULL,  	},  }; -static int __cmd_test(void) +static bool perf_test__matches(int curr, int argc, const char *argv[])  { -	int i = 0; +	int i; -	page_size = sysconf(_SC_PAGE_SIZE); +	if (argc == 0) +		return true; + +	for (i = 0; i < argc; ++i) { +		char *end; +		long nr = strtoul(argv[i], &end, 10); + +		if (*end == '\0') { +			if (nr == curr + 1) +				return true; +			continue; +		} + +		if (strstr(tests[curr].desc, argv[i])) +			return true; +	} + +	return false; +} + +static int __cmd_test(int argc, const char *argv[]) +{ +	int i = 0;  	while (tests[i].func) { -		int err; -		pr_info("%2d: %s:", i + 1, tests[i].desc); +		int curr = i++, err; + +		if (!perf_test__matches(curr, argc, argv)) +			continue; + +		pr_info("%2d: %s:", i, tests[curr].desc);  		pr_debug("\n--- start ---\n"); -		err = tests[i].func(); -		pr_debug("---- end ----\n%s:", tests[i].desc); +		err = tests[curr].func(); +		pr_debug("---- end ----\n%s:", tests[curr].desc);  		pr_info(" %s\n", err ? "FAILED!\n" : "Ok"); -		++i;  	}  	return 0;  } -static const char * const test_usage[] = { -	"perf test [<options>]", -	NULL, -}; +static int perf_test__list(int argc, const char **argv) +{ +	int i = 0; + +	while (tests[i].func) { +		int curr = i++; + +		if (argc > 1 && !strstr(tests[curr].desc, argv[1])) +			continue; -static const struct option test_options[] = { +		pr_info("%2d: %s\n", i, tests[curr].desc); +	} + +	return 0; +} + +int cmd_test(int argc, const char **argv, const char *prefix __used) +{ +	const char * const test_usage[] = { +	"perf test [<options>] [{list <test-name-fragment>|[<test-name-fragments>|<test-numbers>]}]", +	NULL, +	}; +	const struct option test_options[] = {  	OPT_INTEGER('v', "verbose", &verbose,  		    "be more verbose (show symbol address, etc)"),  	OPT_END() -}; +	}; -int cmd_test(int argc, const char **argv, const char *prefix __used) -{  	argc = parse_options(argc, argv, test_options, test_usage, 0); -	if (argc) -		usage_with_options(test_usage, test_options); +	if (argc >= 1 && !strcmp(argv[0], "list")) +		return perf_test__list(argc, argv);  	symbol_conf.priv_size = sizeof(int);  	symbol_conf.sort_by_name = true; @@ -915,5 +1414,5 @@ int cmd_test(int argc, const char **argv, const char *prefix __used)  	setup_pager(); -	return __cmd_test(); +	return __cmd_test(argc, argv);  }  |