diff options
52 files changed, 2050 insertions, 1785 deletions
@@ -1321,10 +1321,12 @@ kernelversion: # Clear a bunch of variables before executing the submake tools/: FORCE - $(Q)$(MAKE) LDFLAGS= MAKEFLAGS= -C $(src)/tools/ + $(Q)mkdir -p $(objtree)/tools + $(Q)$(MAKE) LDFLAGS= MAKEFLAGS= O=$(objtree) subdir=tools -C $(src)/tools/ tools/%: FORCE - $(Q)$(MAKE) LDFLAGS= MAKEFLAGS= -C $(src)/tools/ $* + $(Q)mkdir -p $(objtree)/tools + $(Q)$(MAKE) LDFLAGS= MAKEFLAGS= O=$(objtree) subdir=tools -C $(src)/tools/ $* # Single targets # --------------------------------------------------------------------------- diff --git a/tools/Makefile b/tools/Makefile index 3ae43947a171..1f9a529fe544 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -31,44 +31,44 @@ help: @echo ' clean: a summary clean target to clean _all_ folders' cpupower: FORCE - $(QUIET_SUBDIR0)power/$@/ $(QUIET_SUBDIR1) + $(call descend,power/$@) firewire lguest perf usb virtio vm: FORCE - $(QUIET_SUBDIR0)$@/ $(QUIET_SUBDIR1) + $(call descend,$@) selftests: FORCE - $(QUIET_SUBDIR0)testing/$@/ $(QUIET_SUBDIR1) + $(call descend,testing/$@) turbostat x86_energy_perf_policy: FORCE - $(QUIET_SUBDIR0)power/x86/$@/ $(QUIET_SUBDIR1) + $(call descend,power/x86/$@) cpupower_install: - $(QUIET_SUBDIR0)power/$(@:_install=)/ $(QUIET_SUBDIR1) install + $(call descend,power/$(@:_install=),install) firewire_install lguest_install perf_install usb_install virtio_install vm_install: - $(QUIET_SUBDIR0)$(@:_install=)/ $(QUIET_SUBDIR1) install + $(call descend,$(@:_install=),install) selftests_install: - $(QUIET_SUBDIR0)testing/$(@:_clean=)/ $(QUIET_SUBDIR1) install + $(call descend,testing/$(@:_clean=),install) turbostat_install x86_energy_perf_policy_install: - $(QUIET_SUBDIR0)power/x86/$(@:_install=)/ $(QUIET_SUBDIR1) install + $(call descend,power/x86/$(@:_install=),install) install: cpupower_install firewire_install lguest_install perf_install \ selftests_install turbostat_install usb_install virtio_install \ vm_install x86_energy_perf_policy_install cpupower_clean: - $(QUIET_SUBDIR0)power/cpupower/ $(QUIET_SUBDIR1) clean + $(call descend,power/cpupower,clean) firewire_clean lguest_clean perf_clean usb_clean virtio_clean vm_clean: - $(QUIET_SUBDIR0)$(@:_clean=)/ $(QUIET_SUBDIR1) clean + $(call descend,$(@:_clean=),clean) selftests_clean: - $(QUIET_SUBDIR0)testing/$(@:_clean=)/ $(QUIET_SUBDIR1) clean + $(call descend,testing/$(@:_clean=),clean) turbostat_clean x86_energy_perf_policy_clean: - $(QUIET_SUBDIR0)power/x86/$(@:_clean=)/ $(QUIET_SUBDIR1) clean + $(call descend,power/x86/$(@:_clean=),clean) clean: cpupower_clean firewire_clean lguest_clean perf_clean selftests_clean \ turbostat_clean usb_clean virtio_clean vm_clean \ diff --git a/tools/perf/Makefile b/tools/perf/Makefile index cca5bb8334ad..a7c6aa8d4a8b 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -422,7 +422,9 @@ LIB_OBJS += $(OUTPUT)util/intlist.o LIB_OBJS += $(OUTPUT)util/vdso.o LIB_OBJS += $(OUTPUT)util/stat.o +LIB_OBJS += $(OUTPUT)ui/setup.o LIB_OBJS += $(OUTPUT)ui/helpline.o +LIB_OBJS += $(OUTPUT)ui/progress.o LIB_OBJS += $(OUTPUT)ui/hist.o LIB_OBJS += $(OUTPUT)ui/stdio/hist.o @@ -431,6 +433,17 @@ LIB_OBJS += $(OUTPUT)arch/common.o LIB_OBJS += $(OUTPUT)tests/parse-events.o LIB_OBJS += $(OUTPUT)tests/dso-data.o LIB_OBJS += $(OUTPUT)tests/attr.o +LIB_OBJS += $(OUTPUT)tests/vmlinux-kallsyms.o +LIB_OBJS += $(OUTPUT)tests/open-syscall.o +LIB_OBJS += $(OUTPUT)tests/open-syscall-all-cpus.o +LIB_OBJS += $(OUTPUT)tests/open-syscall-tp-fields.o +LIB_OBJS += $(OUTPUT)tests/mmap-basic.o +LIB_OBJS += $(OUTPUT)tests/perf-record.o +LIB_OBJS += $(OUTPUT)tests/rdpmc.o +LIB_OBJS += $(OUTPUT)tests/evsel-roundtrip-name.o +LIB_OBJS += $(OUTPUT)tests/evsel-tp-sched.o +LIB_OBJS += $(OUTPUT)tests/pmu.o +LIB_OBJS += $(OUTPUT)tests/util.o BUILTIN_OBJS += $(OUTPUT)builtin-annotate.o BUILTIN_OBJS += $(OUTPUT)builtin-bench.o @@ -600,17 +613,16 @@ ifndef NO_NEWT BASIC_CFLAGS += -I/usr/include/slang BASIC_CFLAGS += -DNEWT_SUPPORT EXTLIBS += -lnewt -lslang - LIB_OBJS += $(OUTPUT)ui/setup.o LIB_OBJS += $(OUTPUT)ui/browser.o LIB_OBJS += $(OUTPUT)ui/browsers/annotate.o LIB_OBJS += $(OUTPUT)ui/browsers/hists.o LIB_OBJS += $(OUTPUT)ui/browsers/map.o LIB_OBJS += $(OUTPUT)ui/browsers/scripts.o - LIB_OBJS += $(OUTPUT)ui/progress.o LIB_OBJS += $(OUTPUT)ui/util.o LIB_OBJS += $(OUTPUT)ui/tui/setup.o LIB_OBJS += $(OUTPUT)ui/tui/util.o LIB_OBJS += $(OUTPUT)ui/tui/helpline.o + LIB_OBJS += $(OUTPUT)ui/tui/progress.o LIB_H += ui/browser.h LIB_H += ui/browsers/map.h LIB_H += ui/keysyms.h @@ -636,9 +648,9 @@ ifndef NO_GTK2 LIB_OBJS += $(OUTPUT)ui/gtk/setup.o LIB_OBJS += $(OUTPUT)ui/gtk/util.o LIB_OBJS += $(OUTPUT)ui/gtk/helpline.o + LIB_OBJS += $(OUTPUT)ui/gtk/progress.o # Make sure that it'd be included only once. ifeq ($(findstring -DNEWT_SUPPORT,$(BASIC_CFLAGS)),) - LIB_OBJS += $(OUTPUT)ui/setup.o LIB_OBJS += $(OUTPUT)ui/util.o endif endif diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 5783c3225116..f3151d3c70ce 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -230,11 +230,15 @@ static int perf_record__open(struct perf_record *rec) struct perf_record_opts *opts = &rec->opts; int rc = 0; - perf_evlist__config_attrs(evlist, opts); - + /* + * Set the evsel leader links before we configure attributes, + * since some might depend on this info. + */ if (opts->group) perf_evlist__set_leader(evlist); + perf_evlist__config_attrs(evlist, opts); + list_for_each_entry(pos, &evlist->entries, node) { struct perf_event_attr *attr = &pos->attr; /* @@ -498,6 +502,7 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) struct perf_evlist *evsel_list = rec->evlist; const char *output_name = rec->output_name; struct perf_session *session; + bool disabled = false; rec->progname = argv[0]; @@ -697,7 +702,13 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) } } - perf_evlist__enable(evsel_list); + /* + * When perf is starting the traced process, all the events + * (apart from group members) have enable_on_exec=1 set, + * so don't spoil it by prematurely enabling them. + */ + if (!perf_target__none(&opts->target)) + perf_evlist__enable(evsel_list); /* * Let the child rip @@ -720,8 +731,15 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) waking++; } - if (done) + /* + * When perf is starting the traced process, at the end events + * die with the process and we wait for that. Thus no need to + * disable events in this case. + */ + if (done && !disabled && !perf_target__none(&opts->target)) { perf_evlist__disable(evsel_list); + disabled = true; + } } if (quiet || signr == SIGUSR1) diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 6888960ef8b8..c247faca7127 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -129,8 +129,7 @@ static struct stats runtime_itlb_cache_stats[MAX_NR_CPUS]; static struct stats runtime_dtlb_cache_stats[MAX_NR_CPUS]; static struct stats walltime_nsecs_stats; -static int create_perf_stat_counter(struct perf_evsel *evsel, - struct perf_evsel *first) +static int create_perf_stat_counter(struct perf_evsel *evsel) { struct perf_event_attr *attr = &evsel->attr; bool exclude_guest_missing = false; @@ -153,7 +152,8 @@ retry: return 0; } - if (!perf_target__has_task(&target) && (!group || evsel == first)) { + if (!perf_target__has_task(&target) && + !perf_evsel__is_group_member(evsel)) { attr->disabled = 1; attr->enable_on_exec = 1; } @@ -272,7 +272,7 @@ static int read_counter(struct perf_evsel *counter) static int __run_perf_stat(int argc __maybe_unused, const char **argv) { unsigned long long t0, t1; - struct perf_evsel *counter, *first; + struct perf_evsel *counter; int status = 0; int child_ready_pipe[2], go_pipe[2]; const bool forks = (argc > 0); @@ -332,10 +332,8 @@ static int __run_perf_stat(int argc __maybe_unused, const char **argv) if (group) perf_evlist__set_leader(evsel_list); - first = perf_evlist__first(evsel_list); - list_for_each_entry(counter, &evsel_list->entries, node) { - if (create_perf_stat_counter(counter, first) < 0) { + if (create_perf_stat_counter(counter) < 0) { /* * PPC returns ENXIO for HW counters until 2.6.37 * (behavior changed with commit b0a873e). diff --git a/tools/perf/perf.c b/tools/perf/perf.c index a0ae2902f9c9..0f661fbce6a8 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -85,21 +85,26 @@ int check_pager_config(const char *cmd) return c.val; } -static int tui_command_config(const char *var, const char *value, void *data) +static int browser_command_config(const char *var, const char *value, void *data) { struct pager_config *c = data; if (!prefixcmp(var, "tui.") && !strcmp(var + 4, c->cmd)) c->val = perf_config_bool(var, value); + if (!prefixcmp(var, "gtk.") && !strcmp(var + 4, c->cmd)) + c->val = perf_config_bool(var, value) ? 2 : 0; return 0; } -/* returns 0 for "no tui", 1 for "use tui", and -1 for "not specified" */ -static int check_tui_config(const char *cmd) +/* + * returns 0 for "no tui", 1 for "use tui", 2 for "use gtk", + * and -1 for "not specified" + */ +static int check_browser_config(const char *cmd) { struct pager_config c; c.cmd = cmd; c.val = -1; - perf_config(tui_command_config, &c); + perf_config(browser_command_config, &c); return c.val; } @@ -302,7 +307,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) prefix = NULL; /* setup_perf_directory(); */ if (use_browser == -1) - use_browser = check_tui_config(p->cmd); + use_browser = check_browser_config(p->cmd); if (use_pager == -1 && p->option & RUN_SETUP) use_pager = check_pager_config(p->cmd); diff --git a/tools/perf/perf.h b/tools/perf/perf.h index 054182e41dca..f53ee0bbee88 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h @@ -26,7 +26,7 @@ void get_term_dimensions(struct winsize *ws); #endif #ifdef __powerpc__ -#include "../../arch/powerpc/include/asm/unistd.h" +#include "../../arch/powerpc/include/uapi/asm/unistd.h" #define rmb() asm volatile ("sync" ::: "memory") #define cpu_relax() asm volatile ("" ::: "memory"); #define CPUINFO_PROC "cpu" @@ -178,7 +178,6 @@ extern bool test_attr__enabled; void test_attr__init(void); void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu, int fd, int group_fd, unsigned long flags); -int test_attr__run(void); static inline int sys_perf_event_open(struct perf_event_attr *attr, diff --git a/tools/perf/tests/attr.c b/tools/perf/tests/attr.c index 6e2feee8db2a..25638a986257 100644 --- a/tools/perf/tests/attr.c +++ b/tools/perf/tests/attr.c @@ -27,6 +27,7 @@ #include "../perf.h" #include "util.h" #include "exec_cmd.h" +#include "tests.h" #define ENV "PERF_TEST_ATTR" @@ -151,7 +152,7 @@ static int run_dir(const char *d, const char *perf) return system(cmd); } -int test_attr__run(void) +int test__attr(void) { struct stat st; char path_perf[PATH_MAX]; diff --git a/tools/perf/tests/attr/test-record-group b/tools/perf/tests/attr/test-record-group index b945f770dc9e..a6599e9a19d3 100644 --- a/tools/perf/tests/attr/test-record-group +++ b/tools/perf/tests/attr/test-record-group @@ -15,3 +15,4 @@ sample_type=327 mmap=0 comm=0 enable_on_exec=0 +disabled=0 diff --git a/tools/perf/tests/attr/test-record-group1 b/tools/perf/tests/attr/test-record-group1 index 39bf8609538c..5a8359da38af 100644 --- a/tools/perf/tests/attr/test-record-group1 +++ b/tools/perf/tests/attr/test-record-group1 @@ -15,6 +15,5 @@ config=1 sample_type=327 mmap=0 comm=0 -# TODO this is disabled for --group option, enabled otherwise -# check why.. -enable_on_exec=1 +enable_on_exec=0 +disabled=0 diff --git a/tools/perf/tests/attr/test-stat-group1 b/tools/perf/tests/attr/test-stat-group1 index 5ae2718de864..2a1f86e4a904 100644 --- a/tools/perf/tests/attr/test-stat-group1 +++ b/tools/perf/tests/attr/test-stat-group1 @@ -11,7 +11,5 @@ group_fd=-1 fd=2 group_fd=1 config=1 -# TODO both disabled and enable_on_exec are disabled for --group option, -# enabled otherwise, check why.. -disabled=1 -enable_on_exec=1 +disabled=0 +enable_on_exec=0 diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 5d4354e24457..186f67535494 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -4,1388 +4,11 @@ * Builtin regression testing command: ever growing number of sanity tests */ #include "builtin.h" - -#include "util/cache.h" -#include "util/color.h" -#include "util/debug.h" -#include "util/debugfs.h" -#include "util/evlist.h" -#include "util/machine.h" -#include "util/parse-options.h" -#include "util/parse-events.h" -#include "util/symbol.h" -#include "util/thread_map.h" -#include "util/pmu.h" -#include "event-parse.h" -#include "../../include/linux/hw_breakpoint.h" - -#include <sys/mman.h> - -static int vmlinux_matches_kallsyms_filter(struct map *map __maybe_unused, - struct symbol *sym) -{ - bool *visited = symbol__priv(sym); - *visited = true; - return 0; -} - -static int test__vmlinux_matches_kallsyms(void) -{ - int err = -1; - struct rb_node *nd; - struct symbol *sym; - struct map *kallsyms_map, *vmlinux_map; - struct machine kallsyms, vmlinux; - enum map_type type = MAP__FUNCTION; - struct ref_reloc_sym ref_reloc_sym = { .name = "_stext", }; - - /* - * Step 1: - * - * Init the machines that will hold kernel, modules obtained from - * both vmlinux + .ko files and from /proc/kallsyms split by modules. - */ - machine__init(&kallsyms, "", HOST_KERNEL_ID); - machine__init(&vmlinux, "", HOST_KERNEL_ID); - - /* - * Step 2: - * - * Create the kernel maps for kallsyms and the DSO where we will then - * load /proc/kallsyms. Also create the modules maps from /proc/modules - * and find the .ko files that match them in /lib/modules/`uname -r`/. - */ - if (machine__create_kernel_maps(&kallsyms) < 0) { - pr_debug("machine__create_kernel_maps "); - return -1; - } - - /* - * Step 3: - * - * Load and split /proc/kallsyms into multiple maps, one per module. - */ - if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms", type, NULL) <= 0) { - pr_debug("dso__load_kallsyms "); - goto out; - } - - /* - * Step 4: - * - * kallsyms will be internally on demand sorted by name so that we can - * find the reference relocation * symbol, i.e. the symbol we will use - * to see if the running kernel was relocated by checking if it has the - * same value in the vmlinux file we load. - */ - kallsyms_map = machine__kernel_map(&kallsyms, type); - - sym = map__find_symbol_by_name(kallsyms_map, ref_reloc_sym.name, NULL); - if (sym == NULL) { - pr_debug("dso__find_symbol_by_name "); - goto out; - } - - ref_reloc_sym.addr = sym->start; - - /* - * Step 5: - * - * Now repeat step 2, this time for the vmlinux file we'll auto-locate. - */ - if (machine__create_kernel_maps(&vmlinux) < 0) { - pr_debug("machine__create_kernel_maps "); - goto out; - } - - vmlinux_map = machine__kernel_map(&vmlinux, type); - map__kmap(vmlinux_map)->ref_reloc_sym = &ref_reloc_sym; - - /* - * Step 6: - * - * Locate a vmlinux file in the vmlinux path that has a buildid that - * matches the one of the running kernel. - * - * While doing that look if we find the ref reloc symbol, if we find it - * we'll have its ref_reloc_symbol.unrelocated_addr and then - * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines - * to fixup the symbols. - */ - if (machine__load_vmlinux_path(&vmlinux, type, - vmlinux_matches_kallsyms_filter) <= 0) { - pr_debug("machine__load_vmlinux_path "); - goto out; - } - - err = 0; - /* - * Step 7: - * - * Now look at the symbols in the vmlinux DSO and check if we find all of them - * in the kallsyms dso. For the ones that are in both, check its names and - * end addresses too. - */ - for (nd = rb_first(&vmlinux_map->dso->symbols[type]); nd; nd = rb_next(nd)) { - struct symbol *pair, *first_pair; - bool backwards = true; - - sym = rb_entry(nd, struct symbol, rb_node); - - if (sym->start == sym->end) - continue; - - first_pair = machine__find_kernel_symbol(&kallsyms, type, sym->start, NULL, NULL); - pair = first_pair; - - if (pair && pair->start == sym->start) { -next_pair: - if (strcmp(sym->name, pair->name) == 0) { - /* - * kallsyms don't have the symbol end, so we - * set that by using the next symbol start - 1, - * in some cases we get this up to a page - * wrong, trace_kmalloc when I was developing - * this code was one such example, 2106 bytes - * off the real size. More than that and we - * _really_ have a problem. - */ - s64 skew = sym->end - pair->end; - if (llabs(skew) < page_size) - continue; - - pr_debug("%#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n", - sym->start, sym->name, sym->end, pair->end); - } else { - struct rb_node *nnd; -detour: - nnd = backwards ? rb_prev(&pair->rb_node) : - rb_next(&pair->rb_node); - if (nnd) { - struct symbol *next = rb_entry(nnd, struct symbol, rb_node); - - if (next->start == sym->start) { - pair = next; - goto next_pair; - } - } - - if (backwards) { - backwards = false; - pair = first_pair; - goto detour; - } - - pr_debug("%#" PRIx64 ": diff name v: %s k: %s\n", - sym->start, sym->name, pair->name); - } - } else - pr_debug("%#" PRIx64 ": %s not on kallsyms\n", sym->start, sym->name); - - err = -1; - } - - if (!verbose) - goto out; - - pr_info("Maps only in vmlinux:\n"); - - for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { - struct map *pos = rb_entry(nd, struct map, rb_node), *pair; - /* - * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while - * the kernel will have the path for the vmlinux file being used, - * so use the short name, less descriptive but the same ("[kernel]" in - * both cases. - */ - pair = map_groups__find_by_name(&kallsyms.kmaps, type, - (pos->dso->kernel ? - pos->dso->short_name : - pos->dso->name)); - if (pair) - pair->priv = 1; - else - map__fprintf(pos, stderr); - } - - pr_info("Maps in vmlinux with a different name in kallsyms:\n"); - - for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { - struct map *pos = rb_entry(nd, struct map, rb_node), *pair; - - pair = map_groups__find(&kallsyms.kmaps, type, pos->start); - if (pair == NULL || pair->priv) - continue; - - if (pair->start == pos->start) { - pair->priv = 1; - pr_info(" %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as", - pos->start, pos->end, pos->pgoff, pos->dso->name); - if (pos->pgoff != pair->pgoff || pos->end != pair->end) - pr_info(": \n*%" PRIx64 "-%" PRIx64 " %" PRIx64 "", - pair->start, pair->end, pair->pgoff); - pr_info(" %s\n", pair->dso->name); - pair->priv = 1; - } - } - - pr_info("Maps only in kallsyms:\n"); - - for (nd = rb_first(&kallsyms.kmaps.maps[type]); - nd; nd = rb_next(nd)) { - struct map *pos = rb_entry(nd, struct map, rb_node); - - if (!pos->priv) - map__fprintf(pos, stderr); - } -out: - return err; -} - -#include "util/cpumap.h" -#include "util/evsel.h" -#include <sys/types.h> - -static int trace_event__id(const char *evname) -{ - char *filename; - int err = -1, fd; - - if (asprintf(&filename, - "%s/syscalls/%s/id", - tracing_events_path, evname) < 0) - return -1; - - fd = open(filename, O_RDONLY); - if (fd >= 0) { - char id[16]; - if (read(fd, id, sizeof(id)) > 0) - err = atoi(id); - close(fd); - } - - free(filename); - return err; -} - -static int test__open_syscall_event(void) -{ - int err = -1, fd; - struct thread_map *threads; - struct perf_evsel *evsel; - struct perf_event_attr attr; - unsigned int nr_open_calls = 111, i; - int id = trace_event__id("sys_enter_open"); - - if (id < 0) { - pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); - return -1; - } - - threads = thread_map__new(-1, getpid(), UINT_MAX); - if (threads == NULL) { - pr_debug("thread_map__new\n"); - return -1; - } - - memset(&attr, 0, sizeof(attr)); - attr.type = PERF_TYPE_TRACEPOINT; - attr.config = id; - evsel = perf_evsel__new(&attr, 0); - if (evsel == NULL) { - pr_debug("perf_evsel__new\n"); - goto out_thread_map_delete; - } - - if (perf_evsel__open_per_thread(evsel, threads) < 0) { - pr_debug("failed to open counter: %s, " - "tweak /proc/sys/kernel/perf_event_paranoid?\n", - strerror(errno)); - goto out_evsel_delete; - } - - for (i = 0; i < nr_open_calls; ++i) { - fd = open("/etc/passwd", O_RDONLY); - close(fd); - } - - if (perf_evsel__read_on_cpu(evsel, 0, 0) < 0) { - pr_debug("perf_evsel__read_on_cpu\n"); - goto out_close_fd; - } - - if (evsel->counts->cpu[0].val != nr_open_calls) { - pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls, got %" PRIu64 "\n", - nr_open_calls, evsel->counts->cpu[0].val); - goto out_close_fd; - } - - err = 0; -out_close_fd: - perf_evsel__close_fd(evsel, 1, threads->nr); -out_evsel_delete: - perf_evsel__delete(evsel); -out_thread_map_delete: - thread_map__delete(threads); - return err; -} - -#include <sched.h> - -static int test__open_syscall_event_on_all_cpus(void) -{ - int err = -1, fd, cpu; - struct thread_map *threads; - struct cpu_map *cpus; - struct perf_evsel *evsel; - struct perf_event_attr attr; - unsigned int nr_open_calls = 111, i; - cpu_set_t cpu_set; - int id = trace_event__id("sys_enter_open"); - - if (id < 0) { - pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); - return -1; - } - - threads = thread_map__new(-1, getpid(), UINT_MAX); - if (threads == NULL) { - pr_debug("thread_map__new\n"); - return -1; - } - - cpus = cpu_map__new(NULL); - if (cpus == NULL) { - pr_debug("cpu_map__new\n"); - goto out_thread_map_delete; - } - - - CPU_ZERO(&cpu_set); - - memset(&attr, 0, sizeof(attr)); - attr.type = PERF_TYPE_TRACEPOINT; - attr.config = id; - evsel = perf_evsel__new(&attr, 0); - if (evsel == NULL) { - pr_debug("perf_evsel__new\n"); - goto out_thread_map_delete; - } - - if (perf_evsel__open(evsel, cpus, threads) < 0) { - pr_debug("failed to open counter: %s, " - "tweak /proc/sys/kernel/perf_event_paranoid?\n", - strerror(errno)); - goto out_evsel_delete; - } - - for (cpu = 0; cpu < cpus->nr; ++cpu) { - unsigned int ncalls = nr_open_calls + cpu; - /* - * XXX eventually lift this restriction in a way that - * keeps perf building on older glibc installations - * without CPU_ALLOC. 1024 cpus in 2010 still seems - * a reasonable upper limit tho :-) - */ - if (cpus->map[cpu] >= CPU_SETSIZE) { - pr_debug("Ignoring CPU %d\n", cpus->map[cpu]); - continue; - } - - CPU_SET(cpus->map[cpu], &cpu_set); - if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { - pr_debug("sched_setaffinity() failed on CPU %d: %s ", - cpus->map[cpu], - strerror(errno)); - goto out_close_fd; - } - for (i = 0; i < ncalls; ++i) { - fd = open("/etc/passwd", O_RDONLY); - close(fd); - } - CPU_CLR(cpus->map[cpu], &cpu_set); - } - - /* - * Here we need to explicitely preallocate the counts, as if - * we use the auto allocation it will allocate just for 1 cpu, - * as we start by cpu 0. - */ - if (perf_evsel__alloc_counts(evsel, cpus->nr) < 0) { - pr_debug("perf_evsel__alloc_counts(ncpus=%d)\n", cpus->nr); - goto out_close_fd; - } - - err = 0; - - for (cpu = 0; cpu < cpus->nr; ++cpu) { - unsigned int expected; - - if (cpus->map[cpu] >= CPU_SETSIZE) - continue; - - if (perf_evsel__read_on_cpu(evsel, cpu, 0) < 0) { - pr_debug("perf_evsel__read_on_cpu\n"); - err = -1; - break; - } - - expected = nr_open_calls + cpu; - if (evsel->counts->cpu[cpu].val != expected) { - pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %" PRIu64 "\n", - expected, cpus->map[cpu], evsel->counts->cpu[cpu].val); - err = -1; - } - } - -out_close_fd: - perf_evsel__close_fd(evsel, 1, threads->nr); -out_evsel_delete: - perf_evsel__delete(evsel); -out_thread_map_delete: - thread_map__delete(threads); - return err; -} - -/* - * This test will generate random numbers of calls to some getpid syscalls, - * then establish an mmap for a group of events that are created to monitor - * the syscalls. - * - * It will receive the events, using mmap, use its PERF_SAMPLE_ID generated - * sample.id field to map back to its respective perf_evsel instance. - * - * Then it checks if the number of syscalls reported as perf events by - * the kernel corresponds to the number of syscalls made. - */ -static int test__basic_mmap(void) -{ - int err = -1; - union perf_event *event; - struct thread_map *threads; - struct cpu_map *cpus; - struct perf_evlist *evlist; - struct perf_event_attr attr = { - .type = PERF_TYPE_TRACEPOINT, - .read_format = PERF_FORMAT_ID, - .sample_type = PERF_SAMPLE_ID, - .watermark = 0, - }; - cpu_set_t cpu_set; - const char *syscall_names[] = { "getsid", "getppid", "getpgrp", - "getpgid", }; - pid_t (*syscalls[])(void) = { (void *)getsid, getppid, getpgrp, - (void*)getpgid }; -#define nsyscalls ARRAY_SIZE(syscall_names) - int ids[nsyscalls]; - unsigned int nr_events[nsyscalls], - expected_nr_events[nsyscalls], i, j; - struct perf_evsel *evsels[nsyscalls], *evsel; - - for (i = 0; i < nsyscalls; ++i) { - char name[64]; - - snprintf(name, sizeof(name), "sys_enter_%s", syscall_names[i]); - ids[i] = trace_event__id(name); - if (ids[i] < 0) { - pr_debug("Is debugfs mounted on /sys/kernel/debug?\n"); - return -1; - } - nr_events[i] = 0; - expected_nr_events[i] = random() % 257; - } - - threads = thread_map__new(-1, getpid(), UINT_MAX); - if (threads == NULL) { - pr_debug("thread_map__new\n"); - return -1; - } - - cpus = cpu_map__new(NULL); - if (cpus == NULL) { - pr_debug("cpu_map__new\n"); - goto out_free_threads; - } - - CPU_ZERO(&cpu_set); - CPU_SET(cpus->map[0], &cpu_set); - sched_setaffinity(0, sizeof(cpu_set), &cpu_set); - if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { - pr_debug("sched_setaffinity() failed on CPU %d: %s ", - cpus->map[0], strerror(errno)); - goto out_free_cpus; - } - - evlist = perf_evlist__new(cpus, threads); - if (evlist == NULL) { - pr_debug("perf_evlist__new\n"); - goto out_free_cpus; - } - - /* anonymous union fields, can't be initialized above */ - attr.wakeup_events = 1; - attr.sample_period = 1; - - for (i = 0; i < nsyscalls; ++i) { - attr.config = ids[i]; - evsels[i] = perf_evsel__new(&attr, i); - if (evsels[i] == NULL) { - pr_debug("perf_evsel__new\n"); - goto out_free_evlist; - } - - perf_evlist__add(evlist, evsels[i]); - - if (perf_evsel__open(evsels[i], cpus, threads) < 0) { - pr_debug("failed to open counter: %s, " - "tweak /proc/sys/kernel/perf_event_paranoid?\n", - strerror(errno)); - goto out_close_fd; - } - } - - if (perf_evlist__mmap(evlist, 128, true) < 0) { - pr_debug("failed to mmap events: %d (%s)\n", errno, - strerror(errno)); - goto out_close_fd; - } - - for (i = 0; i < nsyscalls; ++i) - for (j = 0; j < expected_nr_events[i]; ++j) { - int foo = syscalls[i](); - ++foo; - } - - while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) { - struct perf_sample sample; - - if (event->header.type != PERF_RECORD_SAMPLE) { - pr_debug("unexpected %s event\n", - perf_event__name(event->header.type)); - goto out_munmap; - } - - err = perf_evlist__parse_sample(evlist, event, &sample); - if (err) { - pr_err("Can't parse sample, err = %d\n", err); - goto out_munmap; - } - - evsel = perf_evlist__id2evsel(evlist, sample.id); - if (evsel == NULL) { - pr_debug("event with id %" PRIu64 - " doesn't map to an evsel\n", sample.id); - goto out_munmap; - } - nr_events[evsel->idx]++; - } - - list_for_each_entry(evsel, &evlist->entries, node) { - if (nr_events[evsel->idx] != expected_nr_events[evsel->idx]) { - pr_debug("expected %d %s events, got %d\n", - expected_nr_events[evsel->idx], - perf_evsel__name(evsel), nr_events[evsel->idx]); - goto out_munmap; - } - } - - err = 0; -out_munmap: - perf_evlist__munmap(evlist); -out_close_fd: - for (i = 0; i < nsyscalls; ++i) - perf_evsel__close_fd(evsels[i], 1, threads->nr); -out_free_evlist: - perf_evlist__delete(evlist); -out_free_cpus: - cpu_map__delete(cpus); -out_free_threads: - thread_map__delete(threads); - return err; -#undef nsyscalls -} - -static int sched__get_first_possible_cpu(pid_t pid, cpu_set_t *maskp) -{ - int i, cpu = -1, nrcpus = 1024; -realloc: - CPU_ZERO(maskp); - - if (sched_getaffinity(pid, sizeof(*maskp), maskp) == -1) { - 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(i, maskp)) { - if (cpu == -1) - cpu = i; - else - CPU_CLR(i, maskp); - } - } - - return cpu; -} - -static int test__PERF_RECORD(void) -{ - struct perf_record_opts opts = { - .target = { - .uid = UINT_MAX, - .uses_mmap = true, - }, - .no_delay = true, - .freq = 10, - .mmap_pages = 256, - }; - cpu_set_t cpu_mask; - size_t cpu_mask_size = sizeof(cpu_mask); - 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 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; - 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); - 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 = perf_evlist__first(evlist); - 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); - 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_delete_evlist; - } - - /* - * Call sys_perf_event_open on all the fds on all the evsels, - * grouping them if asked to. - */ - err = perf_evlist__open(evlist); - 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; - } - - /* - * 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_evlist__parse_sample(evlist, event, &sample); - 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_delete_evlist: - perf_evlist__delete(evlist); -out: - return (err < 0 || errs > 0) ? -1 : 0; -} - - -#if defined(__x86_64__) || defined(__i386__) - -#define barrier() asm volatile("" ::: "memory") - -static u64 rdpmc(unsigned int counter) -{ - unsigned int low, high; - - asm volatile("rdpmc" : "=a" (low), "=d" (high) : "c" (counter)); - - return low | ((u64)high) << 32; -} - -static u64 rdtsc(void) -{ - unsigned int low, high; - - asm volatile("rdtsc" : "=a" (low), "=d" (high)); - - return low | ((u64)high) << 32; -} - -static u64 mmap_read_self(void *addr) -{ - struct perf_event_mmap_page *pc = addr; - u32 seq, idx, time_mult = 0, time_shift = 0; - u64 count, cyc = 0, time_offset = 0, enabled, running, delta; - - do { - seq = pc->lock; - barrier(); - - enabled = pc->time_enabled; - running = pc->time_running; - - if (enabled != running) { - cyc = rdtsc(); - time_mult = pc->time_mult; - time_shift = pc->time_shift; - time_offset = pc->time_offset; - } - - idx = pc->index; - count = pc->offset; - if (idx) - count += rdpmc(idx - 1); - - barrier(); - } while (pc->lock != seq); - - if (enabled != running) { - u64 quot, rem; - - quot = (cyc >> time_shift); - rem = cyc & ((1 << time_shift) - 1); - delta = time_offset + quot * time_mult + - ((rem * time_mult) >> time_shift); - - enabled += delta; - if (idx) - running += delta; - - quot = count / running; - rem = count % running; - count = quot * enabled + (rem * enabled) / running; - } - - return count; -} - -/* - * If the RDPMC instruction faults then signal this back to the test parent task: - */ -static void segfault_handler(int sig __maybe_unused, - siginfo_t *info __maybe_unused, - void *uc __maybe_unused) -{ - exit(-1); -} - -static int __test__rdpmc(void) -{ - volatile int tmp = 0; - u64 i, loops = 1000; - int n; - int fd; - void *addr; - struct perf_event_attr attr = { - .type = PERF_TYPE_HARDWARE, - .config = PERF_COUNT_HW_INSTRUCTIONS, - .exclude_kernel = 1, - }; - u64 delta_sum = 0; - struct sigaction sa; - - sigfillset(&sa.sa_mask); - sa.sa_sigaction = segfault_handler; - sigaction(SIGSEGV, &sa, NULL); - - fd = sys_perf_event_open(&attr, 0, -1, -1, 0); - if (fd < 0) { - pr_err("Error: sys_perf_event_open() syscall returned " - "with %d (%s)\n", fd, strerror(errno)); - return -1; - } - - addr = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0); - if (addr == (void *)(-1)) { - pr_err("Error: mmap() syscall returned with (%s)\n", - strerror(errno)); - goto out_close; - } - - for (n = 0; n < 6; n++) { - u64 stamp, now, delta; - - stamp = mmap_read_self(addr); - - for (i = 0; i < loops; i++) - tmp++; - - now = mmap_read_self(addr); - loops *= 10; - - delta = now - stamp; - pr_debug("%14d: %14Lu\n", n, (long long)delta); - - delta_sum += delta; - } - - munmap(addr, page_size); - pr_debug(" "); -out_close: - close(fd); - - if (!delta_sum) - return -1; - - return 0; -} - -static int test__rdpmc(void) -{ - int status = 0; - int wret = 0; - int ret; - int pid; - - pid = fork(); - if (pid < 0) - return -1; - - if (!pid) { - ret = __test__rdpmc(); - - exit(ret); - } - - wret = waitpid(pid, &status, 0); - if (wret < 0 || status) - return -1; - - return 0; -} - -#endif - -static int test__perf_pmu(void) -{ - return perf_pmu__test(); -} - -static int perf_evsel__roundtrip_cache_name_test(void) -{ - char name[128]; - int type, op, err = 0, ret = 0, i, idx; - struct perf_evsel *evsel; - struct perf_evlist *evlist = perf_evlist__new(NULL, NULL); - - if (evlist == NULL) - return -ENOMEM; - - for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) { - for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) { - /* skip invalid cache type */ - if (!perf_evsel__is_cache_op_valid(type, op)) - continue; - - for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { - __perf_evsel__hw_cache_type_op_res_name(type, op, i, - name, sizeof(name)); - err = parse_events(evlist, name, 0); - if (err) - ret = err; - } - } - } - - idx = 0; - evsel = perf_evlist__first(evlist); - - for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) { - for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) { - /* skip invalid cache type */ - if (!perf_evsel__is_cache_op_valid(type, op)) - continue; - - for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { - __perf_evsel__hw_cache_type_op_res_name(type, op, i, - name, sizeof(name)); - if (evsel->idx != idx) - continue; - - ++idx; - - if (strcmp(perf_evsel__name(evsel), name)) { - pr_debug("%s != %s\n", perf_evsel__name(evsel), name); - ret = -1; - } - - evsel = perf_evsel__next(evsel); - } - } - } - - perf_evlist__delete(evlist); - return ret; -} - -static int __perf_evsel__name_array_test(const char *names[], int nr_names) -{ - int i, err; - struct perf_evsel *evsel; - struct perf_evlist *evlist = perf_evlist__new(NULL, NULL); - - if (evlist == NULL) - return -ENOMEM; - - for (i = 0; i < nr_names; ++i) { - err = parse_events(evlist, names[i], 0); - if (err) { - pr_debug("failed to parse event '%s', err %d\n", - names[i], err); - goto out_delete_evlist; - } - } - - err = 0; - list_for_each_entry(evsel, &evlist->entries, node) { - if (strcmp(perf_evsel__name(evsel), names[evsel->idx])) { - --err; - pr_debug("%s != %s\n", perf_evsel__name(evsel), names[evsel->idx]); - } - } - -out_delete_evlist: - perf_evlist__delete(evlist); - return err; -} - -#define perf_evsel__name_array_test(names) \ - __perf_evsel__name_array_test(names, ARRAY_SIZE(names)) - -static int perf_evsel__roundtrip_name_test(void) -{ - int err = 0, ret = 0; - - err = perf_evsel__name_array_test(perf_evsel__hw_names); - if (err) - ret = err; - - err = perf_evsel__name_array_test(perf_evsel__sw_names); - if (err) - ret = err; - - err = perf_evsel__roundtrip_cache_name_test(); - if (err) - ret = err; - - return ret; -} - -static int perf_evsel__test_field(struct perf_evsel *evsel, const char *name, - int size, bool should_be_signed) -{ - struct format_field *field = perf_evsel__field(evsel, name); - int is_signed; - int ret = 0; - - if (field == NULL) { - pr_debug("%s: \"%s\" field not found!\n", evsel->name, name); - return -1; - } - - is_signed = !!(field->flags | FIELD_IS_SIGNED); - if (should_be_signed && !is_signed) { - pr_debug("%s: \"%s\" signedness(%d) is wrong, should be %d\n", - evsel->name, name, is_signed, should_be_signed); - ret = -1; - } - - if (field->size != size) { - pr_debug("%s: \"%s\" size (%d) should be %d!\n", - evsel->name, name, field->size, size); - ret = -1; - } - - return ret; -} - -static int perf_evsel__tp_sched_test(void) -{ - struct perf_evsel *evsel = perf_evsel__newtp("sched", "sched_switch", 0); - int ret = 0; - - if (evsel == NULL) { - pr_debug("perf_evsel__new\n"); - return -1; - } - - if (perf_evsel__test_field(evsel, "prev_comm", 16, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "prev_pid", 4, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "prev_prio", 4, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "prev_state", 8, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "next_comm", 16, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "next_pid", 4, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "next_prio", 4, true)) - ret = -1; - - perf_evsel__delete(evsel); - - evsel = perf_evsel__newtp("sched", "sched_wakeup", 0); - - if (perf_evsel__test_field(evsel, "comm", 16, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "pid", 4, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "prio", 4, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "success", 4, true)) - ret = -1; - - if (perf_evsel__test_field(evsel, "target_cpu", 4, true)) - ret = -1; - - return ret; -} - -static int test__syscall_open_tp_fields(void) -{ - struct perf_record_opts opts = { - .target = { - .uid = UINT_MAX, - .uses_mmap = true, - }, - .no_delay = true, - .freq = 1, - .mmap_pages = 256, - .raw_samples = true, - }; - const char *filename = "/etc/passwd"; - int flags = O_RDONLY | O_DIRECTORY; - struct perf_evlist *evlist = perf_evlist__new(NULL, NULL); - struct perf_evsel *evsel; - int err = -1, i, nr_events = 0, nr_polls = 0; - - if (evlist == NULL) { - pr_debug("%s: perf_evlist__new\n", __func__); - goto out; - } - - evsel = perf_evsel__newtp("syscalls", "sys_enter_open", 0); - if (evsel == NULL) { - pr_debug("%s: perf_evsel__newtp\n", __func__); - goto out_delete_evlist; - } - - perf_evlist__add(evlist, evsel); - - err = perf_evlist__create_maps(evlist, &opts.target); - if (err < 0) { - pr_debug("%s: perf_evlist__create_maps\n", __func__); - goto out_delete_evlist; - } - - perf_evsel__config(evsel, &opts, evsel); - - evlist->threads->map[0] = getpid(); - - err = perf_evlist__open(evlist); - if (err < 0) { - pr_debug("perf_evlist__open: %s\n", strerror(errno)); - goto out_delete_evlist; - } - - err = perf_evlist__mmap(evlist, UINT_MAX, false); - if (err < 0) { - pr_debug("perf_evlist__mmap: %s\n", strerror(errno)); - goto out_delete_evlist; - } - - perf_evlist__enable(evlist); - - /* - * Generate the event: - */ - open(filename, flags); - - while (1) { - int before = nr_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; - int tp_flags; - struct perf_sample sample; - - ++nr_events; - - if (type != PERF_RECORD_SAMPLE) - continue; - - err = perf_evsel__parse_sample(evsel, event, &sample); - if (err) { - pr_err("Can't parse sample, err = %d\n", err); - goto out_munmap; - } - - tp_flags = perf_evsel__intval(evsel, &sample, "flags"); - - if (flags != tp_flags) { - pr_debug("%s: Expected flags=%#x, got %#x\n", - __func__, flags, tp_flags); - goto out_munmap; - } - - goto out_ok; - } - } - - if (nr_events == before) - poll(evlist->pollfd, evlist->nr_fds, 10); - - if (++nr_polls > 5) { - pr_debug("%s: no events!\n", __func__); - goto out_munmap; - } - } -out_ok: - err = 0; -out_munmap: - perf_evlist__munmap(evlist); -out_delete_evlist: - perf_evlist__delete(evlist); -out: - return err; -} +#include "tests.h" +#include "debug.h" +#include "color.h" +#include "parse-options.h" +#include "symbol.h" static struct test { const char *desc; @@ -1409,7 +32,7 @@ static struct test { }, { .desc = "parse events tests", - .func = parse_events__test, + .func = test__parse_events, }, #if defined(__x86_64__) || defined(__i386__) { @@ -1423,19 +46,19 @@ static struct test { }, { .desc = "Test perf pmu format parsing", - .func = test__perf_pmu, + .func = test__pmu, }, { .desc = "Test dso data interface", - .func = dso__test_data, + .func = test__dso_data, }, { .desc = "roundtrip evsel->name check", - .func = perf_evsel__roundtrip_name_test, + .func = test__perf_evsel__roundtrip_name_test, }, { .desc = "Check parsing of sched tracepoints fields", - .func = perf_evsel__tp_sched_test, + .func = test__perf_evsel__tp_sched_test, }, { .desc = "Generate and check syscalls:sys_enter_open event fields", @@ -1443,7 +66,7 @@ static struct test { }, { .desc = "struct perf_event_attr setup", - .func = test_attr__run, + .func = test__attr, }, { .func = NULL, diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 0cd42fc9bc13..5eaffa2de9c5 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -8,6 +8,7 @@ #include "machine.h" #include "symbol.h" +#include "tests.h" #define TEST_ASSERT_VAL(text, cond) \ do { \ @@ -25,6 +26,10 @@ static char *test_file(int size) unsigned char *buf; fd = mkstemp(templ); + if (fd < 0) { + perror("mkstemp failed"); + return NULL; + } buf = malloc(size); if (!buf) { @@ -95,7 +100,7 @@ struct test_data_offset offsets[] = { }, }; -int dso__test_data(void) +int test__dso_data(void) { struct machine machine; struct dso *dso; diff --git a/tools/perf/tests/evsel-roundtrip-name.c b/tools/perf/tests/evsel-roundtrip-name.c new file mode 100644 index 000000000000..e61fc828a158 --- /dev/null +++ b/tools/perf/tests/evsel-roundtrip-name.c @@ -0,0 +1,114 @@ +#include "evlist.h" +#include "evsel.h" +#include "parse-events.h" +#include "tests.h" + +static int perf_evsel__roundtrip_cache_name_test(void) +{ + char name[128]; + int type, op, err = 0, ret = 0, i, idx; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(NULL, NULL); + + if (evlist == NULL) + return -ENOMEM; + + for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) { + for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) { + /* skip invalid cache type */ + if (!perf_evsel__is_cache_op_valid(type, op)) + continue; + + for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { + __perf_evsel__hw_cache_type_op_res_name(type, op, i, + name, sizeof(name)); + err = parse_events(evlist, name, 0); + if (err) + ret = err; + } + } + } + + idx = 0; + evsel = perf_evlist__first(evlist); + + for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) { + for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) { + /* skip invalid cache type */ + if (!perf_evsel__is_cache_op_valid(type, op)) + continue; + + for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { + __perf_evsel__hw_cache_type_op_res_name(type, op, i, + name, sizeof(name)); + if (evsel->idx != idx) + continue; + + ++idx; + + if (strcmp(perf_evsel__name(evsel), name)) { + pr_debug("%s != %s\n", perf_evsel__name(evsel), name); + ret = -1; + } + + evsel = perf_evsel__next(evsel); + } + } + } + + perf_evlist__delete(evlist); + return ret; +} + +static int __perf_evsel__name_array_test(const char *names[], int nr_names) +{ + int i, err; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(NULL, NULL); + + if (evlist == NULL) + return -ENOMEM; + + for (i = 0; i < nr_names; ++i) { + err = parse_events(evlist, names[i], 0); + if (err) { + pr_debug("failed to parse event '%s', err %d\n", + names[i], err); + goto out_delete_evlist; + } + } + + err = 0; + list_for_each_entry(evsel, &evlist->entries, node) { + if (strcmp(perf_evsel__name(evsel), names[evsel->idx])) { + --err; + pr_debug("%s != %s\n", perf_evsel__name(evsel), names[evsel->idx]); + } + } + +out_delete_evlist: + perf_evlist__delete(evlist); + return err; +} + +#define perf_evsel__name_array_test(names) \ + __perf_evsel__name_array_test(names, ARRAY_SIZE(names)) + +int test__perf_evsel__roundtrip_name_test(void) +{ + int err = 0, ret = 0; + + err = perf_evsel__name_array_test(perf_evsel__hw_names); + if (err) + ret = err; + + err = perf_evsel__name_array_test(perf_evsel__sw_names); + if (err) + ret = err; + + err = perf_evsel__roundtrip_cache_name_test(); + if (err) + ret = err; + + return ret; +} diff --git a/tools/perf/tests/evsel-tp-sched.c b/tools/perf/tests/evsel-tp-sched.c new file mode 100644 index 000000000000..a5d2fcc5ae35 --- /dev/null +++ b/tools/perf/tests/evsel-tp-sched.c @@ -0,0 +1,84 @@ +#include "evsel.h" +#include "tests.h" +#include "event-parse.h" + +static int perf_evsel__test_field(struct perf_evsel *evsel, const char *name, + int size, bool should_be_signed) +{ + struct format_field *field = perf_evsel__field(evsel, name); + int is_signed; + int ret = 0; + + if (field == NULL) { + pr_debug("%s: \"%s\" field not found!\n", evsel->name, name); + return -1; + } + + is_signed = !!(field->flags | FIELD_IS_SIGNED); + if (should_be_signed && !is_signed) { + pr_debug("%s: \"%s\" signedness(%d) is wrong, should be %d\n", + evsel->name, name, is_signed, should_be_signed); + ret = -1; + } + + if (field->size != size) { + pr_debug("%s: \"%s\" size (%d) should be %d!\n", + evsel->name, name, field->size, size); + ret = -1; + } + + return ret; +} + +int test__perf_evsel__tp_sched_test(void) +{ + struct perf_evsel *evsel = perf_evsel__newtp("sched", "sched_switch", 0); + int ret = 0; + + if (evsel == NULL) { + pr_debug("perf_evsel__new\n"); + return -1; + } + + if (perf_evsel__test_field(evsel, "prev_comm", 16, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "prev_pid", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "prev_prio", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "prev_state", 8, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "next_comm", 16, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "next_pid", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "next_prio", 4, true)) + ret = -1; + + perf_evsel__delete(evsel); + + evsel = perf_evsel__newtp("sched", "sched_wakeup", 0); + + if (perf_evsel__test_field(evsel, "comm", 16, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "pid", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "prio", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "success", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "target_cpu", 4, true)) + ret = -1; + + return ret; +} diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c new file mode 100644 index 000000000000..e1746811e14b --- /dev/null +++ b/tools/perf/tests/mmap-basic.c @@ -0,0 +1,162 @@ +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tests.h" + +/* + * This test will generate random numbers of calls to some getpid syscalls, + * then establish an mmap for a group of events that are created to monitor + * the syscalls. + * + * It will receive the events, using mmap, use its PERF_SAMPLE_ID generated + * sample.id field to map back to its respective perf_evsel instance. + * + * Then it checks if the number of syscalls reported as perf events by + * the kernel corresponds to the number of syscalls made. + */ +int test__basic_mmap(void) +{ + int err = -1; + union perf_event *event; + struct thread_map *threads; + struct cpu_map *cpus; + struct perf_evlist *evlist; + struct perf_event_attr attr = { + .type = PERF_TYPE_TRACEPOINT, + .read_format = PERF_FORMAT_ID, + .sample_type = PERF_SAMPLE_ID, + .watermark = 0, + }; + cpu_set_t cpu_set; + const char *syscall_names[] = { "getsid", "getppid", "getpgrp", + "getpgid", }; + pid_t (*syscalls[])(void) = { (void *)getsid, getppid, getpgrp, + (void*)getpgid }; +#define nsyscalls ARRAY_SIZE(syscall_names) + int ids[nsyscalls]; + unsigned int nr_events[nsyscalls], + expected_nr_events[nsyscalls], i, j; + struct perf_evsel *evsels[nsyscalls], *evsel; + + for (i = 0; i < nsyscalls; ++i) { + char name[64]; + + snprintf(name, sizeof(name), "sys_enter_%s", syscall_names[i]); + ids[i] = trace_event__id(name); + if (ids[i] < 0) { + pr_debug("Is debugfs mounted on /sys/kernel/debug?\n"); + return -1; + } + nr_events[i] = 0; + expected_nr_events[i] = random() % 257; + } + + threads = thread_map__new(-1, getpid(), UINT_MAX); + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + cpus = cpu_map__new(NULL); + if (cpus == NULL) { + pr_debug("cpu_map__new\n"); + goto out_free_threads; + } + + CPU_ZERO(&cpu_set); + CPU_SET(cpus->map[0], &cpu_set); + sched_setaffinity(0, sizeof(cpu_set), &cpu_set); + if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { + pr_debug("sched_setaffinity() failed on CPU %d: %s ", + cpus->map[0], strerror(errno)); + goto out_free_cpus; + } + + evlist = perf_evlist__new(cpus, threads); + if (evlist == NULL) { + pr_debug("perf_evlist__new\n"); + goto out_free_cpus; + } + + /* anonymous union fields, can't be initialized above */ + attr.wakeup_events = 1; + attr.sample_period = 1; + + for (i = 0; i < nsyscalls; ++i) { + attr.config = ids[i]; + evsels[i] = perf_evsel__new(&attr, i); + if (evsels[i] == NULL) { + pr_debug("perf_evsel__new\n"); + goto out_free_evlist; + } + + perf_evlist__add(evlist, evsels[i]); + + if (perf_evsel__open(evsels[i], cpus, threads) < 0) { + pr_debug("failed to open counter: %s, " + "tweak /proc/sys/kernel/perf_event_paranoid?\n", + strerror(errno)); + goto out_close_fd; + } + } + + if (perf_evlist__mmap(evlist, 128, true) < 0) { + pr_debug("failed to mmap events: %d (%s)\n", errno, + strerror(errno)); + goto out_close_fd; + } + + for (i = 0; i < nsyscalls; ++i) + for (j = 0; j < expected_nr_events[i]; ++j) { + int foo = syscalls[i](); + ++foo; + } + + while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) { + struct perf_sample sample; + + if (event->header.type != PERF_RECORD_SAMPLE) { + pr_debug("unexpected %s event\n", + perf_event__name(event->header.type)); + goto out_munmap; + } + + err = perf_evlist__parse_sample(evlist, event, &sample); + if (err) { + pr_err("Can't parse sample, err = %d\n", err); + goto out_munmap; + } + + evsel = perf_evlist__id2evsel(evlist, sample.id); + if (evsel == NULL) { + pr_debug("event with id %" PRIu64 + " doesn't map to an evsel\n", sample.id); + goto out_munmap; + } + nr_events[evsel->idx]++; + } + + list_for_each_entry(evsel, &evlist->entries, node) { + if (nr_events[evsel->idx] != expected_nr_events[evsel->idx]) { + pr_debug("expected %d %s events, got %d\n", + expected_nr_events[evsel->idx], + perf_evsel__name(evsel), nr_events[evsel->idx]); + goto out_munmap; + } + } + + err = 0; +out_munmap: + perf_evlist__munmap(evlist); +out_close_fd: + for (i = 0; i < nsyscalls; ++i) + perf_evsel__close_fd(evsels[i], 1, threads->nr); +out_free_evlist: + perf_evlist__delete(evlist); +out_free_cpus: + cpu_map__delete(cpus); +out_free_threads: + thread_map__delete(threads); + return err; +} diff --git a/tools/perf/tests/open-syscall-all-cpus.c b/tools/perf/tests/open-syscall-all-cpus.c new file mode 100644 index 000000000000..31072aba0d54 --- /dev/null +++ b/tools/perf/tests/open-syscall-all-cpus.c @@ -0,0 +1,120 @@ +#include "evsel.h" +#include "tests.h" +#include "thread_map.h" +#include "cpumap.h" +#include "debug.h" + +int test__open_syscall_event_on_all_cpus(void) +{ + int err = -1, fd, cpu; + struct thread_map *threads; + struct cpu_map *cpus; + struct perf_evsel *evsel; + struct perf_event_attr attr; + unsigned int nr_open_calls = 111, i; + cpu_set_t cpu_set; + int id = trace_event__id("sys_enter_open"); + + if (id < 0) { + pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); + return -1; + } + + threads = thread_map__new(-1, getpid(), UINT_MAX); + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + cpus = cpu_map__new(NULL); + if (cpus == NULL) { + pr_debug("cpu_map__new\n"); + goto out_thread_map_delete; + } + + + CPU_ZERO(&cpu_set); + + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_TRACEPOINT; + attr.config = id; + evsel = perf_evsel__new(&attr, 0); + if (evsel == NULL) { + pr_debug("perf_evsel__new\n"); + goto out_thread_map_delete; + } + + if (perf_evsel__open(evsel, cpus, threads) < 0) { + pr_debug("failed to open counter: %s, " + "tweak /proc/sys/kernel/perf_event_paranoid?\n", + strerror(errno)); + goto out_evsel_delete; + } + + for (cpu = 0; cpu < cpus->nr; ++cpu) { + unsigned int ncalls = nr_open_calls + cpu; + /* + * XXX eventually lift this restriction in a way that + * keeps perf building on older glibc installations + * without CPU_ALLOC. 1024 cpus in 2010 still seems + * a reasonable upper limit tho :-) + */ + if (cpus->map[cpu] >= CPU_SETSIZE) { + pr_debug("Ignoring CPU %d\n", cpus->map[cpu]); + continue; + } + + CPU_SET(cpus->map[cpu], &cpu_set); + if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { + pr_debug("sched_setaffinity() failed on CPU %d: %s ", + cpus->map[cpu], + strerror(errno)); + goto out_close_fd; + } + for (i = 0; i < ncalls; ++i) { + fd = open("/etc/passwd", O_RDONLY); + close(fd); + } + CPU_CLR(cpus->map[cpu], &cpu_set); + } + + /* + * Here we need to explicitely preallocate the counts, as if + * we use the auto allocation it will allocate just for 1 cpu, + * as we start by cpu 0. + */ + if (perf_evsel__alloc_counts(evsel, cpus->nr) < 0) { + pr_debug("perf_evsel__alloc_counts(ncpus=%d)\n", cpus->nr); + goto out_close_fd; + } + + err = 0; + + for (cpu = 0; cpu < cpus->nr; ++cpu) { + unsigned int expected; + + if (cpus->map[cpu] >= CPU_SETSIZE) + continue; + + if (perf_evsel__read_on_cpu(evsel, cpu, 0) < 0) { + pr_debug("perf_evsel__read_on_cpu\n"); + err = -1; + break; + } + + expected = nr_open_calls + cpu; + if (evsel->counts->cpu[cpu].val != expected) { + pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %" PRIu64 "\n", + expected, cpus->map[cpu], evsel->counts->cpu[cpu].val); + err = -1; + } + } + +out_close_fd: + perf_evsel__close_fd(evsel, 1, threads->nr); +out_evsel_delete: + perf_evsel__delete(evsel); +out_thread_map_delete: + thread_map__delete(threads); + return err; +} diff --git a/tools/perf/tests/open-syscall-tp-fields.c b/tools/perf/tests/open-syscall-tp-fields.c new file mode 100644 index 000000000000..1c52fdc1164e --- /dev/null +++ b/tools/perf/tests/open-syscall-tp-fields.c @@ -0,0 +1,117 @@ +#include "perf.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "tests.h" + +int test__syscall_open_tp_fields(void) +{ + struct perf_record_opts opts = { + .target = { + .uid = UINT_MAX, + .uses_mmap = true, + }, + .no_delay = true, + .freq = 1, + .mmap_pages = 256, + .raw_samples = true, + }; + const char *filename = "/etc/passwd"; + int flags = O_RDONLY | O_DIRECTORY; + struct perf_evlist *evlist = perf_evlist__new(NULL, NULL); + struct perf_evsel *evsel; + int err = -1, i, nr_events = 0, nr_polls = 0; + + if (evlist == NULL) { + pr_debug("%s: perf_evlist__new\n", __func__); + goto out; + } + + evsel = perf_evsel__newtp("syscalls", "sys_enter_open", 0); + if (evsel == NULL) { + pr_debug("%s: perf_evsel__newtp\n", __func__); + goto out_delete_evlist; + } + + perf_evlist__add(evlist, evsel); + + err = perf_evlist__create_maps(evlist, &opts.target); + if (err < 0) { + pr_debug("%s: perf_evlist__create_maps\n", __func__); + goto out_delete_evlist; + } + + perf_evsel__config(evsel, &opts); + + evlist->threads->map[0] = getpid(); + + err = perf_evlist__open(evlist); + if (err < 0) { + pr_debug("perf_evlist__open: %s\n", strerror(errno)); + goto out_delete_evlist; + } + + err = perf_evlist__mmap(evlist, UINT_MAX, false); + if (err < 0) { + pr_debug("perf_evlist__mmap: %s\n", strerror(errno)); + goto out_delete_evlist; + } + + perf_evlist__enable(evlist); + + /* + * Generate the event: + */ + open(filename, flags); + + while (1) { + int before = nr_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; + int tp_flags; + struct perf_sample sample; + + ++nr_events; + + if (type != PERF_RECORD_SAMPLE) + continue; + + err = perf_evsel__parse_sample(evsel, event, &sample); + if (err) { + pr_err("Can't parse sample, err = %d\n", err); + goto out_munmap; + } + + tp_flags = perf_evsel__intval(evsel, &sample, "flags"); + + if (flags != tp_flags) { + pr_debug("%s: Expected flags=%#x, got %#x\n", + __func__, flags, tp_flags); + goto out_munmap; + } + + goto out_ok; + } + } + + if (nr_events == before) + poll(evlist->pollfd, evlist->nr_fds, 10); + + if (++nr_polls > 5) { + pr_debug("%s: no events!\n", __func__); + goto out_munmap; + } + } +out_ok: + err = 0; +out_munmap: + perf_evlist__munmap(evlist); +out_delete_evlist: + perf_evlist__delete(evlist); +out: + return err; +} diff --git a/tools/perf/tests/open-syscall.c b/tools/perf/tests/open-syscall.c new file mode 100644 index 000000000000..98be8b518b4f --- /dev/null +++ b/tools/perf/tests/open-syscall.c @@ -0,0 +1,66 @@ +#include "thread_map.h" +#include "evsel.h" +#include "debug.h" +#include "tests.h" + +int test__open_syscall_event(void) +{ + int err = -1, fd; + struct thread_map *threads; + struct perf_evsel *evsel; + struct perf_event_attr attr; + unsigned int nr_open_calls = 111, i; + int id = trace_event__id("sys_enter_open"); + + if (id < 0) { + pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); + return -1; + } + + threads = thread_map__new(-1, getpid(), UINT_MAX); + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_TRACEPOINT; + attr.config = id; + evsel = perf_evsel__new(&attr, 0); + if (evsel == NULL) { + pr_debug("perf_evsel__new\n"); + goto out_thread_map_delete; + } + + if (perf_evsel__open_per_thread(evsel, threads) < 0) { + pr_debug("failed to open counter: %s, " + "tweak /proc/sys/kernel/perf_event_paranoid?\n", + strerror(errno)); + goto out_evsel_delete; + } + + for (i = 0; i < nr_open_calls; ++i) { + fd = open("/etc/passwd", O_RDONLY); + close(fd); + } + + if (perf_evsel__read_on_cpu(evsel, 0, 0) < 0) { + pr_debug("perf_evsel__read_on_cpu\n"); + goto out_close_fd; + } + + if (evsel->counts->cpu[0].val != nr_open_calls) { + pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls, got %" PRIu64 "\n", + nr_open_calls, evsel->counts->cpu[0].val); + goto out_close_fd; + } + + err = 0; +out_close_fd: + perf_evsel__close_fd(evsel, 1, threads->nr); +out_evsel_delete: + perf_evsel__delete(evsel); +out_thread_map_delete: + thread_map__delete(threads); + return err; +} diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c index b49c2eebff33..42a0c8cd3cd5 100644 --- a/tools/perf/tests/parse-events.c +++ b/tools/perf/tests/parse-events.c @@ -4,6 +4,7 @@ #include "evlist.h" #include "sysfs.h" #include "../../../include/linux/hw_breakpoint.h" +#include "tests.h" #define TEST_ASSERT_VAL(text, cond) \ do { \ @@ -520,7 +521,7 @@ static int test__group1(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); /* cycles:upp */ evsel = perf_evsel__next(evsel); @@ -556,7 +557,7 @@ static int test__group2(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); /* cache-references + :u modifier */ evsel = perf_evsel__next(evsel); @@ -582,7 +583,7 @@ static int test__group2(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); return 0; } @@ -605,7 +606,7 @@ static int test__group3(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); TEST_ASSERT_VAL("wrong group name", !strcmp(leader->group_name, "group1")); @@ -635,7 +636,7 @@ static int test__group3(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); TEST_ASSERT_VAL("wrong group name", !strcmp(leader->group_name, "group2")); @@ -662,7 +663,7 @@ static int test__group3(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); return 0; } @@ -686,7 +687,7 @@ static int test__group4(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 1); TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); /* instructions:kp + p */ evsel = perf_evsel__next(evsel); @@ -723,7 +724,7 @@ static int test__group5(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); /* instructions + G */ evsel = perf_evsel__next(evsel); @@ -750,7 +751,7 @@ static int test__group5(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); /* instructions:G */ evsel = perf_evsel__next(evsel); @@ -776,7 +777,7 @@ static int test__group5(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == NULL); + TEST_ASSERT_VAL("wrong leader", !perf_evsel__is_group_member(evsel)); return 0; } @@ -1086,7 +1087,7 @@ static int test_pmu_events(void) return ret; } -int parse_events__test(void) +int test__parse_events(void) { int ret1, ret2 = 0; diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c new file mode 100644 index 000000000000..70e0d4421df8 --- /dev/null +++ b/tools/perf/tests/perf-record.c @@ -0,0 +1,312 @@ +#include <sched.h> +#include "evlist.h" +#include "evsel.h" +#include "perf.h" +#include "debug.h" +#include "tests.h" + +static int sched__get_first_possible_cpu(pid_t pid, cpu_set_t *maskp) +{ + int i, cpu = -1, nrcpus = 1024; +realloc: + CPU_ZERO(maskp); + + if (sched_getaffinity(pid, sizeof(*maskp), maskp) == -1) { + 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(i, maskp)) { + if (cpu == -1) + cpu = i; + else + CPU_CLR(i, maskp); + } + } + + return cpu; +} + +int test__PERF_RECORD(void) +{ + struct perf_record_opts opts = { + .target = { + .uid = UINT_MAX, + .uses_mmap = true, + }, + .no_delay = true, + .freq = 10, + .mmap_pages = 256, + }; + cpu_set_t cpu_mask; + size_t cpu_mask_size = sizeof(cpu_mask); + 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 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; + 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); + 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 = perf_evlist__first(evlist); + 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); + 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_delete_evlist; + } + + /* + * Call sys_perf_event_open on all the fds on all the evsels, + * grouping them if asked to. + */ + err = perf_evlist__open(evlist); + 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; + } + + /* + * 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_evlist__parse_sample(evlist, event, &sample); + 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_delete_evlist: + perf_evlist__delete(evlist); +out: + return (err < 0 || errs > 0) ? -1 : 0; +} diff --git a/tools/perf/tests/pmu.c b/tools/perf/tests/pmu.c new file mode 100644 index 000000000000..a5f379863b8f --- /dev/null +++ b/tools/perf/tests/pmu.c @@ -0,0 +1,178 @@ +#include "parse-events.h" +#include "pmu.h" +#include "util.h" +#include "tests.h" + +/* Simulated format definitions. */ +static struct test_format { + const char *name; + const char *value; +} test_formats[] = { + { "krava01", "config:0-1,62-63\n", }, + { "krava02", "config:10-17\n", }, + { "krava03", "config:5\n", }, + { "krava11", "config1:0,2,4,6,8,20-28\n", }, + { "krava12", "config1:63\n", }, + { "krava13", "config1:45-47\n", }, + { "krava21", "config2:0-3,10-13,20-23,30-33,40-43,50-53,60-63\n", }, + { "krava22", "config2:8,18,48,58\n", }, + { "krava23", "config2:28-29,38\n", }, +}; + +#define TEST_FORMATS_CNT (sizeof(test_formats) / sizeof(struct test_format)) + +/* Simulated users input. */ +static struct parse_events__term test_terms[] = { + { + .config = (char *) "krava01", + .val.num = 15, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava02", + .val.num = 170, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava03", + .val.num = 1, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava11", + .val.num = 27, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava12", + .val.num = 1, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava13", + .val.num = 2, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava21", + .val.num = 119, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava22", + .val.num = 11, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, + { + .config = (char *) "krava23", + .val.num = 2, + .type_val = PARSE_EVENTS__TERM_TYPE_NUM, + .type_term = PARSE_EVENTS__TERM_TYPE_USER, + }, +}; +#define TERMS_CNT (sizeof(test_terms) / sizeof(struct parse_events__term)) + +/* + * Prepare format directory data, exported by kernel + * at /sys/bus/event_source/devices/<dev>/format. + */ +static char *test_format_dir_get(void) +{ + static char dir[PATH_MAX]; + unsigned int i; + + snprintf(dir, PATH_MAX, "/tmp/perf-pmu-test-format-XXXXXX"); + if (!mkdtemp(dir)) + return NULL; + + for (i = 0; i < TEST_FORMATS_CNT; i++) { + static char name[PATH_MAX]; + struct test_format *format = &test_formats[i]; + FILE *file; + + snprintf(name, PATH_MAX, "%s/%s", dir, format->name); + + file = fopen(name, "w"); + if (!file) + return NULL; + + if (1 != fwrite(format->value, strlen(format->value), 1, file)) + break; + + fclose(file); + } + + return dir; +} + +/* Cleanup format directory. */ +static int test_format_dir_put(char *dir) +{ + char buf[PATH_MAX]; + snprintf(buf, PATH_MAX, "rm -f %s/*\n", dir); + if (system(buf)) + return -1; + + snprintf(buf, PATH_MAX, "rmdir %s\n", dir); + return system(buf); +} + +static struct list_head *test_terms_list(void) +{ + static LIST_HEAD(terms); + unsigned int i; + + for (i = 0; i < TERMS_CNT; i++) + list_add_tail(&test_terms[i].list, &terms); + + return &terms; +} + +#undef TERMS_CNT + +int test__pmu(void) +{ + char *format = test_format_dir_get(); + LIST_HEAD(formats); + struct list_head *terms = test_terms_list(); + int ret; + + if (!format) + return -EINVAL; + + do { + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(attr)); + + ret = perf_pmu__format_parse(format, &formats); + if (ret) + break; + + ret = perf_pmu__config_terms(&formats, &attr, terms); + if (ret) + break; + + ret = -EINVAL; + + if (attr.config != 0xc00000000002a823) + break; + if (attr.config1 != 0x8000400000000145) + break; + if (attr.config2 != 0x0400000020041d07) + break; + + ret = 0; + } while (0); + + test_format_dir_put(format); + return ret; +} diff --git a/tools/perf/tests/rdpmc.c b/tools/perf/tests/rdpmc.c new file mode 100644 index 000000000000..ff94886aad99 --- /dev/null +++ b/tools/perf/tests/rdpmc.c @@ -0,0 +1,175 @@ +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/mman.h> +#include "types.h" +#include "perf.h" +#include "debug.h" +#include "tests.h" + +#if defined(__x86_64__) || defined(__i386__) + +#define barrier() asm volatile("" ::: "memory") + +static u64 rdpmc(unsigned int counter) +{ + unsigned int low, high; + + asm volatile("rdpmc" : "=a" (low), "=d" (high) : "c" (counter)); + + return low | ((u64)high) << 32; +} + +static u64 rdtsc(void) +{ + unsigned int low, high; + + asm volatile("rdtsc" : "=a" (low), "=d" (high)); + + return low | ((u64)high) << 32; +} + +static u64 mmap_read_self(void *addr) +{ + struct perf_event_mmap_page *pc = addr; + u32 seq, idx, time_mult = 0, time_shift = 0; + u64 count, cyc = 0, time_offset = 0, enabled, running, delta; + + do { + seq = pc->lock; + barrier(); + + enabled = pc->time_enabled; + running = pc->time_running; + + if (enabled != running) { + cyc = rdtsc(); + time_mult = pc->time_mult; + time_shift = pc->time_shift; + time_offset = pc->time_offset; + } + + idx = pc->index; + count = pc->offset; + if (idx) + count += rdpmc(idx - 1); + + barrier(); + } while (pc->lock != seq); + + if (enabled != running) { + u64 quot, rem; + + quot = (cyc >> time_shift); + rem = cyc & ((1 << time_shift) - 1); + delta = time_offset + quot * time_mult + + ((rem * time_mult) >> time_shift); + + enabled += delta; + if (idx) + running += delta; + + quot = count / running; + rem = count % running; + count = quot * enabled + (rem * enabled) / running; + } + + return count; +} + +/* + * If the RDPMC instruction faults then signal this back to the test parent task: + */ +static void segfault_handler(int sig __maybe_unused, + siginfo_t *info __maybe_unused, + void *uc __maybe_unused) +{ + exit(-1); +} + +static int __test__rdpmc(void) +{ + volatile int tmp = 0; + u64 i, loops = 1000; + int n; + int fd; + void *addr; + struct perf_event_attr attr = { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_INSTRUCTIONS, + .exclude_kernel = 1, + }; + u64 delta_sum = 0; + struct sigaction sa; + + sigfillset(&sa.sa_mask); + sa.sa_sigaction = segfault_handler; + sigaction(SIGSEGV, &sa, NULL); + + fd = sys_perf_event_open(&attr, 0, -1, -1, 0); + if (fd < 0) { + pr_err("Error: sys_perf_event_open() syscall returned " + "with %d (%s)\n", fd, strerror(errno)); + return -1; + } + + addr = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0); + if (addr == (void *)(-1)) { + pr_err("Error: mmap() syscall returned with (%s)\n", + strerror(errno)); + goto out_close; + } + + for (n = 0; n < 6; n++) { + u64 stamp, now, delta; + + stamp = mmap_read_self(addr); + + for (i = 0; i < loops; i++) + tmp++; + + now = mmap_read_self(addr); + loops *= 10; + + delta = now - stamp; + pr_debug("%14d: %14Lu\n", n, (long long)delta); + + delta_sum += delta; + } + + munmap(addr, page_size); + pr_debug(" "); +out_close: + close(fd); + + if (!delta_sum) + return -1; + + return 0; +} + +int test__rdpmc(void) +{ + int status = 0; + int wret = 0; + int ret; + int pid; + + pid = fork(); + if (pid < 0) + return -1; + + if (!pid) { + ret = __test__rdpmc(); + + exit(ret); + } + + wret = waitpid(pid, &status, 0); + if (wret < 0 || status) + return -1; + + return 0; +} + +#endif diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h new file mode 100644 index 000000000000..fc121edab016 --- /dev/null +++ b/tools/perf/tests/tests.h @@ -0,0 +1,22 @@ +#ifndef TESTS_H +#define TESTS_H + +/* Tests */ +int test__vmlinux_matches_kallsyms(void); +int test__open_syscall_event(void); +int test__open_syscall_event_on_all_cpus(void); +int test__basic_mmap(void); +int test__PERF_RECORD(void); +int test__rdpmc(void); +int test__perf_evsel__roundtrip_name_test(void); +int test__perf_evsel__tp_sched_test(void); +int test__syscall_open_tp_fields(void); +int test__pmu(void); +int test__attr(void); +int test__dso_data(void); +int test__parse_events(void); + +/* Util */ +int trace_event__id(const char *evname); + +#endif /* TESTS_H */ diff --git a/tools/perf/tests/util.c b/tools/perf/tests/util.c new file mode 100644 index 000000000000..748f2e8f6961 --- /dev/null +++ b/tools/perf/tests/util.c @@ -0,0 +1,30 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "tests.h" +#include "debugfs.h" + +int trace_event__id(const char *evname) +{ + char *filename; + int err = -1, fd; + + if (asprintf(&filename, + "%s/syscalls/%s/id", + tracing_events_path, evname) < 0) + return -1; + + fd = open(filename, O_RDONLY); + if (fd >= 0) { + char id[16]; + if (read(fd, id, sizeof(id)) > 0) + err = atoi(id); + close(fd); + } + + free(filename); + return err; +} diff --git a/tools/perf/tests/vmlinux-kallsyms.c b/tools/perf/tests/vmlinux-kallsyms.c new file mode 100644 index 000000000000..0d1cdbee2f59 --- /dev/null +++ b/tools/perf/tests/vmlinux-kallsyms.c @@ -0,0 +1,230 @@ +#include <linux/compiler.h> +#include <linux/rbtree.h> +#include <string.h> +#include "map.h" +#include "symbol.h" +#include "util.h" +#include "tests.h" +#include "debug.h" +#include "machine.h" + +static int vmlinux_matches_kallsyms_filter(struct map *map __maybe_unused, + struct symbol *sym) +{ + bool *visited = symbol__priv(sym); + *visited = true; + return 0; +} + +int test__vmlinux_matches_kallsyms(void) +{ + int err = -1; + struct rb_node *nd; + struct symbol *sym; + struct map *kallsyms_map, *vmlinux_map; + struct machine kallsyms, vmlinux; + enum map_type type = MAP__FUNCTION; + struct ref_reloc_sym ref_reloc_sym = { .name = "_stext", }; + + /* + * Step 1: + * + * Init the machines that will hold kernel, modules obtained from + * both vmlinux + .ko files and from /proc/kallsyms split by modules. + */ + machine__init(&kallsyms, "", HOST_KERNEL_ID); + machine__init(&vmlinux, "", HOST_KERNEL_ID); + + /* + * Step 2: + * + * Create the kernel maps for kallsyms and the DSO where we will then + * load /proc/kallsyms. Also create the modules maps from /proc/modules + * and find the .ko files that match them in /lib/modules/`uname -r`/. + */ + if (machine__create_kernel_maps(&kallsyms) < 0) { + pr_debug("machine__create_kernel_maps "); + return -1; + } + + /* + * Step 3: + * + * Load and split /proc/kallsyms into multiple maps, one per module. + */ + if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms", type, NULL) <= 0) { + pr_debug("dso__load_kallsyms "); + goto out; + } + + /* + * Step 4: + * + * kallsyms will be internally on demand sorted by name so that we can + * find the reference relocation * symbol, i.e. the symbol we will use + * to see if the running kernel was relocated by checking if it has the + * same value in the vmlinux file we load. + */ + kallsyms_map = machine__kernel_map(&kallsyms, type); + + sym = map__find_symbol_by_name(kallsyms_map, ref_reloc_sym.name, NULL); + if (sym == NULL) { + pr_debug("dso__find_symbol_by_name "); + goto out; + } + + ref_reloc_sym.addr = sym->start; + + /* + * Step 5: + * + * Now repeat step 2, this time for the vmlinux file we'll auto-locate. + */ + if (machine__create_kernel_maps(&vmlinux) < 0) { + pr_debug("machine__create_kernel_maps "); + goto out; + } + + vmlinux_map = machine__kernel_map(&vmlinux, type); + map__kmap(vmlinux_map)->ref_reloc_sym = &ref_reloc_sym; + + /* + * Step 6: + * + * Locate a vmlinux file in the vmlinux path that has a buildid that + * matches the one of the running kernel. + * + * While doing that look if we find the ref reloc symbol, if we find it + * we'll have its ref_reloc_symbol.unrelocated_addr and then + * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines + * to fixup the symbols. + */ + if (machine__load_vmlinux_path(&vmlinux, type, + vmlinux_matches_kallsyms_filter) <= 0) { + pr_debug("machine__load_vmlinux_path "); + goto out; + } + + err = 0; + /* + * Step 7: + * + * Now look at the symbols in the vmlinux DSO and check if we find all of them + * in the kallsyms dso. For the ones that are in both, check its names and + * end addresses too. + */ + for (nd = rb_first(&vmlinux_map->dso->symbols[type]); nd; nd = rb_next(nd)) { + struct symbol *pair, *first_pair; + bool backwards = true; + + sym = rb_entry(nd, struct symbol, rb_node); + + if (sym->start == sym->end) + continue; + + first_pair = machine__find_kernel_symbol(&kallsyms, type, sym->start, NULL, NULL); + pair = first_pair; + + if (pair && pair->start == sym->start) { +next_pair: + if (strcmp(sym->name, pair->name) == 0) { + /* + * kallsyms don't have the symbol end, so we + * set that by using the next symbol start - 1, + * in some cases we get this up to a page + * wrong, trace_kmalloc when I was developing + * this code was one such example, 2106 bytes + * off the real size. More than that and we + * _really_ have a problem. + */ + s64 skew = sym->end - pair->end; + if (llabs(skew) < page_size) + continue; + + pr_debug("%#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n", + sym->start, sym->name, sym->end, pair->end); + } else { + struct rb_node *nnd; +detour: + nnd = backwards ? rb_prev(&pair->rb_node) : + rb_next(&pair->rb_node); + if (nnd) { + struct symbol *next = rb_entry(nnd, struct symbol, rb_node); + + if (next->start == sym->start) { + pair = next; + goto next_pair; + } + } + + if (backwards) { + backwards = false; + pair = first_pair; + goto detour; + } + + pr_debug("%#" PRIx64 ": diff name v: %s k: %s\n", + sym->start, sym->name, pair->name); + } + } else + pr_debug("%#" PRIx64 ": %s not on kallsyms\n", sym->start, sym->name); + + err = -1; + } + + if (!verbose) + goto out; + + pr_info("Maps only in vmlinux:\n"); + + for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { + struct map *pos = rb_entry(nd, struct map, rb_node), *pair; + /* + * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while + * the kernel will have the path for the vmlinux file being used, + * so use the short name, less descriptive but the same ("[kernel]" in + * both cases. + */ + pair = map_groups__find_by_name(&kallsyms.kmaps, type, + (pos->dso->kernel ? + pos->dso->short_name : + pos->dso->name)); + if (pair) + pair->priv = 1; + else + map__fprintf(pos, stderr); + } + + pr_info("Maps in vmlinux with a different name in kallsyms:\n"); + + for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { + struct map *pos = rb_entry(nd, struct map, rb_node), *pair; + + pair = map_groups__find(&kallsyms.kmaps, type, pos->start); + if (pair == NULL || pair->priv) + continue; + + if (pair->start == pos->start) { + pair->priv = 1; + pr_info(" %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as", + pos->start, pos->end, pos->pgoff, pos->dso->name); + if (pos->pgoff != pair->pgoff || pos->end != pair->end) + pr_info(": \n*%" PRIx64 "-%" PRIx64 " %" PRIx64 "", + pair->start, pair->end, pair->pgoff); + pr_info(" %s\n", pair->dso->name); + pair->priv = 1; + } + } + + pr_info("Maps only in kallsyms:\n"); + + for (nd = rb_first(&kallsyms.kmaps.maps[type]); + nd; nd = rb_next(nd)) { + struct map *pos = rb_entry(nd, struct map, rb_node); + + if (!pos->priv) + map__fprintf(pos, stderr); + } +out: + return err; +} diff --git a/tools/perf/ui/gtk/gtk.h b/tools/perf/ui/gtk/gtk.h index 687af0bba187..856320e2cc05 100644 --- a/tools/perf/ui/gtk/gtk.h +++ b/tools/perf/ui/gtk/gtk.h @@ -30,6 +30,7 @@ struct perf_gtk_context *perf_gtk__activate_context(GtkWidget *window); int perf_gtk__deactivate_context(struct perf_gtk_context **ctx); void perf_gtk__init_helpline(void); +void perf_gtk__init_progress(void); void perf_gtk__init_hpp(void); #ifndef HAVE_GTK_INFO_BAR diff --git a/tools/perf/ui/gtk/progress.c b/tools/perf/ui/gtk/progress.c new file mode 100644 index 000000000000..482bcf3df9b7 --- /dev/null +++ b/tools/perf/ui/gtk/progress.c @@ -0,0 +1,59 @@ +#include <inttypes.h> + +#include "gtk.h" +#include "../progress.h" +#include "util.h" + +static GtkWidget *dialog; +static GtkWidget *progress; + +static void gtk_progress_update(u64 curr, u64 total, const char *title) +{ + double fraction = total ? 1.0 * curr / total : 0.0; + char buf[1024]; + + if (dialog == NULL) { + GtkWidget *vbox = gtk_vbox_new(TRUE, 5); + GtkWidget *label = gtk_label_new(title); + + dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); + progress = gtk_progress_bar_new(); + + gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, FALSE, 3); + gtk_box_pack_start(GTK_BOX(vbox), progress, TRUE, TRUE, 3); + + gtk_container_add(GTK_CONTAINER(dialog), vbox); + + gtk_window_set_title(GTK_WINDOW(dialog), "perf"); + gtk_window_resize(GTK_WINDOW(dialog), 300, 80); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + + gtk_widget_show_all(dialog); + } + + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction); + snprintf(buf, sizeof(buf), "%"PRIu64" / %"PRIu64, curr, total); + gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress), buf); + + /* we didn't call gtk_main yet, so do it manually */ + while (gtk_events_pending()) + gtk_main_iteration(); +} + +static void gtk_progress_finish(void) +{ + /* this will also destroy all of its children */ + gtk_widget_destroy(dialog); + + dialog = NULL; +} + +static struct ui_progress gtk_progress_fns = { + .update = gtk_progress_update, + .finish = gtk_progress_finish, +}; + +void perf_gtk__init_progress(void) +{ + progress_fns = >k_progress_fns; +} diff --git a/tools/perf/ui/gtk/setup.c b/tools/perf/ui/gtk/setup.c index 3c4c6ef78283..6c2dd2e423f3 100644 --- a/tools/perf/ui/gtk/setup.c +++ b/tools/perf/ui/gtk/setup.c @@ -8,7 +8,9 @@ int perf_gtk__init(void) { perf_error__register(&perf_gtk_eops); perf_gtk__init_helpline(); + perf_gtk__init_progress(); perf_gtk__init_hpp(); + return gtk_init_check(NULL, NULL) ? 0 : -1; } diff --git a/tools/perf/ui/gtk/util.c b/tools/perf/ui/gtk/util.c index ccb046aac98b..c06942a41c78 100644 --- a/tools/perf/ui/gtk/util.c +++ b/tools/perf/ui/gtk/util.c @@ -111,14 +111,3 @@ struct perf_error_ops perf_gtk_eops = { .warning = perf_gtk__warning_statusbar, #endif }; - -/* - * FIXME: Functions below should be implemented properly. - * For now, just add stubs for NO_NEWT=1 build. - */ -#ifndef NEWT_SUPPORT -void ui_progress__update(u64 curr __maybe_unused, u64 total __maybe_unused, - const char *title __maybe_unused) -{ -} -#endif diff --git a/tools/perf/ui/progress.c b/tools/perf/ui/progress.c index 13aa64e50e11..3ec695607a4d 100644 --- a/tools/perf/ui/progress.c +++ b/tools/perf/ui/progress.c @@ -1,32 +1,26 @@ #include "../cache.h" #include "progress.h" -#include "libslang.h" -#include "ui.h" -#include "browser.h" -void ui_progress__update(u64 curr, u64 total, const char *title) +static void nop_progress_update(u64 curr __maybe_unused, + u64 total __maybe_unused, + const char *title __maybe_unused) { - int bar, y; - /* - * FIXME: We should have a per UI backend way of showing progress, - * stdio will just show a percentage as NN%, etc. - */ - if (use_browser <= 0) - return; +} - if (total == 0) - return; +static struct ui_progress default_progress_fns = +{ + .update = nop_progress_update, +}; - ui__refresh_dimensions(true); - pthread_mutex_lock(&ui__lock); - y = SLtt_Screen_Rows / 2 - 2; - SLsmg_set_color(0); - SLsmg_draw_box(y, 0, 3, SLtt_Screen_Cols); - SLsmg_gotorc(y++, 1); - SLsmg_write_string((char *)title); - SLsmg_set_color(HE_COLORSET_SELECTED); - bar = ((SLtt_Screen_Cols - 2) * curr) / total; - SLsmg_fill_region(y, 1, 1, bar, ' '); - SLsmg_refresh(); - pthread_mutex_unlock(&ui__lock); +struct ui_progress *progress_fns = &default_progress_fns; + +void ui_progress__update(u64 curr, u64 total, const char *title) +{ + return progress_fns->update(curr, total, title); +} + +void ui_progress__finish(void) +{ + if (progress_fns->finish) + progress_fns->finish(); } diff --git a/tools/perf/ui/progress.h b/tools/perf/ui/progress.h index d9c205b59aa1..257cc224f9cf 100644 --- a/tools/perf/ui/progress.h +++ b/tools/perf/ui/progress.h @@ -3,6 +3,16 @@ #include <../types.h> +struct ui_progress { + void (*update)(u64, u64, const char *); + void (*finish)(void); +}; + +extern struct ui_progress *progress_fns; + +void ui_progress__init(void); + void ui_progress__update(u64 curr, u64 total, const char *title); +void ui_progress__finish(void); #endif diff --git a/tools/perf/ui/tui/progress.c b/tools/perf/ui/tui/progress.c new file mode 100644 index 000000000000..6c2184d53cbf --- /dev/null +++ b/tools/perf/ui/tui/progress.c @@ -0,0 +1,42 @@ +#include "../cache.h" +#include "../progress.h" +#include "../libslang.h" +#include "../ui.h" +#include "../browser.h" + +static void tui_progress__update(u64 curr, u64 total, const char *title) +{ + int bar, y; + /* + * FIXME: We should have a per UI backend way of showing progress, + * stdio will just show a percentage as NN%, etc. + */ + if (use_browser <= 0) + return; + + if (total == 0) + return; + + ui__refresh_dimensions(true); + pthread_mutex_lock(&ui__lock); + y = SLtt_Screen_Rows / 2 - 2; + SLsmg_set_color(0); + SLsmg_draw_box(y, 0, 3, SLtt_Screen_Cols); + SLsmg_gotorc(y++, 1); + SLsmg_write_string((char *)title); + SLsmg_set_color(HE_COLORSET_SELECTED); + bar = ((SLtt_Screen_Cols - 2) * curr) / total; + SLsmg_fill_region(y, 1, 1, bar, ' '); + SLsmg_refresh(); + pthread_mutex_unlock(&ui__lock); +} + +static struct ui_progress tui_progress_fns = +{ + .update = tui_progress__update, +}; + +void ui_progress__init(void) +{ + progress_fns = &tui_progress_fns; +} diff --git a/tools/perf/ui/tui/setup.c b/tools/perf/ui/tui/setup.c index 60debb81537a..81efa192e86c 100644 --- a/tools/perf/ui/tui/setup.c +++ b/tools/perf/ui/tui/setup.c @@ -118,6 +118,7 @@ int ui__init(void) newtSetSuspendCallback(newt_suspend, NULL); ui_helpline__init(); ui_browser__init(); + ui_progress__init(); signal(SIGSEGV, ui__signal); signal(SIGFPE, ui__signal); diff --git a/tools/perf/ui/ui.h b/tools/perf/ui/ui.h index 7b67045479f6..d86359c99907 100644 --- a/tools/perf/ui/ui.h +++ b/tools/perf/ui/ui.h @@ -3,9 +3,37 @@ #include <pthread.h> #include <stdbool.h> +#include <linux/compiler.h> extern pthread_mutex_t ui__lock; +extern int use_browser; + +void setup_browser(bool fallback_to_pager); +void exit_browser(bool wait_for_ok); + +#ifdef NEWT_SUPPORT +int ui__init(void); +void ui__exit(bool wait_for_ok); +#else +static inline int ui__init(void) +{ + return -1; +} +static inline void ui__exit(bool wait_for_ok __maybe_unused) {} +#endif + +#ifdef GTK2_SUPPORT +int perf_gtk__init(void); +void perf_gtk__exit(bool wait_for_ok); +#else +static inline int perf_gtk__init(void) +{ + return -1; +} +static inline void perf_gtk__exit(bool wait_for_ok __maybe_unused) {} +#endif + void ui__refresh_dimensions(bool force); #endif /* _PERF_UI_H_ */ diff --git a/tools/perf/util/cache.h b/tools/perf/util/cache.h index 2bd51370ad28..26e367239873 100644 --- a/tools/perf/util/cache.h +++ b/tools/perf/util/cache.h @@ -5,6 +5,7 @@ #include "util.h" #include "strbuf.h" #include "../perf.h" +#include "../ui/ui.h" #define CMD_EXEC_PATH "--exec-path" #define CMD_PERF_DIR "--perf-dir=" @@ -31,44 +32,6 @@ extern const char *pager_program; extern int pager_in_use(void); extern int pager_use_color; -extern int use_browser; - -#if defined(NEWT_SUPPORT) || defined(GTK2_SUPPORT) -void setup_browser(bool fallback_to_pager); -void exit_browser(bool wait_for_ok); - -#ifdef NEWT_SUPPORT -int ui__init(void); -void ui__exit(bool wait_for_ok); -#else -static inline int ui__init(void) -{ - return -1; -} -static inline void ui__exit(bool wait_for_ok __maybe_unused) {} -#endif - -#ifdef GTK2_SUPPORT -int perf_gtk__init(void); -void perf_gtk__exit(bool wait_for_ok); -#else -static inline int perf_gtk__init(void) -{ - return -1; -} -static inline void perf_gtk__exit(bool wait_for_ok __maybe_unused) {} -#endif - -#else /* NEWT_SUPPORT || GTK2_SUPPORT */ - -static inline void setup_browser(bool fallback_to_pager) -{ - if (fallback_to_pager) - setup_pager(); -} -static inline void exit_browser(bool wait_for_ok __maybe_unused) {} -#endif /* NEWT_SUPPORT || GTK2_SUPPORT */ - char *alias_lookup(const char *alias); int split_cmdline(char *cmdline, const char ***argv); diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index dec98750b484..83e8d234af6b 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -26,6 +26,7 @@ int ui__error(const char *format, ...) __attribute__((format(printf, 1, 2))); static inline void ui_progress__update(u64 curr __maybe_unused, u64 total __maybe_unused, const char *title __maybe_unused) {} +static inline void ui_progress__finish(void) {} #define ui__error(format, arg...) ui__warning(format, ##arg) diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index ca9ca285406a..3cf2c3e0605f 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -193,55 +193,43 @@ static int perf_event__synthesize_mmap_events(struct perf_tool *tool, event->header.misc = PERF_RECORD_MISC_USER; while (1) { - char bf[BUFSIZ], *pbf = bf; - int n; + char bf[BUFSIZ]; + char prot[5]; + char execname[PATH_MAX]; + char anonstr[] = "//anon"; size_t size; + if (fgets(bf, sizeof(bf), fp) == NULL) break; + /* ensure null termination since stack will be reused. */ + strcpy(execname, ""); + /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */ - n = hex2u64(pbf, &event->mmap.start); - if (n < 0) - continue; - pbf += n + 1; - n = hex2u64(pbf, &event->mmap.len); - if (n < 0) + sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %*x:%*x %*u %s\n", + &event->mmap.start, &event->mmap.len, prot, + &event->mmap.pgoff, execname); + + if (prot[2] != 'x') continue; - pbf += n + 3; - if (*pbf == 'x') { /* vm_exec */ - char anonstr[] = "//anon\n"; - char *execname = strchr(bf, '/'); - - /* Catch VDSO */ - if (execname == NULL) - execname = strstr(bf, "[vdso]"); - - /* Catch anonymous mmaps */ - if ((execname == NULL) && !strstr(bf, "[")) - execname = anonstr; - - if (execname == NULL) - continue; - - pbf += 3; - n = hex2u64(pbf, &event->mmap.pgoff); - - size = strlen(execname); - execname[size - 1] = '\0'; /* Remove \n */ - memcpy(event->mmap.filename, execname, size); - size = PERF_ALIGN(size, sizeof(u64)); - event->mmap.len -= event->mmap.start; - event->mmap.header.size = (sizeof(event->mmap) - - (sizeof(event->mmap.filename) - size)); - memset(event->mmap.filename + size, 0, machine->id_hdr_size); - event->mmap.header.size += machine->id_hdr_size; - event->mmap.pid = tgid; - event->mmap.tid = pid; - - if (process(tool, event, &synth_sample, machine) != 0) { - rc = -1; - break; - } + + if (!strcmp(execname, "")) + strcpy(execname, anonstr); + + size = strlen(execname) + 1; + memcpy(event->mmap.filename, execname, size); + size = PERF_ALIGN(size, sizeof(u64)); + event->mmap.len -= event->mmap.start; + event->mmap.header.size = (sizeof(event->mmap) - + (sizeof(event->mmap.filename) - size)); + memset(event->mmap.filename + size, 0, machine->id_hdr_size); + event->mmap.header.size += machine->id_hdr_size; + event->mmap.pid = tgid; + event->mmap.tid = pid; + + if (process(tool, event, &synth_sample, machine) != 0) { + rc = -1; + break; } } diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index a41dc4a5c2de..705293489e3c 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -52,15 +52,13 @@ struct perf_evlist *perf_evlist__new(struct cpu_map *cpus, void perf_evlist__config_attrs(struct perf_evlist *evlist, struct perf_record_opts *opts) { - struct perf_evsel *evsel, *first; + struct perf_evsel *evsel; if (evlist->cpus->map[0] < 0) opts->no_inherit = true; - first = perf_evlist__first(evlist); - list_for_each_entry(evsel, &evlist->entries, node) { - perf_evsel__config(evsel, opts, first); + perf_evsel__config(evsel, opts); if (evlist->nr_entries > 1) evsel->attr.sample_type |= PERF_SAMPLE_ID; @@ -224,6 +222,8 @@ void perf_evlist__disable(struct perf_evlist *evlist) for (cpu = 0; cpu < evlist->cpus->nr; cpu++) { list_for_each_entry(pos, &evlist->entries, node) { + if (perf_evsel__is_group_member(pos)) + continue; for (thread = 0; thread < evlist->threads->nr; thread++) ioctl(FD(pos, cpu, thread), PERF_EVENT_IOC_DISABLE, 0); @@ -238,6 +238,8 @@ void perf_evlist__enable(struct perf_evlist *evlist) for (cpu = 0; cpu < cpu_map__nr(evlist->cpus); cpu++) { list_for_each_entry(pos, &evlist->entries, node) { + if (perf_evsel__is_group_member(pos)) + continue; for (thread = 0; thread < evlist->threads->nr; thread++) ioctl(FD(pos, cpu, thread), PERF_EVENT_IOC_ENABLE, 0); diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 618d41140abd..1fb636c550a1 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -404,13 +404,40 @@ const char *perf_evsel__name(struct perf_evsel *evsel) return evsel->name ?: "unknown"; } -void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts, - struct perf_evsel *first) +/* + * The enable_on_exec/disabled value strategy: + * + * 1) For any type of traced program: + * - all independent events and group leaders are disabled + * - all group members are enabled + * + * Group members are ruled by group leaders. They need to + * be enabled, because the group scheduling relies on that. + * + * 2) For traced programs executed by perf: + * - all independent events and group leaders have + * enable_on_exec set + * - we don't specifically enable or disable any event during + * the record command + * + * Independent events and group leaders are initially disabled + * and get enabled by exec. Group members are ruled by group + * leaders as stated in 1). + * + * 3) For traced programs attached by perf (pid/tid): + * - we specifically enable or disable all events during + * the record command + * + * When attaching events to already running traced we + * enable/disable events specifically, as there's no + * initial traced exec call. + */ +void perf_evsel__config(struct perf_evsel *evsel, + struct perf_record_opts *opts) { struct perf_event_attr *attr = &evsel->attr; int track = !evsel->idx; /* only the first counter needs these */ - attr->disabled = 1; attr->sample_id_all = opts->sample_id_all_missing ? 0 : 1; attr->inherit = !opts->no_inherit; attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | @@ -486,10 +513,21 @@ void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts, attr->mmap = track; attr->comm = track; - if (perf_target__none(&opts->target) && - (!opts->group || evsel == first)) { + /* + * XXX see the function comment above + * + * Disabling only independent events or group leaders, + * keeping group members enabled. + */ + if (!perf_evsel__is_group_member(evsel)) + attr->disabled = 1; + + /* + * Setting enable_on_exec for independent events and + * group leaders for traced executed by perf. + */ + if (perf_target__none(&opts->target) && !perf_evsel__is_group_member(evsel)) attr->enable_on_exec = 1; - } } int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads) @@ -669,7 +707,7 @@ static int get_group_fd(struct perf_evsel *evsel, int cpu, int thread) struct perf_evsel *leader = evsel->leader; int fd; - if (!leader) + if (!perf_evsel__is_group_member(evsel)) return -1; /* diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 6f94d6dea00f..a4c1dd4e149f 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -3,6 +3,7 @@ #include <linux/list.h> #include <stdbool.h> +#include <stddef.h> #include "../../../include/uapi/linux/perf_event.h" #include "types.h" #include "xyarray.h" @@ -92,8 +93,7 @@ void perf_evsel__exit(struct perf_evsel *evsel); void perf_evsel__delete(struct perf_evsel *evsel); void perf_evsel__config(struct perf_evsel *evsel, - struct perf_record_opts *opts, - struct perf_evsel *first); + struct perf_record_opts *opts); bool perf_evsel__is_cache_op_valid(u8 type, u8 op); @@ -225,4 +225,9 @@ static inline struct perf_evsel *perf_evsel__next(struct perf_evsel *evsel) { return list_entry(evsel->node.next, struct perf_evsel, node); } + +static inline bool perf_evsel__is_group_member(const struct perf_evsel *evsel) +{ + return evsel->leader != NULL; +} #endif /* __PERF_EVSEL_H */ diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 7c6e73b1b7ea..cb17e2a8c6ed 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -742,9 +742,8 @@ static struct hist_entry *hists__add_dummy_entry(struct hists *hists, he = hist_entry__new(pair); if (he) { - he->stat.nr_events = 0; - he->stat.period = 0; - he->hists = hists; + memset(&he->stat, 0, sizeof(he->stat)); + he->hists = hists; rb_link_node(&he->rb_node, parent, p); rb_insert_color(&he->rb_node, &hists->entries); hists__inc_nr_entries(hists, he); diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 1278c2c72a96..8b091a51e4a2 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -195,7 +195,7 @@ static inline int hist_entry__tui_annotate(struct hist_entry *self return 0; } -static inline int script_browse(const char *script_opt) +static inline int script_browse(const char *script_opt __maybe_unused) { return 0; } diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index c0b785b50849..020323af3364 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -722,6 +722,27 @@ static int get_event_modifier(struct event_modifier *mod, char *str, return 0; } +/* + * Basic modifier sanity check to validate it contains only one + * instance of any modifier (apart from 'p') present. + */ +static int check_modifier(char *str) +{ + char *p = str; + + /* The sizeof includes 0 byte as well. */ + if (strlen(str) > (sizeof("ukhGHppp") - 1)) + return -1; + + while (*p) { + if (*p != 'p' && strchr(p + 1, *p)) + return -1; + p++; + } + + return 0; +} + int parse_events__modifier_event(struct list_head *list, char *str, bool add) { struct perf_evsel *evsel; @@ -730,6 +751,9 @@ int parse_events__modifier_event(struct list_head *list, char *str, bool add) if (str == NULL) return 0; + if (check_modifier(str)) + return -EINVAL; + if (!add && get_event_modifier(&mod, str, NULL)) return -EINVAL; diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index ac9a6aacf2f5..f6399373d67d 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -99,7 +99,6 @@ void parse_events__set_leader(char *name, struct list_head *list); void parse_events_update_lists(struct list_head *list_event, struct list_head *list_all); void parse_events_error(void *data, void *scanner, char const *msg); -int parse_events__test(void); void print_events(const char *event_glob, bool name_only); void print_events_type(u8 type); diff --git a/tools/perf/util/parse-events.l b/tools/perf/util/parse-events.l index 66959fab6634..e9d1134c2c68 100644 --- a/tools/perf/util/parse-events.l +++ b/tools/perf/util/parse-events.l @@ -82,7 +82,7 @@ num_hex 0x[a-fA-F0-9]+ num_raw_hex [a-fA-F0-9]+ name [a-zA-Z_*?][a-zA-Z0-9_*?]* name_minus [a-zA-Z_*?][a-zA-Z0-9\-_*?]* -modifier_event [ukhpGH]{1,8} +modifier_event [ukhpGH]+ modifier_bp [rwx]{1,3} %% diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 18e84801d4d1..9bdc60c6f138 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -22,7 +22,7 @@ static LIST_HEAD(pmus); * Parse & process all the sysfs attributes located under * the directory specified in 'dir' parameter. */ -static int pmu_format_parse(char *dir, struct list_head *head) +int perf_pmu__format_parse(char *dir, struct list_head *head) { struct dirent *evt_ent; DIR *format_dir; @@ -77,7 +77,7 @@ static int pmu_format(char *name, struct list_head *format) if (stat(path, &st) < 0) return 0; /* no error if format does not exist */ - if (pmu_format_parse(path, format)) + if (perf_pmu__format_parse(path, format)) return -1; return 0; @@ -446,8 +446,9 @@ static int pmu_config_term(struct list_head *formats, return 0; } -static int pmu_config(struct list_head *formats, struct perf_event_attr *attr, - struct list_head *head_terms) +int perf_pmu__config_terms(struct list_head *formats, + struct perf_event_attr *attr, + struct list_head *head_terms) { struct parse_events__term *term; @@ -467,7 +468,7 @@ int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr, struct list_head *head_terms) { attr->type = pmu->type; - return pmu_config(&pmu->format, attr, head_terms); + return perf_pmu__config_terms(&pmu->format, attr, head_terms); } static struct perf_pmu__alias *pmu_find_alias(struct perf_pmu *pmu, @@ -551,177 +552,3 @@ void perf_pmu__set_format(unsigned long *bits, long from, long to) for (b = from; b <= to; b++) set_bit(b, bits); } - -/* Simulated format definitions. */ -static struct test_format { - const char *name; - const char *value; -} test_formats[] = { - { "krava01", "config:0-1,62-63\n", }, - { "krava02", "config:10-17\n", }, - { "krava03", "config:5\n", }, - { "krava11", "config1:0,2,4,6,8,20-28\n", }, - { "krava12", "config1:63\n", }, - { "krava13", "config1:45-47\n", }, - { "krava21", "config2:0-3,10-13,20-23,30-33,40-43,50-53,60-63\n", }, - { "krava22", "config2:8,18,48,58\n", }, - { "krava23", "config2:28-29,38\n", }, -}; - -#define TEST_FORMATS_CNT (sizeof(test_formats) / sizeof(struct test_format)) - -/* Simulated users input. */ -static struct parse_events__term test_terms[] = { - { - .config = (char *) "krava01", - .val.num = 15, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, - { - .config = (char *) "krava02", - .val.num = 170, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, - { - .config = (char *) "krava03", - .val.num = 1, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, - { - .config = (char *) "krava11", - .val.num = 27, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, - { - .config = (char *) "krava12", - .val.num = 1, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, - { - .config = (char *) "krava13", - .val.num = 2, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, - { - .config = (char *) "krava21", - .val.num = 119, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, - { - .config = (char *) "krava22", - .val.num = 11, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, - { - .config = (char *) "krava23", - .val.num = 2, - .type_val = PARSE_EVENTS__TERM_TYPE_NUM, - .type_term = PARSE_EVENTS__TERM_TYPE_USER, - }, -}; -#define TERMS_CNT (sizeof(test_terms) / sizeof(struct parse_events__term)) - -/* - * Prepare format directory data, exported by kernel - * at /sys/bus/event_source/devices/<dev>/format. - */ -static char *test_format_dir_get(void) -{ - static char dir[PATH_MAX]; - unsigned int i; - - snprintf(dir, PATH_MAX, "/tmp/perf-pmu-test-format-XXXXXX"); - if (!mkdtemp(dir)) - return NULL; - - for (i = 0; i < TEST_FORMATS_CNT; i++) { - static char name[PATH_MAX]; - struct test_format *format = &test_formats[i]; - FILE *file; - - snprintf(name, PATH_MAX, "%s/%s", dir, format->name); - - file = fopen(name, "w"); - if (!file) - return NULL; - - if (1 != fwrite(format->value, strlen(format->value), 1, file)) - break; - - fclose(file); - } - - return dir; -} - -/* Cleanup format directory. */ -static int test_format_dir_put(char *dir) -{ - char buf[PATH_MAX]; - snprintf(buf, PATH_MAX, "rm -f %s/*\n", dir); - if (system(buf)) - return -1; - - snprintf(buf, PATH_MAX, "rmdir %s\n", dir); - return system(buf); -} - -static struct list_head *test_terms_list(void) -{ - static LIST_HEAD(terms); - unsigned int i; - - for (i = 0; i < TERMS_CNT; i++) - list_add_tail(&test_terms[i].list, &terms); - - return &terms; -} - -#undef TERMS_CNT - -int perf_pmu__test(void) -{ - char *format = test_format_dir_get(); - LIST_HEAD(formats); - struct list_head *terms = test_terms_list(); - int ret; - - if (!format) - return -EINVAL; - - do { - struct perf_event_attr attr; - - memset(&attr, 0, sizeof(attr)); - - ret = pmu_format_parse(format, &formats); - if (ret) - break; - - ret = pmu_config(&formats, &attr, terms); - if (ret) - break; - - ret = -EINVAL; - - if (attr.config != 0xc00000000002a823) - break; - if (attr.config1 != 0x8000400000000145) - break; - if (attr.config2 != 0x0400000020041d07) - break; - - ret = 0; - } while (0); - - test_format_dir_put(format); - return ret; -} diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 39f3abac7744..07d553fe8d83 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -37,6 +37,9 @@ struct perf_pmu { struct perf_pmu *perf_pmu__find(char *name); int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr, struct list_head *head_terms); +int perf_pmu__config_terms(struct list_head *formats, + struct perf_event_attr *attr, + struct list_head *head_terms); int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms); struct list_head *perf_pmu__alias(struct perf_pmu *pmu, struct list_head *head_terms); @@ -46,6 +49,7 @@ void perf_pmu_error(struct list_head *list, char *name, char const *msg); int perf_pmu__new_format(struct list_head *list, char *name, int config, unsigned long *bits); void perf_pmu__set_format(unsigned long *bits, long from, long to); +int perf_pmu__format_parse(char *dir, struct list_head *head); struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu); diff --git a/tools/perf/util/pstack.c b/tools/perf/util/pstack.c index 13d36faf64eb..daa17aeb6c63 100644 --- a/tools/perf/util/pstack.c +++ b/tools/perf/util/pstack.c @@ -17,59 +17,59 @@ struct pstack { struct pstack *pstack__new(unsigned short max_nr_entries) { - struct pstack *self = zalloc((sizeof(*self) + - max_nr_entries * sizeof(void *))); - if (self != NULL) - self->max_nr_entries = max_nr_entries; - return self; + struct pstack *pstack = zalloc((sizeof(*pstack) + + max_nr_entries * sizeof(void *))); + if (pstack != NULL) + pstack->max_nr_entries = max_nr_entries; + return pstack; } -void pstack__delete(struct pstack *self) +void pstack__delete(struct pstack *pstack) { - free(self); + free(pstack); } -bool pstack__empty(const struct pstack *self) +bool pstack__empty(const struct pstack *pstack) { - return self->top == 0; + return pstack->top == 0; } -void pstack__remove(struct pstack *self, void *key) +void pstack__remove(struct pstack *pstack, void *key) { - unsigned short i = self->top, last_index = self->top - 1; + unsigned short i = pstack->top, last_index = pstack->top - 1; while (i-- != 0) { - if (self->entries[i] == key) { + if (pstack->entries[i] == key) { if (i < last_index) - memmove(self->entries + i, - self->entries + i + 1, + memmove(pstack->entries + i, + pstack->entries + i + 1, (last_index - i) * sizeof(void *)); - --self->top; + --pstack->top; return; } } pr_err("%s: %p not on the pstack!\n", __func__, key); } -void pstack__push(struct pstack *self, void *key) +void pstack__push(struct pstack *pstack, void *key) { - if (self->top == self->max_nr_entries) { - pr_err("%s: top=%d, overflow!\n", __func__, self->top); + if (pstack->top == pstack->max_nr_entries) { + pr_err("%s: top=%d, overflow!\n", __func__, pstack->top); return; } - self->entries[self->top++] = key; + pstack->entries[pstack->top++] = key; } -void *pstack__pop(struct pstack *self) +void *pstack__pop(struct pstack *pstack) { void *ret; - if (self->top == 0) { + if (pstack->top == 0) { pr_err("%s: underflow!\n", __func__); return NULL; } - ret = self->entries[--self->top]; - self->entries[self->top] = NULL; + ret = pstack->entries[--pstack->top]; + pstack->entries[pstack->top] = NULL; return ret; } diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 15abe40dc702..ce6f51162386 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -1458,6 +1458,7 @@ more: session->ordered_samples.next_flush = ULLONG_MAX; err = flush_sample_queue(session, tool); out_err: + ui_progress__finish(); perf_session__warn_about_errors(session, tool); perf_session_free_sample_buffers(session); return err; diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 04ccf2962080..de68f98b236d 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -224,7 +224,6 @@ size_t symbol__fprintf_symname(const struct symbol *sym, FILE *fp); size_t symbol__fprintf(struct symbol *sym, FILE *fp); bool symbol_type__is_a(char symbol_type, enum map_type map_type); -int dso__test_data(void); int dso__load_sym(struct dso *dso, struct map *map, struct symsrc *syms_ss, struct symsrc *runtime_ss, symbol_filter_t filter, int kmodule); diff --git a/tools/scripts/Makefile.include b/tools/scripts/Makefile.include index 96ce80a3743b..2964b96aa55f 100644 --- a/tools/scripts/Makefile.include +++ b/tools/scripts/Makefile.include @@ -1,8 +1,11 @@ -ifeq ("$(origin O)", "command line") +ifeq ($(origin O), command line) dummy := $(if $(shell test -d $(O) || echo $(O)),$(error O=$(O) does not exist),) ABSOLUTE_O := $(shell cd $(O) ; pwd) - OUTPUT := $(ABSOLUTE_O)/ + OUTPUT := $(ABSOLUTE_O)/$(if $(subdir),$(subdir)/) COMMAND_O := O=$(ABSOLUTE_O) +ifeq ($(objtree),) + objtree := $(O) +endif endif ifneq ($(OUTPUT),) @@ -41,7 +44,16 @@ else NO_SUBDIR = : endif -QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +# +# Define a callable command for descending to a new directory +# +# Call by doing: $(call descend,directory[,target]) +# +descend = \ + +mkdir -p $(OUTPUT)$(1) && \ + $(MAKE) $(COMMAND_O) subdir=$(if $(subdir),$(subdir)/$(1),$(1)) $(PRINT_DIR) -C $(1) $(2) + +QUIET_SUBDIR0 = +$(MAKE) $(COMMAND_O) -C # space to separate -C and subdir QUIET_SUBDIR1 = ifneq ($(findstring $(MAKEFLAGS),s),s) @@ -56,5 +68,10 @@ ifndef V $(MAKE) $(PRINT_DIR) -C $$subdir QUIET_FLEX = @echo ' ' FLEX $@; QUIET_BISON = @echo ' ' BISON $@; + + descend = \ + @echo ' ' DESCEND $(1); \ + mkdir -p $(OUTPUT)$(1) && \ + $(MAKE) $(COMMAND_O) subdir=$(if $(subdir),$(subdir)/$(1),$(1)) $(PRINT_DIR) -C $(1) $(2) endif endif |