diff options
Diffstat (limited to 'tools/perf/tests')
254 files changed, 21132 insertions, 4614 deletions
diff --git a/tools/perf/tests/.gitignore b/tools/perf/tests/.gitignore deleted file mode 100644 index d053b325f728..000000000000 --- a/tools/perf/tests/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -llvm-src-base.c -llvm-src-kbuild.c -llvm-src-prologue.c -llvm-src-relocation.c diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build index cd00498a5dce..2181f5a92148 100644 --- a/tools/perf/tests/Build +++ b/tools/perf/tests/Build @@ -1,97 +1,120 @@ # SPDX-License-Identifier: GPL-2.0 -perf-y += builtin-test.o -perf-y += parse-events.o -perf-y += dso-data.o -perf-y += attr.o -perf-y += vmlinux-kallsyms.o -perf-y += openat-syscall.o -perf-y += openat-syscall-all-cpus.o -perf-y += openat-syscall-tp-fields.o -perf-y += mmap-basic.o -perf-y += perf-record.o -perf-y += evsel-roundtrip-name.o -perf-y += evsel-tp-sched.o -perf-y += fdarray.o -perf-y += pmu.o -perf-y += pmu-events.o -perf-y += hists_common.o -perf-y += hists_link.o -perf-y += hists_filter.o -perf-y += hists_output.o -perf-y += hists_cumulate.o -perf-y += python-use.o -perf-y += bp_signal.o -perf-y += bp_signal_overflow.o -perf-y += bp_account.o -perf-y += wp.o -perf-y += task-exit.o -perf-y += sw-clock.o -perf-y += mmap-thread-lookup.o -perf-y += thread-maps-share.o -perf-y += switch-tracking.o -perf-y += keep-tracking.o -perf-y += code-reading.o -perf-y += sample-parsing.o -perf-y += parse-no-sample-id-all.o -perf-y += kmod-path.o -perf-y += thread-map.o -perf-y += llvm.o llvm-src-base.o llvm-src-kbuild.o llvm-src-prologue.o llvm-src-relocation.o -perf-y += bpf.o -perf-y += topology.o -perf-y += mem.o -perf-y += cpumap.o -perf-y += stat.o -perf-y += event_update.o -perf-y += event-times.o -perf-y += expr.o -perf-y += backward-ring-buffer.o -perf-y += sdt.o -perf-y += is_printable_array.o -perf-y += bitmap.o -perf-y += perf-hooks.o -perf-y += clang.o -perf-y += unit_number__scnprintf.o -perf-y += mem2node.o -perf-y += maps.o -perf-y += time-utils-test.o -perf-y += genelf.o -perf-y += api-io.o -perf-y += demangle-java-test.o -perf-y += pfm.o +perf-test-y += builtin-test.o +perf-test-y += tests-scripts.o +perf-test-y += parse-events.o +perf-test-y += dso-data.o +perf-test-y += vmlinux-kallsyms.o +perf-test-y += openat-syscall.o +perf-test-y += openat-syscall-all-cpus.o +perf-test-$(CONFIG_LIBTRACEEVENT) += openat-syscall-tp-fields.o +perf-test-y += mmap-basic.o +perf-test-y += perf-record.o +perf-test-y += evsel-roundtrip-name.o +perf-test-$(CONFIG_LIBTRACEEVENT) += evsel-tp-sched.o +perf-test-y += fdarray.o +perf-test-y += pmu.o +perf-test-y += pmu-events.o +perf-test-y += hists_common.o +perf-test-y += hists_link.o +perf-test-y += hists_filter.o +perf-test-y += hists_output.o +perf-test-y += hists_cumulate.o +perf-test-y += python-use.o +perf-test-y += bp_signal.o +perf-test-y += bp_signal_overflow.o +perf-test-y += bp_account.o +perf-test-y += wp.o +perf-test-y += task-exit.o +perf-test-y += sw-clock.o +perf-test-y += mmap-thread-lookup.o +perf-test-y += thread-maps-share.o +perf-test-$(CONFIG_LIBTRACEEVENT) += switch-tracking.o +perf-test-y += keep-tracking.o +perf-test-y += code-reading.o +perf-test-y += sample-parsing.o +perf-test-y += parse-no-sample-id-all.o +perf-test-y += kmod-path.o +perf-test-y += thread-map.o +perf-test-y += topology.o +perf-test-y += mem.o +perf-test-y += cpumap.o +perf-test-y += stat.o +perf-test-y += event_update.o +perf-test-y += event-times.o +perf-test-y += expr.o +perf-test-y += backward-ring-buffer.o +perf-test-y += sdt.o +perf-test-y += is_printable_array.o +perf-test-y += bitmap.o +perf-test-y += perf-hooks.o +perf-test-y += unit_number__scnprintf.o +perf-test-y += mem2node.o +perf-test-y += maps.o +perf-test-y += time-utils-test.o +perf-test-y += genelf.o +perf-test-y += api-io.o +perf-test-y += demangle-java-test.o +perf-test-y += demangle-ocaml-test.o +perf-test-y += demangle-rust-v0-test.o +perf-test-y += pfm.o +perf-test-y += parse-metric.o +perf-test-y += pe-file-parsing.o +perf-test-y += expand-cgroup.o +perf-test-y += perf-time-to-tsc.o +perf-test-y += dlfilter-test.o +perf-test-y += sigtrap.o +perf-test-y += event_groups.o +perf-test-y += symbols.o +perf-test-y += util.o +perf-test-y += hwmon_pmu.o +perf-test-y += tool_pmu.o -$(OUTPUT)tests/llvm-src-base.c: tests/bpf-script-example.c tests/Build - $(call rule_mkdir) - $(Q)echo '#include <tests/llvm.h>' > $@ - $(Q)echo 'const char test_llvm__bpf_base_prog[] =' >> $@ - $(Q)sed -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/g' $< >> $@ - $(Q)echo ';' >> $@ +ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 arm arm64 powerpc)) +perf-test-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o +endif -$(OUTPUT)tests/llvm-src-kbuild.c: tests/bpf-script-test-kbuild.c tests/Build - $(call rule_mkdir) - $(Q)echo '#include <tests/llvm.h>' > $@ - $(Q)echo 'const char test_llvm__bpf_test_kbuild_prog[] =' >> $@ - $(Q)sed -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/g' $< >> $@ - $(Q)echo ';' >> $@ +CFLAGS_python-use.o += -DPYTHONPATH="BUILD_STR($(OUTPUT)python)" -DPYTHON="BUILD_STR($(PYTHON_WORD))" +CFLAGS_dwarf-unwind.o += -fno-optimize-sibling-calls + +perf-test-y += workloads/ + +ifdef SHELLCHECK + SHELL_TESTS := $(shell find tests/shell -executable -type f -name '*.sh') + SHELL_TEST_LOGS := $(SHELL_TESTS:tests/shell/%=shell/%.shellcheck_log) +else + SHELL_TESTS := + SHELL_TEST_LOGS := +endif -$(OUTPUT)tests/llvm-src-prologue.c: tests/bpf-script-test-prologue.c tests/Build +$(OUTPUT)%.shellcheck_log: % $(call rule_mkdir) - $(Q)echo '#include <tests/llvm.h>' > $@ - $(Q)echo 'const char test_llvm__bpf_test_prologue_prog[] =' >> $@ - $(Q)sed -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/g' $< >> $@ - $(Q)echo ';' >> $@ + $(Q)$(call echo-cmd,test)shellcheck -a -S warning "$<" > $@ || (cat $@ && rm $@ && false) -$(OUTPUT)tests/llvm-src-relocation.c: tests/bpf-script-test-relocation.c tests/Build +perf-test-y += $(SHELL_TEST_LOGS) + +ifdef MYPY + PY_TESTS := $(shell find tests/shell -type f -name '*.py') + MYPY_TEST_LOGS := $(PY_TESTS:tests/shell/%=shell/%.mypy_log) +else + MYPY_TEST_LOGS := +endif + +$(OUTPUT)%.mypy_log: % $(call rule_mkdir) - $(Q)echo '#include <tests/llvm.h>' > $@ - $(Q)echo 'const char test_llvm__bpf_test_relocation[] =' >> $@ - $(Q)sed -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/g' $< >> $@ - $(Q)echo ';' >> $@ + $(Q)$(call echo-cmd,test)mypy "$<" > $@ || (cat $@ && rm $@ && false) -ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 arm arm64 powerpc)) -perf-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o +perf-test-y += $(MYPY_TEST_LOGS) + +ifdef PYLINT + PY_TESTS := $(shell find tests/shell -type f -name '*.py') + PYLINT_TEST_LOGS := $(PY_TESTS:tests/shell/%=shell/%.pylint_log) +else + PYLINT_TEST_LOGS := endif -CFLAGS_attr.o += -DBINDIR="BUILD_STR($(bindir_SQ))" -DPYTHON="BUILD_STR($(PYTHON_WORD))" -CFLAGS_python-use.o += -DPYTHONPATH="BUILD_STR($(OUTPUT)python)" -DPYTHON="BUILD_STR($(PYTHON_WORD))" -CFLAGS_dwarf-unwind.o += -fno-optimize-sibling-calls +$(OUTPUT)%.pylint_log: % + $(call rule_mkdir) + $(Q)$(call echo-cmd,test)pylint "$<" > $@ || (cat $@ && rm $@ && false) + +perf-test-y += $(PYLINT_TEST_LOGS) diff --git a/tools/perf/tests/api-io.c b/tools/perf/tests/api-io.c index 2ada86ad6084..0ba3d5ccebcf 100644 --- a/tools/perf/tests/api-io.c +++ b/tools/perf/tests/api-io.c @@ -12,6 +12,7 @@ #include "tests.h" #include <api/io.h> #include <linux/kernel.h> +#include <linux/zalloc.h> #define TEMPL "/tmp/perf-test-XXXXXX" @@ -79,7 +80,7 @@ static int setup_test(char path[PATH_MAX], const char *contents, static void cleanup_test(char path[PATH_MAX], struct io *io) { - free(io->buf); + zfree(&io->buf); close(io->fd); unlink(path); } @@ -289,8 +290,42 @@ static int test_get_dec(void) return ret; } -int test__api_io(struct test *test __maybe_unused, - int subtest __maybe_unused) +static int test_get_line(void) +{ + char path[PATH_MAX]; + struct io io; + char test_string[1024]; + char *line = NULL; + size_t i, line_len = 0; + size_t buf_size = 128; + int ret = 0; + + for (i = 0; i < 512; i++) + test_string[i] = 'a'; + test_string[512] = '\n'; + for (i = 513; i < 1023; i++) + test_string[i] = 'b'; + test_string[1023] = '\0'; + + if (setup_test(path, test_string, buf_size, &io)) + return -1; + + EXPECT_EQUAL((int)io__getline(&io, &line, &line_len), 513); + EXPECT_EQUAL((int)strlen(line), 513); + for (i = 0; i < 512; i++) + EXPECT_EQUAL(line[i], 'a'); + EXPECT_EQUAL(line[512], '\n'); + EXPECT_EQUAL((int)io__getline(&io, &line, &line_len), 510); + for (i = 0; i < 510; i++) + EXPECT_EQUAL(line[i], 'b'); + + free(line); + cleanup_test(path, &io); + return ret; +} + +static int test__api_io(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { int ret = 0; @@ -300,5 +335,9 @@ int test__api_io(struct test *test __maybe_unused, ret = TEST_FAIL; if (test_get_dec()) ret = TEST_FAIL; + if (test_get_line()) + ret = TEST_FAIL; return ret; } + +DEFINE_SUITE("Test api io", api_io); diff --git a/tools/perf/tests/attr.c b/tools/perf/tests/attr.c deleted file mode 100644 index a9599ab8c471..000000000000 --- a/tools/perf/tests/attr.c +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * The struct perf_event_attr test support. - * - * This test is embedded inside into perf directly and is governed - * by the PERF_TEST_ATTR environment variable and hook inside - * sys_perf_event_open function. - * - * The general idea is to store 'struct perf_event_attr' details for - * each event created within single perf command. Each event details - * are stored into separate text file. Once perf command is finished - * these files can be checked for values we expect for command. - * - * Besides 'struct perf_event_attr' values we also store 'fd' and - * 'group_fd' values to allow checking for groups created. - * - * This all is triggered by setting PERF_TEST_ATTR environment variable. - * It must contain name of existing directory with access and write - * permissions. All the event text files are stored there. - */ - -#include <debug.h> -#include <errno.h> -#include <inttypes.h> -#include <stdlib.h> -#include <stdio.h> -#include <linux/types.h> -#include <linux/kernel.h> -#include <sys/param.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include "../perf-sys.h" -#include <subcmd/exec-cmd.h> -#include "event.h" -#include "tests.h" - -#define ENV "PERF_TEST_ATTR" - -static char *dir; -static bool ready; - -void test_attr__init(void) -{ - dir = getenv(ENV); - test_attr__enabled = (dir != NULL); -} - -#define BUFSIZE 1024 - -#define __WRITE_ASS(str, fmt, data) \ -do { \ - char buf[BUFSIZE]; \ - size_t size; \ - \ - size = snprintf(buf, BUFSIZE, #str "=%"fmt "\n", data); \ - if (1 != fwrite(buf, size, 1, file)) { \ - perror("test attr - failed to write event file"); \ - fclose(file); \ - return -1; \ - } \ - \ -} while (0) - -#define WRITE_ASS(field, fmt) __WRITE_ASS(field, fmt, attr->field) - -static int store_event(struct perf_event_attr *attr, pid_t pid, int cpu, - int fd, int group_fd, unsigned long flags) -{ - FILE *file; - char path[PATH_MAX]; - - if (!ready) - return 0; - - snprintf(path, PATH_MAX, "%s/event-%d-%llu-%d", dir, - attr->type, attr->config, fd); - - file = fopen(path, "w+"); - if (!file) { - perror("test attr - failed to open event file"); - return -1; - } - - if (fprintf(file, "[event-%d-%llu-%d]\n", - attr->type, attr->config, fd) < 0) { - perror("test attr - failed to write event file"); - fclose(file); - return -1; - } - - /* syscall arguments */ - __WRITE_ASS(fd, "d", fd); - __WRITE_ASS(group_fd, "d", group_fd); - __WRITE_ASS(cpu, "d", cpu); - __WRITE_ASS(pid, "d", pid); - __WRITE_ASS(flags, "lu", flags); - - /* struct perf_event_attr */ - WRITE_ASS(type, PRIu32); - WRITE_ASS(size, PRIu32); - WRITE_ASS(config, "llu"); - WRITE_ASS(sample_period, "llu"); - WRITE_ASS(sample_type, "llu"); - WRITE_ASS(read_format, "llu"); - WRITE_ASS(disabled, "d"); - WRITE_ASS(inherit, "d"); - WRITE_ASS(pinned, "d"); - WRITE_ASS(exclusive, "d"); - WRITE_ASS(exclude_user, "d"); - WRITE_ASS(exclude_kernel, "d"); - WRITE_ASS(exclude_hv, "d"); - WRITE_ASS(exclude_idle, "d"); - WRITE_ASS(mmap, "d"); - WRITE_ASS(comm, "d"); - WRITE_ASS(freq, "d"); - WRITE_ASS(inherit_stat, "d"); - WRITE_ASS(enable_on_exec, "d"); - WRITE_ASS(task, "d"); - WRITE_ASS(watermark, "d"); - WRITE_ASS(precise_ip, "d"); - WRITE_ASS(mmap_data, "d"); - WRITE_ASS(sample_id_all, "d"); - WRITE_ASS(exclude_host, "d"); - WRITE_ASS(exclude_guest, "d"); - WRITE_ASS(exclude_callchain_kernel, "d"); - WRITE_ASS(exclude_callchain_user, "d"); - WRITE_ASS(mmap2, "d"); - WRITE_ASS(comm_exec, "d"); - WRITE_ASS(context_switch, "d"); - WRITE_ASS(write_backward, "d"); - WRITE_ASS(namespaces, "d"); - WRITE_ASS(use_clockid, "d"); - WRITE_ASS(wakeup_events, PRIu32); - WRITE_ASS(bp_type, PRIu32); - WRITE_ASS(config1, "llu"); - WRITE_ASS(config2, "llu"); - WRITE_ASS(branch_sample_type, "llu"); - WRITE_ASS(sample_regs_user, "llu"); - WRITE_ASS(sample_stack_user, PRIu32); - - fclose(file); - return 0; -} - -void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu, - int fd, int group_fd, unsigned long flags) -{ - int errno_saved = errno; - - if ((fd != -1) && store_event(attr, pid, cpu, fd, group_fd, flags)) { - pr_err("test attr FAILED"); - exit(128); - } - - errno = errno_saved; -} - -void test_attr__ready(void) -{ - if (unlikely(test_attr__enabled) && !ready) - ready = true; -} - -static int run_dir(const char *d, const char *perf) -{ - char v[] = "-vvvvv"; - int vcnt = min(verbose, (int) sizeof(v) - 1); - char cmd[3*PATH_MAX]; - - if (verbose > 0) - vcnt++; - - scnprintf(cmd, 3*PATH_MAX, PYTHON " %s/attr.py -d %s/attr/ -p %s %.*s", - d, d, perf, vcnt, v); - - return system(cmd) ? TEST_FAIL : TEST_OK; -} - -int test__attr(struct test *test __maybe_unused, int subtest __maybe_unused) -{ - struct stat st; - char path_perf[PATH_MAX]; - char path_dir[PATH_MAX]; - - /* First try development tree tests. */ - if (!lstat("./tests", &st)) - return run_dir("./tests", "./perf"); - - /* Then installed path. */ - snprintf(path_dir, PATH_MAX, "%s/tests", get_argv_exec_path()); - snprintf(path_perf, PATH_MAX, "%s/perf", BINDIR); - - if (!lstat(path_dir, &st) && - !lstat(path_perf, &st)) - return run_dir(path_dir, path_perf); - - return TEST_SKIP; -} diff --git a/tools/perf/tests/attr/test-record-group b/tools/perf/tests/attr/test-record-group deleted file mode 100644 index 14ee60fd3f41..000000000000 --- a/tools/perf/tests/attr/test-record-group +++ /dev/null @@ -1,22 +0,0 @@ -[config] -command = record -args = --no-bpf-event --group -e cycles,instructions kill >/dev/null 2>&1 -ret = 1 - -[event-1:base-record] -fd=1 -group_fd=-1 -sample_type=327 -read_format=4 - -[event-2:base-record] -fd=2 -group_fd=1 -config=1 -sample_type=327 -read_format=4 -mmap=0 -comm=0 -task=0 -enable_on_exec=0 -disabled=0 diff --git a/tools/perf/tests/attr/test-stat-default b/tools/perf/tests/attr/test-stat-default deleted file mode 100644 index d9e99b3f77e6..000000000000 --- a/tools/perf/tests/attr/test-stat-default +++ /dev/null @@ -1,70 +0,0 @@ -[config] -command = stat -args = kill >/dev/null 2>&1 -ret = 1 - -# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] -fd=1 -type=1 -config=1 - -# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] -fd=2 -type=1 -config=3 - -# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] -fd=3 -type=1 -config=4 - -# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] -fd=4 -type=1 -config=2 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] -fd=5 -type=0 -config=0 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] -fd=6 -type=0 -config=7 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] -fd=7 -type=0 -config=8 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] -fd=8 -type=0 -config=1 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] -fd=9 -type=0 -config=4 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] -fd=10 -type=0 -config=5 -optional=1 diff --git a/tools/perf/tests/attr/test-stat-detailed-1 b/tools/perf/tests/attr/test-stat-detailed-1 deleted file mode 100644 index 8b04a055d154..000000000000 --- a/tools/perf/tests/attr/test-stat-detailed-1 +++ /dev/null @@ -1,111 +0,0 @@ -[config] -command = stat -args = -d kill >/dev/null 2>&1 -ret = 1 - - -# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK -[event1:base-stat] -fd=1 -type=1 -config=1 - -# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES -[event2:base-stat] -fd=2 -type=1 -config=3 - -# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS -[event3:base-stat] -fd=3 -type=1 -config=4 - -# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS -[event4:base-stat] -fd=4 -type=1 -config=2 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES -[event5:base-stat] -fd=5 -type=0 -config=0 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND -[event6:base-stat] -fd=6 -type=0 -config=7 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND -[event7:base-stat] -fd=7 -type=0 -config=8 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS -[event8:base-stat] -fd=8 -type=0 -config=1 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS -[event9:base-stat] -fd=9 -type=0 -config=4 -optional=1 - -# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES -[event10:base-stat] -fd=10 -type=0 -config=5 -optional=1 - -# PERF_TYPE_HW_CACHE / -# PERF_COUNT_HW_CACHE_L1D << 0 | -# (PERF_COUNT_HW_CACHE_OP_READ << 8) | -# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event11:base-stat] -fd=11 -type=3 -config=0 -optional=1 - -# PERF_TYPE_HW_CACHE / -# PERF_COUNT_HW_CACHE_L1D << 0 | -# (PERF_COUNT_HW_CACHE_OP_READ << 8) | -# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event12:base-stat] -fd=12 -type=3 -config=65536 -optional=1 - -# PERF_TYPE_HW_CACHE / -# PERF_COUNT_HW_CACHE_LL << 0 | -# (PERF_COUNT_HW_CACHE_OP_READ << 8) | -# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event13:base-stat] -fd=13 -type=3 -config=2 -optional=1 - -# PERF_TYPE_HW_CACHE, -# PERF_COUNT_HW_CACHE_LL << 0 | -# (PERF_COUNT_HW_CACHE_OP_READ << 8) | -# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event14:base-stat] -fd=14 -type=3 -config=65538 -optional=1 diff --git a/tools/perf/tests/attr/test-stat-group b/tools/perf/tests/attr/test-stat-group deleted file mode 100644 index e15d6946e9b3..000000000000 --- a/tools/perf/tests/attr/test-stat-group +++ /dev/null @@ -1,17 +0,0 @@ -[config] -command = stat -args = --group -e cycles,instructions kill >/dev/null 2>&1 -ret = 1 - -[event-1:base-stat] -fd=1 -group_fd=-1 -read_format=3|15 - -[event-2:base-stat] -fd=2 -group_fd=1 -config=1 -disabled=0 -enable_on_exec=0 -read_format=3|15 diff --git a/tools/perf/tests/backward-ring-buffer.c b/tools/perf/tests/backward-ring-buffer.c index 15cea518f5ad..79a980b1e786 100644 --- a/tools/perf/tests/backward-ring-buffer.c +++ b/tools/perf/tests/backward-ring-buffer.c @@ -82,7 +82,7 @@ static int do_test(struct evlist *evlist, int mmap_pages, } -int test__backward_ring_buffer(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int ret = TEST_SKIP, err, sample_count = 0, comm_count = 0; char pid[16], sbuf[STRERR_BUFSIZE]; @@ -109,25 +109,26 @@ int test__backward_ring_buffer(struct test *test __maybe_unused, int subtest __m return TEST_FAIL; } - err = perf_evlist__create_maps(evlist, &opts.target); + err = evlist__create_maps(evlist, &opts.target); if (err < 0) { pr_debug("Not enough memory to create thread/cpu maps\n"); goto out_delete_evlist; } - bzero(&parse_error, sizeof(parse_error)); + parse_events_error__init(&parse_error); /* * Set backward bit, ring buffer should be writing from end. Record * it in aux evlist */ err = parse_events(evlist, "syscalls:sys_enter_prctl/overwrite/", &parse_error); + parse_events_error__exit(&parse_error); if (err) { pr_debug("Failed to parse tracepoint event, try use root\n"); ret = TEST_SKIP; goto out_delete_evlist; } - perf_evlist__config(evlist, &opts, NULL); + evlist__config(evlist, &opts, NULL); err = evlist__open(evlist); if (err < 0) { @@ -166,3 +167,5 @@ out_delete_evlist: evlist__delete(evlist); return ret; } + +DEFINE_SUITE("Read backward ring buffer", backward_ring_buffer); diff --git a/tools/perf/tests/bitmap.c b/tools/perf/tests/bitmap.c index 96c137360918..98956e0e0765 100644 --- a/tools/perf/tests/bitmap.c +++ b/tools/perf/tests/bitmap.c @@ -11,18 +11,19 @@ static unsigned long *get_bitmap(const char *str, int nbits) { struct perf_cpu_map *map = perf_cpu_map__new(str); - unsigned long *bm = NULL; - int i; + unsigned long *bm; - bm = bitmap_alloc(nbits); + bm = bitmap_zalloc(nbits); if (map && bm) { - for (i = 0; i < map->nr; i++) - set_bit(map->map[i], bm); + int i; + struct perf_cpu cpu; + + perf_cpu_map__for_each_cpu(cpu, i, map) + __set_bit(cpu.cpu, bm); } - if (map) - perf_cpu_map__put(map); + perf_cpu_map__put(map); return bm; } @@ -40,7 +41,7 @@ static int test_bitmap(const char *str) return ret; } -int test__bitmap_print(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__bitmap_print(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { TEST_ASSERT_VAL("failed to convert map", test_bitmap("1")); TEST_ASSERT_VAL("failed to convert map", test_bitmap("1,5")); @@ -51,3 +52,5 @@ int test__bitmap_print(struct test *test __maybe_unused, int subtest __maybe_unu TEST_ASSERT_VAL("failed to convert map", test_bitmap("1-10,12-20,22-30,32-40")); return 0; } + +DEFINE_SUITE("Print bitmap", bitmap_print); diff --git a/tools/perf/tests/bp_account.c b/tools/perf/tests/bp_account.c index 489b50604cf2..4cb7d486b5c1 100644 --- a/tools/perf/tests/bp_account.c +++ b/tools/perf/tests/bp_account.c @@ -16,9 +16,23 @@ #include "tests.h" #include "debug.h" #include "event.h" +#include "parse-events.h" #include "../perf-sys.h" #include "cloexec.h" +/* + * PowerPC and S390 do not support creation of instruction breakpoints using the + * perf_event interface. + * + * Just disable the test for these architectures until these issues are + * resolved. + */ +#if defined(__powerpc__) || defined(__s390x__) +#define BP_ACCOUNT_IS_SUPPORTED 0 +#else +#define BP_ACCOUNT_IS_SUPPORTED 1 +#endif + static volatile long the_var; static noinline int test_function(void) @@ -37,7 +51,7 @@ static int __event(bool is_x, void *addr, struct perf_event_attr *attr) attr->config = 0; attr->bp_type = is_x ? HW_BREAKPOINT_X : HW_BREAKPOINT_W; attr->bp_addr = (unsigned long) addr; - attr->bp_len = sizeof(long); + attr->bp_len = is_x ? default_breakpoint_len() : sizeof(long); attr->sample_period = 1; attr->sample_type = PERF_SAMPLE_IP; @@ -79,6 +93,7 @@ static int bp_accounting(int wp_cnt, int share) attr_mod = attr; attr_mod.bp_type = HW_BREAKPOINT_X; attr_mod.bp_addr = (unsigned long) test_function; + attr_mod.bp_len = default_breakpoint_len(); ret = ioctl(fd[0], PERF_EVENT_IOC_MODIFY_ATTRIBUTES, &attr_mod); TEST_ASSERT_VAL("failed to modify wp\n", ret == 0); @@ -138,11 +153,21 @@ static int detect_ioctl(void) static int detect_share(int wp_cnt, int bp_cnt) { struct perf_event_attr attr; - int i, fd[wp_cnt + bp_cnt], ret; + int i, *fd = NULL, ret = -1; + + if (wp_cnt + bp_cnt == 0) + return 0; + + fd = malloc(sizeof(int) * (wp_cnt + bp_cnt)); + if (!fd) + return -1; for (i = 0; i < wp_cnt; i++) { fd[i] = wp_event((void *)&the_var, &attr); - TEST_ASSERT_VAL("failed to create wp\n", fd[i] != -1); + if (fd[i] == -1) { + pr_err("failed to create wp\n"); + goto out; + } } for (; i < (bp_cnt + wp_cnt); i++) { @@ -153,9 +178,11 @@ static int detect_share(int wp_cnt, int bp_cnt) ret = i != (bp_cnt + wp_cnt); +out: while (i--) close(fd[i]); + free(fd); return ret; } @@ -173,13 +200,18 @@ static int detect_share(int wp_cnt, int bp_cnt) * we create another watchpoint to ensure * the slot accounting is correct */ -int test__bp_accounting(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__bp_accounting(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int has_ioctl = detect_ioctl(); int wp_cnt = detect_cnt(false); int bp_cnt = detect_cnt(true); int share = detect_share(wp_cnt, bp_cnt); + if (!BP_ACCOUNT_IS_SUPPORTED) { + pr_debug("Test not supported on this architecture"); + return TEST_SKIP; + } + pr_debug("watchpoints count %d, breakpoints count %d, has_ioctl %d, share %d\n", wp_cnt, bp_cnt, has_ioctl, share); @@ -189,18 +221,4 @@ int test__bp_accounting(struct test *test __maybe_unused, int subtest __maybe_un return bp_accounting(wp_cnt, share); } -bool test__bp_account_is_supported(void) -{ - /* - * PowerPC and S390 do not support creation of instruction - * breakpoints using the perf_event interface. - * - * Just disable the test for these architectures until these - * issues are resolved. - */ -#if defined(__powerpc__) || defined(__s390x__) - return false; -#else - return true; -#endif -} +DEFINE_SUITE("Breakpoint accounting", bp_accounting); diff --git a/tools/perf/tests/bp_signal.c b/tools/perf/tests/bp_signal.c index da8ec1e8e064..3faeb5b6fe0b 100644 --- a/tools/perf/tests/bp_signal.c +++ b/tools/perf/tests/bp_signal.c @@ -26,6 +26,7 @@ #include "tests.h" #include "debug.h" #include "event.h" +#include "parse-events.h" #include "perf-sys.h" #include "cloexec.h" @@ -45,10 +46,13 @@ volatile long the_var; #if defined (__x86_64__) extern void __test_function(volatile long *ptr); asm ( + ".pushsection .text;" ".globl __test_function\n" + ".type __test_function, @function;" "__test_function:\n" "incq (%rdi)\n" - "ret\n"); + "ret\n" + ".popsection\n"); #else static void __test_function(volatile long *ptr) { @@ -108,7 +112,7 @@ static int __event(bool is_x, void *addr, int sig) pe.config = 0; pe.bp_type = is_x ? HW_BREAKPOINT_X : HW_BREAKPOINT_W; pe.bp_addr = (unsigned long) addr; - pe.bp_len = sizeof(long); + pe.bp_len = is_x ? default_breakpoint_len() : sizeof(long); pe.sample_period = 1; pe.sample_type = PERF_SAMPLE_IP; @@ -158,11 +162,16 @@ static long long bp_count(int fd) return count; } -int test__bp_signal(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__bp_signal(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct sigaction sa; long long count1, count2, count3; + if (!BP_SIGNAL_IS_SUPPORTED) { + pr_debug("Test not supported on this architecture"); + return TEST_SKIP; + } + /* setup SIGIO signal handler */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_sigaction = (void *) sig_handler; @@ -222,11 +231,11 @@ int test__bp_signal(struct test *test __maybe_unused, int subtest __maybe_unused * * The test case check following error conditions: * - we get stuck in signal handler because of debug - * exception being triggered receursively due to + * exception being triggered recursively due to * the wrong RF EFLAG management * * - we never trigger the sig_handler breakpoint due - * to the rong RF EFLAG management + * to the wrong RF EFLAG management * */ @@ -239,7 +248,7 @@ int test__bp_signal(struct test *test __maybe_unused, int subtest __maybe_unused ioctl(fd3, PERF_EVENT_IOC_ENABLE, 0); /* - * Kick off the test by trigering 'fd1' + * Kick off the test by triggering 'fd1' * breakpoint. */ test_function(); @@ -282,29 +291,4 @@ int test__bp_signal(struct test *test __maybe_unused, int subtest __maybe_unused TEST_OK : TEST_FAIL; } -bool test__bp_signal_is_supported(void) -{ - /* - * PowerPC and S390 do not support creation of instruction - * breakpoints using the perf_event interface. - * - * ARM requires explicit rounding down of the instruction - * pointer in Thumb mode, and then requires the single-step - * to be handled explicitly in the overflow handler to avoid - * stepping into the SIGIO handler and getting stuck on the - * breakpointed instruction. - * - * Since arm64 has the same issue with arm for the single-step - * handling, this case also gets stuck on the breakpointed - * instruction. - * - * Just disable the test for these architectures until these - * issues are resolved. - */ -#if defined(__powerpc__) || defined(__s390x__) || defined(__arm__) || \ - defined(__aarch64__) - return false; -#else - return true; -#endif -} +DEFINE_SUITE("Breakpoint overflow signal handler", bp_signal); diff --git a/tools/perf/tests/bp_signal_overflow.c b/tools/perf/tests/bp_signal_overflow.c index eb4dbbddf4ff..ee560e156be6 100644 --- a/tools/perf/tests/bp_signal_overflow.c +++ b/tools/perf/tests/bp_signal_overflow.c @@ -25,6 +25,7 @@ #include "tests.h" #include "debug.h" #include "event.h" +#include "parse-events.h" #include "../perf-sys.h" #include "cloexec.h" @@ -59,13 +60,18 @@ static long long bp_count(int fd) #define EXECUTIONS 10000 #define THRESHOLD 100 -int test__bp_signal_overflow(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__bp_signal_overflow(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct perf_event_attr pe; struct sigaction sa; long long count; int fd, i, fails = 0; + if (!BP_SIGNAL_IS_SUPPORTED) { + pr_debug("Test not supported on this architecture"); + return TEST_SKIP; + } + /* setup SIGIO signal handler */ memset(&sa, 0, sizeof(struct sigaction)); sa.sa_sigaction = (void *) sig_handler; @@ -83,7 +89,7 @@ int test__bp_signal_overflow(struct test *test __maybe_unused, int subtest __may pe.config = 0; pe.bp_type = HW_BREAKPOINT_X; pe.bp_addr = (unsigned long) test_function; - pe.bp_len = sizeof(long); + pe.bp_len = default_breakpoint_len(); pe.sample_period = THRESHOLD; pe.sample_type = PERF_SAMPLE_IP; @@ -133,3 +139,5 @@ int test__bp_signal_overflow(struct test *test __maybe_unused, int subtest __may return fails ? TEST_FAIL : TEST_OK; } + +DEFINE_SUITE("Breakpoint overflow sampling", bp_signal_overflow); diff --git a/tools/perf/tests/bpf-script-example.c b/tools/perf/tests/bpf-script-example.c deleted file mode 100644 index ab4b98b3165d..000000000000 --- a/tools/perf/tests/bpf-script-example.c +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * bpf-script-example.c - * Test basic LLVM building - */ -#ifndef LINUX_VERSION_CODE -# error Need LINUX_VERSION_CODE -# error Example: for 4.2 kernel, put 'clang-opt="-DLINUX_VERSION_CODE=0x40200" into llvm section of ~/.perfconfig' -#endif -#define BPF_ANY 0 -#define BPF_MAP_TYPE_ARRAY 2 -#define BPF_FUNC_map_lookup_elem 1 -#define BPF_FUNC_map_update_elem 2 - -static void *(*bpf_map_lookup_elem)(void *map, void *key) = - (void *) BPF_FUNC_map_lookup_elem; -static void *(*bpf_map_update_elem)(void *map, void *key, void *value, int flags) = - (void *) BPF_FUNC_map_update_elem; - -struct bpf_map_def { - unsigned int type; - unsigned int key_size; - unsigned int value_size; - unsigned int max_entries; -}; - -#define SEC(NAME) __attribute__((section(NAME), used)) -struct bpf_map_def SEC("maps") flip_table = { - .type = BPF_MAP_TYPE_ARRAY, - .key_size = sizeof(int), - .value_size = sizeof(int), - .max_entries = 1, -}; - -SEC("func=do_epoll_wait") -int bpf_func__SyS_epoll_pwait(void *ctx) -{ - int ind =0; - int *flag = bpf_map_lookup_elem(&flip_table, &ind); - int new_flag; - if (!flag) - return 0; - /* flip flag and store back */ - new_flag = !*flag; - bpf_map_update_elem(&flip_table, &ind, &new_flag, BPF_ANY); - return new_flag; -} -char _license[] SEC("license") = "GPL"; -int _version SEC("version") = LINUX_VERSION_CODE; diff --git a/tools/perf/tests/bpf-script-test-kbuild.c b/tools/perf/tests/bpf-script-test-kbuild.c deleted file mode 100644 index 219673aa278f..000000000000 --- a/tools/perf/tests/bpf-script-test-kbuild.c +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * bpf-script-test-kbuild.c - * Test include from kernel header - */ -#ifndef LINUX_VERSION_CODE -# error Need LINUX_VERSION_CODE -# error Example: for 4.2 kernel, put 'clang-opt="-DLINUX_VERSION_CODE=0x40200" into llvm section of ~/.perfconfig' -#endif -#define SEC(NAME) __attribute__((section(NAME), used)) - -#include <uapi/linux/fs.h> - -SEC("func=vfs_llseek") -int bpf_func__vfs_llseek(void *ctx) -{ - return 0; -} - -char _license[] SEC("license") = "GPL"; -int _version SEC("version") = LINUX_VERSION_CODE; diff --git a/tools/perf/tests/bpf-script-test-prologue.c b/tools/perf/tests/bpf-script-test-prologue.c deleted file mode 100644 index bd83d364cf30..000000000000 --- a/tools/perf/tests/bpf-script-test-prologue.c +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * bpf-script-test-prologue.c - * Test BPF prologue - */ -#ifndef LINUX_VERSION_CODE -# error Need LINUX_VERSION_CODE -# error Example: for 4.2 kernel, put 'clang-opt="-DLINUX_VERSION_CODE=0x40200" into llvm section of ~/.perfconfig' -#endif -#define SEC(NAME) __attribute__((section(NAME), used)) - -#include <uapi/linux/fs.h> - -/* - * If CONFIG_PROFILE_ALL_BRANCHES is selected, - * 'if' is redefined after include kernel header. - * Recover 'if' for BPF object code. - */ -#ifdef if -# undef if -#endif - -#define FMODE_READ 0x1 -#define FMODE_WRITE 0x2 - -static void (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) = - (void *) 6; - -SEC("func=null_lseek file->f_mode offset orig") -int bpf_func__null_lseek(void *ctx, int err, unsigned long _f_mode, - unsigned long offset, unsigned long orig) -{ - fmode_t f_mode = (fmode_t)_f_mode; - - if (err) - return 0; - if (f_mode & FMODE_WRITE) - return 0; - if (offset & 1) - return 0; - if (orig == SEEK_CUR) - return 0; - return 1; -} - -char _license[] SEC("license") = "GPL"; -int _version SEC("version") = LINUX_VERSION_CODE; diff --git a/tools/perf/tests/bpf-script-test-relocation.c b/tools/perf/tests/bpf-script-test-relocation.c deleted file mode 100644 index 74006e4b2d24..000000000000 --- a/tools/perf/tests/bpf-script-test-relocation.c +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * bpf-script-test-relocation.c - * Test BPF loader checking relocation - */ -#ifndef LINUX_VERSION_CODE -# error Need LINUX_VERSION_CODE -# error Example: for 4.2 kernel, put 'clang-opt="-DLINUX_VERSION_CODE=0x40200" into llvm section of ~/.perfconfig' -#endif -#define BPF_ANY 0 -#define BPF_MAP_TYPE_ARRAY 2 -#define BPF_FUNC_map_lookup_elem 1 -#define BPF_FUNC_map_update_elem 2 - -static void *(*bpf_map_lookup_elem)(void *map, void *key) = - (void *) BPF_FUNC_map_lookup_elem; -static void *(*bpf_map_update_elem)(void *map, void *key, void *value, int flags) = - (void *) BPF_FUNC_map_update_elem; - -struct bpf_map_def { - unsigned int type; - unsigned int key_size; - unsigned int value_size; - unsigned int max_entries; -}; - -#define SEC(NAME) __attribute__((section(NAME), used)) -struct bpf_map_def SEC("maps") my_table = { - .type = BPF_MAP_TYPE_ARRAY, - .key_size = sizeof(int), - .value_size = sizeof(int), - .max_entries = 1, -}; - -int this_is_a_global_val; - -SEC("func=sys_write") -int bpf_func__sys_write(void *ctx) -{ - int key = 0; - int value = 0; - - /* - * Incorrect relocation. Should not allow this program be - * loaded into kernel. - */ - bpf_map_update_elem(&this_is_a_global_val, &key, &value, 0); - return 0; -} -char _license[] SEC("license") = "GPL"; -int _version SEC("version") = LINUX_VERSION_CODE; diff --git a/tools/perf/tests/bpf.c b/tools/perf/tests/bpf.c deleted file mode 100644 index 5d20bf8397f0..000000000000 --- a/tools/perf/tests/bpf.c +++ /dev/null @@ -1,367 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <errno.h> -#include <stdio.h> -#include <sys/epoll.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <util/record.h> -#include <util/util.h> -#include <util/bpf-loader.h> -#include <util/evlist.h> -#include <linux/bpf.h> -#include <linux/filter.h> -#include <linux/kernel.h> -#include <linux/string.h> -#include <api/fs/fs.h> -#include <bpf/bpf.h> -#include <perf/mmap.h> -#include "tests.h" -#include "llvm.h" -#include "debug.h" -#include "parse-events.h" -#include "util/mmap.h" -#define NR_ITERS 111 -#define PERF_TEST_BPF_PATH "/sys/fs/bpf/perf_test" - -#ifdef HAVE_LIBBPF_SUPPORT - -static int epoll_pwait_loop(void) -{ - int i; - - /* Should fail NR_ITERS times */ - for (i = 0; i < NR_ITERS; i++) - epoll_pwait(-(i + 1), NULL, 0, 0, NULL); - return 0; -} - -#ifdef HAVE_BPF_PROLOGUE - -static int llseek_loop(void) -{ - int fds[2], i; - - fds[0] = open("/dev/null", O_RDONLY); - fds[1] = open("/dev/null", O_RDWR); - - if (fds[0] < 0 || fds[1] < 0) - return -1; - - for (i = 0; i < NR_ITERS; i++) { - lseek(fds[i % 2], i, (i / 2) % 2 ? SEEK_CUR : SEEK_SET); - lseek(fds[(i + 1) % 2], i, (i / 2) % 2 ? SEEK_CUR : SEEK_SET); - } - close(fds[0]); - close(fds[1]); - return 0; -} - -#endif - -static struct { - enum test_llvm__testcase prog_id; - const char *desc; - const char *name; - const char *msg_compile_fail; - const char *msg_load_fail; - int (*target_func)(void); - int expect_result; - bool pin; -} bpf_testcase_table[] = { - { - .prog_id = LLVM_TESTCASE_BASE, - .desc = "Basic BPF filtering", - .name = "[basic_bpf_test]", - .msg_compile_fail = "fix 'perf test LLVM' first", - .msg_load_fail = "load bpf object failed", - .target_func = &epoll_pwait_loop, - .expect_result = (NR_ITERS + 1) / 2, - }, - { - .prog_id = LLVM_TESTCASE_BASE, - .desc = "BPF pinning", - .name = "[bpf_pinning]", - .msg_compile_fail = "fix kbuild first", - .msg_load_fail = "check your vmlinux setting?", - .target_func = &epoll_pwait_loop, - .expect_result = (NR_ITERS + 1) / 2, - .pin = true, - }, -#ifdef HAVE_BPF_PROLOGUE - { - .prog_id = LLVM_TESTCASE_BPF_PROLOGUE, - .desc = "BPF prologue generation", - .name = "[bpf_prologue_test]", - .msg_compile_fail = "fix kbuild first", - .msg_load_fail = "check your vmlinux setting?", - .target_func = &llseek_loop, - .expect_result = (NR_ITERS + 1) / 4, - }, -#endif - { - .prog_id = LLVM_TESTCASE_BPF_RELOCATION, - .desc = "BPF relocation checker", - .name = "[bpf_relocation_test]", - .msg_compile_fail = "fix 'perf test LLVM' first", - .msg_load_fail = "libbpf error when dealing with relocation", - }, -}; - -static int do_test(struct bpf_object *obj, int (*func)(void), - int expect) -{ - struct record_opts opts = { - .target = { - .uid = UINT_MAX, - .uses_mmap = true, - }, - .freq = 0, - .mmap_pages = 256, - .default_interval = 1, - }; - - char pid[16]; - char sbuf[STRERR_BUFSIZE]; - struct evlist *evlist; - int i, ret = TEST_FAIL, err = 0, count = 0; - - struct parse_events_state parse_state; - struct parse_events_error parse_error; - - bzero(&parse_error, sizeof(parse_error)); - bzero(&parse_state, sizeof(parse_state)); - parse_state.error = &parse_error; - INIT_LIST_HEAD(&parse_state.list); - - err = parse_events_load_bpf_obj(&parse_state, &parse_state.list, obj, NULL); - if (err || list_empty(&parse_state.list)) { - pr_debug("Failed to add events selected by BPF\n"); - return TEST_FAIL; - } - - snprintf(pid, sizeof(pid), "%d", getpid()); - pid[sizeof(pid) - 1] = '\0'; - opts.target.tid = opts.target.pid = pid; - - /* Instead of perf_evlist__new_default, don't add default events */ - evlist = evlist__new(); - if (!evlist) { - pr_debug("Not enough memory to create evlist\n"); - return TEST_FAIL; - } - - 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; - } - - perf_evlist__splice_list_tail(evlist, &parse_state.list); - evlist->nr_groups = parse_state.nr_groups; - - perf_evlist__config(evlist, &opts, NULL); - - err = evlist__open(evlist); - if (err < 0) { - pr_debug("perf_evlist__open: %s\n", - str_error_r(errno, sbuf, sizeof(sbuf))); - goto out_delete_evlist; - } - - err = evlist__mmap(evlist, opts.mmap_pages); - if (err < 0) { - pr_debug("evlist__mmap: %s\n", - str_error_r(errno, sbuf, sizeof(sbuf))); - goto out_delete_evlist; - } - - evlist__enable(evlist); - (*func)(); - evlist__disable(evlist); - - for (i = 0; i < evlist->core.nr_mmaps; i++) { - union perf_event *event; - struct mmap *md; - - md = &evlist->mmap[i]; - if (perf_mmap__read_init(&md->core) < 0) - continue; - - while ((event = perf_mmap__read_event(&md->core)) != NULL) { - const u32 type = event->header.type; - - if (type == PERF_RECORD_SAMPLE) - count ++; - } - perf_mmap__read_done(&md->core); - } - - if (count != expect) { - pr_debug("BPF filter result incorrect, expected %d, got %d samples\n", expect, count); - goto out_delete_evlist; - } - - ret = TEST_OK; - -out_delete_evlist: - evlist__delete(evlist); - return ret; -} - -static struct bpf_object * -prepare_bpf(void *obj_buf, size_t obj_buf_sz, const char *name) -{ - struct bpf_object *obj; - - obj = bpf__prepare_load_buffer(obj_buf, obj_buf_sz, name); - if (IS_ERR(obj)) { - pr_debug("Compile BPF program failed.\n"); - return NULL; - } - return obj; -} - -static int __test__bpf(int idx) -{ - int ret; - void *obj_buf; - size_t obj_buf_sz; - struct bpf_object *obj; - - ret = test_llvm__fetch_bpf_obj(&obj_buf, &obj_buf_sz, - bpf_testcase_table[idx].prog_id, - true, NULL); - if (ret != TEST_OK || !obj_buf || !obj_buf_sz) { - pr_debug("Unable to get BPF object, %s\n", - bpf_testcase_table[idx].msg_compile_fail); - if (idx == 0) - return TEST_SKIP; - else - return TEST_FAIL; - } - - obj = prepare_bpf(obj_buf, obj_buf_sz, - bpf_testcase_table[idx].name); - if ((!!bpf_testcase_table[idx].target_func) != (!!obj)) { - if (!obj) - pr_debug("Fail to load BPF object: %s\n", - bpf_testcase_table[idx].msg_load_fail); - else - pr_debug("Success unexpectedly: %s\n", - bpf_testcase_table[idx].msg_load_fail); - ret = TEST_FAIL; - goto out; - } - - if (obj) { - ret = do_test(obj, - bpf_testcase_table[idx].target_func, - bpf_testcase_table[idx].expect_result); - if (ret != TEST_OK) - goto out; - if (bpf_testcase_table[idx].pin) { - int err; - - if (!bpf_fs__mount()) { - pr_debug("BPF filesystem not mounted\n"); - ret = TEST_FAIL; - goto out; - } - err = mkdir(PERF_TEST_BPF_PATH, 0777); - if (err && errno != EEXIST) { - pr_debug("Failed to make perf_test dir: %s\n", - strerror(errno)); - ret = TEST_FAIL; - goto out; - } - if (bpf_object__pin(obj, PERF_TEST_BPF_PATH)) - ret = TEST_FAIL; - if (rm_rf(PERF_TEST_BPF_PATH)) - ret = TEST_FAIL; - } - } - -out: - bpf__clear(); - return ret; -} - -int test__bpf_subtest_get_nr(void) -{ - return (int)ARRAY_SIZE(bpf_testcase_table); -} - -const char *test__bpf_subtest_get_desc(int i) -{ - if (i < 0 || i >= (int)ARRAY_SIZE(bpf_testcase_table)) - return NULL; - return bpf_testcase_table[i].desc; -} - -static int check_env(void) -{ - int err; - unsigned int kver_int; - char license[] = "GPL"; - - struct bpf_insn insns[] = { - BPF_MOV64_IMM(BPF_REG_0, 1), - BPF_EXIT_INSN(), - }; - - err = fetch_kernel_version(&kver_int, NULL, 0); - if (err) { - pr_debug("Unable to get kernel version\n"); - return err; - } - - err = bpf_load_program(BPF_PROG_TYPE_KPROBE, insns, - sizeof(insns) / sizeof(insns[0]), - license, kver_int, NULL, 0); - if (err < 0) { - pr_err("Missing basic BPF support, skip this test: %s\n", - strerror(errno)); - return err; - } - close(err); - - return 0; -} - -int test__bpf(struct test *test __maybe_unused, int i) -{ - int err; - - if (i < 0 || i >= (int)ARRAY_SIZE(bpf_testcase_table)) - return TEST_FAIL; - - if (geteuid() != 0) { - pr_debug("Only root can run BPF test\n"); - return TEST_SKIP; - } - - if (check_env()) - return TEST_SKIP; - - err = __test__bpf(i); - return err; -} - -#else -int test__bpf_subtest_get_nr(void) -{ - return 0; -} - -const char *test__bpf_subtest_get_desc(int i __maybe_unused) -{ - return NULL; -} - -int test__bpf(struct test *test __maybe_unused, int i __maybe_unused) -{ - pr_debug("Skip BPF test because BPF support is not compiled\n"); - return TEST_SKIP; -} -#endif diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index da5b6cc23f25..45d3d8b3317a 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -6,7 +6,9 @@ */ #include <fcntl.h> #include <errno.h> +#include <poll.h> #include <unistd.h> +#include <setjmp.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> @@ -14,340 +16,187 @@ #include <sys/wait.h> #include <sys/stat.h> #include "builtin.h" +#include "config.h" #include "hist.h" #include "intlist.h" #include "tests.h" #include "debug.h" #include "color.h" #include <subcmd/parse-options.h> +#include <subcmd/run-command.h> #include "string2.h" #include "symbol.h" #include "util/rlimit.h" +#include "util/strbuf.h" #include <linux/kernel.h> #include <linux/string.h> #include <subcmd/exec-cmd.h> +#include <linux/zalloc.h> +#include "tests-scripts.h" + +/* + * Command line option to not fork the test running in the same process and + * making them easier to debug. + */ static bool dont_fork; +/* Fork the tests in parallel and wait for their completion. */ +static bool sequential; +/* Number of times each test is run. */ +static unsigned int runs_per_test = 1; +const char *dso_to_test; +const char *test_objdump_path = "objdump"; -struct test __weak arch_tests[] = { - { - .func = NULL, - }, +/* + * List of architecture specific tests. Not a weak symbol as the array length is + * dependent on the initialization, as such GCC with LTO complains of + * conflicting definitions with a weak symbol. + */ +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || defined(__powerpc64__) +extern struct test_suite *arch_tests[]; +#else +static struct test_suite *arch_tests[] = { + NULL, }; - -static struct test generic_tests[] = { - { - .desc = "vmlinux symtab matches kallsyms", - .func = test__vmlinux_matches_kallsyms, - }, - { - .desc = "Detect openat syscall event", - .func = test__openat_syscall_event, - }, - { - .desc = "Detect openat syscall event on all cpus", - .func = test__openat_syscall_event_on_all_cpus, - }, - { - .desc = "Read samples using the mmap interface", - .func = test__basic_mmap, - }, - { - .desc = "Test data source output", - .func = test__mem, - }, - { - .desc = "Parse event definition strings", - .func = test__parse_events, - }, - { - .desc = "Simple expression parser", - .func = test__expr, - }, - { - .desc = "PERF_RECORD_* events & perf_sample fields", - .func = test__PERF_RECORD, - }, - { - .desc = "Parse perf pmu format", - .func = test__pmu, - }, - { - .desc = "PMU events", - .func = test__pmu_events, - .subtest = { - .skip_if_fail = false, - .get_nr = test__pmu_events_subtest_get_nr, - .get_desc = test__pmu_events_subtest_get_desc, - .skip_reason = test__pmu_events_subtest_skip_reason, - }, - - }, - { - .desc = "DSO data read", - .func = test__dso_data, - }, - { - .desc = "DSO data cache", - .func = test__dso_data_cache, - }, - { - .desc = "DSO data reopen", - .func = test__dso_data_reopen, - }, - { - .desc = "Roundtrip evsel->name", - .func = test__perf_evsel__roundtrip_name_test, - }, - { - .desc = "Parse sched tracepoints fields", - .func = test__perf_evsel__tp_sched_test, - }, - { - .desc = "syscalls:sys_enter_openat event fields", - .func = test__syscall_openat_tp_fields, - }, - { - .desc = "Setup struct perf_event_attr", - .func = test__attr, - }, - { - .desc = "Match and link multiple hists", - .func = test__hists_link, - }, - { - .desc = "'import perf' in python", - .func = test__python_use, - }, - { - .desc = "Breakpoint overflow signal handler", - .func = test__bp_signal, - .is_supported = test__bp_signal_is_supported, - }, - { - .desc = "Breakpoint overflow sampling", - .func = test__bp_signal_overflow, - .is_supported = test__bp_signal_is_supported, - }, - { - .desc = "Breakpoint accounting", - .func = test__bp_accounting, - .is_supported = test__bp_account_is_supported, - }, - { - .desc = "Watchpoint", - .func = test__wp, - .is_supported = test__wp_is_supported, - .subtest = { - .skip_if_fail = false, - .get_nr = test__wp_subtest_get_nr, - .get_desc = test__wp_subtest_get_desc, - }, - }, - { - .desc = "Number of exit events of a simple workload", - .func = test__task_exit, - }, - { - .desc = "Software clock events period values", - .func = test__sw_clock_freq, - }, - { - .desc = "Object code reading", - .func = test__code_reading, - }, - { - .desc = "Sample parsing", - .func = test__sample_parsing, - }, - { - .desc = "Use a dummy software event to keep tracking", - .func = test__keep_tracking, - }, - { - .desc = "Parse with no sample_id_all bit set", - .func = test__parse_no_sample_id_all, - }, - { - .desc = "Filter hist entries", - .func = test__hists_filter, - }, - { - .desc = "Lookup mmap thread", - .func = test__mmap_thread_lookup, - }, - { - .desc = "Share thread maps", - .func = test__thread_maps_share, - }, - { - .desc = "Sort output of hist entries", - .func = test__hists_output, - }, - { - .desc = "Cumulate child hist entries", - .func = test__hists_cumulate, - }, - { - .desc = "Track with sched_switch", - .func = test__switch_tracking, - }, - { - .desc = "Filter fds with revents mask in a fdarray", - .func = test__fdarray__filter, - }, - { - .desc = "Add fd to a fdarray, making it autogrow", - .func = test__fdarray__add, - }, - { - .desc = "kmod_path__parse", - .func = test__kmod_path__parse, - }, - { - .desc = "Thread map", - .func = test__thread_map, - }, - { - .desc = "LLVM search and compile", - .func = test__llvm, - .subtest = { - .skip_if_fail = true, - .get_nr = test__llvm_subtest_get_nr, - .get_desc = test__llvm_subtest_get_desc, - }, - }, - { - .desc = "Session topology", - .func = test__session_topology, - }, - { - .desc = "BPF filter", - .func = test__bpf, - .subtest = { - .skip_if_fail = true, - .get_nr = test__bpf_subtest_get_nr, - .get_desc = test__bpf_subtest_get_desc, - }, - }, - { - .desc = "Synthesize thread map", - .func = test__thread_map_synthesize, - }, - { - .desc = "Remove thread map", - .func = test__thread_map_remove, - }, - { - .desc = "Synthesize cpu map", - .func = test__cpu_map_synthesize, - }, - { - .desc = "Synthesize stat config", - .func = test__synthesize_stat_config, - }, - { - .desc = "Synthesize stat", - .func = test__synthesize_stat, - }, - { - .desc = "Synthesize stat round", - .func = test__synthesize_stat_round, - }, - { - .desc = "Synthesize attr update", - .func = test__event_update, - }, - { - .desc = "Event times", - .func = test__event_times, - }, - { - .desc = "Read backward ring buffer", - .func = test__backward_ring_buffer, - }, - { - .desc = "Print cpu map", - .func = test__cpu_map_print, - }, - { - .desc = "Merge cpu map", - .func = test__cpu_map_merge, - }, - - { - .desc = "Probe SDT events", - .func = test__sdt_event, - }, - { - .desc = "is_printable_array", - .func = test__is_printable_array, - }, - { - .desc = "Print bitmap", - .func = test__bitmap_print, - }, - { - .desc = "perf hooks", - .func = test__perf_hooks, - }, - { - .desc = "builtin clang support", - .func = test__clang, - .subtest = { - .skip_if_fail = true, - .get_nr = test__clang_subtest_get_nr, - .get_desc = test__clang_subtest_get_desc, - } - }, - { - .desc = "unit_number__scnprintf", - .func = test__unit_number__scnprint, - }, - { - .desc = "mem2node", - .func = test__mem2node, - }, - { - .desc = "time utils", - .func = test__time_utils, - }, - { - .desc = "Test jit_write_elf", - .func = test__jit_write_elf, - }, - { - .desc = "Test libpfm4 support", - .func = test__pfm, - .subtest = { - .skip_if_fail = true, - .get_nr = test__pfm_subtest_get_nr, - .get_desc = test__pfm_subtest_get_desc, - } - }, - { - .desc = "Test api io", - .func = test__api_io, - }, - { - .desc = "maps__merge_in", - .func = test__maps__merge_in, - }, - { - .desc = "Demangle Java", - .func = test__demangle_java, - }, - { - .func = NULL, - }, +#endif + +static struct test_suite *generic_tests[] = { + &suite__vmlinux_matches_kallsyms, + &suite__openat_syscall_event, + &suite__openat_syscall_event_on_all_cpus, + &suite__basic_mmap, + &suite__mem, + &suite__parse_events, + &suite__expr, + &suite__PERF_RECORD, + &suite__pmu, + &suite__pmu_events, + &suite__hwmon_pmu, + &suite__tool_pmu, + &suite__dso_data, + &suite__perf_evsel__roundtrip_name_test, +#ifdef HAVE_LIBTRACEEVENT + &suite__perf_evsel__tp_sched_test, + &suite__syscall_openat_tp_fields, +#endif + &suite__hists_link, + &suite__python_use, + &suite__bp_signal, + &suite__bp_signal_overflow, + &suite__bp_accounting, + &suite__wp, + &suite__task_exit, + &suite__sw_clock_freq, + &suite__code_reading, + &suite__sample_parsing, + &suite__keep_tracking, + &suite__parse_no_sample_id_all, + &suite__hists_filter, + &suite__mmap_thread_lookup, + &suite__thread_maps_share, + &suite__hists_output, + &suite__hists_cumulate, +#ifdef HAVE_LIBTRACEEVENT + &suite__switch_tracking, +#endif + &suite__fdarray__filter, + &suite__fdarray__add, + &suite__kmod_path__parse, + &suite__thread_map, + &suite__session_topology, + &suite__thread_map_synthesize, + &suite__thread_map_remove, + &suite__cpu_map, + &suite__synthesize_stat_config, + &suite__synthesize_stat, + &suite__synthesize_stat_round, + &suite__event_update, + &suite__event_times, + &suite__backward_ring_buffer, + &suite__sdt_event, + &suite__is_printable_array, + &suite__bitmap_print, + &suite__perf_hooks, + &suite__unit_number__scnprint, + &suite__mem2node, + &suite__time_utils, + &suite__jit_write_elf, + &suite__pfm, + &suite__api_io, + &suite__maps__merge_in, + &suite__demangle_java, + &suite__demangle_ocaml, + &suite__demangle_rust, + &suite__parse_metric, + &suite__pe_file_parsing, + &suite__expand_cgroup_events, + &suite__perf_time_to_tsc, + &suite__dlfilter, + &suite__sigtrap, + &suite__event_groups, + &suite__symbols, + &suite__util, + NULL, }; -static struct test *tests[] = { - generic_tests, - arch_tests, +static struct test_workload *workloads[] = { + &workload__noploop, + &workload__thloop, + &workload__leafloop, + &workload__sqrtloop, + &workload__brstack, + &workload__datasym, + &workload__landlock, }; -static bool perf_test__matches(const char *desc, int curr, int argc, const char *argv[]) +#define workloads__for_each(workload) \ + for (unsigned i = 0; i < ARRAY_SIZE(workloads) && ({ workload = workloads[i]; 1; }); i++) + +#define test_suite__for_each_test_case(suite, idx) \ + for (idx = 0; (suite)->test_cases && (suite)->test_cases[idx].name != NULL; idx++) + +static int test_suite__num_test_cases(const struct test_suite *t) +{ + int num; + + test_suite__for_each_test_case(t, num); + + return num; +} + +static const char *skip_reason(const struct test_suite *t, int test_case) +{ + if (!t->test_cases) + return NULL; + + return t->test_cases[test_case >= 0 ? test_case : 0].skip_reason; +} + +static const char *test_description(const struct test_suite *t, int test_case) +{ + if (t->test_cases && test_case >= 0) + return t->test_cases[test_case].desc; + + return t->desc; +} + +static test_fnptr test_function(const struct test_suite *t, int test_case) +{ + if (test_case <= 0) + return t->test_cases[0].run_case; + + return t->test_cases[test_case].run_case; +} + +static bool test_exclusive(const struct test_suite *t, int test_case) +{ + if (test_case <= 0) + return t->test_cases[0].exclusive; + + return t->test_cases[test_case].exclusive; +} + +static bool perf_test__matches(const char *desc, int suite_num, int argc, const char *argv[]) { int i; @@ -359,7 +208,7 @@ static bool perf_test__matches(const char *desc, int curr, int argc, const char long nr = strtoul(argv[i], &end, 10); if (*end == '\0') { - if (nr == curr + 1) + if (nr == suite_num + 1) return true; continue; } @@ -371,90 +220,75 @@ static bool perf_test__matches(const char *desc, int curr, int argc, const char return false; } -static int run_test(struct test *test, int subtest) -{ - int status, err = -1, child = dont_fork ? 0 : fork(); - char sbuf[STRERR_BUFSIZE]; - - if (child < 0) { - pr_err("failed to fork test: %s\n", - str_error_r(errno, sbuf, sizeof(sbuf))); - return -1; - } - - if (!child) { - if (!dont_fork) { - pr_debug("test child forked, pid %d\n", getpid()); +struct child_test { + struct child_process process; + struct test_suite *test; + int suite_num; + int test_case_num; +}; - if (verbose <= 0) { - int nullfd = open("/dev/null", O_WRONLY); +static jmp_buf run_test_jmp_buf; - if (nullfd >= 0) { - close(STDERR_FILENO); - close(STDOUT_FILENO); +static void child_test_sig_handler(int sig) +{ + siglongjmp(run_test_jmp_buf, sig); +} - dup2(nullfd, STDOUT_FILENO); - dup2(STDOUT_FILENO, STDERR_FILENO); - close(nullfd); - } - } else { - signal(SIGSEGV, sighandler_dump_stack); - signal(SIGFPE, sighandler_dump_stack); - } - } +static int run_test_child(struct child_process *process) +{ + const int signals[] = { + SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM, + }; + struct child_test *child = container_of(process, struct child_test, process); + int err; - err = test->func(test, subtest); - if (!dont_fork) - exit(err); + err = sigsetjmp(run_test_jmp_buf, 1); + if (err) { + fprintf(stderr, "\n---- unexpected signal (%d) ----\n", err); + err = err > 0 ? -err : -1; + goto err_out; } - if (!dont_fork) { - wait(&status); + for (size_t i = 0; i < ARRAY_SIZE(signals); i++) + signal(signals[i], child_test_sig_handler); - if (WIFEXITED(status)) { - err = (signed char)WEXITSTATUS(status); - pr_debug("test child finished with %d\n", err); - } else if (WIFSIGNALED(status)) { - err = -1; - pr_debug("test child interrupted\n"); - } - } + pr_debug("--- start ---\n"); + pr_debug("test child forked, pid %d\n", getpid()); + err = test_function(child->test, child->test_case_num)(child->test, child->test_case_num); + pr_debug("---- end(%d) ----\n", err); - return err; +err_out: + fflush(NULL); + for (size_t i = 0; i < ARRAY_SIZE(signals); i++) + signal(signals[i], SIG_DFL); + return -err; } -#define for_each_test(j, t) \ - for (j = 0; j < ARRAY_SIZE(tests); j++) \ - for (t = &tests[j][0]; t->func; t++) +#define TEST_RUNNING -3 -static int test_and_print(struct test *t, bool force_skip, int subtest) +static int print_test_result(struct test_suite *t, int curr_suite, int curr_test_case, + int result, int width, int running) { - int err; - - if (!force_skip) { - pr_debug("\n--- start ---\n"); - err = run_test(t, subtest); - pr_debug("---- end ----\n"); - } else { - pr_debug("\n--- force skipped ---\n"); - err = TEST_SKIP; - } + if (test_suite__num_test_cases(t) > 1) { + int subw = width > 2 ? width - 2 : width; - if (!t->subtest.get_nr) - pr_debug("%s:", t->desc); - else - pr_debug("%s subtest %d:", t->desc, subtest + 1); + pr_info("%3d.%1d: %-*s:", curr_suite + 1, curr_test_case + 1, subw, + test_description(t, curr_test_case)); + } else + pr_info("%3d: %-*s:", curr_suite + 1, width, test_description(t, curr_test_case)); - switch (err) { + switch (result) { + case TEST_RUNNING: + color_fprintf(stderr, PERF_COLOR_YELLOW, " Running (%d active)\n", running); + break; case TEST_OK: pr_info(" Ok\n"); break; case TEST_SKIP: { - const char *skip_reason = NULL; - if (t->subtest.skip_reason) - skip_reason = t->subtest.skip_reason(subtest); - if (skip_reason) - color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip (%s)\n", skip_reason); + const char *reason = skip_reason(t, curr_test_case); + + if (reason) + color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip (%s)\n", reason); else color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip\n"); } @@ -465,298 +299,391 @@ static int test_and_print(struct test *t, bool force_skip, int subtest) break; } - return err; + return 0; } -static const char *shell_test__description(char *description, size_t size, - const char *path, const char *name) +static void finish_test(struct child_test **child_tests, int running_test, int child_test_num, + int width) { - FILE *fp; - char filename[PATH_MAX]; - - path__join(filename, sizeof(filename), path, name); - fp = fopen(filename, "r"); - if (!fp) - return NULL; - - /* Skip shebang */ - while (fgetc(fp) != '\n'); - - description = fgets(description, size, fp); - fclose(fp); + struct child_test *child_test = child_tests[running_test]; + struct test_suite *t; + int curr_suite, curr_test_case, err; + bool err_done = false; + struct strbuf err_output = STRBUF_INIT; + int last_running = -1; + int ret; + + if (child_test == NULL) { + /* Test wasn't started. */ + return; + } + t = child_test->test; + curr_suite = child_test->suite_num; + curr_test_case = child_test->test_case_num; + err = child_test->process.err; + /* + * For test suites with subtests, display the suite name ahead of the + * sub test names. + */ + if (test_suite__num_test_cases(t) > 1 && curr_test_case == 0) + pr_info("%3d: %-*s:\n", curr_suite + 1, width, test_description(t, -1)); - return description ? strim(description + 1) : NULL; -} + /* + * Busy loop reading from the child's stdout/stderr that are set to be + * non-blocking until EOF. + */ + if (err > 0) + fcntl(err, F_SETFL, O_NONBLOCK); + if (verbose > 1) { + if (test_suite__num_test_cases(t) > 1) + pr_info("%3d.%1d: %s:\n", curr_suite + 1, curr_test_case + 1, + test_description(t, curr_test_case)); + else + pr_info("%3d: %s:\n", curr_suite + 1, test_description(t, -1)); + } + while (!err_done) { + struct pollfd pfds[1] = { + { .fd = err, + .events = POLLIN | POLLERR | POLLHUP | POLLNVAL, + }, + }; + if (perf_use_color_default) { + int running = 0; -#define for_each_shell_test(dir, base, ent) \ - while ((ent = readdir(dir)) != NULL) \ - if (!is_directory(base, ent) && ent->d_name[0] != '.') + for (int y = running_test; y < child_test_num; y++) { + if (child_tests[y] == NULL) + continue; + if (check_if_command_finished(&child_tests[y]->process) == 0) + running++; + } + if (running != last_running) { + if (last_running != -1) { + /* + * Erase "Running (.. active)" line + * printed before poll/sleep. + */ + fprintf(debug_file(), PERF_COLOR_DELETE_LINE); + } + print_test_result(t, curr_suite, curr_test_case, TEST_RUNNING, + width, running); + last_running = running; + } + } -static const char *shell_tests__dir(char *path, size_t size) -{ - const char *devel_dirs[] = { "./tools/perf/tests", "./tests", }; - char *exec_path; - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(devel_dirs); ++i) { - struct stat st; - if (!lstat(devel_dirs[i], &st)) { - scnprintf(path, size, "%s/shell", devel_dirs[i]); - if (!lstat(devel_dirs[i], &st)) - return path; + err_done = true; + if (err <= 0) { + /* No child stderr to poll, sleep for 10ms for child to complete. */ + usleep(10 * 1000); + } else { + /* Poll to avoid excessive spinning, timeout set for 100ms. */ + poll(pfds, ARRAY_SIZE(pfds), /*timeout=*/100); + if (pfds[0].revents) { + char buf[512]; + ssize_t len; + + len = read(err, buf, sizeof(buf) - 1); + + if (len > 0) { + err_done = false; + buf[len] = '\0'; + strbuf_addstr(&err_output, buf); + } + } } + if (err_done) + err_done = check_if_command_finished(&child_test->process); } - - /* Then installed path. */ - exec_path = get_argv_exec_path(); - scnprintf(path, size, "%s/tests/shell", exec_path); - free(exec_path); - return path; + if (perf_use_color_default && last_running != -1) { + /* Erase "Running (.. active)" line printed before poll/sleep. */ + fprintf(debug_file(), PERF_COLOR_DELETE_LINE); + } + /* Clean up child process. */ + ret = finish_command(&child_test->process); + if (verbose > 1 || (verbose == 1 && ret == TEST_FAIL)) + fprintf(stderr, "%s", err_output.buf); + + strbuf_release(&err_output); + print_test_result(t, curr_suite, curr_test_case, ret, width, /*running=*/0); + if (err > 0) + close(err); + zfree(&child_tests[running_test]); } -static int shell_tests__max_desc_width(void) +static int start_test(struct test_suite *test, int curr_suite, int curr_test_case, + struct child_test **child, int width, int pass) { - DIR *dir; - struct dirent *ent; - char path_dir[PATH_MAX]; - const char *path = shell_tests__dir(path_dir, sizeof(path_dir)); - int width = 0; - - if (path == NULL) - return -1; - - dir = opendir(path); - if (!dir) - return -1; - - for_each_shell_test(dir, path, ent) { - char bf[256]; - const char *desc = shell_test__description(bf, sizeof(bf), path, ent->d_name); - - if (desc) { - int len = strlen(desc); + int err; - if (width < len) - width = len; + *child = NULL; + if (dont_fork) { + if (pass == 1) { + pr_debug("--- start ---\n"); + err = test_function(test, curr_test_case)(test, curr_test_case); + pr_debug("---- end ----\n"); + print_test_result(test, curr_suite, curr_test_case, err, width, + /*running=*/0); } + return 0; } - - closedir(dir); - return width; + if (pass == 1 && !sequential && test_exclusive(test, curr_test_case)) { + /* When parallel, skip exclusive tests on the first pass. */ + return 0; + } + if (pass != 1 && (sequential || !test_exclusive(test, curr_test_case))) { + /* Sequential and non-exclusive tests were run on the first pass. */ + return 0; + } + *child = zalloc(sizeof(**child)); + if (!*child) + return -ENOMEM; + + (*child)->test = test; + (*child)->suite_num = curr_suite; + (*child)->test_case_num = curr_test_case; + (*child)->process.pid = -1; + (*child)->process.no_stdin = 1; + if (verbose <= 0) { + (*child)->process.no_stdout = 1; + (*child)->process.no_stderr = 1; + } else { + (*child)->process.stdout_to_stderr = 1; + (*child)->process.out = -1; + (*child)->process.err = -1; + } + (*child)->process.no_exec_cmd = run_test_child; + if (sequential || pass == 2) { + err = start_command(&(*child)->process); + if (err) + return err; + finish_test(child, /*running_test=*/0, /*child_test_num=*/1, width); + return 0; + } + return start_command(&(*child)->process); } -struct shell_test { - const char *dir; - const char *file; -}; - -static int shell_test__run(struct test *test, int subdir __maybe_unused) -{ - int err; - char script[PATH_MAX]; - struct shell_test *st = test->priv; - - path__join(script, sizeof(script), st->dir, st->file); - - err = system(script); - if (!err) - return TEST_OK; +/* State outside of __cmd_test for the sake of the signal handler. */ - return WEXITSTATUS(err) == 2 ? TEST_SKIP : TEST_FAIL; -} +static size_t num_tests; +static struct child_test **child_tests; +static jmp_buf cmd_test_jmp_buf; -static int run_shell_tests(int argc, const char *argv[], int i, int width) +static void cmd_test_sig_handler(int sig) { - DIR *dir; - struct dirent *ent; - char path_dir[PATH_MAX]; - struct shell_test st = { - .dir = shell_tests__dir(path_dir, sizeof(path_dir)), - }; - - if (st.dir == NULL) - return -1; - - dir = opendir(st.dir); - if (!dir) { - pr_err("failed to open shell test directory: %s\n", - st.dir); - return -1; - } - - for_each_shell_test(dir, st.dir, ent) { - int curr = i++; - char desc[256]; - struct test test = { - .desc = shell_test__description(desc, sizeof(desc), st.dir, ent->d_name), - .func = shell_test__run, - .priv = &st, - }; - - if (!perf_test__matches(test.desc, curr, argc, argv)) - continue; - - st.file = ent->d_name; - pr_info("%2d: %-*s:", i, width, test.desc); - test_and_print(&test, false, -1); - } - - closedir(dir); - return 0; + siglongjmp(cmd_test_jmp_buf, sig); } -static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) +static int __cmd_test(struct test_suite **suites, int argc, const char *argv[], + struct intlist *skiplist) { - struct test *t; - unsigned int j; - int i = 0; - int width = shell_tests__max_desc_width(); + static int width = 0; + int err = 0; - for_each_test(j, t) { - int len = strlen(t->desc); + for (struct test_suite **t = suites; *t; t++) { + int i, len = strlen(test_description(*t, -1)); if (width < len) width = len; - } - - for_each_test(j, t) { - int curr = i++, err; - int subi; - - if (!perf_test__matches(t->desc, curr, argc, argv)) { - bool skip = true; - int subn; - if (!t->subtest.get_nr) + test_suite__for_each_test_case(*t, i) { + len = strlen(test_description(*t, i)); + if (width < len) + width = len; + num_tests += runs_per_test; + } + } + child_tests = calloc(num_tests, sizeof(*child_tests)); + if (!child_tests) + return -ENOMEM; + + err = sigsetjmp(cmd_test_jmp_buf, 1); + if (err) { + pr_err("\nSignal (%d) while running tests.\nTerminating tests with the same signal\n", + err); + for (size_t x = 0; x < num_tests; x++) { + struct child_test *child_test = child_tests[x]; + + if (!child_test || child_test->process.pid <= 0) continue; - subn = t->subtest.get_nr(); + pr_debug3("Killing %d pid %d\n", + child_test->suite_num + 1, + child_test->process.pid); + kill(child_test->process.pid, err); + } + goto err_out; + } + signal(SIGINT, cmd_test_sig_handler); + signal(SIGTERM, cmd_test_sig_handler); - for (subi = 0; subi < subn; subi++) { - if (perf_test__matches(t->subtest.get_desc(subi), curr, argc, argv)) - skip = false; + /* + * In parallel mode pass 1 runs non-exclusive tests in parallel, pass 2 + * runs the exclusive tests sequentially. In other modes all tests are + * run in pass 1. + */ + for (int pass = 1; pass <= 2; pass++) { + int child_test_num = 0; + int curr_suite = 0; + + for (struct test_suite **t = suites; *t; t++, curr_suite++) { + int curr_test_case; + + if (!perf_test__matches(test_description(*t, -1), curr_suite, argc, argv)) { + /* + * Test suite shouldn't be run based on + * description. See if any test case should. + */ + bool skip = true; + + test_suite__for_each_test_case(*t, curr_test_case) { + if (perf_test__matches(test_description(*t, curr_test_case), + curr_suite, argc, argv)) { + skip = false; + break; + } + } + if (skip) + continue; } - if (skip) + if (intlist__find(skiplist, curr_suite + 1)) { + pr_info("%3d: %-*s:", curr_suite + 1, width, + test_description(*t, -1)); + color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip (user override)\n"); continue; - } + } - if (t->is_supported && !t->is_supported()) { - pr_debug("%2d: %-*s: Disabled\n", i, width, t->desc); - continue; + for (unsigned int run = 0; run < runs_per_test; run++) { + test_suite__for_each_test_case(*t, curr_test_case) { + if (!perf_test__matches(test_description(*t, curr_test_case), + curr_suite, argc, argv)) + continue; + + err = start_test(*t, curr_suite, curr_test_case, + &child_tests[child_test_num++], + width, pass); + if (err) + goto err_out; + } + } } - - pr_info("%2d: %-*s:", i, width, t->desc); - - if (intlist__find(skiplist, i)) { - color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip (user override)\n"); - continue; + if (!sequential) { + /* Parallel mode starts tests but doesn't finish them. Do that now. */ + for (size_t x = 0; x < num_tests; x++) + finish_test(child_tests, x, num_tests, width); } + } +err_out: + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + if (err) { + pr_err("Internal test harness failure. Completing any started tests:\n:"); + for (size_t x = 0; x < num_tests; x++) + finish_test(child_tests, x, num_tests, width); + } + free(child_tests); + return err; +} - if (!t->subtest.get_nr) { - test_and_print(t, false, -1); - } else { - int subn = t->subtest.get_nr(); - /* - * minus 2 to align with normal testcases. - * For subtest we print additional '.x' in number. - * for example: - * - * 35: Test LLVM searching and compiling : - * 35.1: Basic BPF llvm compiling test : Ok - */ - int subw = width > 2 ? width - 2 : width; - bool skip = false; - - if (subn <= 0) { - color_fprintf(stderr, PERF_COLOR_YELLOW, - " Skip (not compiled in)\n"); - continue; - } - pr_info("\n"); +static int perf_test__list(FILE *fp, struct test_suite **suites, int argc, const char **argv) +{ + int curr_suite = 0; - for (subi = 0; subi < subn; subi++) { - int len = strlen(t->subtest.get_desc(subi)); + for (struct test_suite **t = suites; *t; t++, curr_suite++) { + int curr_test_case; - if (subw < len) - subw = len; - } + if (!perf_test__matches(test_description(*t, -1), curr_suite, argc, argv)) + continue; - for (subi = 0; subi < subn; subi++) { - if (!perf_test__matches(t->subtest.get_desc(subi), curr, argc, argv)) - continue; + fprintf(fp, "%3d: %s\n", curr_suite + 1, test_description(*t, -1)); - pr_info("%2d.%1d: %-*s:", i, subi + 1, subw, - t->subtest.get_desc(subi)); - err = test_and_print(t, skip, subi); - if (err != TEST_OK && t->subtest.skip_if_fail) - skip = true; - } + if (test_suite__num_test_cases(*t) <= 1) + continue; + + test_suite__for_each_test_case(*t, curr_test_case) { + fprintf(fp, "%3d.%1d: %s\n", curr_suite + 1, curr_test_case + 1, + test_description(*t, curr_test_case)); } } - - return run_shell_tests(argc, argv, i, width); + return 0; } -static int perf_test__list_shell(int argc, const char **argv, int i) +static int workloads__fprintf_list(FILE *fp) { - DIR *dir; - struct dirent *ent; - char path_dir[PATH_MAX]; - const char *path = shell_tests__dir(path_dir, sizeof(path_dir)); + struct test_workload *twl; + int printed = 0; - if (path == NULL) - return -1; + workloads__for_each(twl) + printed += fprintf(fp, "%s\n", twl->name); - dir = opendir(path); - if (!dir) - return -1; - - for_each_shell_test(dir, path, ent) { - int curr = i++; - char bf[256]; - struct test t = { - .desc = shell_test__description(bf, sizeof(bf), path, ent->d_name), - }; + return printed; +} - if (!perf_test__matches(t.desc, curr, argc, argv)) - continue; +static int run_workload(const char *work, int argc, const char **argv) +{ + struct test_workload *twl; - pr_info("%2d: %s\n", i, t.desc); + workloads__for_each(twl) { + if (!strcmp(twl->name, work)) + return twl->func(argc, argv); } - closedir(dir); + pr_info("No workload found: %s\n", work); + return -1; +} + +static int perf_test__config(const char *var, const char *value, + void *data __maybe_unused) +{ + if (!strcmp(var, "annotate.objdump")) + test_objdump_path = value; + return 0; } -static int perf_test__list(int argc, const char **argv) +static struct test_suite **build_suites(void) { - unsigned int j; - struct test *t; - int i = 0; + /* + * TODO: suites is static to avoid needing to clean up the scripts tests + * for leak sanitizer. + */ + static struct test_suite **suites[] = { + generic_tests, + arch_tests, + NULL, + }; + struct test_suite **result; + struct test_suite *t; + size_t n = 0, num_suites = 0; - for_each_test(j, t) { - int curr = i++; + if (suites[2] == NULL) + suites[2] = create_script_test_suites(); - if (!perf_test__matches(t->desc, curr, argc, argv) || - (t->is_supported && !t->is_supported())) - continue; +#define for_each_suite(suite) \ + for (size_t i = 0, j = 0; i < ARRAY_SIZE(suites); i++, j = 0) \ + while ((suite = suites[i][j++]) != NULL) - pr_info("%2d: %s\n", i, t->desc); + for_each_suite(t) + num_suites++; - if (t->subtest.get_nr) { - int subn = t->subtest.get_nr(); - int subi; + result = calloc(num_suites + 1, sizeof(struct test_suite *)); - for (subi = 0; subi < subn; subi++) - pr_info("%2d:%1d: %s\n", i, subi + 1, - t->subtest.get_desc(subi)); + for (int pass = 1; pass <= 2; pass++) { + for_each_suite(t) { + bool exclusive = false; + int curr_test_case; + + test_suite__for_each_test_case(t, curr_test_case) { + if (test_exclusive(t, curr_test_case)) { + exclusive = true; + break; + } + } + if ((!exclusive && pass == 1) || (exclusive && pass == 2)) + result[n++] = t; } } - - perf_test__list_shell(argc, argv, i); - - return 0; + return result; +#undef for_each_suite } int cmd_test(int argc, const char **argv) @@ -766,29 +693,61 @@ int cmd_test(int argc, const char **argv) NULL, }; const char *skip = NULL; + const char *workload = NULL; + bool list_workloads = false; const struct option test_options[] = { OPT_STRING('s', "skip", &skip, "tests", "tests to skip"), OPT_INCR('v', "verbose", &verbose, "be more verbose (show symbol address, etc)"), OPT_BOOLEAN('F', "dont-fork", &dont_fork, "Do not fork for testcase"), + OPT_BOOLEAN('S', "sequential", &sequential, + "Run the tests one after another rather than in parallel"), + OPT_UINTEGER('r', "runs-per-test", &runs_per_test, + "Run each test the given number of times, default 1"), + OPT_STRING('w', "workload", &workload, "work", "workload to run for testing, use '--list-workloads' to list the available ones."), + OPT_BOOLEAN(0, "list-workloads", &list_workloads, "List the available builtin workloads to use with -w/--workload"), + OPT_STRING(0, "dso", &dso_to_test, "dso", "dso to test"), + OPT_STRING(0, "objdump", &test_objdump_path, "path", + "objdump binary to use for disassembly and annotations"), OPT_END() }; const char * const test_subcommands[] = { "list", NULL }; struct intlist *skiplist = NULL; int ret = hists__init(); + struct test_suite **suites; if (ret < 0) return ret; + perf_config(perf_test__config, NULL); + + /* Unbuffered output */ + setvbuf(stdout, NULL, _IONBF, 0); + argc = parse_options_subcommand(argc, argv, test_options, test_subcommands, test_usage, 0); - if (argc >= 1 && !strcmp(argv[0], "list")) - return perf_test__list(argc - 1, argv + 1); + if (argc >= 1 && !strcmp(argv[0], "list")) { + suites = build_suites(); + ret = perf_test__list(stdout, suites, argc - 1, argv + 1); + free(suites); + return ret; + } + + if (workload) + return run_workload(workload, argc, argv); + + if (list_workloads) { + workloads__fprintf_list(stdout); + return 0; + } + + if (dont_fork) + sequential = true; symbol_conf.priv_size = sizeof(int); - symbol_conf.sort_by_name = true; symbol_conf.try_vmlinux_path = true; + if (symbol__init(NULL) < 0) return -1; @@ -800,5 +759,8 @@ int cmd_test(int argc, const char **argv) */ rlimit__bump_memlock(); - return __cmd_test(argc, argv, skiplist); + suites = build_suites(); + ret = __cmd_test(suites, argc, argv, skiplist); + free(suites); + return ret; } diff --git a/tools/perf/tests/clang.c b/tools/perf/tests/clang.c deleted file mode 100644 index 2577d3ed1531..000000000000 --- a/tools/perf/tests/clang.c +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include "tests.h" -#include "c++/clang-c.h" -#include <linux/kernel.h> - -static struct { - int (*func)(void); - const char *desc; -} clang_testcase_table[] = { -#ifdef HAVE_LIBCLANGLLVM_SUPPORT - { - .func = test__clang_to_IR, - .desc = "builtin clang compile C source to IR", - }, - { - .func = test__clang_to_obj, - .desc = "builtin clang compile C source to ELF object", - }, -#endif -}; - -int test__clang_subtest_get_nr(void) -{ - return (int)ARRAY_SIZE(clang_testcase_table); -} - -const char *test__clang_subtest_get_desc(int i) -{ - if (i < 0 || i >= (int)ARRAY_SIZE(clang_testcase_table)) - return NULL; - return clang_testcase_table[i].desc; -} - -#ifndef HAVE_LIBCLANGLLVM_SUPPORT -int test__clang(struct test *test __maybe_unused, int i __maybe_unused) -{ - return TEST_SKIP; -} -#else -int test__clang(struct test *test __maybe_unused, int i) -{ - if (i < 0 || i >= (int)ARRAY_SIZE(clang_testcase_table)) - return TEST_FAIL; - return clang_testcase_table[i].func(); -} -#endif diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c index 6fe221d31f07..cf6edbe697b2 100644 --- a/tools/perf/tests/code-reading.c +++ b/tools/perf/tests/code-reading.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include <errno.h> +#include <linux/kconfig.h> #include <linux/kernel.h> #include <linux/types.h> #include <inttypes.h> @@ -8,6 +9,7 @@ #include <stdio.h> #include <string.h> #include <sys/param.h> +#include <sys/utsname.h> #include <perf/cpumap.h> #include <perf/evlist.h> #include <perf/mmap.h> @@ -16,7 +18,6 @@ #include "dso.h" #include "env.h" #include "parse-events.h" -#include "trace-event.h" #include "evlist.h" #include "evsel.h" #include "thread_map.h" @@ -26,7 +27,9 @@ #include "event.h" #include "record.h" #include "util/mmap.h" +#include "util/string2.h" #include "util/synthetic-events.h" +#include "util/util.h" #include "thread.h" #include "tests.h" @@ -41,15 +44,6 @@ struct state { size_t done_cnt; }; -static unsigned int hex(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - return c - 'A' + 10; -} - static size_t read_objdump_chunk(const char **line, unsigned char **buf, size_t *buf_len) { @@ -87,7 +81,7 @@ static size_t read_objdump_chunk(const char **line, unsigned char **buf, * see disassemble_bytes() at binutils/objdump.c for details * how objdump chooses display endian) */ - if (bytes_read > 1 && !bigendian()) { + if (bytes_read > 1 && !host_is_bigendian()) { unsigned char *chunk_end = chunk_start + bytes_read - 1; unsigned char tmp; @@ -184,16 +178,104 @@ static int read_objdump_output(FILE *f, void *buf, size_t *len, u64 start_addr) return err; } +/* + * Only gets GNU objdump version. Returns 0 for llvm-objdump. + */ +static int objdump_version(void) +{ + size_t line_len; + char cmd[PATH_MAX * 2]; + char *line = NULL; + const char *fmt; + FILE *f; + int ret; + + int version_tmp, version_num = 0; + char *version = 0, *token; + + fmt = "%s --version"; + ret = snprintf(cmd, sizeof(cmd), fmt, test_objdump_path); + if (ret <= 0 || (size_t)ret >= sizeof(cmd)) + return -1; + /* Ignore objdump errors */ + strcat(cmd, " 2>/dev/null"); + f = popen(cmd, "r"); + if (!f) { + pr_debug("popen failed\n"); + return -1; + } + /* Get first line of objdump --version output */ + ret = getline(&line, &line_len, f); + pclose(f); + if (ret < 0) { + pr_debug("getline failed\n"); + return -1; + } + + token = strsep(&line, " "); + if (token != NULL && !strcmp(token, "GNU")) { + // version is last part of first line of objdump --version output. + while ((token = strsep(&line, " "))) + version = token; + + // Convert version into a format we can compare with + token = strsep(&version, "."); + version_num = atoi(token); + if (version_num) + version_num *= 10000; + + token = strsep(&version, "."); + version_tmp = atoi(token); + if (token) + version_num += version_tmp * 100; + + token = strsep(&version, "."); + version_tmp = atoi(token); + if (token) + version_num += version_tmp; + } + + return version_num; +} + static int read_via_objdump(const char *filename, u64 addr, void *buf, size_t len) { + u64 stop_address = addr + len; + struct utsname uname_buf; char cmd[PATH_MAX * 2]; const char *fmt; FILE *f; int ret; + ret = uname(&uname_buf); + if (ret) { + pr_debug("uname failed\n"); + return -1; + } + + if (!strncmp(uname_buf.machine, "riscv", 5)) { + int version = objdump_version(); + + /* Default to this workaround if version parsing fails */ + if (version < 0 || version > 24100) { + /* + * Starting at riscv objdump version 2.41, dumping in + * the middle of an instruction is not supported. riscv + * instructions are aligned along 2-byte intervals and + * can be either 2-bytes or 4-bytes. This makes it + * possible that the stop-address lands in the middle of + * a 4-byte instruction. Increase the stop_address by + * two to ensure an instruction is not cut in half, but + * leave the len as-is so only the expected number of + * bytes are collected. + */ + stop_address += 2; + } + } + fmt = "%s -z -d --start-address=0x%"PRIx64" --stop-address=0x%"PRIx64" %s"; - ret = snprintf(cmd, sizeof(cmd), fmt, "objdump", addr, addr + len, + ret = snprintf(cmd, sizeof(cmd), fmt, test_objdump_path, addr, stop_address, filename); if (ret <= 0 || (size_t)ret >= sizeof(cmd)) return -1; @@ -237,33 +319,35 @@ static int read_object_code(u64 addr, size_t len, u8 cpumode, struct thread *thread, struct state *state) { struct addr_location al; - unsigned char buf1[BUFSZ]; - unsigned char buf2[BUFSZ]; + unsigned char buf1[BUFSZ] = {0}; + unsigned char buf2[BUFSZ] = {0}; size_t ret_len; u64 objdump_addr; const char *objdump_name; char decomp_name[KMOD_DECOMP_LEN]; bool decomp = false; - int ret; + int ret, err = 0; + struct dso *dso; pr_debug("Reading object code for memory address: %#"PRIx64"\n", addr); - if (!thread__find_map(thread, cpumode, addr, &al) || !al.map->dso) { + addr_location__init(&al); + if (!thread__find_map(thread, cpumode, addr, &al) || !map__dso(al.map)) { if (cpumode == PERF_RECORD_MISC_HYPERVISOR) { pr_debug("Hypervisor address can not be resolved - skipping\n"); - return 0; + goto out; } pr_debug("thread__find_map failed\n"); - return -1; + err = -1; + goto out; } + dso = map__dso(al.map); + pr_debug("File is: %s\n", dso__long_name(dso)); - pr_debug("File is: %s\n", al.map->dso->long_name); - - if (al.map->dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS && - !dso__is_kcore(al.map->dso)) { + if (dso__symtab_type(dso) == DSO_BINARY_TYPE__KALLSYMS && !dso__is_kcore(dso)) { pr_debug("Unexpected kernel address - skipping\n"); - return 0; + goto out; } pr_debug("On file address is: %#"PRIx64"\n", al.addr); @@ -272,49 +356,63 @@ static int read_object_code(u64 addr, size_t len, u8 cpumode, len = BUFSZ; /* Do not go off the map */ - if (addr + len > al.map->end) - len = al.map->end - addr; + if (addr + len > map__end(al.map)) + len = map__end(al.map) - addr; + + /* + * Some architectures (ex: powerpc) have stubs (trampolines) in kernel + * modules to manage long jumps. Check if the ip offset falls in stubs + * sections for kernel modules. And skip module address after text end + */ + if (dso__is_kmod(dso) && al.addr > dso__text_end(dso)) { + pr_debug("skipping the module address %#"PRIx64" after text end\n", al.addr); + goto out; + } /* Read the object code using perf */ - ret_len = dso__data_read_offset(al.map->dso, thread->maps->machine, + ret_len = dso__data_read_offset(dso, maps__machine(thread__maps(thread)), al.addr, buf1, len); if (ret_len != len) { pr_debug("dso__data_read_offset failed\n"); - return -1; + err = -1; + goto out; } /* * Converting addresses for use by objdump requires more information. * map__load() does that. See map__rip_2objdump() for details. */ - if (map__load(al.map)) - return -1; + if (map__load(al.map)) { + err = -1; + goto out; + } /* objdump struggles with kcore - try each map only once */ - if (dso__is_kcore(al.map->dso)) { + if (dso__is_kcore(dso)) { size_t d; for (d = 0; d < state->done_cnt; d++) { - if (state->done[d] == al.map->start) { + if (state->done[d] == map__start(al.map)) { pr_debug("kcore map tested already"); pr_debug(" - skipping\n"); - return 0; + goto out; } } if (state->done_cnt >= ARRAY_SIZE(state->done)) { pr_debug("Too many kcore maps - skipping\n"); - return 0; + goto out; } - state->done[state->done_cnt++] = al.map->start; + state->done[state->done_cnt++] = map__start(al.map); } - objdump_name = al.map->dso->long_name; - if (dso__needs_decompress(al.map->dso)) { - if (dso__decompress_kmodule_path(al.map->dso, objdump_name, + objdump_name = dso__long_name(dso); + if (dso__needs_decompress(dso)) { + if (dso__decompress_kmodule_path(dso, objdump_name, decomp_name, sizeof(decomp_name)) < 0) { pr_debug("decompression failed\n"); - return -1; + err = -1; + goto out; } decomp = true; @@ -338,22 +436,23 @@ static int read_object_code(u64 addr, size_t len, u8 cpumode, len -= ret; if (len) { pr_debug("Reducing len to %zu\n", len); - } else if (dso__is_kcore(al.map->dso)) { + } else if (dso__is_kcore(dso)) { /* * objdump cannot handle very large segments * that may be found in kcore. */ pr_debug("objdump failed for kcore"); pr_debug(" - skipping\n"); - return 0; } else { - return -1; + err = -1; } + goto out; } } if (ret < 0) { pr_debug("read_via_objdump failed\n"); - return -1; + err = -1; + goto out; } /* The results should be identical */ @@ -363,11 +462,13 @@ static int read_object_code(u64 addr, size_t len, u8 cpumode, dump_buf(buf1, len); pr_debug("buf2 (objdump):\n"); dump_buf(buf2, len); - return -1; + err = -1; + goto out; } pr_debug("Bytes read match those read by objdump\n"); - - return 0; +out: + addr_location__exit(&al); + return err; } static int process_sample_event(struct machine *machine, @@ -378,19 +479,25 @@ static int process_sample_event(struct machine *machine, struct thread *thread; int ret; - if (perf_evlist__parse_sample(evlist, event, &sample)) { - pr_debug("perf_evlist__parse_sample failed\n"); - return -1; + perf_sample__init(&sample, /*all=*/false); + ret = evlist__parse_sample(evlist, event, &sample); + if (ret) { + pr_debug("evlist__parse_sample failed\n"); + ret = -1; + goto out; } thread = machine__findnew_thread(machine, sample.pid, sample.tid); if (!thread) { pr_debug("machine__findnew_thread failed\n"); - return -1; + ret = -1; + goto out; } ret = read_object_code(sample.ip, READLEN, sample.cpumode, thread, state); thread__put(thread); +out: + perf_sample__exit(&sample); return ret; } @@ -500,38 +607,6 @@ static void fs_something(void) } } -#ifdef __s390x__ -#include "header.h" // for get_cpuid() -#endif - -static const char *do_determine_event(bool excl_kernel) -{ - const char *event = excl_kernel ? "cycles:u" : "cycles"; - -#ifdef __s390x__ - char cpuid[128], model[16], model_c[16], cpum_cf_v[16]; - unsigned int family; - int ret, cpum_cf_a; - - if (get_cpuid(cpuid, sizeof(cpuid))) - goto out_clocks; - ret = sscanf(cpuid, "%*[^,],%u,%[^,],%[^,],%[^,],%x", &family, model_c, - model, cpum_cf_v, &cpum_cf_a); - if (ret != 5) /* Not available */ - goto out_clocks; - if (excl_kernel && (cpum_cf_a & 4)) - return event; - if (!excl_kernel && (cpum_cf_a & 2)) - return event; - - /* Fall through: missing authorization */ -out_clocks: - event = excl_kernel ? "cpu-clock:u" : "cpu-clock"; - -#endif - return event; -} - static void do_something(void) { fs_something(); @@ -572,7 +647,10 @@ static int do_test_code_reading(bool try_kcore) int err = -1, ret; pid_t pid; struct map *map; - bool have_vmlinux, have_kcore, excl_kernel = false; + bool have_vmlinux, have_kcore; + struct dso *dso; + const char *events[] = { "cycles", "cycles:u", "cpu-clock", "cpu-clock:u", NULL }; + int evidx = 0; pid = getpid(); @@ -596,8 +674,9 @@ static int do_test_code_reading(bool try_kcore) pr_debug("map__load failed\n"); goto out_err; } - have_vmlinux = dso__is_vmlinux(map->dso); - have_kcore = dso__is_kcore(map->dso); + dso = map__dso(map); + have_vmlinux = dso__is_vmlinux(dso); + have_kcore = dso__is_kcore(dso); /* 2nd time through we just try kcore */ if (try_kcore && !have_kcore) @@ -605,7 +684,7 @@ static int do_test_code_reading(bool try_kcore) /* No point getting kernel events if there is no kernel object */ if (!have_vmlinux && !have_kcore) - excl_kernel = true; + evidx++; threads = thread_map__new_by_tid(pid); if (!threads) { @@ -614,7 +693,8 @@ static int do_test_code_reading(bool try_kcore) } ret = perf_event__synthesize_thread_map(NULL, threads, - perf_event__process, machine, false); + perf_event__process, machine, + true, false); if (ret < 0) { pr_debug("perf_event__synthesize_thread_map failed\n"); goto out_err; @@ -626,67 +706,67 @@ static int do_test_code_reading(bool try_kcore) goto out_put; } - cpus = perf_cpu_map__new(NULL); + cpus = perf_cpu_map__new_online_cpus(); if (!cpus) { pr_debug("perf_cpu_map__new failed\n"); goto out_put; } - while (1) { + while (events[evidx]) { const char *str; evlist = evlist__new(); if (!evlist) { - pr_debug("perf_evlist__new failed\n"); + pr_debug("evlist__new failed\n"); goto out_put; } perf_evlist__set_maps(&evlist->core, cpus, threads); - str = do_determine_event(excl_kernel); + str = events[evidx]; pr_debug("Parsing event '%s'\n", str); - ret = parse_events(evlist, str, NULL); + ret = parse_event(evlist, str); if (ret < 0) { pr_debug("parse_events failed\n"); goto out_put; } - perf_evlist__config(evlist, &opts, NULL); + evlist__config(evlist, &opts, NULL); - evsel = evlist__first(evlist); - - evsel->core.attr.comm = 1; - evsel->core.attr.disabled = 1; - evsel->core.attr.enable_on_exec = 0; + evlist__for_each_entry(evlist, evsel) { + evsel->core.attr.comm = 1; + evsel->core.attr.disabled = 1; + evsel->core.attr.enable_on_exec = 0; + } ret = evlist__open(evlist); if (ret < 0) { - if (!excl_kernel) { - excl_kernel = true; - /* - * Both cpus and threads are now owned by evlist - * and will be freed by following perf_evlist__set_maps - * call. Getting refference to keep them alive. - */ - perf_cpu_map__get(cpus); - perf_thread_map__get(threads); - perf_evlist__set_maps(&evlist->core, NULL, NULL); - evlist__delete(evlist); - evlist = NULL; - continue; - } + evidx++; - if (verbose > 0) { + if (events[evidx] == NULL && verbose > 0) { char errbuf[512]; - perf_evlist__strerror_open(evlist, errno, errbuf, sizeof(errbuf)); + evlist__strerror_open(evlist, errno, errbuf, sizeof(errbuf)); pr_debug("perf_evlist__open() failed!\n%s\n", errbuf); } - goto out_put; + /* + * Both cpus and threads are now owned by evlist + * and will be freed by following perf_evlist__set_maps + * call. Getting reference to keep them alive. + */ + perf_cpu_map__get(cpus); + perf_thread_map__get(threads); + perf_evlist__set_maps(&evlist->core, NULL, NULL); + evlist__delete(evlist); + evlist = NULL; + continue; } break; } + if (events[evidx] == NULL) + goto out_put; + ret = evlist__mmap(evlist, UINT_MAX); if (ret < 0) { pr_debug("evlist__mmap failed\n"); @@ -707,27 +787,22 @@ static int do_test_code_reading(bool try_kcore) err = TEST_CODE_READING_NO_KERNEL_OBJ; else if (!have_vmlinux && !try_kcore) err = TEST_CODE_READING_NO_VMLINUX; - else if (excl_kernel) + else if (strstr(events[evidx], ":u")) err = TEST_CODE_READING_NO_ACCESS; else err = TEST_CODE_READING_OK; out_put: thread__put(thread); out_err: - - if (evlist) { - evlist__delete(evlist); - } else { - perf_cpu_map__put(cpus); - perf_thread_map__put(threads); - } - machine__delete_threads(machine); + evlist__delete(evlist); + perf_cpu_map__put(cpus); + perf_thread_map__put(threads); machine__delete(machine); return err; } -int test__code_reading(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__code_reading(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int ret; @@ -754,3 +829,5 @@ int test__code_reading(struct test *test __maybe_unused, int subtest __maybe_unu return -1; }; } + +DEFINE_SUITE("Object code reading", code_reading); diff --git a/tools/perf/tests/config-fragments/README b/tools/perf/tests/config-fragments/README new file mode 100644 index 000000000000..fe7de5d93674 --- /dev/null +++ b/tools/perf/tests/config-fragments/README @@ -0,0 +1,7 @@ +This folder is for kernel config fragments that can be merged with +defconfig to give full test coverage of a perf test run. This is only +an optimistic set as some features require hardware support in order to +pass and not skip. + +'config' is shared across all platforms, and for arch specific files, +the file name should match that used in the ARCH=... make option. diff --git a/tools/perf/tests/config-fragments/arm64 b/tools/perf/tests/config-fragments/arm64 new file mode 100644 index 000000000000..64c4ab17cd58 --- /dev/null +++ b/tools/perf/tests/config-fragments/arm64 @@ -0,0 +1 @@ +CONFIG_CORESIGHT_SOURCE_ETM4X=y diff --git a/tools/perf/tests/config-fragments/config b/tools/perf/tests/config-fragments/config new file mode 100644 index 000000000000..4fca12851016 --- /dev/null +++ b/tools/perf/tests/config-fragments/config @@ -0,0 +1,14 @@ +CONFIG_TRACEPOINTS=y +CONFIG_STACKTRACE=y +CONFIG_NOP_TRACER=y +CONFIG_RING_BUFFER=y +CONFIG_EVENT_TRACING=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_TRACING=y +CONFIG_GENERIC_TRACER=y +CONFIG_FTRACE=y +CONFIG_FTRACE_SYSCALLS=y +CONFIG_BRANCH_PROFILE_NONE=y +CONFIG_KPROBES=y +CONFIG_KPROBE_EVENTS=y +CONFIG_UPROBE_EVENTS=y diff --git a/tools/perf/tests/cpumap.c b/tools/perf/tests/cpumap.c index 29c793ac7d10..2354246afc5a 100644 --- a/tools/perf/tests/cpumap.c +++ b/tools/perf/tests/cpumap.c @@ -6,52 +6,53 @@ #include "util/synthetic-events.h" #include <string.h> #include <linux/bitops.h> -#include <perf/cpumap.h> +#include <internal/cpumap.h> #include "debug.h" struct machine; -static int process_event_mask(struct perf_tool *tool __maybe_unused, +static int process_event_mask(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) { struct perf_record_cpu_map *map_event = &event->cpu_map; - struct perf_record_record_cpu_map *mask; struct perf_record_cpu_map_data *data; struct perf_cpu_map *map; - int i; + unsigned int long_size; data = &map_event->data; TEST_ASSERT_VAL("wrong type", data->type == PERF_CPU_MAP__MASK); - mask = (struct perf_record_record_cpu_map *)data->data; + long_size = data->mask32_data.long_size; - TEST_ASSERT_VAL("wrong nr", mask->nr == 1); + TEST_ASSERT_VAL("wrong long_size", long_size == 4 || long_size == 8); - for (i = 0; i < 20; i++) { - TEST_ASSERT_VAL("wrong cpu", test_bit(i, mask->mask)); - } + TEST_ASSERT_VAL("wrong nr", data->mask32_data.nr == 1); + + TEST_ASSERT_VAL("wrong cpu", perf_record_cpu_map_data__test_bit(0, data)); + TEST_ASSERT_VAL("wrong cpu", !perf_record_cpu_map_data__test_bit(1, data)); + for (int i = 2; i <= 20; i++) + TEST_ASSERT_VAL("wrong cpu", perf_record_cpu_map_data__test_bit(i, data)); map = cpu_map__new_data(data); - TEST_ASSERT_VAL("wrong nr", map->nr == 20); + TEST_ASSERT_VAL("wrong nr", perf_cpu_map__nr(map) == 20); - for (i = 0; i < 20; i++) { - TEST_ASSERT_VAL("wrong cpu", map->map[i] == i); - } + TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, 0).cpu == 0); + for (int i = 2; i <= 20; i++) + TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, i - 1).cpu == i); perf_cpu_map__put(map); return 0; } -static int process_event_cpus(struct perf_tool *tool __maybe_unused, +static int process_event_cpus(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) { struct perf_record_cpu_map *map_event = &event->cpu_map; - struct cpu_map_entries *cpus; struct perf_record_cpu_map_data *data; struct perf_cpu_map *map; @@ -59,41 +60,73 @@ static int process_event_cpus(struct perf_tool *tool __maybe_unused, TEST_ASSERT_VAL("wrong type", data->type == PERF_CPU_MAP__CPUS); - cpus = (struct cpu_map_entries *)data->data; + TEST_ASSERT_VAL("wrong nr", data->cpus_data.nr == 2); + TEST_ASSERT_VAL("wrong cpu", data->cpus_data.cpu[0] == 1); + TEST_ASSERT_VAL("wrong cpu", data->cpus_data.cpu[1] == 256); + + map = cpu_map__new_data(data); + TEST_ASSERT_VAL("wrong nr", perf_cpu_map__nr(map) == 2); + TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, 0).cpu == 1); + TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, 1).cpu == 256); + TEST_ASSERT_VAL("wrong refcnt", refcount_read(perf_cpu_map__refcnt(map)) == 1); + perf_cpu_map__put(map); + return 0; +} + +static int process_event_range_cpus(const struct perf_tool *tool __maybe_unused, + union perf_event *event, + struct perf_sample *sample __maybe_unused, + struct machine *machine __maybe_unused) +{ + struct perf_record_cpu_map *map_event = &event->cpu_map; + struct perf_record_cpu_map_data *data; + struct perf_cpu_map *map; + + data = &map_event->data; + + TEST_ASSERT_VAL("wrong type", data->type == PERF_CPU_MAP__RANGE_CPUS); - TEST_ASSERT_VAL("wrong nr", cpus->nr == 2); - TEST_ASSERT_VAL("wrong cpu", cpus->cpu[0] == 1); - TEST_ASSERT_VAL("wrong cpu", cpus->cpu[1] == 256); + TEST_ASSERT_VAL("wrong any_cpu", data->range_cpu_data.any_cpu == 0); + TEST_ASSERT_VAL("wrong start_cpu", data->range_cpu_data.start_cpu == 1); + TEST_ASSERT_VAL("wrong end_cpu", data->range_cpu_data.end_cpu == 256); map = cpu_map__new_data(data); - TEST_ASSERT_VAL("wrong nr", map->nr == 2); - TEST_ASSERT_VAL("wrong cpu", map->map[0] == 1); - TEST_ASSERT_VAL("wrong cpu", map->map[1] == 256); - TEST_ASSERT_VAL("wrong refcnt", refcount_read(&map->refcnt) == 1); + TEST_ASSERT_VAL("wrong nr", perf_cpu_map__nr(map) == 256); + TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, 0).cpu == 1); + TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__max(map).cpu == 256); + TEST_ASSERT_VAL("wrong refcnt", refcount_read(perf_cpu_map__refcnt(map)) == 1); perf_cpu_map__put(map); return 0; } -int test__cpu_map_synthesize(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__cpu_map_synthesize(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct perf_cpu_map *cpus; - /* This one is better stores in mask. */ - cpus = perf_cpu_map__new("0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19"); + /* This one is better stored in a mask. */ + cpus = perf_cpu_map__new("0,2-20"); TEST_ASSERT_VAL("failed to synthesize map", !perf_event__synthesize_cpu_map(NULL, cpus, process_event_mask, NULL)); perf_cpu_map__put(cpus); - /* This one is better stores in cpu values. */ + /* This one is better stored in cpu values. */ cpus = perf_cpu_map__new("1,256"); TEST_ASSERT_VAL("failed to synthesize map", !perf_event__synthesize_cpu_map(NULL, cpus, process_event_cpus, NULL)); perf_cpu_map__put(cpus); + + /* This one is better stored as a range. */ + cpus = perf_cpu_map__new("1-256"); + + TEST_ASSERT_VAL("failed to synthesize map", + !perf_event__synthesize_cpu_map(NULL, cpus, process_event_range_cpus, NULL)); + + perf_cpu_map__put(cpus); return 0; } @@ -106,10 +139,12 @@ static int cpu_map_print(const char *str) return -1; cpu_map__snprint(map, buf, sizeof(buf)); + perf_cpu_map__put(map); + return !strcmp(buf, str); } -int test__cpu_map_print(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__cpu_map_print(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { TEST_ASSERT_VAL("failed to convert map", cpu_map_print("1")); TEST_ASSERT_VAL("failed to convert map", cpu_map_print("1,5")); @@ -121,17 +156,139 @@ int test__cpu_map_print(struct test *test __maybe_unused, int subtest __maybe_un return 0; } -int test__cpu_map_merge(struct test *test __maybe_unused, int subtest __maybe_unused) +static int __test__cpu_map_merge(const char *lhs, const char *rhs, int nr, const char *expected) { - struct perf_cpu_map *a = perf_cpu_map__new("4,2,1"); - struct perf_cpu_map *b = perf_cpu_map__new("4,5,7"); - struct perf_cpu_map *c = perf_cpu_map__merge(a, b); + struct perf_cpu_map *a = perf_cpu_map__new(lhs); + struct perf_cpu_map *b = perf_cpu_map__new(rhs); char buf[100]; - TEST_ASSERT_VAL("failed to merge map: bad nr", c->nr == 5); + perf_cpu_map__merge(&a, b); + TEST_ASSERT_VAL("failed to merge map: bad nr", perf_cpu_map__nr(a) == nr); + cpu_map__snprint(a, buf, sizeof(buf)); + TEST_ASSERT_VAL("failed to merge map: bad result", !strcmp(buf, expected)); + perf_cpu_map__put(b); + + /* + * If 'b' is a superset of 'a', 'a' points to the same map with the + * map 'b'. In this case, the owner 'b' has released the resource above + * but 'a' still keeps the ownership, the reference counter should be 1. + */ + TEST_ASSERT_VAL("unexpected refcnt: bad result", + refcount_read(perf_cpu_map__refcnt(a)) == 1); + + perf_cpu_map__put(a); + return 0; +} + +static int test__cpu_map_merge(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int ret; + + ret = __test__cpu_map_merge("4,2,1", "4,5,7", 5, "1-2,4-5,7"); + if (ret) + return ret; + ret = __test__cpu_map_merge("1-8", "6-9", 9, "1-9"); + if (ret) + return ret; + ret = __test__cpu_map_merge("1-8,12-20", "6-9,15", 18, "1-9,12-20"); + if (ret) + return ret; + ret = __test__cpu_map_merge("4,2,1", "1", 3, "1-2,4"); + if (ret) + return ret; + ret = __test__cpu_map_merge("1", "4,2,1", 3, "1-2,4"); + if (ret) + return ret; + ret = __test__cpu_map_merge("1", "1", 1, "1"); + return ret; +} + +static int __test__cpu_map_intersect(const char *lhs, const char *rhs, int nr, const char *expected) +{ + struct perf_cpu_map *a = perf_cpu_map__new(lhs); + struct perf_cpu_map *b = perf_cpu_map__new(rhs); + struct perf_cpu_map *c = perf_cpu_map__intersect(a, b); + char buf[100]; + + TEST_ASSERT_EQUAL("failed to intersect map: bad nr", perf_cpu_map__nr(c), nr); cpu_map__snprint(c, buf, sizeof(buf)); - TEST_ASSERT_VAL("failed to merge map: bad result", !strcmp(buf, "1-2,4-5,7")); + TEST_ASSERT_VAL("failed to intersect map: bad result", !strcmp(buf, expected)); + perf_cpu_map__put(a); perf_cpu_map__put(b); perf_cpu_map__put(c); return 0; } + +static int test__cpu_map_intersect(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int ret; + + ret = __test__cpu_map_intersect("4,2,1", "4,5,7", 1, "4"); + if (ret) + return ret; + ret = __test__cpu_map_intersect("1-8", "6-9", 3, "6-8"); + if (ret) + return ret; + ret = __test__cpu_map_intersect("1-8,12-20", "6-9,15", 4, "6-8,15"); + if (ret) + return ret; + ret = __test__cpu_map_intersect("4,2,1", "1", 1, "1"); + if (ret) + return ret; + ret = __test__cpu_map_intersect("1", "4,2,1", 1, "1"); + if (ret) + return ret; + ret = __test__cpu_map_intersect("1", "1", 1, "1"); + return ret; +} + +static int test__cpu_map_equal(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + struct perf_cpu_map *any = perf_cpu_map__new_any_cpu(); + struct perf_cpu_map *one = perf_cpu_map__new("1"); + struct perf_cpu_map *two = perf_cpu_map__new("2"); + struct perf_cpu_map *empty = perf_cpu_map__intersect(one, two); + struct perf_cpu_map *pair = perf_cpu_map__new("1-2"); + struct perf_cpu_map *tmp; + struct perf_cpu_map **maps[] = {&empty, &any, &one, &two, &pair}; + + for (size_t i = 0; i < ARRAY_SIZE(maps); i++) { + /* Maps equal themself. */ + TEST_ASSERT_VAL("equal", perf_cpu_map__equal(*maps[i], *maps[i])); + for (size_t j = 0; j < ARRAY_SIZE(maps); j++) { + /* Maps dont't equal each other. */ + if (i == j) + continue; + TEST_ASSERT_VAL("not equal", !perf_cpu_map__equal(*maps[i], *maps[j])); + } + } + + /* Maps equal made maps. */ + perf_cpu_map__merge(&two, one); + TEST_ASSERT_VAL("pair", perf_cpu_map__equal(pair, two)); + + tmp = perf_cpu_map__intersect(pair, one); + TEST_ASSERT_VAL("one", perf_cpu_map__equal(one, tmp)); + perf_cpu_map__put(tmp); + + for (size_t i = 0; i < ARRAY_SIZE(maps); i++) + perf_cpu_map__put(*maps[i]); + + return TEST_OK; +} + +static struct test_case tests__cpu_map[] = { + TEST_CASE("Synthesize cpu map", cpu_map_synthesize), + TEST_CASE("Print cpu map", cpu_map_print), + TEST_CASE("Merge cpu map", cpu_map_merge), + TEST_CASE("Intersect cpu map", cpu_map_intersect), + TEST_CASE("Equal cpu map", cpu_map_equal), + { .name = NULL, } +}; + +struct test_suite suite__cpu_map = { + .desc = "CPU map", + .test_cases = tests__cpu_map, +}; diff --git a/tools/perf/tests/demangle-java-test.c b/tools/perf/tests/demangle-java-test.c index 8f3b90832fb0..0fb3e5a4a0ed 100644 --- a/tools/perf/tests/demangle-java-test.c +++ b/tools/perf/tests/demangle-java-test.c @@ -2,12 +2,12 @@ #include <string.h> #include <stdlib.h> #include <stdio.h> -#include "tests.h" -#include "session.h" +#include <linux/kernel.h> #include "debug.h" -#include "demangle-java.h" +#include "symbol.h" +#include "tests.h" -int test__demangle_java(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__demangle_java(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int ret = TEST_OK; char *buf = NULL; @@ -17,19 +17,24 @@ int test__demangle_java(struct test *test __maybe_unused, int subtest __maybe_un const char *mangled, *demangled; } test_cases[] = { { "Ljava/lang/StringLatin1;equals([B[B)Z", - "boolean java.lang.StringLatin1.equals(byte[], byte[])" }, + "java.lang.StringLatin1.equals(byte[], byte[])" }, { "Ljava/util/zip/ZipUtils;CENSIZ([BI)J", - "long java.util.zip.ZipUtils.CENSIZ(byte[], int)" }, + "java.util.zip.ZipUtils.CENSIZ(byte[], int)" }, { "Ljava/util/regex/Pattern$BmpCharProperty;match(Ljava/util/regex/Matcher;ILjava/lang/CharSequence;)Z", - "boolean java.util.regex.Pattern$BmpCharProperty.match(java.util.regex.Matcher, int, java.lang.CharSequence)" }, + "java.util.regex.Pattern$BmpCharProperty.match(java.util.regex.Matcher, int, java.lang.CharSequence)" }, { "Ljava/lang/AbstractStringBuilder;appendChars(Ljava/lang/String;II)V", - "void java.lang.AbstractStringBuilder.appendChars(java.lang.String, int, int)" }, + "java.lang.AbstractStringBuilder.appendChars(java.lang.String, int, int)" }, { "Ljava/lang/Object;<init>()V", - "void java.lang.Object<init>()" }, + "java.lang.Object<init>()" }, }; - for (i = 0; i < sizeof(test_cases) / sizeof(test_cases[0]); i++) { - buf = java_demangle_sym(test_cases[i].mangled, 0); + for (i = 0; i < ARRAY_SIZE(test_cases); i++) { + buf = dso__demangle_sym(/*dso=*/NULL, /*kmodule=*/0, test_cases[i].mangled); + if (!buf) { + pr_debug("FAILED to demangle: \"%s\"\n \"%s\"\n", test_cases[i].mangled, + test_cases[i].demangled); + continue; + } if (strcmp(buf, test_cases[i].demangled)) { pr_debug("FAILED: %s: %s != %s\n", test_cases[i].mangled, buf, test_cases[i].demangled); @@ -40,3 +45,5 @@ int test__demangle_java(struct test *test __maybe_unused, int subtest __maybe_un return ret; } + +DEFINE_SUITE("Demangle Java", demangle_java); diff --git a/tools/perf/tests/demangle-ocaml-test.c b/tools/perf/tests/demangle-ocaml-test.c new file mode 100644 index 000000000000..612c788b7e0d --- /dev/null +++ b/tools/perf/tests/demangle-ocaml-test.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include "debug.h" +#include "symbol.h" +#include "tests.h" + +static int test__demangle_ocaml(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + int ret = TEST_OK; + char *buf = NULL; + size_t i; + + struct { + const char *mangled, *demangled; + } test_cases[] = { + { "main", + NULL }, + { "camlStdlib__array__map_154", + "Stdlib.array.map_154" }, + { "camlStdlib__anon_fn$5bstdlib$2eml$3a334$2c0$2d$2d54$5d_1453", + "Stdlib.anon_fn[stdlib.ml:334,0--54]_1453" }, + { "camlStdlib__bytes__$2b$2b_2205", + "Stdlib.bytes.++_2205" }, + }; + + for (i = 0; i < ARRAY_SIZE(test_cases); i++) { + buf = dso__demangle_sym(/*dso=*/NULL, /*kmodule=*/0, test_cases[i].mangled); + if ((buf == NULL && test_cases[i].demangled != NULL) + || (buf != NULL && test_cases[i].demangled == NULL) + || (buf != NULL && strcmp(buf, test_cases[i].demangled))) { + pr_debug("FAILED: %s: %s != %s\n", test_cases[i].mangled, + buf == NULL ? "(null)" : buf, + test_cases[i].demangled == NULL ? "(null)" : test_cases[i].demangled); + ret = TEST_FAIL; + } + free(buf); + } + + return ret; +} + +DEFINE_SUITE("Demangle OCaml", demangle_ocaml); diff --git a/tools/perf/tests/demangle-rust-v0-test.c b/tools/perf/tests/demangle-rust-v0-test.c new file mode 100644 index 000000000000..904f966c65d7 --- /dev/null +++ b/tools/perf/tests/demangle-rust-v0-test.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT +#include "tests.h" +#include "debug.h" +#include "symbol.h" +#include <linux/kernel.h> +#include <stdlib.h> +#include <string.h> + +static int test__demangle_rust(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + int ret = TEST_OK; + char *buf = NULL; + size_t i; + + struct { + const char *mangled, *demangled; + } test_cases[] = { + { "_RNvMsr_NtCs3ssYzQotkvD_3std4pathNtB5_7PathBuf3newCs15kBYyAo9fc_7mycrate", + "<std::path::PathBuf>::new" }, + { "_RNvCs15kBYyAo9fc_7mycrate7example", + "mycrate::example" }, + { "_RNvMs_Cs4Cv8Wi1oAIB_7mycrateNtB4_7Example3foo", + "<mycrate::Example>::foo" }, + { "_RNvXCs15kBYyAo9fc_7mycrateNtB2_7ExampleNtB2_5Trait3foo", + "<mycrate::Example as mycrate::Trait>::foo" }, + { "_RNvMCs7qp2U7fqm6G_7mycrateNtB2_7Example3foo", + "<mycrate::Example>::foo" }, + { "_RNvMs_Cs7qp2U7fqm6G_7mycrateNtB4_7Example3bar", + "<mycrate::Example>::bar" }, + { "_RNvYNtCs15kBYyAo9fc_7mycrate7ExampleNtB4_5Trait7exampleB4_", + "<mycrate::Example as mycrate::Trait>::example" }, + { "_RNCNvCsgStHSCytQ6I_7mycrate4main0B3_", + "mycrate::main::{closure#0}" }, + { "_RNCNvCsgStHSCytQ6I_7mycrate4mains_0B3_", + "mycrate::main::{closure#1}" }, + { "_RINvCsgStHSCytQ6I_7mycrate7examplelKj1_EB2_", + "mycrate::example::<i32, 1>" }, + { "_RINvCs7qp2U7fqm6G_7mycrate7exampleFG0_RL1_hRL0_tEuEB2_", + "mycrate::example::<for<'a, 'b> fn(&'a u8, &'b u16)>", + }, + { "_RINvCs7qp2U7fqm6G_7mycrate7exampleKy12345678_EB2_", + "mycrate::example::<305419896>" }, + { "_RNvNvMCsd9PVOYlP1UU_7mycrateINtB4_7ExamplepKpE3foo14EXAMPLE_STATIC", + "<mycrate::Example<_, _>>::foo::EXAMPLE_STATIC", + }, + { "_RINvCs7qp2U7fqm6G_7mycrate7exampleAtj8_EB2_", + "mycrate::example::<[u16; 8]>" }, + { "_RINvCs7qp2U7fqm6G_7mycrate7exampleNtB2_7ExampleBw_EB2_", + "mycrate::example::<mycrate::Example, mycrate::Example>" }, + { "_RINvMsY_NtCseXNvpPnDBDp_3std4pathNtB6_4Path3neweECs7qp2U7fqm6G_7mycrate", + "<std::path::Path>::new::<str>" }, + { "_RNvNvNvCs7qp2U7fqm6G_7mycrate7EXAMPLE7___getit5___KEY", + "mycrate::EXAMPLE::__getit::__KEY" }, + }; + + for (i = 0; i < ARRAY_SIZE(test_cases); i++) { + buf = dso__demangle_sym(/*dso=*/NULL, /*kmodule=*/0, test_cases[i].mangled); + if (!buf) { + pr_debug("FAILED to demangle: \"%s\"\n \"%s\"\n", test_cases[i].mangled, + test_cases[i].demangled); + continue; + } + if (strcmp(buf, test_cases[i].demangled)) { + pr_debug("FAILED: %s: %s != %s\n", test_cases[i].mangled, + buf, test_cases[i].demangled); + ret = TEST_FAIL; + } + free(buf); + } + + return ret; +} + +DEFINE_SUITE("Demangle Rust", demangle_rust); diff --git a/tools/perf/tests/dlfilter-test.c b/tools/perf/tests/dlfilter-test.c new file mode 100644 index 000000000000..54f59d1246bc --- /dev/null +++ b/tools/perf/tests/dlfilter-test.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test dlfilter C API. A perf.data file is synthesized and then processed + * by perf script with dlfilters named dlfilter-test-api-v*.so. Also a C file + * is compiled to provide a dso to match the synthesized perf.data file. + */ + +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/perf_event.h> +#include <internal/lib.h> +#include <subcmd/exec-cmd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <libgen.h> +#include <string.h> +#include <errno.h> +#include "debug.h" +#include "tool.h" +#include "event.h" +#include "header.h" +#include "machine.h" +#include "dso.h" +#include "map.h" +#include "symbol.h" +#include "synthetic-events.h" +#include "util.h" +#include "archinsn.h" +#include "dlfilter.h" +#include "tests.h" +#include "util/sample.h" + +#define MAP_START 0x400000 + +#define DLFILTER_TEST_NAME_MAX 128 + +struct test_data { + struct perf_tool tool; + struct machine *machine; + int fd; + u64 foo; + u64 bar; + u64 ip; + u64 addr; + char name[DLFILTER_TEST_NAME_MAX]; + char desc[DLFILTER_TEST_NAME_MAX]; + char perf[PATH_MAX]; + char perf_data_file_name[PATH_MAX]; + char c_file_name[PATH_MAX]; + char prog_file_name[PATH_MAX]; + char dlfilters[PATH_MAX]; +}; + +static int test_result(const char *msg, int ret) +{ + pr_debug("%s\n", msg); + return ret; +} + +static int process(const struct perf_tool *tool, union perf_event *event, + struct perf_sample *sample __maybe_unused, + struct machine *machine __maybe_unused) +{ + struct test_data *td = container_of(tool, struct test_data, tool); + int fd = td->fd; + + if (writen(fd, event, event->header.size) != event->header.size) + return -1; + + return 0; +} + +#define MAXCMD 4096 +#define REDIRECT_TO_DEV_NULL " >/dev/null 2>&1" + +static __printf(1, 2) int system_cmd(const char *fmt, ...) +{ + char cmd[MAXCMD + sizeof(REDIRECT_TO_DEV_NULL)]; + int ret; + + va_list args; + + va_start(args, fmt); + ret = vsnprintf(cmd, MAXCMD, fmt, args); + va_end(args); + + if (ret <= 0 || ret >= MAXCMD) + return -1; + + if (verbose <= 0) + strcat(cmd, REDIRECT_TO_DEV_NULL); + + pr_debug("Command: %s\n", cmd); + ret = system(cmd); + if (ret) + pr_debug("Failed with return value %d\n", ret); + + return ret; +} + +static bool have_gcc(void) +{ + pr_debug("Checking for gcc\n"); + return !system_cmd("gcc --version"); +} + +static int write_attr(struct test_data *td, u64 sample_type, u64 *id) +{ + struct perf_event_attr attr = { + .size = sizeof(attr), + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS, + .sample_type = sample_type, + .sample_period = 1, + }; + + return perf_event__synthesize_attr(&td->tool, &attr, 1, id, process); +} + +static int write_comm(int fd, pid_t pid, pid_t tid, const char *comm_str) +{ + struct perf_record_comm comm; + ssize_t sz = sizeof(comm); + + comm.header.type = PERF_RECORD_COMM; + comm.header.misc = PERF_RECORD_MISC_USER; + comm.header.size = sz; + + comm.pid = pid; + comm.tid = tid; + strncpy(comm.comm, comm_str, 16); + + if (writen(fd, &comm, sz) != sz) { + pr_debug("%s failed\n", __func__); + return -1; + } + + return 0; +} + +static int write_mmap(int fd, pid_t pid, pid_t tid, u64 start, u64 len, u64 pgoff, + const char *filename) +{ + char buf[PERF_SAMPLE_MAX_SIZE]; + struct perf_record_mmap *mmap = (struct perf_record_mmap *)buf; + size_t fsz = roundup(strlen(filename) + 1, 8); + ssize_t sz = sizeof(*mmap) - sizeof(mmap->filename) + fsz; + + mmap->header.type = PERF_RECORD_MMAP; + mmap->header.misc = PERF_RECORD_MISC_USER; + mmap->header.size = sz; + + mmap->pid = pid; + mmap->tid = tid; + mmap->start = start; + mmap->len = len; + mmap->pgoff = pgoff; + strncpy(mmap->filename, filename, sizeof(mmap->filename)); + + if (writen(fd, mmap, sz) != sz) { + pr_debug("%s failed\n", __func__); + return -1; + } + + return 0; +} + +static int write_sample(struct test_data *td, u64 sample_type, u64 id, pid_t pid, pid_t tid) +{ + char buf[PERF_SAMPLE_MAX_SIZE]; + union perf_event *event = (union perf_event *)buf; + struct perf_sample sample = { + .ip = td->ip, + .addr = td->addr, + .id = id, + .time = 1234567890, + .cpu = 31, + .pid = pid, + .tid = tid, + .period = 543212345, + .stream_id = 101, + }; + int err; + + event->header.type = PERF_RECORD_SAMPLE; + event->header.misc = PERF_RECORD_MISC_USER; + event->header.size = perf_event__sample_event_size(&sample, sample_type, 0); + err = perf_event__synthesize_sample(event, sample_type, 0, &sample); + if (err) + return test_result("perf_event__synthesize_sample() failed", TEST_FAIL); + + err = process(&td->tool, event, &sample, td->machine); + if (err) + return test_result("Failed to write sample", TEST_FAIL); + + return TEST_OK; +} + +static void close_fd(int fd) +{ + if (fd >= 0) + close(fd); +} + +static const char *prog = "int bar(){};int foo(){bar();};int main(){foo();return 0;}"; + +static int write_prog(char *file_name) +{ + int fd = creat(file_name, 0644); + ssize_t n = strlen(prog); + bool err = fd < 0 || writen(fd, prog, n) != n; + + close_fd(fd); + return err ? -1 : 0; +} + +static int get_dlfilters_path(const char *name, char *buf, size_t sz) +{ + char perf[PATH_MAX]; + char path[PATH_MAX]; + char *perf_path; + char *exec_path; + + perf_exe(perf, sizeof(perf)); + perf_path = dirname(perf); + snprintf(path, sizeof(path), "%s/dlfilters/%s", perf_path, name); + if (access(path, R_OK)) { + exec_path = get_argv_exec_path(); + if (!exec_path) + return -1; + snprintf(path, sizeof(path), "%s/dlfilters/%s", exec_path, name); + free(exec_path); + if (access(path, R_OK)) + return -1; + } + strlcpy(buf, dirname(path), sz); + return 0; +} + +static int check_filter_desc(struct test_data *td) +{ + char *long_desc = NULL; + char *desc = NULL; + int ret; + + if (get_filter_desc(td->dlfilters, td->name, &desc, &long_desc) && + long_desc && !strcmp(long_desc, "Filter used by the 'dlfilter C API' perf test") && + desc && !strcmp(desc, td->desc)) + ret = 0; + else + ret = -1; + + free(desc); + free(long_desc); + return ret; +} + +static int get_ip_addr(struct test_data *td) +{ + struct map *map; + struct symbol *sym; + + map = dso__new_map(td->prog_file_name); + if (!map) + return -1; + + sym = map__find_symbol_by_name(map, "foo"); + if (sym) + td->foo = sym->start; + + sym = map__find_symbol_by_name(map, "bar"); + if (sym) + td->bar = sym->start; + + map__put(map); + + td->ip = MAP_START + td->foo; + td->addr = MAP_START + td->bar; + + return td->foo && td->bar ? 0 : -1; +} + +static int do_run_perf_script(struct test_data *td, int do_early) +{ + return system_cmd("%s script -i %s " + "--dlfilter %s/%s " + "--dlarg first " + "--dlarg %d " + "--dlarg %" PRIu64 " " + "--dlarg %" PRIu64 " " + "--dlarg %d " + "--dlarg last", + td->perf, td->perf_data_file_name, td->dlfilters, + td->name, verbose, td->ip, td->addr, do_early); +} + +static int run_perf_script(struct test_data *td) +{ + int do_early; + int err; + + for (do_early = 0; do_early < 3; do_early++) { + err = do_run_perf_script(td, do_early); + if (err) + return err; + } + return 0; +} + +#define TEST_SAMPLE_TYPE (PERF_SAMPLE_IP | PERF_SAMPLE_TID | \ + PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TIME | \ + PERF_SAMPLE_ADDR | PERF_SAMPLE_CPU | \ + PERF_SAMPLE_PERIOD | PERF_SAMPLE_STREAM_ID) + +static int test__dlfilter_test(struct test_data *td) +{ + u64 sample_type = TEST_SAMPLE_TYPE; + pid_t pid = 12345; + pid_t tid = 12346; + u64 id = 99; + int err; + + if (get_dlfilters_path(td->name, td->dlfilters, PATH_MAX)) + return test_result("dlfilters not found", TEST_SKIP); + + if (check_filter_desc(td)) + return test_result("Failed to get expected filter description", TEST_FAIL); + + if (!have_gcc()) + return test_result("gcc not found", TEST_SKIP); + + pr_debug("dlfilters path: %s\n", td->dlfilters); + + if (write_prog(td->c_file_name)) + return test_result("Failed to write test C file", TEST_FAIL); + + if (verbose > 1) + system_cmd("cat %s ; echo", td->c_file_name); + + if (system_cmd("gcc -g -o %s %s", td->prog_file_name, td->c_file_name)) + return TEST_FAIL; + + if (verbose > 2) + system_cmd("objdump -x -dS %s", td->prog_file_name); + + if (get_ip_addr(td)) + return test_result("Failed to find program symbols", TEST_FAIL); + + pr_debug("Creating new host machine structure\n"); + td->machine = machine__new_host(); + td->machine->env = &perf_env; + + td->fd = creat(td->perf_data_file_name, 0644); + if (td->fd < 0) + return test_result("Failed to create test perf.data file", TEST_FAIL); + + err = perf_header__write_pipe(td->fd); + if (err < 0) + return test_result("perf_header__write_pipe() failed", TEST_FAIL); + + err = write_attr(td, sample_type, &id); + if (err) + return test_result("perf_event__synthesize_attr() failed", TEST_FAIL); + + if (write_comm(td->fd, pid, tid, "test-prog")) + return TEST_FAIL; + + if (write_mmap(td->fd, pid, tid, MAP_START, 0x10000, 0, td->prog_file_name)) + return TEST_FAIL; + + if (write_sample(td, sample_type, id, pid, tid) != TEST_OK) + return TEST_FAIL; + + if (verbose > 1) + system_cmd("%s script -i %s -D", td->perf, td->perf_data_file_name); + + err = run_perf_script(td); + if (err) + return TEST_FAIL; + + return TEST_OK; +} + +static void unlink_path(const char *path) +{ + if (*path) + unlink(path); +} + +static void test_data__free(struct test_data *td) +{ + machine__delete(td->machine); + close_fd(td->fd); + if (verbose <= 2) { + unlink_path(td->c_file_name); + unlink_path(td->prog_file_name); + unlink_path(td->perf_data_file_name); + } +} + +static int test__dlfilter_ver(int ver) +{ + struct test_data td = {.fd = -1}; + int pid = getpid(); + int err; + + pr_debug("\n-- Testing version %d API --\n", ver); + + perf_exe(td.perf, sizeof(td.perf)); + + snprintf(td.name, sizeof(td.name), "dlfilter-test-api-v%d.so", ver); + snprintf(td.desc, sizeof(td.desc), "dlfilter to test v%d C API", ver); + snprintf(td.perf_data_file_name, PATH_MAX, "/tmp/dlfilter-test-%u-perf-data", pid); + snprintf(td.c_file_name, PATH_MAX, "/tmp/dlfilter-test-%u-prog.c", pid); + snprintf(td.prog_file_name, PATH_MAX, "/tmp/dlfilter-test-%u-prog", pid); + + err = test__dlfilter_test(&td); + test_data__free(&td); + return err; +} + +static int test__dlfilter(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + int err = test__dlfilter_ver(0); + + if (err) + return err; + /* No test for version 1 */ + return test__dlfilter_ver(2); +} + +DEFINE_SUITE("dlfilter C API", dlfilter); diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 627c1aaf1c9e..a1fff4203b75 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -10,6 +10,7 @@ #include <sys/resource.h> #include <api/fs/fs.h> #include "dso.h" +#include "dsos.h" #include "machine.h" #include "symbol.h" #include "tests.h" @@ -105,15 +106,26 @@ struct test_data_offset offsets[] = { /* move it from util/dso.c for compatibility */ static int dso__data_fd(struct dso *dso, struct machine *machine) { - int fd = dso__data_get_fd(dso, machine); + int fd = -1; - if (fd >= 0) + if (dso__data_get_fd(dso, machine, &fd)) dso__data_put_fd(dso); return fd; } -int test__dso_data(struct test *test __maybe_unused, int subtest __maybe_unused) +static void dsos__delete(struct dsos *dsos) +{ + for (unsigned int i = 0; i < dsos->cnt; i++) { + struct dso *dso = dsos->dsos[i]; + + dso__data_close(dso); + unlink(dso__name(dso)); + } + dsos__exit(dsos); +} + +static int test__dso_data(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct machine machine; struct dso *dso; @@ -123,9 +135,10 @@ int test__dso_data(struct test *test __maybe_unused, int subtest __maybe_unused) TEST_ASSERT_VAL("No test file", file); memset(&machine, 0, sizeof(machine)); + dsos__init(&machine.dsos); - dso = dso__new((const char *)file); - + dso = dso__new(file); + TEST_ASSERT_VAL("Failed to add dso", !dsos__add(&machine.dsos, dso)); TEST_ASSERT_VAL("Failed to access to dso", dso__data_fd(dso, &machine) >= 0); @@ -170,6 +183,7 @@ int test__dso_data(struct test *test __maybe_unused, int subtest __maybe_unused) } dso__put(dso); + dsos__delete(&machine.dsos); unlink(file); return 0; } @@ -199,40 +213,24 @@ static long open_files_cnt(void) return nr - 1; } -static struct dso **dsos; - -static int dsos__create(int cnt, int size) +static int dsos__create(int cnt, int size, struct dsos *dsos) { int i; - dsos = malloc(sizeof(*dsos) * cnt); - TEST_ASSERT_VAL("failed to alloc dsos array", dsos); + dsos__init(dsos); for (i = 0; i < cnt; i++) { - char *file; + struct dso *dso; + char *file = test_file(size); - file = test_file(size); TEST_ASSERT_VAL("failed to get dso file", file); - - dsos[i] = dso__new(file); - TEST_ASSERT_VAL("failed to get dso", dsos[i]); - } - - return 0; -} - -static void dsos__delete(int cnt) -{ - int i; - - for (i = 0; i < cnt; i++) { - struct dso *dso = dsos[i]; - - unlink(dso->name); + dso = dso__new(file); + TEST_ASSERT_VAL("failed to get dso", dso); + TEST_ASSERT_VAL("failed to add dso", !dsos__add(dsos, dso)); dso__put(dso); } - free(dsos); + return 0; } static int set_fd_limit(int n) @@ -248,7 +246,7 @@ static int set_fd_limit(int n) return setrlimit(RLIMIT_NOFILE, &rlim); } -int test__dso_data_cache(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__dso_data_cache(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct machine machine; long nr_end, nr = open_files_cnt(); @@ -266,10 +264,10 @@ int test__dso_data_cache(struct test *test __maybe_unused, int subtest __maybe_u /* and this is now our dso open FDs limit */ dso_cnt = limit / 2; TEST_ASSERT_VAL("failed to create dsos\n", - !dsos__create(dso_cnt, TEST_FILE_SIZE)); + !dsos__create(dso_cnt, TEST_FILE_SIZE, &machine.dsos)); for (i = 0; i < (dso_cnt - 1); i++) { - struct dso *dso = dsos[i]; + struct dso *dso = machine.dsos.dsos[i]; /* * Open dsos via dso__data_fd(), it opens the data @@ -289,17 +287,17 @@ int test__dso_data_cache(struct test *test __maybe_unused, int subtest __maybe_u } /* verify the first one is already open */ - TEST_ASSERT_VAL("dsos[0] is not open", dsos[0]->data.fd != -1); + TEST_ASSERT_VAL("dsos[0] is not open", dso__data(machine.dsos.dsos[0])->fd != -1); /* open +1 dso to reach the allowed limit */ - fd = dso__data_fd(dsos[i], &machine); + fd = dso__data_fd(machine.dsos.dsos[i], &machine); TEST_ASSERT_VAL("failed to get fd", fd > 0); /* should force the first one to be closed */ - TEST_ASSERT_VAL("failed to close dsos[0]", dsos[0]->data.fd == -1); + TEST_ASSERT_VAL("failed to close dsos[0]", dso__data(machine.dsos.dsos[0])->fd == -1); /* cleanup everything */ - dsos__delete(dso_cnt); + dsos__delete(&machine.dsos); /* Make sure we did not leak any file descriptor. */ nr_end = open_files_cnt(); @@ -308,15 +306,25 @@ int test__dso_data_cache(struct test *test __maybe_unused, int subtest __maybe_u return 0; } -int test__dso_data_reopen(struct test *test __maybe_unused, int subtest __maybe_unused) +static long new_limit(int count) +{ + int fd = open("/dev/null", O_RDONLY); + long ret = fd; + if (count > 0) + ret = new_limit(--count); + close(fd); + return ret; +} + +static int test__dso_data_reopen(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct machine machine; - long nr_end, nr = open_files_cnt(); + long nr_end, nr = open_files_cnt(), lim = new_limit(3); int fd, fd_extra; -#define dso_0 (dsos[0]) -#define dso_1 (dsos[1]) -#define dso_2 (dsos[2]) +#define dso_0 (machine.dsos.dsos[0]) +#define dso_1 (machine.dsos.dsos[1]) +#define dso_2 (machine.dsos.dsos[2]) /* Rest the internal dso open counter limit. */ reset_fd_limit(); @@ -334,9 +342,10 @@ int test__dso_data_reopen(struct test *test __maybe_unused, int subtest __maybe_ /* Make sure we are able to open 3 fds anyway */ TEST_ASSERT_VAL("failed to set file limit", - !set_fd_limit((nr + 3))); + !set_fd_limit((lim))); - TEST_ASSERT_VAL("failed to create dsos\n", !dsos__create(3, TEST_FILE_SIZE)); + TEST_ASSERT_VAL("failed to create dsos\n", + !dsos__create(3, TEST_FILE_SIZE, &machine.dsos)); /* open dso_0 */ fd = dso__data_fd(dso_0, &machine); @@ -361,7 +370,7 @@ int test__dso_data_reopen(struct test *test __maybe_unused, int subtest __maybe_ * dso_0 should get closed, because we reached * the file descriptor limit */ - TEST_ASSERT_VAL("failed to close dso_0", dso_0->data.fd == -1); + TEST_ASSERT_VAL("failed to close dso_0", dso__data(dso_0)->fd == -1); /* open dso_0 */ fd = dso__data_fd(dso_0, &machine); @@ -371,11 +380,11 @@ int test__dso_data_reopen(struct test *test __maybe_unused, int subtest __maybe_ * dso_1 should get closed, because we reached * the file descriptor limit */ - TEST_ASSERT_VAL("failed to close dso_1", dso_1->data.fd == -1); + TEST_ASSERT_VAL("failed to close dso_1", dso__data(dso_1)->fd == -1); /* cleanup everything */ close(fd_extra); - dsos__delete(3); + dsos__delete(&machine.dsos); /* Make sure we did not leak any file descriptor. */ nr_end = open_files_cnt(); @@ -383,3 +392,16 @@ int test__dso_data_reopen(struct test *test __maybe_unused, int subtest __maybe_ TEST_ASSERT_VAL("failed leaking files", nr == nr_end); return 0; } + + +static struct test_case tests__dso_data[] = { + TEST_CASE("read", dso_data), + TEST_CASE("cache", dso_data_cache), + TEST_CASE("reopen", dso_data_reopen), + { .name = NULL, } +}; + +struct test_suite suite__dso_data = { + .desc = "DSO data tests", + .test_cases = tests__dso_data, +}; diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c index 2491d167bf76..525c46b7971a 100644 --- a/tools/perf/tests/dwarf-unwind.c +++ b/tools/perf/tests/dwarf-unwind.c @@ -15,32 +15,26 @@ #include "symbol.h" #include "thread.h" #include "callchain.h" -#include "util/synthetic-events.h" - -#if defined (__x86_64__) || defined (__i386__) || defined (__powerpc__) -#include "arch-tests.h" -#endif /* For bsearch. We try to unwind functions in shared object. */ #include <stdlib.h> -static int mmap_handler(struct perf_tool *tool __maybe_unused, - union perf_event *event, - struct perf_sample *sample, - struct machine *machine) -{ - return machine__process_mmap2_event(machine, event, sample); -} - -static int init_live_machine(struct machine *machine) -{ - union perf_event event; - pid_t pid = getpid(); - - memset(&event, 0, sizeof(event)); - return perf_event__synthesize_mmap_events(NULL, &event, pid, pid, - mmap_handler, machine, true); -} +/* + * The test will assert frames are on the stack but tail call optimizations lose + * the frame of the caller. Clang can disable this optimization on a called + * function but GCC currently (11/2020) lacks this attribute. The barrier is + * used to inhibit tail calls in these cases. + */ +#ifdef __has_attribute +#if __has_attribute(disable_tail_calls) +#define NO_TAIL_CALL_ATTRIBUTE __attribute__((disable_tail_calls)) +#define NO_TAIL_CALL_BARRIER +#endif +#endif +#ifndef NO_TAIL_CALL_ATTRIBUTE +#define NO_TAIL_CALL_ATTRIBUTE +#define NO_TAIL_CALL_BARRIER __asm__ __volatile__("" : : : "memory"); +#endif /* * We need to keep these functions global, despite the @@ -54,6 +48,7 @@ int test_dwarf_unwind__compare(void *p1, void *p2); int test_dwarf_unwind__krava_3(struct thread *thread); int test_dwarf_unwind__krava_2(struct thread *thread); int test_dwarf_unwind__krava_1(struct thread *thread); +int test__dwarf_unwind(struct test_suite *test, int subtest); #define MAX_STACK 8 @@ -95,21 +90,20 @@ static int unwind_entry(struct unwind_entry *entry, void *arg) return strcmp((const char *) symbol, funcs[idx]); } -__no_tail_call noinline int test_dwarf_unwind__thread(struct thread *thread) +NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__thread(struct thread *thread) { struct perf_sample sample; unsigned long cnt = 0; int err = -1; - memset(&sample, 0, sizeof(sample)); - + perf_sample__init(&sample, /*all=*/true); if (test__arch_unwind_sample(&sample, thread)) { pr_debug("failed to get unwind sample\n"); goto out; } err = unwind__get_entries(unwind_entry, &cnt, thread, - &sample, MAX_STACK); + &sample, MAX_STACK, false); if (err) pr_debug("unwind failed\n"); else if (cnt != MAX_STACK) { @@ -120,13 +114,14 @@ __no_tail_call noinline int test_dwarf_unwind__thread(struct thread *thread) out: zfree(&sample.user_stack.data); - zfree(&sample.user_regs.regs); + zfree(&sample.user_regs->regs); + perf_sample__exit(&sample); return err; } static int global_unwind_retval = -INT_MAX; -__no_tail_call noinline int test_dwarf_unwind__compare(void *p1, void *p2) +NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__compare(void *p1, void *p2) { /* Any possible value should be 'thread' */ struct thread *thread = *(struct thread **)p1; @@ -145,7 +140,7 @@ __no_tail_call noinline int test_dwarf_unwind__compare(void *p1, void *p2) return p1 - p2; } -__no_tail_call noinline int test_dwarf_unwind__krava_3(struct thread *thread) +NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__krava_3(struct thread *thread) { struct thread *array[2] = {thread, thread}; void *fp = &bsearch; @@ -164,23 +159,36 @@ __no_tail_call noinline int test_dwarf_unwind__krava_3(struct thread *thread) return global_unwind_retval; } -__no_tail_call noinline int test_dwarf_unwind__krava_2(struct thread *thread) +NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__krava_2(struct thread *thread) { - return test_dwarf_unwind__krava_3(thread); + int ret; + + ret = test_dwarf_unwind__krava_3(thread); + NO_TAIL_CALL_BARRIER; + return ret; } -__no_tail_call noinline int test_dwarf_unwind__krava_1(struct thread *thread) +NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__krava_1(struct thread *thread) { - return test_dwarf_unwind__krava_2(thread); + int ret; + + ret = test_dwarf_unwind__krava_2(thread); + NO_TAIL_CALL_BARRIER; + return ret; } -int test__dwarf_unwind(struct test *test __maybe_unused, int subtest __maybe_unused) +noinline int test__dwarf_unwind(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { struct machine *machine; struct thread *thread; int err = -1; + pid_t pid = getpid(); + + callchain_param.record_mode = CALLCHAIN_DWARF; + dwarf_callchain_users = true; - machine = machine__new_host(); + machine = machine__new_live(/*kernel_maps=*/true, pid); if (!machine) { pr_err("Could not get machine\n"); return -1; @@ -191,18 +199,10 @@ int test__dwarf_unwind(struct test *test __maybe_unused, int subtest __maybe_unu return -1; } - callchain_param.record_mode = CALLCHAIN_DWARF; - dwarf_callchain_users = true; - - if (init_live_machine(machine)) { - pr_err("Could not init machine\n"); - goto out; - } - if (verbose > 1) machine__fprintf(machine, stderr); - thread = machine__find_thread(machine, getpid(), getpid()); + thread = machine__find_thread(machine, pid, pid); if (!thread) { pr_err("Could not get thread\n"); goto out; @@ -212,7 +212,8 @@ int test__dwarf_unwind(struct test *test __maybe_unused, int subtest __maybe_unu thread__put(thread); out: - machine__delete_threads(machine); machine__delete(machine); return err; } + +DEFINE_SUITE("Test dwarf unwind", dwarf_unwind); diff --git a/tools/perf/tests/event-times.c b/tools/perf/tests/event-times.c index db68894a6f40..deefe5003bfc 100644 --- a/tools/perf/tests/event-times.c +++ b/tools/perf/tests/event-times.c @@ -26,13 +26,13 @@ static int attach__enable_on_exec(struct evlist *evlist) pr_debug("attaching to spawned child, enable on exec\n"); - err = perf_evlist__create_maps(evlist, &target); + err = evlist__create_maps(evlist, &target); if (err < 0) { pr_debug("Not enough memory to create thread/cpu maps\n"); return err; } - err = perf_evlist__prepare_workload(evlist, &target, argv, false, NULL); + err = evlist__prepare_workload(evlist, &target, argv, false, NULL); if (err < 0) { pr_debug("Couldn't run the workload!\n"); return err; @@ -47,7 +47,7 @@ static int attach__enable_on_exec(struct evlist *evlist) return err; } - return perf_evlist__start_workload(evlist) == 1 ? TEST_OK : TEST_FAIL; + return evlist__start_workload(evlist) == 1 ? TEST_OK : TEST_FAIL; } static int detach__enable_on_exec(struct evlist *evlist) @@ -126,6 +126,7 @@ static int attach__cpu_disabled(struct evlist *evlist) evsel->core.attr.disabled = 1; err = evsel__open_per_cpu(evsel, cpus, -1); + perf_cpu_map__put(cpus); if (err) { if (err == -EACCES) return TEST_SKIP; @@ -134,7 +135,6 @@ static int attach__cpu_disabled(struct evlist *evlist) return err; } - perf_cpu_map__put(cpus); return evsel__enable(evsel); } @@ -153,10 +153,10 @@ static int attach__cpu_enabled(struct evlist *evlist) } err = evsel__open_per_cpu(evsel, cpus, -1); + perf_cpu_map__put(cpus); if (err == -EACCES) return TEST_SKIP; - perf_cpu_map__put(cpus); return err ? TEST_FAIL : TEST_OK; } @@ -174,7 +174,7 @@ static int test_times(int (attach)(struct evlist *), goto out_err; } - err = parse_events(evlist, "cpu-clock:u", NULL); + err = parse_event(evlist, "cpu-clock:u"); if (err) { pr_debug("failed to parse event cpu-clock:u\n"); goto out_err; @@ -188,6 +188,7 @@ static int test_times(int (attach)(struct evlist *), err = attach(evlist); if (err == TEST_SKIP) { pr_debug(" SKIP : not enough rights\n"); + evlist__delete(evlist); return err; } @@ -216,7 +217,7 @@ out_err: * and checks that enabled and running times * match. */ -int test__event_times(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__event_times(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int err, ret = 0; @@ -239,3 +240,5 @@ int test__event_times(struct test *test __maybe_unused, int subtest __maybe_unus #undef _T return ret; } + +DEFINE_SUITE("Event times", event_times); diff --git a/tools/perf/tests/event_groups.c b/tools/perf/tests/event_groups.c new file mode 100644 index 000000000000..c119ff114948 --- /dev/null +++ b/tools/perf/tests/event_groups.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include "linux/perf_event.h" +#include "tests.h" +#include "debug.h" +#include "pmu.h" +#include "pmus.h" +#include "header.h" +#include "../perf-sys.h" + +/* hw: cycles,instructions sw: context-switch, uncore: [arch dependent] */ +static int types[] = {0, 1, -1}; +static unsigned long configs[] = {0, 3, 0}; +static unsigned long configs_hw[] = {1}; + +#define NR_UNCORE_PMUS 5 + +/* Uncore pmus that support more than 3 counters */ +static struct uncore_pmus { + const char *name; + __u64 config; +} uncore_pmus[NR_UNCORE_PMUS] = { + { "amd_l3", 0x0 }, + { "amd_df", 0x0 }, + { "uncore_imc_0", 0x1 }, /* Intel */ + { "core_imc", 0x318 }, /* PowerPC: core_imc/CPM_STCX_FIN/ */ + { "hv_24x7", 0x22000000003 }, /* PowerPC: hv_24x7/CPM_STCX_FIN/ */ +}; + +static int event_open(int type, unsigned long config, int group_fd) +{ + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(struct perf_event_attr)); + attr.type = type; + attr.size = sizeof(struct perf_event_attr); + attr.config = config; + /* + * When creating an event group, typically the group leader is + * initialized with disabled set to 1 and any child events are + * initialized with disabled set to 0. Despite disabled being 0, + * the child events will not start until the group leader is + * enabled. + */ + attr.disabled = group_fd == -1 ? 1 : 0; + + return sys_perf_event_open(&attr, -1, 0, group_fd, 0); +} + +static int setup_uncore_event(void) +{ + struct perf_pmu *pmu = NULL; + int i, fd; + + while ((pmu = perf_pmus__scan(pmu)) != NULL) { + for (i = 0; i < NR_UNCORE_PMUS; i++) { + if (!strcmp(uncore_pmus[i].name, pmu->name)) { + pr_debug("Using %s for uncore pmu event\n", pmu->name); + types[2] = pmu->type; + configs[2] = uncore_pmus[i].config; + /* + * Check if the chosen uncore pmu event can be + * used in the test. For example, incase of accessing + * hv_24x7 pmu counters, partition should have + * additional permissions. If not, event open will + * fail. So check if the event open succeeds + * before proceeding. + */ + fd = event_open(types[2], configs[2], -1); + if (fd < 0) + return -1; + close(fd); + return 0; + } + } + } + return -1; +} + +static int run_test(int i, int j, int k) +{ + int erroneous = ((((1 << i) | (1 << j) | (1 << k)) & 5) == 5); + int group_fd, sibling_fd1, sibling_fd2; + + group_fd = event_open(types[i], configs[i], -1); + if (group_fd == -1) + return -1; + + sibling_fd1 = event_open(types[j], configs[j], group_fd); + if (sibling_fd1 == -1) { + close(group_fd); + return erroneous ? 0 : -1; + } + + /* + * if all three events (leader and two sibling events) + * are hardware events, use instructions as one of the + * sibling event. There is event constraint in powerpc that + * events using same counter cannot be programmed in a group. + * Since PERF_COUNT_HW_INSTRUCTIONS is a generic hardware + * event and present in all platforms, lets use that. + */ + if (!i && !j && !k) + sibling_fd2 = event_open(types[k], configs_hw[k], group_fd); + else + sibling_fd2 = event_open(types[k], configs[k], group_fd); + if (sibling_fd2 == -1) { + close(sibling_fd1); + close(group_fd); + return erroneous ? 0 : -1; + } + + close(sibling_fd2); + close(sibling_fd1); + close(group_fd); + return erroneous ? -1 : 0; +} + +static int test__event_groups(struct test_suite *text __maybe_unused, int subtest __maybe_unused) +{ + int i, j, k; + int ret; + int r; + + ret = setup_uncore_event(); + if (ret || types[2] == -1) + return TEST_SKIP; + + ret = TEST_OK; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + for (k = 0; k < 3; k++) { + r = run_test(i, j, k); + if (r) + ret = TEST_FAIL; + + /* + * For all three events as HW events, second sibling + * event is picked from configs_hw. So print accordingly + */ + if (!i && !j && !k) + pr_debug("0x%x 0x%lx, 0x%x 0x%lx, 0x%x 0x%lx: %s\n", + types[i], configs[i], types[j], configs[j], + types[k], configs_hw[k], r ? "Fail" : "Pass"); + else + pr_debug("0x%x 0x%lx, 0x%x 0x%lx, 0x%x 0x%lx: %s\n", + types[i], configs[i], types[j], configs[j], + types[k], configs[k], r ? "Fail" : "Pass"); + } + } + } + return ret; +} + +DEFINE_SUITE("Event groups", event_groups); diff --git a/tools/perf/tests/event_update.c b/tools/perf/tests/event_update.c index bdcf032f8516..9301fde11366 100644 --- a/tools/perf/tests/event_update.c +++ b/tools/perf/tests/event_update.c @@ -12,7 +12,7 @@ #include "tests.h" #include "debug.h" -static int process_event_unit(struct perf_tool *tool __maybe_unused, +static int process_event_unit(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) @@ -21,23 +21,20 @@ static int process_event_unit(struct perf_tool *tool __maybe_unused, TEST_ASSERT_VAL("wrong id", ev->id == 123); TEST_ASSERT_VAL("wrong id", ev->type == PERF_EVENT_UPDATE__UNIT); - TEST_ASSERT_VAL("wrong unit", !strcmp(ev->data, "KRAVA")); + TEST_ASSERT_VAL("wrong unit", !strcmp(ev->unit, "KRAVA")); return 0; } -static int process_event_scale(struct perf_tool *tool __maybe_unused, +static int process_event_scale(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) { struct perf_record_event_update *ev = (struct perf_record_event_update *)event; - struct perf_record_event_update_scale *ev_data; - - ev_data = (struct perf_record_event_update_scale *)ev->data; TEST_ASSERT_VAL("wrong id", ev->id == 123); TEST_ASSERT_VAL("wrong id", ev->type == PERF_EVENT_UPDATE__SCALE); - TEST_ASSERT_VAL("wrong scale", ev_data->scale == 0.123); + TEST_ASSERT_VAL("wrong scale", ev->scale.scale == 0.123); return 0; } @@ -46,7 +43,7 @@ struct event_name { const char *name; }; -static int process_event_name(struct perf_tool *tool, +static int process_event_name(const struct perf_tool *tool, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) @@ -56,40 +53,36 @@ static int process_event_name(struct perf_tool *tool, TEST_ASSERT_VAL("wrong id", ev->id == 123); TEST_ASSERT_VAL("wrong id", ev->type == PERF_EVENT_UPDATE__NAME); - TEST_ASSERT_VAL("wrong name", !strcmp(ev->data, tmp->name)); + TEST_ASSERT_VAL("wrong name", !strcmp(ev->name, tmp->name)); return 0; } -static int process_event_cpus(struct perf_tool *tool __maybe_unused, +static int process_event_cpus(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) { struct perf_record_event_update *ev = (struct perf_record_event_update *)event; - struct perf_record_event_update_cpus *ev_data; struct perf_cpu_map *map; - ev_data = (struct perf_record_event_update_cpus *) ev->data; - - map = cpu_map__new_data(&ev_data->cpus); + map = cpu_map__new_data(&ev->cpus.cpus); TEST_ASSERT_VAL("wrong id", ev->id == 123); TEST_ASSERT_VAL("wrong type", ev->type == PERF_EVENT_UPDATE__CPUS); - TEST_ASSERT_VAL("wrong cpus", map->nr == 3); - TEST_ASSERT_VAL("wrong cpus", map->map[0] == 1); - TEST_ASSERT_VAL("wrong cpus", map->map[1] == 2); - TEST_ASSERT_VAL("wrong cpus", map->map[2] == 3); + TEST_ASSERT_VAL("wrong cpus", perf_cpu_map__nr(map) == 3); + TEST_ASSERT_VAL("wrong cpus", perf_cpu_map__cpu(map, 0).cpu == 1); + TEST_ASSERT_VAL("wrong cpus", perf_cpu_map__cpu(map, 1).cpu == 2); + TEST_ASSERT_VAL("wrong cpus", perf_cpu_map__cpu(map, 2).cpu == 3); perf_cpu_map__put(map); return 0; } -int test__event_update(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__event_update(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - struct evlist *evlist; struct evsel *evsel; struct event_name tmp; + struct evlist *evlist = evlist__new_default(); - evlist = perf_evlist__new_default(); TEST_ASSERT_VAL("failed to get evlist", evlist); evsel = evlist__first(evlist); @@ -99,6 +92,7 @@ int test__event_update(struct test *test __maybe_unused, int subtest __maybe_unu perf_evlist__id_add(&evlist->core, &evsel->core, 0, 0, 123); + free((char *)evsel->unit); evsel->unit = strdup("KRAVA"); TEST_ASSERT_VAL("failed to synthesize attr update unit", @@ -109,16 +103,20 @@ int test__event_update(struct test *test __maybe_unused, int subtest __maybe_unu TEST_ASSERT_VAL("failed to synthesize attr update scale", !perf_event__synthesize_event_update_scale(NULL, evsel, process_event_scale)); + perf_tool__init(&tmp.tool, /*ordered_events=*/false); tmp.name = evsel__name(evsel); TEST_ASSERT_VAL("failed to synthesize attr update name", !perf_event__synthesize_event_update_name(&tmp.tool, evsel, process_event_name)); + perf_cpu_map__put(evsel->core.own_cpus); evsel->core.own_cpus = perf_cpu_map__new("1,2,3"); TEST_ASSERT_VAL("failed to synthesize attr update cpus", !perf_event__synthesize_event_update_cpus(&tmp.tool, evsel, process_event_cpus)); - perf_cpu_map__put(evsel->core.own_cpus); + evlist__delete(evlist); return 0; } + +DEFINE_SUITE("Synthesize attr update", event_update); diff --git a/tools/perf/tests/evsel-roundtrip-name.c b/tools/perf/tests/evsel-roundtrip-name.c index f7f3e5b4c180..1922cac13a24 100644 --- a/tools/perf/tests/evsel-roundtrip-name.c +++ b/tools/perf/tests/evsel-roundtrip-name.c @@ -4,107 +4,93 @@ #include "parse-events.h" #include "tests.h" #include "debug.h" -#include <errno.h> #include <linux/kernel.h> static int perf_evsel__roundtrip_cache_name_test(void) { - char name[128]; - int type, op, err = 0, ret = 0, i, idx; - struct evsel *evsel; - struct evlist *evlist = evlist__new(); + int ret = TEST_OK; - 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++) { + for (int type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) { + for (int op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) { /* skip invalid cache type */ if (!evsel__is_cache_op_valid(type, op)) continue; - for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { - __evsel__hw_cache_type_op_res_name(type, op, i, name, sizeof(name)); - err = parse_events(evlist, name, NULL); - if (err) - ret = err; - } - } - } - - idx = 0; - evsel = 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 (!evsel__is_cache_op_valid(type, op)) - continue; + for (int res = 0; res < PERF_COUNT_HW_CACHE_RESULT_MAX; res++) { + char name[128]; + struct evlist *evlist = evlist__new(); + struct evsel *evsel; + int err; - for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { - __evsel__hw_cache_type_op_res_name(type, op, i, name, sizeof(name)); - if (evsel->idx != idx) + if (evlist == NULL) { + pr_debug("Failed to alloc evlist"); + return TEST_FAIL; + } + __evsel__hw_cache_type_op_res_name(type, op, res, + name, sizeof(name)); + + err = parse_event(evlist, name); + if (err) { + pr_debug("Failure to parse cache event '%s' possibly as PMUs don't support it", + name); + evlist__delete(evlist); continue; - - ++idx; - - if (strcmp(evsel__name(evsel), name)) { - pr_debug("%s != %s\n", evsel__name(evsel), name); - ret = -1; } - - evsel = evsel__next(evsel); + evlist__for_each_entry(evlist, evsel) { + if (!evsel__name_is(evsel, name)) { + pr_debug("%s != %s\n", evsel__name(evsel), name); + ret = TEST_FAIL; + } + } + evlist__delete(evlist); } } } - - evlist__delete(evlist); return ret; } -static int __perf_evsel__name_array_test(const char *names[], int nr_names) +static int perf_evsel__name_array_test(const char *const names[], int nr_names) { - int i, err; - struct evsel *evsel; - struct evlist *evlist = evlist__new(); + int ret = TEST_OK; - if (evlist == NULL) - return -ENOMEM; + for (int i = 0; i < nr_names; ++i) { + struct evlist *evlist = evlist__new(); + struct evsel *evsel; + int err; - for (i = 0; i < nr_names; ++i) { - err = parse_events(evlist, names[i], NULL); + if (evlist == NULL) { + pr_debug("Failed to alloc evlist"); + return TEST_FAIL; + } + err = parse_event(evlist, names[i]); if (err) { pr_debug("failed to parse event '%s', err %d\n", names[i], err); - goto out_delete_evlist; + evlist__delete(evlist); + ret = TEST_FAIL; + continue; } - } - - err = 0; - evlist__for_each_entry(evlist, evsel) { - if (strcmp(evsel__name(evsel), names[evsel->idx])) { - --err; - pr_debug("%s != %s\n", evsel__name(evsel), names[evsel->idx]); + evlist__for_each_entry(evlist, evsel) { + if (!evsel__name_is(evsel, names[i])) { + pr_debug("%s != %s\n", evsel__name(evsel), names[i]); + ret = TEST_FAIL; + } } + evlist__delete(evlist); } - -out_delete_evlist: - evlist__delete(evlist); - return err; + return ret; } -#define perf_evsel__name_array_test(names) \ - __perf_evsel__name_array_test(names, ARRAY_SIZE(names)) - -int test__perf_evsel__roundtrip_name_test(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__perf_evsel__roundtrip_name_test(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { - int err = 0, ret = 0; + int err = 0, ret = TEST_OK; - err = perf_evsel__name_array_test(evsel__hw_names); + err = perf_evsel__name_array_test(evsel__hw_names, PERF_COUNT_HW_MAX); if (err) ret = err; - err = __perf_evsel__name_array_test(evsel__sw_names, PERF_COUNT_SW_DUMMY + 1); + err = perf_evsel__name_array_test(evsel__sw_names, PERF_COUNT_SW_DUMMY + 1); if (err) ret = err; @@ -114,3 +100,5 @@ int test__perf_evsel__roundtrip_name_test(struct test *test __maybe_unused, int return ret; } + +DEFINE_SUITE("Roundtrip evsel->name", perf_evsel__roundtrip_name_test); diff --git a/tools/perf/tests/evsel-tp-sched.c b/tools/perf/tests/evsel-tp-sched.c index 0e224a0a55d9..226196fb9677 100644 --- a/tools/perf/tests/evsel-tp-sched.c +++ b/tools/perf/tests/evsel-tp-sched.c @@ -1,12 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/err.h> -#include <traceevent/event-parse.h> +#include <event-parse.h> #include "evsel.h" #include "tests.h" #include "debug.h" -static int perf_evsel__test_field(struct evsel *evsel, const char *name, - int size, bool should_be_signed) +static int evsel__test_field(struct evsel *evsel, const char *name, int size, bool should_be_signed) { struct tep_format_field *field = evsel__field(evsel, name); int is_signed; @@ -33,36 +32,37 @@ static int perf_evsel__test_field(struct evsel *evsel, const char *name, return ret; } -int test__perf_evsel__tp_sched_test(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__perf_evsel__tp_sched_test(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { struct evsel *evsel = evsel__newtp("sched", "sched_switch"); - int ret = 0; + int ret = TEST_OK; if (IS_ERR(evsel)) { pr_debug("evsel__newtp failed with %ld\n", PTR_ERR(evsel)); - return -1; + return PTR_ERR(evsel) == -EACCES ? TEST_SKIP : TEST_FAIL; } - if (perf_evsel__test_field(evsel, "prev_comm", 16, false)) - ret = -1; + if (evsel__test_field(evsel, "prev_comm", 16, false)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "prev_pid", 4, true)) - ret = -1; + if (evsel__test_field(evsel, "prev_pid", 4, true)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "prev_prio", 4, true)) - ret = -1; + if (evsel__test_field(evsel, "prev_prio", 4, true)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "prev_state", sizeof(long), true)) - ret = -1; + if (evsel__test_field(evsel, "prev_state", sizeof(long), true)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "next_comm", 16, false)) - ret = -1; + if (evsel__test_field(evsel, "next_comm", 16, false)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "next_pid", 4, true)) - ret = -1; + if (evsel__test_field(evsel, "next_pid", 4, true)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "next_prio", 4, true)) - ret = -1; + if (evsel__test_field(evsel, "next_prio", 4, true)) + ret = TEST_FAIL; evsel__delete(evsel); @@ -70,21 +70,33 @@ int test__perf_evsel__tp_sched_test(struct test *test __maybe_unused, int subtes if (IS_ERR(evsel)) { pr_debug("evsel__newtp failed with %ld\n", PTR_ERR(evsel)); - return -1; + return TEST_FAIL; } - if (perf_evsel__test_field(evsel, "comm", 16, false)) - ret = -1; + if (evsel__test_field(evsel, "comm", 16, false)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "pid", 4, true)) - ret = -1; + if (evsel__test_field(evsel, "pid", 4, true)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "prio", 4, true)) - ret = -1; + if (evsel__test_field(evsel, "prio", 4, true)) + ret = TEST_FAIL; - if (perf_evsel__test_field(evsel, "target_cpu", 4, true)) - ret = -1; + if (evsel__test_field(evsel, "target_cpu", 4, true)) + ret = TEST_FAIL; evsel__delete(evsel); return ret; } + +static struct test_case tests__perf_evsel__tp_sched_test[] = { + TEST_CASE_REASON("Parse sched tracepoints fields", + perf_evsel__tp_sched_test, + "permissions"), + { .name = NULL, } +}; + +struct test_suite suite__perf_evsel__tp_sched_test = { + .desc = "Parse sched tracepoints fields", + .test_cases = tests__perf_evsel__tp_sched_test, +}; diff --git a/tools/perf/tests/expand-cgroup.c b/tools/perf/tests/expand-cgroup.c new file mode 100644 index 000000000000..31966ff856f8 --- /dev/null +++ b/tools/perf/tests/expand-cgroup.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "tests.h" +#include "debug.h" +#include "evlist.h" +#include "cgroup.h" +#include "rblist.h" +#include "metricgroup.h" +#include "parse-events.h" +#include "pmu-events/pmu-events.h" +#include "pfm.h" +#include <subcmd/parse-options.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static int test_expand_events(struct evlist *evlist, + struct rblist *metric_events) +{ + int i, ret = TEST_FAIL; + int nr_events; + bool was_group_event; + int nr_members; /* for the first evsel only */ + const char cgrp_str[] = "A,B,C"; + const char *cgrp_name[] = { "A", "B", "C" }; + int nr_cgrps = ARRAY_SIZE(cgrp_name); + char **ev_name; + struct evsel *evsel; + + TEST_ASSERT_VAL("evlist is empty", !evlist__empty(evlist)); + + nr_events = evlist->core.nr_entries; + ev_name = calloc(nr_events, sizeof(*ev_name)); + if (ev_name == NULL) { + pr_debug("memory allocation failure\n"); + return TEST_FAIL; + } + i = 0; + evlist__for_each_entry(evlist, evsel) { + ev_name[i] = strdup(evsel->name); + if (ev_name[i] == NULL) { + pr_debug("memory allocation failure\n"); + goto out; + } + i++; + } + /* remember grouping info */ + was_group_event = evsel__is_group_event(evlist__first(evlist)); + nr_members = evlist__first(evlist)->core.nr_members; + + ret = evlist__expand_cgroup(evlist, cgrp_str, metric_events, false); + if (ret < 0) { + pr_debug("failed to expand events for cgroups\n"); + goto out; + } + + ret = TEST_FAIL; + if (evlist->core.nr_entries != nr_events * nr_cgrps) { + pr_debug("event count doesn't match\n"); + goto out; + } + + i = 0; + evlist__for_each_entry(evlist, evsel) { + if (!evsel__name_is(evsel, ev_name[i % nr_events])) { + pr_debug("event name doesn't match:\n"); + pr_debug(" evsel[%d]: %s\n expected: %s\n", + i, evsel->name, ev_name[i % nr_events]); + goto out; + } + if (strcmp(evsel->cgrp->name, cgrp_name[i / nr_events])) { + pr_debug("cgroup name doesn't match:\n"); + pr_debug(" evsel[%d]: %s\n expected: %s\n", + i, evsel->cgrp->name, cgrp_name[i / nr_events]); + goto out; + } + + if ((i % nr_events) == 0) { + if (evsel__is_group_event(evsel) != was_group_event) { + pr_debug("event group doesn't match: got %s, expect %s\n", + evsel__is_group_event(evsel) ? "true" : "false", + was_group_event ? "true" : "false"); + goto out; + } + if (evsel->core.nr_members != nr_members) { + pr_debug("event group member doesn't match: %d vs %d\n", + evsel->core.nr_members, nr_members); + goto out; + } + } + i++; + } + ret = TEST_OK; + +out: for (i = 0; i < nr_events; i++) + free(ev_name[i]); + free(ev_name); + return ret; +} + +static int expand_default_events(void) +{ + int ret; + struct rblist metric_events; + struct evlist *evlist = evlist__new_default(); + + TEST_ASSERT_VAL("failed to get evlist", evlist); + + rblist__init(&metric_events); + ret = test_expand_events(evlist, &metric_events); + evlist__delete(evlist); + return ret; +} + +static int expand_group_events(void) +{ + int ret; + struct evlist *evlist; + struct rblist metric_events; + struct parse_events_error err; + const char event_str[] = "{cycles,instructions}"; + + symbol_conf.event_group = true; + + evlist = evlist__new(); + TEST_ASSERT_VAL("failed to get evlist", evlist); + + parse_events_error__init(&err); + ret = parse_events(evlist, event_str, &err); + if (ret < 0) { + pr_debug("failed to parse event '%s', err %d\n", event_str, ret); + parse_events_error__print(&err, event_str); + goto out; + } + + rblist__init(&metric_events); + ret = test_expand_events(evlist, &metric_events); +out: + parse_events_error__exit(&err); + evlist__delete(evlist); + return ret; +} + +static int expand_libpfm_events(void) +{ + int ret; + struct evlist *evlist; + struct rblist metric_events; + const char event_str[] = "CYCLES"; + struct option opt = { + .value = &evlist, + }; + + symbol_conf.event_group = true; + + evlist = evlist__new(); + TEST_ASSERT_VAL("failed to get evlist", evlist); + + ret = parse_libpfm_events_option(&opt, event_str, 0); + if (ret < 0) { + pr_debug("failed to parse libpfm event '%s', err %d\n", + event_str, ret); + goto out; + } + if (evlist__empty(evlist)) { + pr_debug("libpfm was not enabled\n"); + goto out; + } + + rblist__init(&metric_events); + ret = test_expand_events(evlist, &metric_events); +out: + evlist__delete(evlist); + return ret; +} + +static int expand_metric_events(void) +{ + int ret; + struct evlist *evlist; + struct rblist metric_events; + const char metric_str[] = "CPI"; + const struct pmu_metrics_table *pme_test; + + evlist = evlist__new(); + TEST_ASSERT_VAL("failed to get evlist", evlist); + + rblist__init(&metric_events); + pme_test = find_core_metrics_table("testarch", "testcpu"); + ret = metricgroup__parse_groups_test(evlist, pme_test, metric_str, &metric_events); + if (ret < 0) { + pr_debug("failed to parse '%s' metric\n", metric_str); + goto out; + } + + ret = test_expand_events(evlist, &metric_events); + +out: + metricgroup__rblist_exit(&metric_events); + evlist__delete(evlist); + return ret; +} + +static int test__expand_cgroup_events(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int ret; + + ret = expand_default_events(); + TEST_ASSERT_EQUAL("failed to expand default events", ret, 0); + + ret = expand_group_events(); + TEST_ASSERT_EQUAL("failed to expand event group", ret, 0); + + ret = expand_libpfm_events(); + TEST_ASSERT_EQUAL("failed to expand event group", ret, 0); + + ret = expand_metric_events(); + TEST_ASSERT_EQUAL("failed to expand metric events", ret, 0); + + return ret; +} + +DEFINE_SUITE("Event expansion for cgroups", expand_cgroup_events); diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c index 1cb02ca2b15f..726cf8d4da28 100644 --- a/tools/perf/tests/expr.c +++ b/tools/perf/tests/expr.c @@ -1,79 +1,298 @@ // SPDX-License-Identifier: GPL-2.0 +#include "util/cputopo.h" #include "util/debug.h" #include "util/expr.h" +#include "util/hashmap.h" +#include "util/header.h" +#include "util/smt.h" #include "tests.h" +#include <perf/cpumap.h> +#include <math.h> #include <stdlib.h> #include <string.h> +#include <string2.h> #include <linux/zalloc.h> +static int test_ids_union(void) +{ + struct hashmap *ids1, *ids2; + + /* Empty union. */ + ids1 = ids__new(); + TEST_ASSERT_VAL("ids__new", ids1); + ids2 = ids__new(); + TEST_ASSERT_VAL("ids__new", ids2); + + ids1 = ids__union(ids1, ids2); + TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 0); + + /* Union {foo, bar} against {}. */ + ids2 = ids__new(); + TEST_ASSERT_VAL("ids__new", ids2); + + TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids1, strdup("foo")), 0); + TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids1, strdup("bar")), 0); + + ids1 = ids__union(ids1, ids2); + TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 2); + + /* Union {foo, bar} against {foo}. */ + ids2 = ids__new(); + TEST_ASSERT_VAL("ids__new", ids2); + TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("foo")), 0); + + ids1 = ids__union(ids1, ids2); + TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 2); + + /* Union {foo, bar} against {bar,baz}. */ + ids2 = ids__new(); + TEST_ASSERT_VAL("ids__new", ids2); + TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("bar")), 0); + TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("baz")), 0); + + ids1 = ids__union(ids1, ids2); + TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 3); + + ids__free(ids1); + + return 0; +} + static int test(struct expr_parse_ctx *ctx, const char *e, double val2) { double val; - if (expr__parse(&val, ctx, e, 1)) + if (expr__parse(&val, ctx, e)) TEST_ASSERT_VAL("parse test failed", 0); TEST_ASSERT_VAL("unexpected value", val == val2); return 0; } -int test__expr(struct test *t __maybe_unused, int subtest __maybe_unused) +static int test__expr(struct test_suite *t __maybe_unused, int subtest __maybe_unused) { + struct expr_id_data *val_ptr; const char *p; - double val, *val_ptr; + double val, num_cpus_online, num_cpus, num_cores, num_dies, num_packages; int ret; - struct expr_parse_ctx ctx; - - expr__ctx_init(&ctx); - expr__add_id(&ctx, strdup("FOO"), 1); - expr__add_id(&ctx, strdup("BAR"), 2); - - ret = test(&ctx, "1+1", 2); - ret |= test(&ctx, "FOO+BAR", 3); - ret |= test(&ctx, "(BAR/2)%2", 1); - ret |= test(&ctx, "1 - -4", 5); - ret |= test(&ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5); - ret |= test(&ctx, "1-1 | 1", 1); - ret |= test(&ctx, "1-1 & 1", 0); - ret |= test(&ctx, "min(1,2) + 1", 2); - ret |= test(&ctx, "max(1,2) + 1", 3); - ret |= test(&ctx, "1+1 if 3*4 else 0", 2); - ret |= test(&ctx, "1.1 + 2.1", 3.2); - ret |= test(&ctx, ".1 + 2.", 2.1); - - if (ret) + struct expr_parse_ctx *ctx; + char strcmp_cpuid_buf[256]; + struct perf_cpu cpu = {-1}; + char *cpuid = get_cpuid_allow_env_override(cpu); + char *escaped_cpuid1, *escaped_cpuid2; + + TEST_ASSERT_VAL("get_cpuid", cpuid); + + TEST_ASSERT_EQUAL("ids_union", test_ids_union(), 0); + + ctx = expr__ctx_new(); + TEST_ASSERT_VAL("expr__ctx_new", ctx); + expr__add_id_val(ctx, strdup("FOO"), 1); + expr__add_id_val(ctx, strdup("BAR"), 2); + + ret = test(ctx, "1+1", 2); + ret |= test(ctx, "FOO+BAR", 3); + ret |= test(ctx, "(BAR/2)%2", 1); + ret |= test(ctx, "1 - -4", 5); + ret |= test(ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5); + ret |= test(ctx, "1-1 | 1", 1); + ret |= test(ctx, "1-1 & 1", 0); + ret |= test(ctx, "min(1,2) + 1", 2); + ret |= test(ctx, "max(1,2) + 1", 3); + ret |= test(ctx, "1+1 if 3*4 else 0", 2); + ret |= test(ctx, "100 if 1 else 200 if 1 else 300", 100); + ret |= test(ctx, "100 if 0 else 200 if 1 else 300", 200); + ret |= test(ctx, "100 if 1 else 200 if 0 else 300", 100); + ret |= test(ctx, "100 if 0 else 200 if 0 else 300", 300); + ret |= test(ctx, "1.1 + 2.1", 3.2); + ret |= test(ctx, ".1 + 2.", 2.1); + ret |= test(ctx, "d_ratio(1, 2)", 0.5); + ret |= test(ctx, "d_ratio(2.5, 0)", 0); + ret |= test(ctx, "1.1 < 2.2", 1); + ret |= test(ctx, "2.2 > 1.1", 1); + ret |= test(ctx, "1.1 < 1.1", 0); + ret |= test(ctx, "2.2 > 2.2", 0); + ret |= test(ctx, "2.2 < 1.1", 0); + ret |= test(ctx, "1.1 > 2.2", 0); + ret |= test(ctx, "1.1e10 < 1.1e100", 1); + ret |= test(ctx, "1.1e2 > 1.1e-2", 1); + + if (ret) { + expr__ctx_free(ctx); return ret; + } p = "FOO/0"; - ret = expr__parse(&val, &ctx, p, 1); - TEST_ASSERT_VAL("division by zero", ret == -1); + ret = expr__parse(&val, ctx, p); + TEST_ASSERT_VAL("division by zero", ret == 0); + TEST_ASSERT_VAL("division by zero", isnan(val)); p = "BAR/"; - ret = expr__parse(&val, &ctx, p, 1); + ret = expr__parse(&val, ctx, p); TEST_ASSERT_VAL("missing operand", ret == -1); - expr__ctx_clear(&ctx); - TEST_ASSERT_VAL("find other", - expr__find_other("FOO + BAR + BAZ + BOZO", "FOO", - &ctx, 1) == 0); - TEST_ASSERT_VAL("find other", hashmap__size(&ctx.ids) == 3); - TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BAR", - (void **)&val_ptr)); - TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BAZ", - (void **)&val_ptr)); - TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "BOZO", - (void **)&val_ptr)); - - expr__ctx_clear(&ctx); - TEST_ASSERT_VAL("find other", - expr__find_other("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@", - NULL, &ctx, 3) == 0); - TEST_ASSERT_VAL("find other", hashmap__size(&ctx.ids) == 2); - TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "EVENT1,param=3/", - (void **)&val_ptr)); - TEST_ASSERT_VAL("find other", hashmap__find(&ctx.ids, "EVENT2,param=3/", - (void **)&val_ptr)); - - expr__ctx_clear(&ctx); + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("FOO + BAR + BAZ + BOZO", "FOO", + ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 3); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BAR", &val_ptr)); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BAZ", &val_ptr)); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BOZO", &val_ptr)); - return 0; + expr__ctx_clear(ctx); + ctx->sctx.runtime = 3; + TEST_ASSERT_VAL("find ids", + expr__find_ids("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@", + NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 2); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1,param=3@", &val_ptr)); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT2,param=3@", &val_ptr)); + + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("dash\\-event1 - dash\\-event2", + NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 2); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "dash-event1", &val_ptr)); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "dash-event2", &val_ptr)); + + /* Only EVENT1 or EVENT2 need be measured depending on the value of smt_on. */ + { + bool smton = smt_on(); + bool corewide = core_wide(/*system_wide=*/false, + /*user_requested_cpus=*/false); + + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("EVENT1 if #smt_on else EVENT2", + NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, + smton ? "EVENT1" : "EVENT2", + &val_ptr)); + + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("EVENT1 if #core_wide else EVENT2", + NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, + corewide ? "EVENT1" : "EVENT2", + &val_ptr)); + + } + /* The expression is a constant 1.0 without needing to evaluate EVENT1. */ + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("1.0 if EVENT1 > 100.0 else 1.0", + NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0); + + /* The expression is a constant 0.0 without needing to evaluate EVENT1. */ + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("0 & EVENT1 > 0", NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0); + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("EVENT1 > 0 & 0", NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0); + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("1 & EVENT1 > 0", NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1", &val_ptr)); + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("EVENT1 > 0 & 1", NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1", &val_ptr)); + + /* The expression is a constant 1.0 without needing to evaluate EVENT1. */ + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("1 | EVENT1 > 0", NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0); + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("EVENT1 > 0 | 1", NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0); + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("0 | EVENT1 > 0", NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1", &val_ptr)); + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("find ids", + expr__find_ids("EVENT1 > 0 | 0", NULL, ctx) == 0); + TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1); + TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1", &val_ptr)); + + /* Test toplogy constants appear well ordered. */ + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("#num_cpus_online", + expr__parse(&num_cpus_online, ctx, "#num_cpus_online") == 0); + TEST_ASSERT_VAL("#num_cpus", expr__parse(&num_cpus, ctx, "#num_cpus") == 0); + TEST_ASSERT_VAL("#num_cpus >= #num_cpus_online", num_cpus >= num_cpus_online); + TEST_ASSERT_VAL("#num_cores", expr__parse(&num_cores, ctx, "#num_cores") == 0); + TEST_ASSERT_VAL("#num_cpus >= #num_cores", num_cpus >= num_cores); + TEST_ASSERT_VAL("#num_dies", expr__parse(&num_dies, ctx, "#num_dies") == 0); + TEST_ASSERT_VAL("#num_cores >= #num_dies", num_cores >= num_dies); + TEST_ASSERT_VAL("#num_packages", expr__parse(&num_packages, ctx, "#num_packages") == 0); + + if (num_dies) // Some platforms do not have CPU die support, for example s390 + TEST_ASSERT_VAL("#num_dies >= #num_packages", num_dies >= num_packages); + + + if (expr__parse(&val, ctx, "#system_tsc_freq") == 0) { + bool is_intel = strstr(cpuid, "Intel") != NULL; + + if (is_intel) + TEST_ASSERT_VAL("#system_tsc_freq > 0", val > 0); + else + TEST_ASSERT_VAL("#system_tsc_freq == 0", fpclassify(val) == FP_ZERO); + } else { +#if defined(__i386__) || defined(__x86_64__) + TEST_ASSERT_VAL("#system_tsc_freq unsupported", 0); +#endif + } + /* + * Source count returns the number of events aggregating in a leader + * event including the leader. Check parsing yields an id. + */ + expr__ctx_clear(ctx); + TEST_ASSERT_VAL("source count", + expr__find_ids("source_count(EVENT1)", + NULL, ctx) == 0); + TEST_ASSERT_VAL("source count", hashmap__size(ctx->ids) == 1); + TEST_ASSERT_VAL("source count", hashmap__find(ctx->ids, "EVENT1", &val_ptr)); + + + /* Test no cpuid match */ + ret = test(ctx, "strcmp_cpuid_str(0x0)", 0); + + /* + * Test cpuid match with current cpuid. Special chars have to be + * escaped. + */ + escaped_cpuid1 = strreplace_chars('-', cpuid, "\\-"); + free(cpuid); + escaped_cpuid2 = strreplace_chars(',', escaped_cpuid1, "\\,"); + free(escaped_cpuid1); + escaped_cpuid1 = strreplace_chars('=', escaped_cpuid2, "\\="); + free(escaped_cpuid2); + scnprintf(strcmp_cpuid_buf, sizeof(strcmp_cpuid_buf), + "strcmp_cpuid_str(%s)", escaped_cpuid1); + free(escaped_cpuid1); + ret |= test(ctx, strcmp_cpuid_buf, 1); + + /* has_event returns 1 when an event exists. */ + expr__add_id_val(ctx, strdup("cycles"), 2); + ret |= test(ctx, "has_event(cycles)", 1); + + expr__ctx_free(ctx); + + return ret; } + +DEFINE_SUITE("Simple expression parser", expr); diff --git a/tools/perf/tests/fdarray.c b/tools/perf/tests/fdarray.c index c7c81c4a5b2b..40983c3574b1 100644 --- a/tools/perf/tests/fdarray.c +++ b/tools/perf/tests/fdarray.c @@ -12,6 +12,7 @@ static void fdarray__init_revents(struct fdarray *fda, short revents) for (fd = 0; fd < fda->nr; ++fd) { fda->entries[fd].fd = fda->nr - fd; + fda->entries[fd].events = revents; fda->entries[fd].revents = revents; } } @@ -27,9 +28,9 @@ static int fdarray__fprintf_prefix(struct fdarray *fda, const char *prefix, FILE return printed + fdarray__fprintf(fda, fp); } -int test__fdarray__filter(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__fdarray__filter(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - int nr_fds, expected_fd[2], fd, err = TEST_FAIL; + int nr_fds, err = TEST_FAIL; struct fdarray *fda = fdarray__new(5, 5); if (fda == NULL) { @@ -55,7 +56,6 @@ int test__fdarray__filter(struct test *test __maybe_unused, int subtest __maybe_ fdarray__init_revents(fda, POLLHUP); fda->entries[2].revents = POLLIN; - expected_fd[0] = fda->entries[2].fd; pr_debug("\nfiltering all but fda->entries[2]:"); fdarray__fprintf_prefix(fda, "before", stderr); @@ -66,17 +66,9 @@ int test__fdarray__filter(struct test *test __maybe_unused, int subtest __maybe_ goto out_delete; } - if (fda->entries[0].fd != expected_fd[0]) { - pr_debug("\nfda->entries[0].fd=%d != %d\n", - fda->entries[0].fd, expected_fd[0]); - goto out_delete; - } - fdarray__init_revents(fda, POLLHUP); fda->entries[0].revents = POLLIN; - expected_fd[0] = fda->entries[0].fd; fda->entries[3].revents = POLLIN; - expected_fd[1] = fda->entries[3].fd; pr_debug("\nfiltering all but (fda->entries[0], fda->entries[3]):"); fdarray__fprintf_prefix(fda, "before", stderr); @@ -88,14 +80,6 @@ int test__fdarray__filter(struct test *test __maybe_unused, int subtest __maybe_ goto out_delete; } - for (fd = 0; fd < 2; ++fd) { - if (fda->entries[fd].fd != expected_fd[fd]) { - pr_debug("\nfda->entries[%d].fd=%d != %d\n", fd, - fda->entries[fd].fd, expected_fd[fd]); - goto out_delete; - } - } - pr_debug("\n"); err = 0; @@ -105,7 +89,7 @@ out: return err; } -int test__fdarray__add(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__fdarray__add(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int err = TEST_FAIL; struct fdarray *fda = fdarray__new(2, 2); @@ -128,7 +112,7 @@ int test__fdarray__add(struct test *test __maybe_unused, int subtest __maybe_unu } #define FDA_ADD(_idx, _fd, _revents, _nr) \ - if (fdarray__add(fda, _fd, _revents) < 0) { \ + if (fdarray__add(fda, _fd, _revents, fdarray_flag__default) < 0) { \ pr_debug("\n%d: fdarray__add(fda, %d, %d) failed!", \ __LINE__,_fd, _revents); \ goto out_delete; \ @@ -174,3 +158,6 @@ out_delete: out: return err; } + +DEFINE_SUITE("Filter fds with revents mask in a fdarray", fdarray__filter); +DEFINE_SUITE("Add fd to a fdarray, making it autogrow", fdarray__add); diff --git a/tools/perf/tests/genelf.c b/tools/perf/tests/genelf.c index f797f9823e89..95f3be1b683a 100644 --- a/tools/perf/tests/genelf.c +++ b/tools/perf/tests/genelf.c @@ -16,8 +16,8 @@ #define TEMPL "/tmp/perf-test-XXXXXX" -int test__jit_write_elf(struct test *test __maybe_unused, - int subtest __maybe_unused) +static int test__jit_write_elf(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { #ifdef HAVE_JITDUMP static unsigned char x86_code[] = { @@ -49,3 +49,5 @@ int test__jit_write_elf(struct test *test __maybe_unused, return TEST_SKIP; #endif } + +DEFINE_SUITE("Test jit_write_elf", jit_write_elf); diff --git a/tools/perf/tests/hists_common.c b/tools/perf/tests/hists_common.c index 6f34d08b84e5..187f12f5bc21 100644 --- a/tools/perf/tests/hists_common.c +++ b/tools/perf/tests/hists_common.c @@ -146,7 +146,7 @@ struct machine *setup_fake_machine(struct machines *machines) goto out; } - symbols__insert(&dso->symbols, sym); + symbols__insert(dso__symbols(dso), sym); } dso__put(dso); @@ -179,9 +179,11 @@ void print_hists_in(struct hists *hists) he = rb_entry(node, struct hist_entry, rb_node_in); if (!he->filtered) { + struct dso *dso = map__dso(he->ms.map); + pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n", i, thread__comm_str(he->thread), - he->ms.map->dso->short_name, + dso__short_name(dso), he->ms.sym->name, he->stat.period); } @@ -206,9 +208,11 @@ void print_hists_out(struct hists *hists) he = rb_entry(node, struct hist_entry, rb_node); if (!he->filtered) { + struct dso *dso = map__dso(he->ms.map); + pr_info("%2d: entry: %8s:%5d [%-8s] %20s: period = %"PRIu64"/%"PRIu64"\n", - i, thread__comm_str(he->thread), he->thread->tid, - he->ms.map->dso->short_name, + i, thread__comm_str(he->thread), thread__tid(he->thread), + dso__short_name(dso), he->ms.sym->name, he->stat.period, he->stat_acc ? he->stat_acc->period : 0); } diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c index 3f2e1a581247..1e0f5a310fd5 100644 --- a/tools/perf/tests/hists_cumulate.c +++ b/tools/perf/tests/hists_cumulate.c @@ -8,8 +8,8 @@ #include "util/evsel.h" #include "util/evlist.h" #include "util/machine.h" -#include "util/thread.h" #include "util/parse-events.h" +#include "util/thread.h" #include "tests/tests.h" #include "tests/hists_common.h" #include <linux/kernel.h> @@ -47,7 +47,7 @@ static struct sample fake_samples[] = { }; /* - * Will be casted to struct ip_callchain which has all 64 bit entries + * Will be cast to struct ip_callchain which has all 64 bit entries * of nr and ips[]. */ static u64 fake_callchains[][10] = { @@ -84,6 +84,7 @@ static int add_hist_entries(struct hists *hists, struct machine *machine) struct perf_sample sample = { .period = 1000, }; size_t i; + addr_location__init(&al); for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { struct hist_entry_iter iter = { .evsel = evsel, @@ -107,19 +108,22 @@ static int add_hist_entries(struct hists *hists, struct machine *machine) if (hist_entry_iter__add(&iter, &al, sysctl_perf_event_max_stack, NULL) < 0) { - addr_location__put(&al); goto out; } - fake_samples[i].thread = al.thread; - fake_samples[i].map = al.map; + thread__put(fake_samples[i].thread); + fake_samples[i].thread = thread__get(al.thread); + map__put(fake_samples[i].map); + fake_samples[i].map = map__get(al.map); fake_samples[i].sym = al.sym; } + addr_location__exit(&al); return TEST_OK; out: pr_debug("Not enough memory for adding a hist entry\n"); + addr_location__exit(&al); return TEST_FAIL; } @@ -147,15 +151,24 @@ static void del_hist_entries(struct hists *hists) } } +static void put_fake_samples(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + map__zput(fake_samples[i].map); + thread__zput(fake_samples[i].thread); + } +} + typedef int (*test_fn_t)(struct evsel *, struct machine *); #define COMM(he) (thread__comm_str(he->thread)) -#define DSO(he) (he->ms.map->dso->short_name) +#define DSO(he) (dso__short_name(map__dso(he->ms.map))) #define SYM(he) (he->ms.sym->name) #define CPU(he) (he->cpu) -#define PID(he) (he->thread->tid) #define DEPTH(he) (he->callchain->max_depth) -#define CDSO(cl) (cl->ms.map->dso->short_name) +#define CDSO(cl) (dso__short_name(map__dso(cl->ms.map))) #define CSYM(cl) (cl->ms.sym->name) struct result { @@ -297,7 +310,7 @@ out: return err; } -/* callcain + NO children */ +/* callchain + NO children */ static int test2(struct evsel *evsel, struct machine *machine) { int err; @@ -689,7 +702,7 @@ out: return err; } -int test__hists_cumulate(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__hists_cumulate(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int err = TEST_FAIL; struct machines machines; @@ -706,7 +719,7 @@ int test__hists_cumulate(struct test *test __maybe_unused, int subtest __maybe_u TEST_ASSERT_VAL("No memory", evlist); - err = parse_events(evlist, "cpu-clock", NULL); + err = parse_event(evlist, "cpu-clock"); if (err) goto out; err = TEST_FAIL; @@ -733,6 +746,9 @@ out: /* tear down everything */ evlist__delete(evlist); machines__exit(&machines); + put_fake_samples(); return err; } + +DEFINE_SUITE("Cumulate child hist entries", hists_cumulate); diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c index 123e07d35b55..4b2e4f2fbe48 100644 --- a/tools/perf/tests/hists_filter.c +++ b/tools/perf/tests/hists_filter.c @@ -8,6 +8,7 @@ #include "util/evlist.h" #include "util/machine.h" #include "util/parse-events.h" +#include "util/thread.h" #include "tests/tests.h" #include "tests/hists_common.h" #include <linux/kernel.h> @@ -53,6 +54,7 @@ static int add_hist_entries(struct evlist *evlist, struct perf_sample sample = { .period = 100, }; size_t i; + addr_location__init(&al); /* * each evsel will have 10 samples but the 4th sample * (perf [perf] main) will be collapsed to an existing entry @@ -84,24 +86,34 @@ static int add_hist_entries(struct evlist *evlist, al.socket = fake_samples[i].socket; if (hist_entry_iter__add(&iter, &al, sysctl_perf_event_max_stack, NULL) < 0) { - addr_location__put(&al); goto out; } - fake_samples[i].thread = al.thread; - fake_samples[i].map = al.map; + thread__put(fake_samples[i].thread); + fake_samples[i].thread = thread__get(al.thread); + map__put(fake_samples[i].map); + fake_samples[i].map = map__get(al.map); fake_samples[i].sym = al.sym; } } - + addr_location__exit(&al); return 0; out: pr_debug("Not enough memory for adding a hist entry\n"); + addr_location__exit(&al); return TEST_FAIL; } -int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unused) +static void put_fake_samples(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) + map__put(fake_samples[i].map); +} + +static int test__hists_filter(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int err = TEST_FAIL; struct machines machines; @@ -111,10 +123,10 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu TEST_ASSERT_VAL("No memory", evlist); - err = parse_events(evlist, "cpu-clock", NULL); + err = parse_event(evlist, "cpu-clock"); if (err) goto out; - err = parse_events(evlist, "task-clock", NULL); + err = parse_event(evlist, "task-clock"); if (err) goto out; err = TEST_FAIL; @@ -150,13 +162,13 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu } TEST_ASSERT_VAL("Invalid nr samples", - hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + hists->stats.nr_samples == 10); TEST_ASSERT_VAL("Invalid nr hist entries", hists->nr_entries == 9); TEST_ASSERT_VAL("Invalid total period", hists->stats.total_period == 1000); TEST_ASSERT_VAL("Unmatched nr samples", - hists->stats.nr_events[PERF_RECORD_SAMPLE] == + hists->stats.nr_samples == hists->stats.nr_non_filtered_samples); TEST_ASSERT_VAL("Unmatched nr hist entries", hists->nr_entries == hists->nr_non_filtered_entries); @@ -175,7 +187,7 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu /* normal stats should be invariant */ TEST_ASSERT_VAL("Invalid nr samples", - hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + hists->stats.nr_samples == 10); TEST_ASSERT_VAL("Invalid nr hist entries", hists->nr_entries == 9); TEST_ASSERT_VAL("Invalid total period", @@ -194,7 +206,7 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu hists__filter_by_thread(hists); /* now applying dso filter for 'kernel' */ - hists->dso_filter = fake_samples[0].map->dso; + hists->dso_filter = map__dso(fake_samples[0].map); hists__filter_by_dso(hists); if (verbose > 2) { @@ -204,7 +216,7 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu /* normal stats should be invariant */ TEST_ASSERT_VAL("Invalid nr samples", - hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + hists->stats.nr_samples == 10); TEST_ASSERT_VAL("Invalid nr hist entries", hists->nr_entries == 9); TEST_ASSERT_VAL("Invalid total period", @@ -239,7 +251,7 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu /* normal stats should be invariant */ TEST_ASSERT_VAL("Invalid nr samples", - hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + hists->stats.nr_samples == 10); TEST_ASSERT_VAL("Invalid nr hist entries", hists->nr_entries == 9); TEST_ASSERT_VAL("Invalid total period", @@ -268,7 +280,7 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu /* normal stats should be invariant */ TEST_ASSERT_VAL("Invalid nr samples", - hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + hists->stats.nr_samples == 10); TEST_ASSERT_VAL("Invalid nr hist entries", hists->nr_entries == 9); TEST_ASSERT_VAL("Invalid total period", @@ -288,7 +300,7 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu /* now applying all filters at once. */ hists->thread_filter = fake_samples[1].thread; - hists->dso_filter = fake_samples[1].map->dso; + hists->dso_filter = map__dso(fake_samples[1].map); hists__filter_by_thread(hists); hists__filter_by_dso(hists); @@ -299,7 +311,7 @@ int test__hists_filter(struct test *test __maybe_unused, int subtest __maybe_unu /* normal stats should be invariant */ TEST_ASSERT_VAL("Invalid nr samples", - hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + hists->stats.nr_samples == 10); TEST_ASSERT_VAL("Invalid nr hist entries", hists->nr_entries == 9); TEST_ASSERT_VAL("Invalid total period", @@ -322,6 +334,9 @@ out: evlist__delete(evlist); reset_output_field(); machines__exit(&machines); + put_fake_samples(); return err; } + +DEFINE_SUITE("Filter hist entries", hists_filter); diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c index a024d3f3a412..5b6f1e883466 100644 --- a/tools/perf/tests/hists_link.c +++ b/tools/perf/tests/hists_link.c @@ -6,7 +6,9 @@ #include "evsel.h" #include "evlist.h" #include "machine.h" +#include "map.h" #include "parse-events.h" +#include "thread.h" #include "hists_common.h" #include "util/mmap.h" #include <errno.h> @@ -69,6 +71,7 @@ static int add_hist_entries(struct evlist *evlist, struct machine *machine) struct perf_sample sample = { .period = 1, .weight = 1, }; size_t i = 0, k; + addr_location__init(&al); /* * each evsel will have 10 samples - 5 common and 5 distinct. * However the second evsel also has a collapsed entry for @@ -87,14 +90,15 @@ static int add_hist_entries(struct evlist *evlist, struct machine *machine) goto out; he = hists__add_entry(hists, &al, NULL, - NULL, NULL, &sample, true); + NULL, NULL, NULL, &sample, true); if (he == NULL) { - addr_location__put(&al); goto out; } - fake_common_samples[k].thread = al.thread; - fake_common_samples[k].map = al.map; + thread__put(fake_common_samples[k].thread); + fake_common_samples[k].thread = thread__get(al.thread); + map__put(fake_common_samples[k].map); + fake_common_samples[k].map = map__get(al.map); fake_common_samples[k].sym = al.sym; } @@ -106,31 +110,46 @@ static int add_hist_entries(struct evlist *evlist, struct machine *machine) goto out; he = hists__add_entry(hists, &al, NULL, - NULL, NULL, &sample, true); + NULL, NULL, NULL, &sample, true); if (he == NULL) { - addr_location__put(&al); goto out; } - fake_samples[i][k].thread = al.thread; - fake_samples[i][k].map = al.map; + thread__put(fake_samples[i][k].thread); + fake_samples[i][k].thread = thread__get(al.thread); + map__put(fake_samples[i][k].map); + fake_samples[i][k].map = map__get(al.map); fake_samples[i][k].sym = al.sym; } i++; } + addr_location__exit(&al); return 0; - out: + addr_location__exit(&al); pr_debug("Not enough memory for adding a hist entry\n"); return -1; } +static void put_fake_samples(void) +{ + size_t i, j; + + for (i = 0; i < ARRAY_SIZE(fake_common_samples); i++) + map__put(fake_common_samples[i].map); + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + for (j = 0; j < ARRAY_SIZE(fake_samples[0]); j++) + map__put(fake_samples[i][j].map); + } +} + static int find_sample(struct sample *samples, size_t nr_samples, struct thread *t, struct map *m, struct symbol *s) { while (nr_samples--) { - if (samples->thread == t && samples->map == m && + if (RC_CHK_EQUAL(samples->thread, t) && + RC_CHK_EQUAL(samples->map, m) && samples->sym == s) return 1; samples++; @@ -264,7 +283,7 @@ static int validate_link(struct hists *leader, struct hists *other) return __validate_link(leader, 0) || __validate_link(other, 1); } -int test__hists_link(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__hists_link(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int err = -1; struct hists *hists, *first_hists; @@ -276,10 +295,10 @@ int test__hists_link(struct test *test __maybe_unused, int subtest __maybe_unuse if (evlist == NULL) return -ENOMEM; - err = parse_events(evlist, "cpu-clock", NULL); + err = parse_event(evlist, "cpu-clock"); if (err) goto out; - err = parse_events(evlist, "task-clock", NULL); + err = parse_event(evlist, "task-clock"); if (err) goto out; @@ -336,6 +355,9 @@ out: evlist__delete(evlist); reset_output_field(); machines__exit(&machines); + put_fake_samples(); return err; } + +DEFINE_SUITE("Match and link multiple hists", hists_link); diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c index 8973f35df604..33b5cc8352a7 100644 --- a/tools/perf/tests/hists_output.c +++ b/tools/perf/tests/hists_output.c @@ -54,6 +54,7 @@ static int add_hist_entries(struct hists *hists, struct machine *machine) struct perf_sample sample = { .period = 100, }; size_t i; + addr_location__init(&al); for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { struct hist_entry_iter iter = { .evsel = evsel, @@ -73,19 +74,21 @@ static int add_hist_entries(struct hists *hists, struct machine *machine) if (hist_entry_iter__add(&iter, &al, sysctl_perf_event_max_stack, NULL) < 0) { - addr_location__put(&al); goto out; } fake_samples[i].thread = al.thread; - fake_samples[i].map = al.map; + map__put(fake_samples[i].map); + fake_samples[i].map = map__get(al.map); fake_samples[i].sym = al.sym; } + addr_location__exit(&al); return TEST_OK; out: pr_debug("Not enough memory for adding a hist entry\n"); + addr_location__exit(&al); return TEST_FAIL; } @@ -113,13 +116,23 @@ static void del_hist_entries(struct hists *hists) } } +static void put_fake_samples(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + map__put(fake_samples[i].map); + fake_samples[i].map = NULL; + } +} + typedef int (*test_fn_t)(struct evsel *, struct machine *); #define COMM(he) (thread__comm_str(he->thread)) -#define DSO(he) (he->ms.map->dso->short_name) +#define DSO(he) (dso__short_name(map__dso(he->ms.map))) #define SYM(he) (he->ms.sym->name) #define CPU(he) (he->cpu) -#define PID(he) (he->thread->tid) +#define PID(he) (thread__tid(he->thread)) /* default sort keys (no field) */ static int test1(struct evsel *evsel, struct machine *machine) @@ -575,7 +588,7 @@ out: return err; } -int test__hists_output(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__hists_output(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int err = TEST_FAIL; struct machines machines; @@ -593,7 +606,7 @@ int test__hists_output(struct test *test __maybe_unused, int subtest __maybe_unu TEST_ASSERT_VAL("No memory", evlist); - err = parse_events(evlist, "cpu-clock", NULL); + err = parse_event(evlist, "cpu-clock"); if (err) goto out; err = TEST_FAIL; @@ -620,6 +633,9 @@ out: /* tear down everything */ evlist__delete(evlist); machines__exit(&machines); + put_fake_samples(); return err; } + +DEFINE_SUITE("Sort output of hist entries", hists_output); diff --git a/tools/perf/tests/hwmon_pmu.c b/tools/perf/tests/hwmon_pmu.c new file mode 100644 index 000000000000..0837aca1cdfa --- /dev/null +++ b/tools/perf/tests/hwmon_pmu.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +#include "debug.h" +#include "evlist.h" +#include "hwmon_pmu.h" +#include "parse-events.h" +#include "tests.h" +#include <fcntl.h> +#include <sys/stat.h> +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <linux/string.h> + +static const struct test_event { + const char *name; + const char *alias; + union hwmon_pmu_event_key key; +} test_events[] = { + { + "temp_test_hwmon_event1", + "temp1", + .key = { + .num = 1, + .type = 10 + }, + }, + { + "temp_test_hwmon_event2", + "temp2", + .key = { + .num = 2, + .type = 10 + }, + }, +}; + +/* Cleanup test PMU directory. */ +static int test_pmu_put(const char *dir, struct perf_pmu *hwm) +{ + char buf[PATH_MAX + 20]; + int ret; + + if (scnprintf(buf, sizeof(buf), "rm -fr %s", dir) < 0) { + pr_err("Failure to set up buffer for \"%s\"\n", dir); + return -EINVAL; + } + ret = system(buf); + if (ret) + pr_err("Failure to \"%s\"\n", buf); + + list_del(&hwm->list); + perf_pmu__delete(hwm); + return ret; +} + +/* + * Prepare test PMU directory data, normally exported by kernel at + * /sys/class/hwmon/hwmon<number>/. Give as input a buffer to hold the file + * path, the result is PMU loaded using that directory. + */ +static struct perf_pmu *test_pmu_get(char *dir, size_t sz) +{ + const char *test_hwmon_name_nl = "A test hwmon PMU\n"; + const char *test_hwmon_name = "A test hwmon PMU"; + /* Simulated hwmon items. */ + const struct test_item { + const char *name; + const char *value; + } test_items[] = { + { "temp1_label", "test hwmon event1\n", }, + { "temp1_input", "40000\n", }, + { "temp2_label", "test hwmon event2\n", }, + { "temp2_input", "50000\n", }, + }; + int hwmon_dirfd = -1, test_dirfd = -1, file; + struct perf_pmu *hwm = NULL; + ssize_t len; + + /* Create equivalent of sysfs mount point. */ + scnprintf(dir, sz, "/tmp/perf-hwmon-pmu-test-XXXXXX"); + if (!mkdtemp(dir)) { + pr_err("mkdtemp failed\n"); + dir[0] = '\0'; + return NULL; + } + test_dirfd = open(dir, O_PATH|O_DIRECTORY); + if (test_dirfd < 0) { + pr_err("Failed to open test directory \"%s\"\n", dir); + goto err_out; + } + + /* Create the test hwmon directory and give it a name. */ + if (mkdirat(test_dirfd, "hwmon1234", 0755) < 0) { + pr_err("Failed to mkdir hwmon directory\n"); + goto err_out; + } + hwmon_dirfd = openat(test_dirfd, "hwmon1234", O_DIRECTORY); + if (hwmon_dirfd < 0) { + pr_err("Failed to open test hwmon directory \"%s/hwmon1234\"\n", dir); + goto err_out; + } + file = openat(hwmon_dirfd, "name", O_WRONLY | O_CREAT, 0600); + if (file < 0) { + pr_err("Failed to open for writing file \"name\"\n"); + goto err_out; + } + len = strlen(test_hwmon_name_nl); + if (write(file, test_hwmon_name_nl, len) < len) { + close(file); + pr_err("Failed to write to 'name' file\n"); + goto err_out; + } + close(file); + + /* Create test hwmon files. */ + for (size_t i = 0; i < ARRAY_SIZE(test_items); i++) { + const struct test_item *item = &test_items[i]; + + file = openat(hwmon_dirfd, item->name, O_WRONLY | O_CREAT, 0600); + if (file < 0) { + pr_err("Failed to open for writing file \"%s\"\n", item->name); + goto err_out; + } + + if (write(file, item->value, strlen(item->value)) < 0) { + pr_err("Failed to write to file \"%s\"\n", item->name); + close(file); + goto err_out; + } + close(file); + } + + /* Make the PMU reading the files created above. */ + hwm = perf_pmus__add_test_hwmon_pmu(hwmon_dirfd, "hwmon1234", test_hwmon_name); + if (!hwm) + pr_err("Test hwmon creation failed\n"); + +err_out: + if (!hwm) { + test_pmu_put(dir, hwm); + if (hwmon_dirfd >= 0) + close(hwmon_dirfd); + } + if (test_dirfd >= 0) + close(test_dirfd); + return hwm; +} + +static int do_test(size_t i, bool with_pmu, bool with_alias) +{ + const char *test_event = with_alias ? test_events[i].alias : test_events[i].name; + struct evlist *evlist = evlist__new(); + struct evsel *evsel; + struct parse_events_error err; + int ret; + char str[128]; + bool found = false; + + if (!evlist) { + pr_err("evlist allocation failed\n"); + return TEST_FAIL; + } + + if (with_pmu) + snprintf(str, sizeof(str), "hwmon_a_test_hwmon_pmu/%s/", test_event); + else + strlcpy(str, test_event, sizeof(str)); + + pr_debug("Testing '%s'\n", str); + parse_events_error__init(&err); + ret = parse_events(evlist, str, &err); + if (ret) { + pr_debug("FAILED %s:%d failed to parse event '%s', err %d\n", + __FILE__, __LINE__, str, ret); + parse_events_error__print(&err, str); + ret = TEST_FAIL; + goto out; + } + + ret = TEST_OK; + if (with_pmu ? (evlist->core.nr_entries != 1) : (evlist->core.nr_entries < 1)) { + pr_debug("FAILED %s:%d Unexpected number of events for '%s' of %d\n", + __FILE__, __LINE__, str, evlist->core.nr_entries); + ret = TEST_FAIL; + goto out; + } + + evlist__for_each_entry(evlist, evsel) { + if (!evsel->pmu || !evsel->pmu->name || + strcmp(evsel->pmu->name, "hwmon_a_test_hwmon_pmu")) + continue; + + if (evsel->core.attr.config != (u64)test_events[i].key.type_and_num) { + pr_debug("FAILED %s:%d Unexpected config for '%s', %lld != %ld\n", + __FILE__, __LINE__, str, + evsel->core.attr.config, + test_events[i].key.type_and_num); + ret = TEST_FAIL; + goto out; + } + found = true; + } + + if (!found) { + pr_debug("FAILED %s:%d Didn't find hwmon event '%s' in parsed evsels\n", + __FILE__, __LINE__, str); + ret = TEST_FAIL; + } + +out: + parse_events_error__exit(&err); + evlist__delete(evlist); + return ret; +} + +static int test__hwmon_pmu(bool with_pmu) +{ + char dir[PATH_MAX]; + struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir)); + int ret = TEST_OK; + + if (!pmu) + return TEST_FAIL; + + for (size_t i = 0; i < ARRAY_SIZE(test_events); i++) { + ret = do_test(i, with_pmu, /*with_alias=*/false); + + if (ret != TEST_OK) + break; + + ret = do_test(i, with_pmu, /*with_alias=*/true); + + if (ret != TEST_OK) + break; + } + test_pmu_put(dir, pmu); + return ret; +} + +static int test__hwmon_pmu_without_pmu(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + return test__hwmon_pmu(/*with_pmu=*/false); +} + +static int test__hwmon_pmu_with_pmu(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + return test__hwmon_pmu(/*with_pmu=*/true); +} + +static int test__parse_hwmon_filename(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + const struct hwmon_parse_test { + const char *filename; + enum hwmon_type type; + int number; + enum hwmon_item item; + bool alarm; + bool parse_ok; + } tests[] = { + { + .filename = "cpu0_accuracy", + .type = HWMON_TYPE_CPU, + .number = 0, + .item = HWMON_ITEM_ACCURACY, + .alarm = false, + .parse_ok = true, + }, + { + .filename = "temp1_input", + .type = HWMON_TYPE_TEMP, + .number = 1, + .item = HWMON_ITEM_INPUT, + .alarm = false, + .parse_ok = true, + }, + { + .filename = "fan2_vid", + .type = HWMON_TYPE_FAN, + .number = 2, + .item = HWMON_ITEM_VID, + .alarm = false, + .parse_ok = true, + }, + { + .filename = "power3_crit_alarm", + .type = HWMON_TYPE_POWER, + .number = 3, + .item = HWMON_ITEM_CRIT, + .alarm = true, + .parse_ok = true, + }, + { + .filename = "intrusion4_average_interval_min_alarm", + .type = HWMON_TYPE_INTRUSION, + .number = 4, + .item = HWMON_ITEM_AVERAGE_INTERVAL_MIN, + .alarm = true, + .parse_ok = true, + }, + { + .filename = "badtype5_baditem", + .type = HWMON_TYPE_NONE, + .number = 5, + .item = HWMON_ITEM_NONE, + .alarm = false, + .parse_ok = false, + }, + { + .filename = "humidity6_baditem", + .type = HWMON_TYPE_NONE, + .number = 6, + .item = HWMON_ITEM_NONE, + .alarm = false, + .parse_ok = false, + }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(tests); i++) { + enum hwmon_type type; + int number; + enum hwmon_item item; + bool alarm; + + TEST_ASSERT_EQUAL("parse_hwmon_filename", + parse_hwmon_filename( + tests[i].filename, + &type, + &number, + &item, + &alarm), + tests[i].parse_ok + ); + if (tests[i].parse_ok) { + TEST_ASSERT_EQUAL("parse_hwmon_filename type", type, tests[i].type); + TEST_ASSERT_EQUAL("parse_hwmon_filename number", number, tests[i].number); + TEST_ASSERT_EQUAL("parse_hwmon_filename item", item, tests[i].item); + TEST_ASSERT_EQUAL("parse_hwmon_filename alarm", alarm, tests[i].alarm); + } + } + return TEST_OK; +} + +static struct test_case tests__hwmon_pmu[] = { + TEST_CASE("Basic parsing test", parse_hwmon_filename), + TEST_CASE("Parsing without PMU name", hwmon_pmu_without_pmu), + TEST_CASE("Parsing with PMU name", hwmon_pmu_with_pmu), + { .name = NULL, } +}; + +struct test_suite suite__hwmon_pmu = { + .desc = "Hwmon PMU", + .test_cases = tests__hwmon_pmu, +}; diff --git a/tools/perf/tests/is_printable_array.c b/tools/perf/tests/is_printable_array.c index 9c7b3baca4fe..f72de2457ff1 100644 --- a/tools/perf/tests/is_printable_array.c +++ b/tools/perf/tests/is_printable_array.c @@ -5,7 +5,7 @@ #include "debug.h" #include "print_binary.h" -int test__is_printable_array(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__is_printable_array(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { char buf1[] = { 'k', 'r', 4, 'v', 'a', 0 }; char buf2[] = { 'k', 'r', 'a', 'v', 4, 0 }; @@ -36,3 +36,5 @@ int test__is_printable_array(struct test *test __maybe_unused, int subtest __may return TEST_OK; } + +DEFINE_SUITE("is_printable_array", is_printable_array); diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c index 50a0c9fcde7d..5a3b2bed07f3 100644 --- a/tools/perf/tests/keep-tracking.c +++ b/tools/perf/tests/keep-tracking.c @@ -61,7 +61,7 @@ static int find_comm(struct evlist *evlist, const char *comm) * when an event is disabled but a dummy software event is not disabled. If the * test passes %0 is returned, otherwise %-1 is returned. */ -int test__keep_tracking(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__keep_tracking(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct record_opts opts = { .mmap_pages = UINT_MAX, @@ -81,7 +81,7 @@ int test__keep_tracking(struct test *test __maybe_unused, int subtest __maybe_un threads = thread_map__new(-1, getpid(), UINT_MAX); CHECK_NOT_NULL__(threads); - cpus = perf_cpu_map__new(NULL); + cpus = perf_cpu_map__new_online_cpus(); CHECK_NOT_NULL__(cpus); evlist = evlist__new(); @@ -89,10 +89,10 @@ int test__keep_tracking(struct test *test __maybe_unused, int subtest __maybe_un perf_evlist__set_maps(&evlist->core, cpus, threads); - CHECK__(parse_events(evlist, "dummy:u", NULL)); - CHECK__(parse_events(evlist, "cycles:u", NULL)); + CHECK__(parse_event(evlist, "dummy:u")); + CHECK__(parse_event(evlist, "cycles:u")); - perf_evlist__config(evlist, &opts, NULL); + evlist__config(evlist, &opts, NULL); evsel = evlist__first(evlist); @@ -154,10 +154,11 @@ out_err: if (evlist) { evlist__disable(evlist); evlist__delete(evlist); - } else { - perf_cpu_map__put(cpus); - perf_thread_map__put(threads); } + perf_cpu_map__put(cpus); + perf_thread_map__put(threads); return err; } + +DEFINE_SUITE("Use a dummy software event to keep tracking", keep_tracking); diff --git a/tools/perf/tests/kmod-path.c b/tools/perf/tests/kmod-path.c index e483210b176b..dfe1bd5dabaa 100644 --- a/tools/perf/tests/kmod-path.c +++ b/tools/perf/tests/kmod-path.c @@ -47,7 +47,7 @@ static int test_is_kernel_module(const char *path, int cpumode, bool expect) #define M(path, c, e) \ TEST_ASSERT_VAL("failed", !test_is_kernel_module(path, c, e)) -int test__kmod_path__parse(struct test *t __maybe_unused, int subtest __maybe_unused) +static int test__kmod_path__parse(struct test_suite *t __maybe_unused, int subtest __maybe_unused) { /* path alloc_name kmod comp name */ T("/xxxx/xxxx/x-x.ko", true , true, 0 , "[x_x]"); @@ -159,3 +159,5 @@ int test__kmod_path__parse(struct test *t __maybe_unused, int subtest __maybe_un return 0; } + +DEFINE_SUITE("kmod_path__parse", kmod_path__parse); diff --git a/tools/perf/tests/llvm.c b/tools/perf/tests/llvm.c deleted file mode 100644 index ae6cda81c209..000000000000 --- a/tools/perf/tests/llvm.c +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <bpf/libbpf.h> -#include <util/llvm-utils.h> -#include "llvm.h" -#include "tests.h" -#include "debug.h" - -#ifdef HAVE_LIBBPF_SUPPORT -static int test__bpf_parsing(void *obj_buf, size_t obj_buf_sz) -{ - struct bpf_object *obj; - - obj = bpf_object__open_buffer(obj_buf, obj_buf_sz, NULL); - if (libbpf_get_error(obj)) - return TEST_FAIL; - bpf_object__close(obj); - return TEST_OK; -} -#else -static int test__bpf_parsing(void *obj_buf __maybe_unused, - size_t obj_buf_sz __maybe_unused) -{ - pr_debug("Skip bpf parsing\n"); - return TEST_OK; -} -#endif - -static struct { - const char *source; - const char *desc; - bool should_load_fail; -} bpf_source_table[__LLVM_TESTCASE_MAX] = { - [LLVM_TESTCASE_BASE] = { - .source = test_llvm__bpf_base_prog, - .desc = "Basic BPF llvm compile", - }, - [LLVM_TESTCASE_KBUILD] = { - .source = test_llvm__bpf_test_kbuild_prog, - .desc = "kbuild searching", - }, - [LLVM_TESTCASE_BPF_PROLOGUE] = { - .source = test_llvm__bpf_test_prologue_prog, - .desc = "Compile source for BPF prologue generation", - }, - [LLVM_TESTCASE_BPF_RELOCATION] = { - .source = test_llvm__bpf_test_relocation, - .desc = "Compile source for BPF relocation", - .should_load_fail = true, - }, -}; - -int -test_llvm__fetch_bpf_obj(void **p_obj_buf, - size_t *p_obj_buf_sz, - enum test_llvm__testcase idx, - bool force, - bool *should_load_fail) -{ - const char *source; - const char *desc; - const char *tmpl_old, *clang_opt_old; - char *tmpl_new = NULL, *clang_opt_new = NULL; - int err, old_verbose, ret = TEST_FAIL; - - if (idx >= __LLVM_TESTCASE_MAX) - return TEST_FAIL; - - source = bpf_source_table[idx].source; - desc = bpf_source_table[idx].desc; - if (should_load_fail) - *should_load_fail = bpf_source_table[idx].should_load_fail; - - /* - * Skip this test if user's .perfconfig doesn't set [llvm] section - * and clang is not found in $PATH, and this is not perf test -v - */ - if (!force && (verbose <= 0 && - !llvm_param.user_set_param && - llvm__search_clang())) { - pr_debug("No clang and no verbosive, skip this test\n"); - return TEST_SKIP; - } - - /* - * llvm is verbosity when error. Suppress all error output if - * not 'perf test -v'. - */ - old_verbose = verbose; - if (verbose == 0) - verbose = -1; - - *p_obj_buf = NULL; - *p_obj_buf_sz = 0; - - if (!llvm_param.clang_bpf_cmd_template) - goto out; - - if (!llvm_param.clang_opt) - llvm_param.clang_opt = strdup(""); - - err = asprintf(&tmpl_new, "echo '%s' | %s%s", source, - llvm_param.clang_bpf_cmd_template, - old_verbose ? "" : " 2>/dev/null"); - if (err < 0) - goto out; - err = asprintf(&clang_opt_new, "-xc %s", llvm_param.clang_opt); - if (err < 0) - goto out; - - tmpl_old = llvm_param.clang_bpf_cmd_template; - llvm_param.clang_bpf_cmd_template = tmpl_new; - clang_opt_old = llvm_param.clang_opt; - llvm_param.clang_opt = clang_opt_new; - - err = llvm__compile_bpf("-", p_obj_buf, p_obj_buf_sz); - - llvm_param.clang_bpf_cmd_template = tmpl_old; - llvm_param.clang_opt = clang_opt_old; - - verbose = old_verbose; - if (err) - goto out; - - ret = TEST_OK; -out: - free(tmpl_new); - free(clang_opt_new); - if (ret != TEST_OK) - pr_debug("Failed to compile test case: '%s'\n", desc); - return ret; -} - -int test__llvm(struct test *test __maybe_unused, int subtest) -{ - int ret; - void *obj_buf = NULL; - size_t obj_buf_sz = 0; - bool should_load_fail = false; - - if ((subtest < 0) || (subtest >= __LLVM_TESTCASE_MAX)) - return TEST_FAIL; - - ret = test_llvm__fetch_bpf_obj(&obj_buf, &obj_buf_sz, - subtest, false, &should_load_fail); - - if (ret == TEST_OK && !should_load_fail) { - ret = test__bpf_parsing(obj_buf, obj_buf_sz); - if (ret != TEST_OK) { - pr_debug("Failed to parse test case '%s'\n", - bpf_source_table[subtest].desc); - } - } - free(obj_buf); - - return ret; -} - -int test__llvm_subtest_get_nr(void) -{ - return __LLVM_TESTCASE_MAX; -} - -const char *test__llvm_subtest_get_desc(int subtest) -{ - if ((subtest < 0) || (subtest >= __LLVM_TESTCASE_MAX)) - return NULL; - - return bpf_source_table[subtest].desc; -} diff --git a/tools/perf/tests/llvm.h b/tools/perf/tests/llvm.h deleted file mode 100644 index f68b0d9b8ae2..000000000000 --- a/tools/perf/tests/llvm.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef PERF_TEST_LLVM_H -#define PERF_TEST_LLVM_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stddef.h> /* for size_t */ -#include <stdbool.h> /* for bool */ - -extern const char test_llvm__bpf_base_prog[]; -extern const char test_llvm__bpf_test_kbuild_prog[]; -extern const char test_llvm__bpf_test_prologue_prog[]; -extern const char test_llvm__bpf_test_relocation[]; - -enum test_llvm__testcase { - LLVM_TESTCASE_BASE, - LLVM_TESTCASE_KBUILD, - LLVM_TESTCASE_BPF_PROLOGUE, - LLVM_TESTCASE_BPF_RELOCATION, - __LLVM_TESTCASE_MAX, -}; - -int test_llvm__fetch_bpf_obj(void **p_obj_buf, size_t *p_obj_buf_sz, - enum test_llvm__testcase index, bool force, - bool *should_load_fail); -#ifdef __cplusplus -} -#endif -#endif diff --git a/tools/perf/tests/make b/tools/perf/tests/make index 9b651dfe0a6b..0ee94caf9ec1 100644 --- a/tools/perf/tests/make +++ b/tools/perf/tests/make @@ -29,7 +29,7 @@ endif PARALLEL_OPT= ifeq ($(SET_PARALLEL),1) ifeq ($(JOBS),) - cores := $(shell (getconf _NPROCESSORS_ONLN || egrep -c '^processor|^CPU[0-9]' /proc/cpuinfo) 2>/dev/null) + cores := $(shell (getconf _NPROCESSORS_ONLN || grep -E -c '^processor|^CPU[0-9]' /proc/cpuinfo) 2>/dev/null) ifeq ($(cores),0) cores := 1 endif @@ -62,35 +62,43 @@ lib = lib endif has = $(shell which $1 2>/dev/null) +python_perf_so := $(shell $(MAKE) python_perf_target|grep "Target is:"|awk '{print $$3}') # standard single make variable specified make_clean_all := clean all -make_python_perf_so := python/perf.so +make_python_perf_so := $(python_perf_so) make_debug := DEBUG=1 +make_nondistro := BUILD_NONDISTRO=1 +make_extra_tests := EXTRA_TESTS=1 +make_jevents_all := JEVENTS_ARCH=all +make_no_bpf_skel := BUILD_BPF_SKEL=0 +make_gen_vmlinux_h := GEN_VMLINUX_H=1 make_no_libperl := NO_LIBPERL=1 make_no_libpython := NO_LIBPYTHON=1 make_no_scripts := NO_LIBPYTHON=1 NO_LIBPERL=1 -make_no_newt := NO_NEWT=1 make_no_slang := NO_SLANG=1 make_no_gtk2 := NO_GTK2=1 -make_no_ui := NO_NEWT=1 NO_SLANG=1 NO_GTK2=1 +make_no_ui := NO_SLANG=1 NO_GTK2=1 make_no_demangle := NO_DEMANGLE=1 make_no_libelf := NO_LIBELF=1 -make_no_libunwind := NO_LIBUNWIND=1 +make_libunwind := LIBUNWIND=1 make_no_libdw_dwarf_unwind := NO_LIBDW_DWARF_UNWIND=1 make_no_backtrace := NO_BACKTRACE=1 +make_no_libcapstone := NO_CAPSTONE=1 make_no_libnuma := NO_LIBNUMA=1 -make_no_libaudit := NO_LIBAUDIT=1 make_no_libbionic := NO_LIBBIONIC=1 make_no_auxtrace := NO_AUXTRACE=1 make_no_libbpf := NO_LIBBPF=1 +make_libbpf_dynamic := LIBBPF_DYNAMIC=1 make_no_libbpf_DEBUG := NO_LIBBPF=1 DEBUG=1 make_no_libcrypto := NO_LIBCRYPTO=1 +make_no_libllvm := NO_LIBLLVM=1 make_with_babeltrace:= LIBBABELTRACE=1 +make_with_coresight := CORESIGHT=1 make_no_sdt := NO_SDT=1 -make_no_syscall_tbl := NO_SYSCALL_TABLE=1 -make_with_clangllvm := LIBCLANGLLVM=1 -make_with_libpfm4 := LIBPFM4=1 +make_no_libpfm4 := NO_LIBPFM4=1 +make_with_gtk2 := GTK2=1 +make_refcnt_check := EXTRA_CFLAGS="-DREFCNT_CHECKING=1" make_tags := tags make_cscope := cscope make_help := help @@ -107,15 +115,15 @@ make_install_info := install-info make_install_pdf := install-pdf make_install_prefix := install prefix=/tmp/krava make_install_prefix_slash := install prefix=/tmp/krava/ -make_static := LDFLAGS=-static NO_PERF_READ_VDSO32=1 NO_PERF_READ_VDSOX32=1 NO_JVMTI=1 +make_static := LDFLAGS=-static NO_PERF_READ_VDSO32=1 NO_PERF_READ_VDSOX32=1 NO_JVMTI=1 NO_LIBTRACEEVENT=1 NO_LIBELF=1 # all the NO_* variable combined -make_minimal := NO_LIBPERL=1 NO_LIBPYTHON=1 NO_NEWT=1 NO_GTK2=1 -make_minimal += NO_DEMANGLE=1 NO_LIBELF=1 NO_LIBUNWIND=1 NO_BACKTRACE=1 -make_minimal += NO_LIBNUMA=1 NO_LIBAUDIT=1 NO_LIBBIONIC=1 +make_minimal := NO_LIBPERL=1 NO_LIBPYTHON=1 NO_GTK2=1 +make_minimal += NO_DEMANGLE=1 NO_LIBELF=1 NO_BACKTRACE=1 +make_minimal += NO_LIBNUMA=1 NO_LIBBIONIC=1 make_minimal += NO_LIBDW_DWARF_UNWIND=1 NO_AUXTRACE=1 NO_LIBBPF=1 make_minimal += NO_LIBCRYPTO=1 NO_SDT=1 NO_JVMTI=1 NO_LIBZSTD=1 -make_minimal += NO_LIBCAP=1 NO_SYSCALL_TABLE=1 +make_minimal += NO_LIBCAP=1 NO_CAPSTONE=1 # $(run) contains all available tests run := make_pure @@ -130,30 +138,37 @@ MAKE_F := $(MAKE) -f $(MK) endif run += make_python_perf_so run += make_debug +run += make_nondistro +run += make_extra_tests +run += make_jevents_all +run += make_no_bpf_skel +run += make_gen_vmlinux_h run += make_no_libperl run += make_no_libpython run += make_no_scripts -run += make_no_newt run += make_no_slang run += make_no_gtk2 run += make_no_ui run += make_no_demangle run += make_no_libelf -run += make_no_libunwind +run += make_libunwind run += make_no_libdw_dwarf_unwind run += make_no_backtrace +run += make_no_libcapstone run += make_no_libnuma -run += make_no_libaudit run += make_no_libbionic run += make_no_auxtrace run += make_no_libbpf run += make_no_libbpf_DEBUG run += make_no_libcrypto +run += make_no_libllvm run += make_no_sdt run += make_no_syscall_tbl run += make_with_babeltrace +run += make_with_coresight run += make_with_clangllvm -run += make_with_libpfm4 +run += make_no_libpfm4 +run += make_refcnt_check run += make_help run += make_doc run += make_perf_o @@ -170,7 +185,12 @@ run += make_install_prefix_slash # run += make_install_info # run += make_install_pdf run += make_minimal -run += make_static + +old_libbpf := $(shell echo '\#include <bpf/libbpf.h>' | $(CC) -E -dM -x c -| grep -q -E "define[[:space:]]+LIBBPF_MAJOR_VERSION[[:space:]]+0{1}") + +ifneq ($(old_libbpf),) +run += make_libbpf_dynamic +endif ifneq ($(call has,ctags),) run += make_tags @@ -200,7 +220,7 @@ test_make_doc := $(test_ok) test_make_help_O := $(test_ok) test_make_doc_O := $(test_ok) -test_make_python_perf_so := test -f $(PERF_O)/python/perf.so +test_make_python_perf_so := test -f $(PERF_O)/$(python_perf_so) test_make_perf_o := test -f $(PERF_O)/perf.o test_make_util_map_o := test -f $(PERF_O)/util/map.o @@ -218,19 +238,7 @@ installed_files_bin := bin/perf installed_files_bin += etc/bash_completion.d/perf installed_files_bin += libexec/perf-core/perf-archive -installed_files_plugins := $(lib)/traceevent/plugins/plugin_cfg80211.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_scsi.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_xen.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_function.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_sched_switch.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_mac80211.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_kvm.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_kmem.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_hrtimer.so -installed_files_plugins += $(lib)/traceevent/plugins/plugin_jbd2.so - installed_files_all := $(installed_files_bin) -installed_files_all += $(installed_files_plugins) test_make_install := $(call test_dest_files,$(installed_files_all)) test_make_install_O := $(call test_dest_files,$(installed_files_all)) @@ -266,6 +274,9 @@ test_make_install_info_O := $(test_ok) test_make_install_pdf := $(test_ok) test_make_install_pdf_O := $(test_ok) +test_make_libbpf_dynamic := ldd $(PERF_O)/perf | grep -q libbpf +test_make_libbpf_dynamic_O := ldd $$TMP_O/perf | grep -q libbpf + test_make_python_perf_so_O := test -f $$TMP_O/python/perf.so test_make_perf_o_O := test -f $$TMP_O/perf.o test_make_util_map_o_O := test -f $$TMP_O/util/map.o @@ -305,6 +316,26 @@ $(run): $(call test,$@) && \ rm -rf $@ $$TMP_DEST || (cat $@ ; false) +make_with_gtk2: + $(call clean) + @TMP_DEST=$$(mktemp -d); \ + cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \ + printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \ + ( eval $$cmd ) >> $@ 2>&1; \ + echo " test: $(call test,$@)" >> $@ 2>&1; \ + $(call test,$@) && \ + rm -rf $@ $$TMP_DEST || (cat $@ ; false) + +make_static: + $(call clean) + @TMP_DEST=$$(mktemp -d); \ + cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \ + printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \ + ( eval $$cmd ) >> $@ 2>&1; \ + echo " test: $(call test,$@)" >> $@ 2>&1; \ + $(call test,$@) && \ + rm -rf $@ $$TMP_DEST || (cat $@ ; false) + $(run_O): $(call clean) @TMP_O=$$(mktemp -d); \ diff --git a/tools/perf/tests/maps.c b/tools/perf/tests/maps.c index edcbc70ff9d6..4f1f9385ea9c 100644 --- a/tools/perf/tests/maps.c +++ b/tools/perf/tests/maps.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include <inttypes.h> #include <linux/compiler.h> #include <linux/kernel.h> #include "tests.h" @@ -13,29 +14,65 @@ struct map_def { u64 end; }; -static int check_maps(struct map_def *merged, unsigned int size, struct maps *maps) +struct check_maps_cb_args { + struct map_def *merged; + unsigned int i; +}; + +static int check_maps_cb(struct map *map, void *data) { - struct map *map; - unsigned int i = 0; + struct check_maps_cb_args *args = data; + struct map_def *merged = &args->merged[args->i]; + + if (map__start(map) != merged->start || + map__end(map) != merged->end || + strcmp(dso__name(map__dso(map)), merged->name) || + refcount_read(map__refcnt(map)) != 1) { + return 1; + } + args->i++; + return 0; +} - maps__for_each_entry(maps, map) { - if (i > 0) - TEST_ASSERT_VAL("less maps expected", (map && i < size) || (!map && i == size)); +static int failed_cb(struct map *map, void *data __maybe_unused) +{ + pr_debug("\tstart: %" PRIu64 " end: %" PRIu64 " name: '%s' refcnt: %d\n", + map__start(map), + map__end(map), + dso__name(map__dso(map)), + refcount_read(map__refcnt(map))); - TEST_ASSERT_VAL("wrong map start", map->start == merged[i].start); - TEST_ASSERT_VAL("wrong map end", map->end == merged[i].end); - TEST_ASSERT_VAL("wrong map name", !strcmp(map->dso->name, merged[i].name)); - TEST_ASSERT_VAL("wrong map refcnt", refcount_read(&map->refcnt) == 1); + return 0; +} - i++; +static int check_maps(struct map_def *merged, unsigned int size, struct maps *maps) +{ + bool failed = false; + + if (maps__nr_maps(maps) != size) { + pr_debug("Expected %d maps, got %d", size, maps__nr_maps(maps)); + failed = true; + } else { + struct check_maps_cb_args args = { + .merged = merged, + .i = 0, + }; + failed = maps__for_each_map(maps, check_maps_cb, &args); } - - return TEST_OK; + if (failed) { + pr_debug("Expected:\n"); + for (unsigned int i = 0; i < size; i++) { + pr_debug("\tstart: %" PRIu64 " end: %" PRIu64 " name: '%s' refcnt: 1\n", + merged[i].start, merged[i].end, merged[i].name); + } + pr_debug("Got:\n"); + maps__for_each_map(maps, failed_cb, NULL); + } + return failed ? TEST_FAIL : TEST_OK; } -int test__maps__merge_in(struct test *t __maybe_unused, int subtest __maybe_unused) +static int test__maps__merge_in(struct test_suite *t __maybe_unused, int subtest __maybe_unused) { - struct maps maps; unsigned int i; struct map_def bpf_progs[] = { { "bpf_prog_1", 200, 300 }, @@ -63,8 +100,9 @@ int test__maps__merge_in(struct test *t __maybe_unused, int subtest __maybe_unus }; struct map *map_kcore1, *map_kcore2, *map_kcore3; int ret; + struct maps *maps = maps__new(NULL); - maps__init(&maps, NULL); + TEST_ASSERT_VAL("failed to create maps", maps); for (i = 0; i < ARRAY_SIZE(bpf_progs); i++) { struct map *map; @@ -72,9 +110,9 @@ int test__maps__merge_in(struct test *t __maybe_unused, int subtest __maybe_unus map = dso__new_map(bpf_progs[i].name); TEST_ASSERT_VAL("failed to create map", map); - map->start = bpf_progs[i].start; - map->end = bpf_progs[i].end; - maps__insert(&maps, map); + map__set_start(map, bpf_progs[i].start); + map__set_end(map, bpf_progs[i].end); + TEST_ASSERT_VAL("failed to insert map", maps__insert(maps, map) == 0); map__put(map); } @@ -88,33 +126,40 @@ int test__maps__merge_in(struct test *t __maybe_unused, int subtest __maybe_unus TEST_ASSERT_VAL("failed to create map", map_kcore3); /* kcore1 map overlaps over all bpf maps */ - map_kcore1->start = 100; - map_kcore1->end = 1000; + map__set_start(map_kcore1, 100); + map__set_end(map_kcore1, 1000); /* kcore2 map hides behind bpf_prog_2 */ - map_kcore2->start = 550; - map_kcore2->end = 570; + map__set_start(map_kcore2, 550); + map__set_end(map_kcore2, 570); /* kcore3 map hides behind bpf_prog_3, kcore1 and adds new map */ - map_kcore3->start = 880; - map_kcore3->end = 1100; + map__set_start(map_kcore3, 880); + map__set_end(map_kcore3, 1100); - ret = maps__merge_in(&maps, map_kcore1); + ret = maps__merge_in(maps, map_kcore1); TEST_ASSERT_VAL("failed to merge map", !ret); - ret = check_maps(merged12, ARRAY_SIZE(merged12), &maps); + ret = check_maps(merged12, ARRAY_SIZE(merged12), maps); TEST_ASSERT_VAL("merge check failed", !ret); - ret = maps__merge_in(&maps, map_kcore2); + ret = maps__merge_in(maps, map_kcore2); TEST_ASSERT_VAL("failed to merge map", !ret); - ret = check_maps(merged12, ARRAY_SIZE(merged12), &maps); + ret = check_maps(merged12, ARRAY_SIZE(merged12), maps); TEST_ASSERT_VAL("merge check failed", !ret); - ret = maps__merge_in(&maps, map_kcore3); + ret = maps__merge_in(maps, map_kcore3); TEST_ASSERT_VAL("failed to merge map", !ret); - ret = check_maps(merged3, ARRAY_SIZE(merged3), &maps); + ret = check_maps(merged3, ARRAY_SIZE(merged3), maps); TEST_ASSERT_VAL("merge check failed", !ret); + + maps__zput(maps); + map__zput(map_kcore1); + map__zput(map_kcore2); + map__zput(map_kcore3); return TEST_OK; } + +DEFINE_SUITE("maps__merge_in", maps__merge_in); diff --git a/tools/perf/tests/mem.c b/tools/perf/tests/mem.c index 673a11a6cd1b..cb3d749e157b 100644 --- a/tools/perf/tests/mem.c +++ b/tools/perf/tests/mem.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include "util/map_symbol.h" #include "util/mem-events.h" +#include "util/mem-info.h" #include "util/symbol.h" #include "linux/perf_event.h" #include "util/debug.h" @@ -12,18 +13,20 @@ static int check(union perf_mem_data_src data_src, { char out[100]; char failure[100]; - struct mem_info mi = { .data_src = data_src }; - + struct mem_info *mi = mem_info__new(); int n; - n = perf_mem__snp_scnprintf(out, sizeof out, &mi); - n += perf_mem__lvl_scnprintf(out + n, sizeof out - n, &mi); + TEST_ASSERT_VAL("Memory allocation failed", mi); + *mem_info__data_src(mi) = data_src; + n = perf_mem__snp_scnprintf(out, sizeof out, mi); + n += perf_mem__lvl_scnprintf(out + n, sizeof out - n, mi); + mem_info__put(mi); scnprintf(failure, sizeof failure, "unexpected %s", out); TEST_ASSERT_VAL(failure, !strcmp(string, out)); return 0; } -int test__mem(struct test *text __maybe_unused, int subtest __maybe_unused) +static int test__mem(struct test_suite *text __maybe_unused, int subtest __maybe_unused) { int ret = 0; union perf_mem_data_src src; @@ -56,3 +59,5 @@ int test__mem(struct test *text __maybe_unused, int subtest __maybe_unused) return ret; } + +DEFINE_SUITE("Test data source output", mem); diff --git a/tools/perf/tests/mem2node.c b/tools/perf/tests/mem2node.c index a258bd51f1a4..a0e88c496107 100644 --- a/tools/perf/tests/mem2node.c +++ b/tools/perf/tests/mem2node.c @@ -25,14 +25,15 @@ static unsigned long *get_bitmap(const char *str, int nbits) { struct perf_cpu_map *map = perf_cpu_map__new(str); unsigned long *bm = NULL; - int i; - bm = bitmap_alloc(nbits); + bm = bitmap_zalloc(nbits); if (map && bm) { - for (i = 0; i < map->nr; i++) { - set_bit(map->map[i], bm); - } + struct perf_cpu cpu; + int i; + + perf_cpu_map__for_each_cpu(cpu, i, map) + __set_bit(cpu.cpu, bm); } if (map) @@ -43,7 +44,7 @@ static unsigned long *get_bitmap(const char *str, int nbits) return bm && map ? bm : NULL; } -int test__mem2node(struct test *t __maybe_unused, int subtest __maybe_unused) +static int test__mem2node(struct test_suite *t __maybe_unused, int subtest __maybe_unused) { struct mem2node map; struct memory_node nodes[3]; @@ -77,3 +78,5 @@ int test__mem2node(struct test *t __maybe_unused, int subtest __maybe_unused) mem2node__exit(&map); return 0; } + +DEFINE_SUITE("mem2node", mem2node); diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c index 7b0dbfc0e17d..bd2106628b34 100644 --- a/tools/perf/tests/mmap-basic.c +++ b/tools/perf/tests/mmap-basic.c @@ -1,17 +1,17 @@ // SPDX-License-Identifier: GPL-2.0 #include <errno.h> #include <inttypes.h> -/* For the CLR_() macros */ -#include <pthread.h> #include <stdlib.h> #include <perf/cpumap.h> #include "debug.h" +#include "event.h" #include "evlist.h" #include "evsel.h" #include "thread_map.h" #include "tests.h" #include "util/mmap.h" +#include "util/sample.h" #include <linux/err.h> #include <linux/kernel.h> #include <linux/string.h> @@ -29,9 +29,9 @@ * 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(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - int err = -1; + int err = TEST_FAIL; union perf_event *event; struct perf_thread_map *threads; struct perf_cpu_map *cpus; @@ -52,24 +52,25 @@ int test__basic_mmap(struct test *test __maybe_unused, int subtest __maybe_unuse return -1; } - cpus = perf_cpu_map__new(NULL); + cpus = perf_cpu_map__new_online_cpus(); if (cpus == NULL) { pr_debug("perf_cpu_map__new\n"); goto out_free_threads; } CPU_ZERO(&cpu_set); - CPU_SET(cpus->map[0], &cpu_set); + CPU_SET(perf_cpu_map__cpu(cpus, 0).cpu, &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], str_error_r(errno, sbuf, sizeof(sbuf))); + perf_cpu_map__cpu(cpus, 0).cpu, + str_error_r(errno, sbuf, sizeof(sbuf))); goto out_free_cpus; } evlist = evlist__new(); if (evlist == NULL) { - pr_debug("perf_evlist__new\n"); + pr_debug("evlist__new\n"); goto out_free_cpus; } @@ -82,6 +83,10 @@ int test__basic_mmap(struct test *test __maybe_unused, int subtest __maybe_unuse evsels[i] = evsel__newtp("syscalls", name); if (IS_ERR(evsels[i])) { pr_debug("evsel__new(%s)\n", name); + if (PTR_ERR(evsels[i]) == -EACCES) { + /* Permissions failure, flag the failure as a skip. */ + err = TEST_SKIP; + } goto out_delete_evlist; } @@ -109,8 +114,7 @@ int test__basic_mmap(struct test *test __maybe_unused, int subtest __maybe_unuse for (i = 0; i < nsyscalls; ++i) for (j = 0; j < expected_nr_events[i]; ++j) { - int foo = syscalls[i](); - ++foo; + syscalls[i](); } md = &evlist->mmap[0]; @@ -126,20 +130,23 @@ int test__basic_mmap(struct test *test __maybe_unused, int subtest __maybe_unuse goto out_delete_evlist; } - err = perf_evlist__parse_sample(evlist, event, &sample); + perf_sample__init(&sample, /*all=*/false); + err = evlist__parse_sample(evlist, event, &sample); if (err) { pr_err("Can't parse sample, err = %d\n", err); + perf_sample__exit(&sample); goto out_delete_evlist; } err = -1; - evsel = perf_evlist__id2evsel(evlist, sample.id); + evsel = evlist__id2evsel(evlist, sample.id); + perf_sample__exit(&sample); if (evsel == NULL) { pr_debug("event with id %" PRIu64 " doesn't map to an evsel\n", sample.id); goto out_delete_evlist; } - nr_events[evsel->idx]++; + nr_events[evsel->core.idx]++; perf_mmap__consume(&md->core); } perf_mmap__read_done(&md->core); @@ -147,10 +154,10 @@ int test__basic_mmap(struct test *test __maybe_unused, int subtest __maybe_unuse out_init: err = 0; evlist__for_each_entry(evlist, evsel) { - if (nr_events[evsel->idx] != expected_nr_events[evsel->idx]) { + if (nr_events[evsel->core.idx] != expected_nr_events[evsel->core.idx]) { pr_debug("expected %d %s events, got %d\n", - expected_nr_events[evsel->idx], - evsel__name(evsel), nr_events[evsel->idx]); + expected_nr_events[evsel->core.idx], + evsel__name(evsel), nr_events[evsel->core.idx]); err = -1; goto out_delete_evlist; } @@ -158,11 +165,148 @@ out_init: out_delete_evlist: evlist__delete(evlist); - cpus = NULL; - threads = NULL; out_free_cpus: perf_cpu_map__put(cpus); out_free_threads: perf_thread_map__put(threads); return err; } + +static int test_stat_user_read(int event) +{ + struct perf_counts_values counts = { .val = 0 }; + struct perf_thread_map *threads; + struct perf_evsel *evsel; + struct perf_event_mmap_page *pc; + struct perf_event_attr attr = { + .type = PERF_TYPE_HARDWARE, + .config = event, +#ifdef __aarch64__ + .config1 = 0x2, /* Request user access */ +#endif + }; + int err, i, ret = TEST_FAIL; + bool opened = false, mapped = false; + + threads = perf_thread_map__new_dummy(); + TEST_ASSERT_VAL("failed to create threads", threads); + + perf_thread_map__set_pid(threads, 0, 0); + + evsel = perf_evsel__new(&attr); + TEST_ASSERT_VAL("failed to create evsel", evsel); + + err = perf_evsel__open(evsel, NULL, threads); + if (err) { + pr_err("failed to open evsel: %s\n", strerror(-err)); + ret = TEST_SKIP; + goto out; + } + opened = true; + + err = perf_evsel__mmap(evsel, 0); + if (err) { + pr_err("failed to mmap evsel: %s\n", strerror(-err)); + goto out; + } + mapped = true; + + pc = perf_evsel__mmap_base(evsel, 0, 0); + if (!pc) { + pr_err("failed to get mmapped address\n"); + goto out; + } + + if (!pc->cap_user_rdpmc || !pc->index) { + pr_err("userspace counter access not %s\n", + !pc->cap_user_rdpmc ? "supported" : "enabled"); + ret = TEST_SKIP; + goto out; + } + if (pc->pmc_width < 32) { + pr_err("userspace counter width not set (%d)\n", pc->pmc_width); + goto out; + } + + perf_evsel__read(evsel, 0, 0, &counts); + if (counts.val == 0) { + pr_err("failed to read value for evsel\n"); + goto out; + } + + for (i = 0; i < 5; i++) { + volatile int count = 0x10000 << i; + __u64 start, end, last = 0; + + pr_debug("\tloop = %u, ", count); + + perf_evsel__read(evsel, 0, 0, &counts); + start = counts.val; + + while (count--) ; + + perf_evsel__read(evsel, 0, 0, &counts); + end = counts.val; + + if ((end - start) < last) { + pr_err("invalid counter data: end=%llu start=%llu last= %llu\n", + end, start, last); + goto out; + } + last = end - start; + pr_debug("count = %llu\n", end - start); + } + ret = TEST_OK; + +out: + if (mapped) + perf_evsel__munmap(evsel); + if (opened) + perf_evsel__close(evsel); + perf_evsel__delete(evsel); + + perf_thread_map__put(threads); + return ret; +} + +static int test__mmap_user_read_instr(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + return test_stat_user_read(PERF_COUNT_HW_INSTRUCTIONS); +} + +static int test__mmap_user_read_cycles(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + return test_stat_user_read(PERF_COUNT_HW_CPU_CYCLES); +} + +static struct test_case tests__basic_mmap[] = { + TEST_CASE_REASON("Read samples using the mmap interface", + basic_mmap, + "permissions"), + TEST_CASE_REASON("User space counter reading of instructions", + mmap_user_read_instr, +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ + (defined(__riscv) && __riscv_xlen == 64) + "permissions" +#else + "unsupported" +#endif + ), + TEST_CASE_REASON("User space counter reading of cycles", + mmap_user_read_cycles, +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ + (defined(__riscv) && __riscv_xlen == 64) + "permissions" +#else + "unsupported" +#endif + ), + { .name = NULL, } +}; + +struct test_suite suite__basic_mmap = { + .desc = "mmap interface tests", + .test_cases = tests__basic_mmap, +}; diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c index 8d9d4cbff76d..446a3615d720 100644 --- a/tools/perf/tests/mmap-thread-lookup.c +++ b/tools/perf/tests/mmap-thread-lookup.c @@ -135,7 +135,7 @@ static int synth_all(struct machine *machine) { return perf_event__synthesize_threads(NULL, perf_event__process, - machine, 0, 1); + machine, 1, 0, 1); } static int synth_process(struct machine *machine) @@ -147,7 +147,7 @@ static int synth_process(struct machine *machine) err = perf_event__synthesize_thread_map(NULL, map, perf_event__process, - machine, 0); + machine, 1, 0); perf_thread_map__put(map); return err; @@ -187,6 +187,7 @@ static int mmap_events(synth_cb synth) struct addr_location al; struct thread *thread; + addr_location__init(&al); thread = machine__findnew_thread(machine, getpid(), td->tid); pr_debug("looking for map %p\n", td->map); @@ -199,13 +200,14 @@ static int mmap_events(synth_cb synth) if (!al.map) { pr_debug("failed, couldn't find map\n"); err = -1; + addr_location__exit(&al); break; } - pr_debug("map %p, addr %" PRIx64 "\n", al.map, al.map->start); + pr_debug("map %p, addr %" PRIx64 "\n", al.map, map__start(al.map)); + addr_location__exit(&al); } - machine__delete_threads(machine); machine__delete(machine); return err; } @@ -224,15 +226,17 @@ static int mmap_events(synth_cb synth) * * by using all thread objects. */ -int test__mmap_thread_lookup(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__mmap_thread_lookup(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { /* perf_event__synthesize_threads synthesize */ - TEST_ASSERT_VAL("failed with sythesizing all", + TEST_ASSERT_VAL("failed with synthesizing all", !mmap_events(synth_all)); /* perf_event__synthesize_thread_map synthesize */ - TEST_ASSERT_VAL("failed with sythesizing process", + TEST_ASSERT_VAL("failed with synthesizing process", !mmap_events(synth_process)); return 0; } + +DEFINE_SUITE("Lookup mmap thread", mmap_thread_lookup); diff --git a/tools/perf/tests/openat-syscall-all-cpus.c b/tools/perf/tests/openat-syscall-all-cpus.c index 71f85e2cc127..fb114118c876 100644 --- a/tools/perf/tests/openat-syscall-all-cpus.c +++ b/tools/perf/tests/openat-syscall-all-cpus.c @@ -2,7 +2,7 @@ #include <errno.h> #include <inttypes.h> /* For the CPU_* macros */ -#include <pthread.h> +#include <sched.h> #include <sys/types.h> #include <sys/stat.h> @@ -15,14 +15,15 @@ #include "tests.h" #include "thread_map.h" #include <perf/cpumap.h> -#include <internal/cpumap.h> #include "debug.h" #include "stat.h" #include "util/counts.h" -int test__openat_syscall_event_on_all_cpus(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__openat_syscall_event_on_all_cpus(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { - int err = -1, fd, cpu; + int err = TEST_FAIL, fd, idx; + struct perf_cpu cpu; struct perf_cpu_map *cpus; struct evsel *evsel; unsigned int nr_openat_calls = 111, i; @@ -36,7 +37,7 @@ int test__openat_syscall_event_on_all_cpus(struct test *test __maybe_unused, int return -1; } - cpus = perf_cpu_map__new(NULL); + cpus = perf_cpu_map__new_online_cpus(); if (cpus == NULL) { pr_debug("perf_cpu_map__new\n"); goto out_thread_map_delete; @@ -48,6 +49,7 @@ int test__openat_syscall_event_on_all_cpus(struct test *test __maybe_unused, int if (IS_ERR(evsel)) { tracing_path__strerror_open_tp(errno, errbuf, sizeof(errbuf), "syscalls", "sys_enter_openat"); pr_debug("%s\n", errbuf); + err = TEST_SKIP; goto out_cpu_map_delete; } @@ -55,26 +57,27 @@ int test__openat_syscall_event_on_all_cpus(struct test *test __maybe_unused, int pr_debug("failed to open counter: %s, " "tweak /proc/sys/kernel/perf_event_paranoid?\n", str_error_r(errno, sbuf, sizeof(sbuf))); + err = TEST_SKIP; goto out_evsel_delete; } - for (cpu = 0; cpu < cpus->nr; ++cpu) { - unsigned int ncalls = nr_openat_calls + cpu; + perf_cpu_map__for_each_cpu(cpu, idx, cpus) { + unsigned int ncalls = nr_openat_calls + idx; /* * 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]); + if (cpu.cpu >= CPU_SETSIZE) { + pr_debug("Ignoring CPU %d\n", cpu.cpu); continue; } - CPU_SET(cpus->map[cpu], &cpu_set); + CPU_SET(cpu.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], + cpu.cpu, str_error_r(errno, sbuf, sizeof(sbuf))); goto out_close_fd; } @@ -82,38 +85,30 @@ int test__openat_syscall_event_on_all_cpus(struct test *test __maybe_unused, int fd = openat(0, "/etc/passwd", O_RDONLY); close(fd); } - CPU_CLR(cpus->map[cpu], &cpu_set); + CPU_CLR(cpu.cpu, &cpu_set); } - /* - * Here we need to explicitly preallocate the counts, as if - * we use the auto allocation it will allocate just for 1 cpu, - * as we start by cpu 0. - */ - if (evsel__alloc_counts(evsel, cpus->nr, 1) < 0) { - pr_debug("evsel__alloc_counts(ncpus=%d)\n", cpus->nr); - goto out_close_fd; - } + evsel->core.cpus = perf_cpu_map__get(cpus); - err = 0; + err = TEST_OK; - for (cpu = 0; cpu < cpus->nr; ++cpu) { + perf_cpu_map__for_each_cpu(cpu, idx, cpus) { unsigned int expected; - if (cpus->map[cpu] >= CPU_SETSIZE) + if (cpu.cpu >= CPU_SETSIZE) continue; - if (evsel__read_on_cpu(evsel, cpu, 0) < 0) { + if (evsel__read_on_cpu(evsel, idx, 0) < 0) { pr_debug("evsel__read_on_cpu\n"); - err = -1; + err = TEST_FAIL; break; } - expected = nr_openat_calls + cpu; - if (perf_counts(evsel->counts, cpu, 0)->val != expected) { + expected = nr_openat_calls + idx; + if (perf_counts(evsel->counts, idx, 0)->val != expected) { pr_debug("evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %" PRIu64 "\n", - expected, cpus->map[cpu], perf_counts(evsel->counts, cpu, 0)->val); - err = -1; + expected, cpu.cpu, perf_counts(evsel->counts, idx, 0)->val); + err = TEST_FAIL; } } @@ -128,3 +123,16 @@ out_thread_map_delete: perf_thread_map__put(threads); return err; } + + +static struct test_case tests__openat_syscall_event_on_all_cpus[] = { + TEST_CASE_REASON("Detect openat syscall event on all cpus", + openat_syscall_event_on_all_cpus, + "permissions"), + { .name = NULL, } +}; + +struct test_suite suite__openat_syscall_event_on_all_cpus = { + .desc = "Detect openat syscall event on all cpus", + .test_cases = tests__openat_syscall_event_on_all_cpus, +}; diff --git a/tools/perf/tests/openat-syscall-tp-fields.c b/tools/perf/tests/openat-syscall-tp-fields.c index 1f5f5e79ae25..0ef4ba7c1571 100644 --- a/tools/perf/tests/openat-syscall-tp-fields.c +++ b/tools/perf/tests/openat-syscall-tp-fields.c @@ -14,6 +14,7 @@ #include "util/mmap.h" #include <errno.h> #include <perf/mmap.h> +#include "util/sample.h" #ifndef O_DIRECTORY #define O_DIRECTORY 00200000 @@ -22,7 +23,8 @@ #define AT_FDCWD -100 #endif -int test__syscall_openat_tp_fields(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__syscall_openat_tp_fields(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { struct record_opts opts = { .target = { @@ -38,25 +40,26 @@ int test__syscall_openat_tp_fields(struct test *test __maybe_unused, int subtest int flags = O_RDONLY | O_DIRECTORY; struct evlist *evlist = evlist__new(); struct evsel *evsel; - int err = -1, i, nr_events = 0, nr_polls = 0; + int ret = TEST_FAIL, err, i, nr_events = 0, nr_polls = 0; char sbuf[STRERR_BUFSIZE]; if (evlist == NULL) { - pr_debug("%s: perf_evlist__new\n", __func__); + pr_debug("%s: evlist__new\n", __func__); goto out; } evsel = evsel__newtp("syscalls", "sys_enter_openat"); if (IS_ERR(evsel)) { pr_debug("%s: evsel__newtp\n", __func__); + ret = PTR_ERR(evsel) == -EACCES ? TEST_SKIP : TEST_FAIL; goto out_delete_evlist; } evlist__add(evlist, evsel); - err = perf_evlist__create_maps(evlist, &opts.target); + err = evlist__create_maps(evlist, &opts.target); if (err < 0) { - pr_debug("%s: perf_evlist__create_maps\n", __func__); + pr_debug("%s: evlist__create_maps\n", __func__); goto out_delete_evlist; } @@ -108,14 +111,16 @@ int test__syscall_openat_tp_fields(struct test *test __maybe_unused, int subtest continue; } + perf_sample__init(&sample, /*all=*/false); err = evsel__parse_sample(evsel, event, &sample); if (err) { pr_debug("Can't parse sample, err = %d\n", err); + perf_sample__exit(&sample); goto out_delete_evlist; } tp_flags = evsel__intval(evsel, &sample, "flags"); - + perf_sample__exit(&sample); if (flags != tp_flags) { pr_debug("%s: Expected flags=%#x, got %#x\n", __func__, flags, tp_flags); @@ -136,9 +141,21 @@ int test__syscall_openat_tp_fields(struct test *test __maybe_unused, int subtest } } out_ok: - err = 0; + ret = TEST_OK; out_delete_evlist: evlist__delete(evlist); out: - return err; + return ret; } + +static struct test_case tests__syscall_openat_tp_fields[] = { + TEST_CASE_REASON("syscalls:sys_enter_openat event fields", + syscall_openat_tp_fields, + "permissions"), + { .name = NULL, } +}; + +struct test_suite suite__syscall_openat_tp_fields = { + .desc = "syscalls:sys_enter_openat event fields", + .test_cases = tests__syscall_openat_tp_fields, +}; diff --git a/tools/perf/tests/openat-syscall.c b/tools/perf/tests/openat-syscall.c index 85a8f0fe7aea..131b62271bfa 100644 --- a/tools/perf/tests/openat-syscall.c +++ b/tools/perf/tests/openat-syscall.c @@ -7,15 +7,17 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> +#include <unistd.h> #include "thread_map.h" #include "evsel.h" #include "debug.h" #include "tests.h" #include "util/counts.h" -int test__openat_syscall_event(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__openat_syscall_event(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { - int err = -1, fd; + int err = TEST_FAIL, fd; struct evsel *evsel; unsigned int nr_openat_calls = 111, i; struct perf_thread_map *threads = thread_map__new(-1, getpid(), UINT_MAX); @@ -24,13 +26,14 @@ int test__openat_syscall_event(struct test *test __maybe_unused, int subtest __m if (threads == NULL) { pr_debug("thread_map__new\n"); - return -1; + return TEST_FAIL; } evsel = evsel__newtp("syscalls", "sys_enter_openat"); if (IS_ERR(evsel)) { tracing_path__strerror_open_tp(errno, errbuf, sizeof(errbuf), "syscalls", "sys_enter_openat"); pr_debug("%s\n", errbuf); + err = TEST_SKIP; goto out_thread_map_delete; } @@ -38,6 +41,7 @@ int test__openat_syscall_event(struct test *test __maybe_unused, int subtest __m pr_debug("failed to open counter: %s, " "tweak /proc/sys/kernel/perf_event_paranoid?\n", str_error_r(errno, sbuf, sizeof(sbuf))); + err = TEST_SKIP; goto out_evsel_delete; } @@ -57,7 +61,7 @@ int test__openat_syscall_event(struct test *test __maybe_unused, int subtest __m goto out_close_fd; } - err = 0; + err = TEST_OK; out_close_fd: perf_evsel__close_fd(&evsel->core); out_evsel_delete: @@ -66,3 +70,15 @@ out_thread_map_delete: perf_thread_map__put(threads); return err; } + +static struct test_case tests__openat_syscall_event[] = { + TEST_CASE_REASON("Detect openat syscall event", + openat_syscall_event, + "permissions"), + { .name = NULL, } +}; + +struct test_suite suite__openat_syscall_event = { + .desc = "Detect openat syscall event", + .test_cases = tests__openat_syscall_event, +}; diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c index 895188b63f96..5ec2e5607987 100644 --- a/tools/perf/tests/parse-events.c +++ b/tools/perf/tests/parse-events.c @@ -6,8 +6,10 @@ #include "tests.h" #include "debug.h" #include "pmu.h" +#include "pmus.h" #include <dirent.h> #include <errno.h> +#include "fncache.h" #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> @@ -18,9 +20,43 @@ #define PERF_TP_SAMPLE_TYPE (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | \ PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD) +static int num_core_entries(void) +{ + /* + * If the kernel supports extended type, expect events to be + * opened once for each core PMU type. Otherwise fall back to the legacy + * behavior of opening only one event even though there are multiple + * PMUs + */ + if (perf_pmus__supports_extended_type()) + return perf_pmus__num_core_pmus(); + + return 1; +} + +static bool test_config(const struct evsel *evsel, __u64 expected_config) +{ + __u32 type = evsel->core.attr.type; + __u64 config = evsel->core.attr.config; + + if (type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE) { + /* + * HARDWARE and HW_CACHE events encode the PMU's extended type + * in the top 32-bits. Mask in order to ignore. + */ + config &= PERF_HW_EVENT_MASK; + } + return config == expected_config; +} + +static bool test_perf_config(const struct perf_evsel *evsel, __u64 expected_config) +{ + return (evsel->attr.config & PERF_HW_EVENT_MASK) == expected_config; +} + #if defined(__s390x__) /* Return true if kvm module is available and loaded. Test this - * and retun success when trace point kvm_s390_create_vm + * and return success when trace point kvm_s390_create_vm * exists. Otherwise this test always fails. */ static bool kvm_s390_create_vm_valid(void) @@ -49,12 +85,12 @@ static int test__checkevent_tracepoint(struct evlist *evlist) struct evsel *evsel = evlist__first(evlist); TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 0 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of groups", 0 == evlist__nr_groups(evlist)); TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->core.attr.type); TEST_ASSERT_VAL("wrong sample_type", PERF_TP_SAMPLE_TYPE == evsel->core.attr.sample_type); TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->core.attr.sample_period); - return 0; + return TEST_OK; } static int test__checkevent_tracepoint_multi(struct evlist *evlist) @@ -62,7 +98,7 @@ static int test__checkevent_tracepoint_multi(struct evlist *evlist) struct evsel *evsel; TEST_ASSERT_VAL("wrong number of entries", evlist->core.nr_entries > 1); - TEST_ASSERT_VAL("wrong number of groups", 0 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of groups", 0 == evlist__nr_groups(evlist)); evlist__for_each_entry(evlist, evsel) { TEST_ASSERT_VAL("wrong type", @@ -72,17 +108,45 @@ static int test__checkevent_tracepoint_multi(struct evlist *evlist) TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->core.attr.sample_period); } - return 0; + return TEST_OK; } static int test__checkevent_raw(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); - - TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0x1a == evsel->core.attr.config); - return 0; + struct perf_evsel *evsel; + bool raw_type_match = false; + + TEST_ASSERT_VAL("wrong number of entries", 0 != evlist->core.nr_entries); + + perf_evlist__for_each_evsel(&evlist->core, evsel) { + struct perf_pmu *pmu __maybe_unused = NULL; + bool type_matched = false; + + TEST_ASSERT_VAL("wrong config", test_perf_config(evsel, 0x1a)); + TEST_ASSERT_VAL("event not parsed as raw type", + evsel->attr.type == PERF_TYPE_RAW); +#if defined(__aarch64__) + /* + * Arm doesn't have a real raw type PMU in sysfs, so raw events + * would never match any PMU. However, RAW events on Arm will + * always successfully open on the first available core PMU + * so no need to test for a matching type here. + */ + type_matched = raw_type_match = true; +#else + while ((pmu = perf_pmus__scan(pmu)) != NULL) { + if (pmu->type == evsel->attr.type) { + TEST_ASSERT_VAL("PMU type expected once", !type_matched); + type_matched = true; + if (pmu->type == PERF_TYPE_RAW) + raw_type_match = true; + } + } +#endif + TEST_ASSERT_VAL("No PMU found for type", type_matched); + } + TEST_ASSERT_VAL("Raw PMU not matched", raw_type_match); + return TEST_OK; } static int test__checkevent_numeric(struct evlist *evlist) @@ -91,40 +155,62 @@ static int test__checkevent_numeric(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", 1 == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 1 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 1)); + return TEST_OK; +} + + +static int assert_hw(struct perf_evsel *evsel, enum perf_hw_id id, const char *name) +{ + struct perf_pmu *pmu; + + if (evsel->attr.type == PERF_TYPE_HARDWARE) { + TEST_ASSERT_VAL("wrong config", test_perf_config(evsel, id)); + return 0; + } + pmu = perf_pmus__find_by_type(evsel->attr.type); + + TEST_ASSERT_VAL("unexpected PMU type", pmu); + TEST_ASSERT_VAL("PMU missing event", perf_pmu__have_event(pmu, name)); return 0; } static int test__checkevent_symbolic_name(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); + struct perf_evsel *evsel; - TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_INSTRUCTIONS == evsel->core.attr.config); - return 0; + TEST_ASSERT_VAL("wrong number of entries", 0 != evlist->core.nr_entries); + + perf_evlist__for_each_evsel(&evlist->core, evsel) { + int ret = assert_hw(evsel, PERF_COUNT_HW_INSTRUCTIONS, "instructions"); + + if (ret) + return ret; + } + + return TEST_OK; } static int test__checkevent_symbolic_name_config(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); + struct perf_evsel *evsel; - TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - /* - * The period value gets configured within perf_evlist__config, - * while this test executes only parse events method. - */ - TEST_ASSERT_VAL("wrong period", - 0 == evsel->core.attr.sample_period); - TEST_ASSERT_VAL("wrong config1", - 0 == evsel->core.attr.config1); - TEST_ASSERT_VAL("wrong config2", - 1 == evsel->core.attr.config2); - return 0; + TEST_ASSERT_VAL("wrong number of entries", 0 != evlist->core.nr_entries); + + perf_evlist__for_each_evsel(&evlist->core, evsel) { + int ret = assert_hw(evsel, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + + if (ret) + return ret; + /* + * The period value gets configured within evlist__config, + * while this test executes only parse events method. + */ + TEST_ASSERT_VAL("wrong period", 0 == evsel->attr.sample_period); + TEST_ASSERT_VAL("wrong config1", 0 == evsel->attr.config1); + TEST_ASSERT_VAL("wrong config2", 1 == evsel->attr.config2); + } + return TEST_OK; } static int test__checkevent_symbolic_alias(struct evlist *evlist) @@ -133,19 +219,21 @@ static int test__checkevent_symbolic_alias(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_SW_PAGE_FAULTS == evsel->core.attr.config); - return 0; + TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_SW_PAGE_FAULTS)); + return TEST_OK; } static int test__checkevent_genhw(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); + struct perf_evsel *evsel; - TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HW_CACHE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", (1 << 16) == evsel->core.attr.config); - return 0; + TEST_ASSERT_VAL("wrong number of entries", 0 != evlist->core.nr_entries); + + perf_evlist__for_each_entry(&evlist->core, evsel) { + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HW_CACHE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", test_perf_config(evsel, 1 << 16)); + } + return TEST_OK; } static int test__checkevent_breakpoint(struct evlist *evlist) @@ -154,12 +242,12 @@ static int test__checkevent_breakpoint(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 0)); TEST_ASSERT_VAL("wrong bp_type", (HW_BREAKPOINT_R | HW_BREAKPOINT_W) == evsel->core.attr.bp_type); TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_4 == evsel->core.attr.bp_len); - return 0; + return TEST_OK; } static int test__checkevent_breakpoint_x(struct evlist *evlist) @@ -168,11 +256,11 @@ static int test__checkevent_breakpoint_x(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 0)); TEST_ASSERT_VAL("wrong bp_type", HW_BREAKPOINT_X == evsel->core.attr.bp_type); - TEST_ASSERT_VAL("wrong bp_len", sizeof(long) == evsel->core.attr.bp_len); - return 0; + TEST_ASSERT_VAL("wrong bp_len", default_breakpoint_len() == evsel->core.attr.bp_len); + return TEST_OK; } static int test__checkevent_breakpoint_r(struct evlist *evlist) @@ -182,12 +270,12 @@ static int test__checkevent_breakpoint_r(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 0)); TEST_ASSERT_VAL("wrong bp_type", HW_BREAKPOINT_R == evsel->core.attr.bp_type); TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_4 == evsel->core.attr.bp_len); - return 0; + return TEST_OK; } static int test__checkevent_breakpoint_w(struct evlist *evlist) @@ -197,12 +285,12 @@ static int test__checkevent_breakpoint_w(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 0)); TEST_ASSERT_VAL("wrong bp_type", HW_BREAKPOINT_W == evsel->core.attr.bp_type); TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_4 == evsel->core.attr.bp_len); - return 0; + return TEST_OK; } static int test__checkevent_breakpoint_rw(struct evlist *evlist) @@ -212,12 +300,12 @@ static int test__checkevent_breakpoint_rw(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 0)); TEST_ASSERT_VAL("wrong bp_type", (HW_BREAKPOINT_R|HW_BREAKPOINT_W) == evsel->core.attr.bp_type); TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_4 == evsel->core.attr.bp_len); - return 0; + return TEST_OK; } static int test__checkevent_tracepoint_modifier(struct evlist *evlist) @@ -235,17 +323,15 @@ static int test__checkevent_tracepoint_modifier(struct evlist *evlist) static int test__checkevent_tracepoint_multi_modifier(struct evlist *evlist) { - struct evsel *evsel; + struct perf_evsel *evsel; TEST_ASSERT_VAL("wrong number of entries", evlist->core.nr_entries > 1); - evlist__for_each_entry(evlist, evsel) { - TEST_ASSERT_VAL("wrong exclude_user", - !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", - evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + perf_evlist__for_each_entry(&evlist->core, evsel) { + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); } return test__checkevent_tracepoint_multi(evlist); @@ -253,57 +339,65 @@ test__checkevent_tracepoint_multi_modifier(struct evlist *evlist) static int test__checkevent_raw_modifier(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); - - TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); + struct perf_evsel *evsel; + perf_evlist__for_each_entry(&evlist->core, evsel) { + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + } return test__checkevent_raw(evlist); } static int test__checkevent_numeric_modifier(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); - - TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); + struct perf_evsel *evsel; + perf_evlist__for_each_entry(&evlist->core, evsel) { + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + } return test__checkevent_numeric(evlist); } static int test__checkevent_symbolic_name_modifier(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); + struct perf_evsel *evsel; - TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == num_core_entries()); + perf_evlist__for_each_entry(&evlist->core, evsel) { + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + } return test__checkevent_symbolic_name(evlist); } static int test__checkevent_exclude_host_modifier(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); - - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + struct perf_evsel *evsel; + perf_evlist__for_each_entry(&evlist->core, evsel) { + TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + } return test__checkevent_symbolic_name(evlist); } static int test__checkevent_exclude_guest_modifier(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); - - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + struct perf_evsel *evsel; + perf_evlist__for_each_entry(&evlist->core, evsel) { + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + } return test__checkevent_symbolic_name(evlist); } @@ -321,13 +415,14 @@ static int test__checkevent_symbolic_alias_modifier(struct evlist *evlist) static int test__checkevent_genhw_modifier(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); - - TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); + struct perf_evsel *evsel; + perf_evlist__for_each_entry(&evlist->core, evsel) { + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + } return test__checkevent_genhw(evlist); } @@ -370,8 +465,7 @@ static int test__checkevent_breakpoint_modifier(struct evlist *evlist) TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong name", - !strcmp(evsel__name(evsel), "mem:0:u")); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "mem:0:u")); return test__checkevent_breakpoint(evlist); } @@ -384,8 +478,7 @@ static int test__checkevent_breakpoint_x_modifier(struct evlist *evlist) TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong name", - !strcmp(evsel__name(evsel), "mem:0:x:k")); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "mem:0:x:k")); return test__checkevent_breakpoint_x(evlist); } @@ -398,8 +491,7 @@ static int test__checkevent_breakpoint_r_modifier(struct evlist *evlist) TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong name", - !strcmp(evsel__name(evsel), "mem:0:r:hp")); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "mem:0:r:hp")); return test__checkevent_breakpoint_r(evlist); } @@ -412,8 +504,7 @@ static int test__checkevent_breakpoint_w_modifier(struct evlist *evlist) TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong name", - !strcmp(evsel__name(evsel), "mem:0:w:up")); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "mem:0:w:up")); return test__checkevent_breakpoint_w(evlist); } @@ -426,12 +517,93 @@ static int test__checkevent_breakpoint_rw_modifier(struct evlist *evlist) TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong name", - !strcmp(evsel__name(evsel), "mem:0:rw:kp")); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "mem:0:rw:kp")); return test__checkevent_breakpoint_rw(evlist); } +static int test__checkevent_breakpoint_modifier_name(struct evlist *evlist) +{ + struct evsel *evsel = evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "breakpoint")); + + return test__checkevent_breakpoint(evlist); +} + +static int test__checkevent_breakpoint_x_modifier_name(struct evlist *evlist) +{ + struct evsel *evsel = evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "breakpoint")); + + return test__checkevent_breakpoint_x(evlist); +} + +static int test__checkevent_breakpoint_r_modifier_name(struct evlist *evlist) +{ + struct evsel *evsel = evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "breakpoint")); + + return test__checkevent_breakpoint_r(evlist); +} + +static int test__checkevent_breakpoint_w_modifier_name(struct evlist *evlist) +{ + struct evsel *evsel = evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "breakpoint")); + + return test__checkevent_breakpoint_w(evlist); +} + +static int test__checkevent_breakpoint_rw_modifier_name(struct evlist *evlist) +{ + struct evsel *evsel = evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "breakpoint")); + + return test__checkevent_breakpoint_rw(evlist); +} + +static int test__checkevent_breakpoint_2_events(struct evlist *evlist) +{ + struct evsel *evsel = evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); + + TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "breakpoint1")); + + evsel = evsel__next(evsel); + + TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "breakpoint2")); + + return TEST_OK; +} + static int test__checkevent_pmu(struct evlist *evlist) { @@ -439,36 +611,40 @@ static int test__checkevent_pmu(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 10 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 10)); TEST_ASSERT_VAL("wrong config1", 1 == evsel->core.attr.config1); TEST_ASSERT_VAL("wrong config2", 3 == evsel->core.attr.config2); + TEST_ASSERT_VAL("wrong config3", 0 == evsel->core.attr.config3); /* - * The period value gets configured within perf_evlist__config, + * The period value gets configured within evlist__config, * while this test executes only parse events method. */ TEST_ASSERT_VAL("wrong period", 0 == evsel->core.attr.sample_period); - return 0; + return TEST_OK; } static int test__checkevent_list(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); - TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->core.nr_entries); + TEST_ASSERT_VAL("wrong number of entries", 3 <= evlist->core.nr_entries); /* r1 */ - TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 1 == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong config1", 0 == evsel->core.attr.config1); - TEST_ASSERT_VAL("wrong config2", 0 == evsel->core.attr.config2); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT != evsel->core.attr.type); + while (evsel->core.attr.type != PERF_TYPE_TRACEPOINT) { + TEST_ASSERT_VAL("wrong config", test_config(evsel, 1)); + TEST_ASSERT_VAL("wrong config1", 0 == evsel->core.attr.config1); + TEST_ASSERT_VAL("wrong config2", 0 == evsel->core.attr.config2); + TEST_ASSERT_VAL("wrong config3", 0 == evsel->core.attr.config3); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + evsel = evsel__next(evsel); + } /* syscalls:sys_enter_openat:k */ - evsel = evsel__next(evsel); TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->core.attr.type); TEST_ASSERT_VAL("wrong sample_type", PERF_TP_SAMPLE_TYPE == evsel->core.attr.sample_type); @@ -481,13 +657,13 @@ static int test__checkevent_list(struct evlist *evlist) /* 1:1:hp */ evsel = evsel__next(evsel); TEST_ASSERT_VAL("wrong type", 1 == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 1 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 1)); TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); - return 0; + return TEST_OK; } static int test__checkevent_pmu_name(struct evlist *evlist) @@ -497,18 +673,17 @@ static int test__checkevent_pmu_name(struct evlist *evlist) /* cpu/config=1,name=krava/u */ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 1 == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong name", !strcmp(evsel__name(evsel), "krava")); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 1)); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "krava")); /* cpu/config=2/u" */ evsel = evsel__next(evsel); TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 2 == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong name", - !strcmp(evsel__name(evsel), "cpu/config=2/u")); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 2)); + TEST_ASSERT_VAL("wrong name", evsel__name_is(evsel, "cpu/config=2/u")); - return 0; + return TEST_OK; } static int test__checkevent_pmu_partial_time_callgraph(struct evlist *evlist) @@ -518,10 +693,9 @@ static int test__checkevent_pmu_partial_time_callgraph(struct evlist *evlist) /* cpu/config=1,call-graph=fp,time,period=100000/ */ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 1 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 1)); /* - * The period, time and callgraph value gets configured - * within perf_evlist__config, + * The period, time and callgraph value gets configured within evlist__config, * while this test executes only parse events method. */ TEST_ASSERT_VAL("wrong period", 0 == evsel->core.attr.sample_period); @@ -531,17 +705,16 @@ static int test__checkevent_pmu_partial_time_callgraph(struct evlist *evlist) /* cpu/config=2,call-graph=no,time=0,period=2000/ */ evsel = evsel__next(evsel); TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 2 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 2)); /* - * The period, time and callgraph value gets configured - * within perf_evlist__config, + * The period, time and callgraph value gets configured within evlist__config, * while this test executes only parse events method. */ TEST_ASSERT_VAL("wrong period", 0 == evsel->core.attr.sample_period); TEST_ASSERT_VAL("wrong callgraph", !evsel__has_callchain(evsel)); TEST_ASSERT_VAL("wrong time", !(PERF_SAMPLE_TIME & evsel->core.attr.sample_type)); - return 0; + return TEST_OK; } static int test__checkevent_pmu_events(struct evlist *evlist) @@ -549,7 +722,8 @@ static int test__checkevent_pmu_events(struct evlist *evlist) struct evsel *evsel = evlist__first(evlist); TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type || + strcmp(evsel->pmu->name, "cpu")); TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); TEST_ASSERT_VAL("wrong exclude_kernel", @@ -557,29 +731,36 @@ static int test__checkevent_pmu_events(struct evlist *evlist) TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); TEST_ASSERT_VAL("wrong pinned", !evsel->core.attr.pinned); + TEST_ASSERT_VAL("wrong exclusive", !evsel->core.attr.exclusive); - return 0; + return TEST_OK; } static int test__checkevent_pmu_events_mix(struct evlist *evlist) { - struct evsel *evsel = evlist__first(evlist); - - /* pmu-event:u */ - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong exclude_user", - !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", - evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong pinned", !evsel->core.attr.pinned); + struct evsel *evsel = NULL; + /* + * The wild card event will be opened at least once, but it may be + * opened on each core PMU. + */ + TEST_ASSERT_VAL("wrong number of entries", evlist->core.nr_entries >= 2); + for (int i = 0; i < evlist->core.nr_entries - 1; i++) { + evsel = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + /* pmu-event:u */ + TEST_ASSERT_VAL("wrong exclude_user", + !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", + evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong pinned", !evsel->core.attr.pinned); + TEST_ASSERT_VAL("wrong exclusive", !evsel->core.attr.exclusive); + } /* cpu/pmu-event/u*/ evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type); + TEST_ASSERT_VAL("wrong type", evsel__find_pmu(evsel)->is_core); TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); TEST_ASSERT_VAL("wrong exclude_kernel", @@ -587,22 +768,23 @@ static int test__checkevent_pmu_events_mix(struct evlist *evlist) TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); TEST_ASSERT_VAL("wrong pinned", !evsel->core.attr.pinned); + TEST_ASSERT_VAL("wrong exclusive", !evsel->core.attr.pinned); - return 0; + return TEST_OK; } -static int test__checkterms_simple(struct list_head *terms) +static int test__checkterms_simple(struct parse_events_terms *terms) { struct parse_events_term *term; /* config=10 */ - term = list_entry(terms->next, struct parse_events_term, list); + term = list_entry(terms->terms.next, struct parse_events_term, list); TEST_ASSERT_VAL("wrong type term", term->type_term == PARSE_EVENTS__TERM_TYPE_CONFIG); TEST_ASSERT_VAL("wrong type val", term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); TEST_ASSERT_VAL("wrong val", term->val.num == 10); - TEST_ASSERT_VAL("wrong config", !term->config); + TEST_ASSERT_VAL("wrong config", !strcmp(term->config, "config")); /* config1 */ term = list_entry(term->list.next, struct parse_events_term, list); @@ -611,7 +793,7 @@ static int test__checkterms_simple(struct list_head *terms) TEST_ASSERT_VAL("wrong type val", term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); TEST_ASSERT_VAL("wrong val", term->val.num == 1); - TEST_ASSERT_VAL("wrong config", !term->config); + TEST_ASSERT_VAL("wrong config", !strcmp(term->config, "config1")); /* config2=3 */ term = list_entry(term->list.next, struct parse_events_term, list); @@ -620,7 +802,16 @@ static int test__checkterms_simple(struct list_head *terms) TEST_ASSERT_VAL("wrong type val", term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); TEST_ASSERT_VAL("wrong val", term->val.num == 3); - TEST_ASSERT_VAL("wrong config", !term->config); + TEST_ASSERT_VAL("wrong config", !strcmp(term->config, "config2")); + + /* config3=4 */ + term = list_entry(term->list.next, struct parse_events_term, list); + TEST_ASSERT_VAL("wrong type term", + term->type_term == PARSE_EVENTS__TERM_TYPE_CONFIG3); + TEST_ASSERT_VAL("wrong type val", + term->type_val == PARSE_EVENTS__TERM_TYPE_NUM); + TEST_ASSERT_VAL("wrong val", term->val.num == 4); + TEST_ASSERT_VAL("wrong config", !strcmp(term->config, "config3")); /* umask=1*/ term = list_entry(term->list.next, struct parse_events_term, list); @@ -631,637 +822,819 @@ static int test__checkterms_simple(struct list_head *terms) TEST_ASSERT_VAL("wrong val", term->val.num == 1); TEST_ASSERT_VAL("wrong config", !strcmp(term->config, "umask")); - return 0; + /* + * read + * + * The perf_pmu__test_parse_init injects 'read' term into + * perf_pmu_events_list, so 'read' is evaluated as read term + * and not as raw event with 'ead' hex value. + */ + term = list_entry(term->list.next, struct parse_events_term, list); + TEST_ASSERT_VAL("wrong type term", + term->type_term == PARSE_EVENTS__TERM_TYPE_RAW); + TEST_ASSERT_VAL("wrong type val", + term->type_val == PARSE_EVENTS__TERM_TYPE_STR); + TEST_ASSERT_VAL("wrong val", !strcmp(term->val.str, "read")); + TEST_ASSERT_VAL("wrong config", !strcmp(term->config, "raw")); + + /* + * r0xead + * + * To be still able to pass 'ead' value with 'r' syntax, + * we added support to parse 'r0xHEX' event. + */ + term = list_entry(term->list.next, struct parse_events_term, list); + TEST_ASSERT_VAL("wrong type term", + term->type_term == PARSE_EVENTS__TERM_TYPE_RAW); + TEST_ASSERT_VAL("wrong type val", + term->type_val == PARSE_EVENTS__TERM_TYPE_STR); + TEST_ASSERT_VAL("wrong val", !strcmp(term->val.str, "r0xead")); + TEST_ASSERT_VAL("wrong config", !strcmp(term->config, "raw")); + return TEST_OK; } static int test__group1(struct evlist *evlist) { struct evsel *evsel, *leader; - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (num_core_entries() * 2)); + TEST_ASSERT_VAL("wrong number of groups", + evlist__nr_groups(evlist) == num_core_entries()); - /* instructions:k */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_INSTRUCTIONS == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - /* cycles:upp */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - /* use of precise requires exclude_guest */ - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip == 2); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + /* instructions:k */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_INSTRUCTIONS, "instructions"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* cycles:upp */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip == 2); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + } + return TEST_OK; } static int test__group2(struct evlist *evlist) { - struct evsel *evsel, *leader; + struct evsel *evsel, *leader = NULL; - TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); - - /* faults + :ku modifier */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_SW_PAGE_FAULTS == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); - - /* cache-references + :u modifier */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CACHE_REFERENCES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (2 * num_core_entries() + 1)); + /* + * TODO: Currently the software event won't be grouped with the hardware + * event except for 1 PMU. + */ + TEST_ASSERT_VAL("wrong number of groups", 1 == evlist__nr_groups(evlist)); - /* cycles:k */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + evlist__for_each_entry(evlist, evsel) { + int ret; + + if (evsel->core.attr.type == PERF_TYPE_SOFTWARE) { + /* faults + :ku modifier */ + leader = evsel; + TEST_ASSERT_VAL("wrong config", + test_config(evsel, PERF_COUNT_SW_PAGE_FAULTS)); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + continue; + } + if (evsel->core.attr.type == PERF_TYPE_HARDWARE && + test_config(evsel, PERF_COUNT_HW_BRANCH_INSTRUCTIONS)) { + /* branches + :u modifier */ + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + if (evsel__has_leader(evsel, leader)) + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + continue; + } + /* cycles:k */ + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + } + return TEST_OK; } static int test__group3(struct evlist *evlist __maybe_unused) { - struct evsel *evsel, *leader; - - TEST_ASSERT_VAL("wrong number of entries", 5 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 2 == evlist->nr_groups); + struct evsel *evsel, *group1_leader = NULL, *group2_leader = NULL; + int ret; - /* group1 syscalls:sys_enter_openat:H */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong sample_type", - PERF_TP_SAMPLE_TYPE == evsel->core.attr.sample_type); - TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->core.attr.sample_period); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong group name", - !strcmp(leader->group_name, "group1")); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); - - /* group1 cycles:kppp */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - /* use of precise requires exclude_guest */ - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip == 3); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); - - /* group2 cycles + G modifier */ - evsel = leader = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong group name", - !strcmp(leader->group_name, "group2")); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); - - /* group2 1:3 + G modifier */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", 1 == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 3 == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (3 * perf_pmus__num_core_pmus() + 2)); + /* + * Currently the software event won't be grouped with the hardware event + * except for 1 PMU. This means there are always just 2 groups + * regardless of the number of core PMUs. + */ + TEST_ASSERT_VAL("wrong number of groups", 2 == evlist__nr_groups(evlist)); - /* instructions:u */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_INSTRUCTIONS == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + evlist__for_each_entry(evlist, evsel) { + if (evsel->core.attr.type == PERF_TYPE_TRACEPOINT) { + /* group1 syscalls:sys_enter_openat:H */ + group1_leader = evsel; + TEST_ASSERT_VAL("wrong sample_type", + evsel->core.attr.sample_type == PERF_TP_SAMPLE_TYPE); + TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->core.attr.sample_period); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong group name", !strcmp(evsel->group_name, "group1")); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + continue; + } + if (evsel->core.attr.type == PERF_TYPE_HARDWARE && + test_config(evsel, PERF_COUNT_HW_CPU_CYCLES)) { + if (evsel->core.attr.exclude_user) { + /* group1 cycles:kppp */ + TEST_ASSERT_VAL("wrong exclude_user", + evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", + !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", + !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", + !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", + evsel->core.attr.precise_ip == 3); + if (evsel__has_leader(evsel, group1_leader)) { + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong group_idx", + evsel__group_idx(evsel) == 1); + } + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + } else { + /* group2 cycles + G modifier */ + group2_leader = evsel; + TEST_ASSERT_VAL("wrong exclude_kernel", + !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", + !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", + !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", + evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + if (evsel->core.nr_members == 2) { + TEST_ASSERT_VAL("wrong group_idx", + evsel__group_idx(evsel) == 0); + } + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + } + continue; + } + if (evsel->core.attr.type == 1) { + /* group2 1:3 + G modifier */ + TEST_ASSERT_VAL("wrong config", test_config(evsel, 3)); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + if (evsel__has_leader(evsel, group2_leader)) + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + continue; + } + /* instructions:u */ + ret = assert_hw(&evsel->core, PERF_COUNT_HW_INSTRUCTIONS, "instructions"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + } + return TEST_OK; } static int test__group4(struct evlist *evlist __maybe_unused) { struct evsel *evsel, *leader; - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (num_core_entries() * 2)); + TEST_ASSERT_VAL("wrong number of groups", + num_core_entries() == evlist__nr_groups(evlist)); - /* cycles:u + p */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - /* use of precise requires exclude_guest */ - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip == 1); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); - - /* instructions:kp + p */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_INSTRUCTIONS == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - /* use of precise requires exclude_guest */ - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip == 2); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - return 0; + /* cycles:u + p */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip == 1); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* instructions:kp + p */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_INSTRUCTIONS, "instructions"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip == 2); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + } + return TEST_OK; } static int test__group5(struct evlist *evlist __maybe_unused) { - struct evsel *evsel, *leader; + struct evsel *evsel = NULL, *leader; + int ret; - TEST_ASSERT_VAL("wrong number of entries", 5 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 2 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (5 * num_core_entries())); + TEST_ASSERT_VAL("wrong number of groups", + evlist__nr_groups(evlist) == (2 * num_core_entries())); - /* cycles + G */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + for (int i = 0; i < num_core_entries(); i++) { + /* cycles + G */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - /* instructions + G */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_INSTRUCTIONS == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); - - /* cycles:G */ - evsel = leader = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); - TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* instructions + G */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_INSTRUCTIONS, "instructions"); + if (ret) + return ret; - /* instructions:G */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_INSTRUCTIONS == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + } + for (int i = 0; i < num_core_entries(); i++) { + /* cycles:G */ + evsel = leader = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - /* cycles */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* instructions:G */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_INSTRUCTIONS, "instructions"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + } + for (int i = 0; i < num_core_entries(); i++) { + /* cycles */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + } + return TEST_OK; } static int test__group_gh1(struct evlist *evlist) { - struct evsel *evsel, *leader; + struct evsel *evsel = NULL, *leader; - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (2 * num_core_entries())); + TEST_ASSERT_VAL("wrong number of groups", + evlist__nr_groups(evlist) == num_core_entries()); - /* cycles + :H group modifier */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - /* cache-misses:G + :H group modifier */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CACHE_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + /* cycles + :H group modifier */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + + /* cache-misses:G + :H group modifier */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CACHE_MISSES, "cache-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + } + return TEST_OK; } static int test__group_gh2(struct evlist *evlist) { - struct evsel *evsel, *leader; + struct evsel *evsel = NULL, *leader; - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (2 * num_core_entries())); + TEST_ASSERT_VAL("wrong number of groups", + evlist__nr_groups(evlist) == num_core_entries()); - /* cycles + :G group modifier */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - /* cache-misses:H + :G group modifier */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CACHE_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + /* cycles + :G group modifier */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + + /* cache-misses:H + :G group modifier */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CACHE_MISSES, "cache-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + } + return TEST_OK; } static int test__group_gh3(struct evlist *evlist) { - struct evsel *evsel, *leader; + struct evsel *evsel = NULL, *leader; - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (2 * num_core_entries())); + TEST_ASSERT_VAL("wrong number of groups", + evlist__nr_groups(evlist) == num_core_entries()); - /* cycles:G + :u group modifier */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - /* cache-misses:H + :u group modifier */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CACHE_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + /* cycles:G + :u group modifier */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + + /* cache-misses:H + :u group modifier */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CACHE_MISSES, "cache-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + } + return TEST_OK; } static int test__group_gh4(struct evlist *evlist) { - struct evsel *evsel, *leader; + struct evsel *evsel = NULL, *leader; - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); - TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (2 * num_core_entries())); + TEST_ASSERT_VAL("wrong number of groups", + evlist__nr_groups(evlist) == num_core_entries()); - /* cycles:G + :uG group modifier */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); - TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - /* cache-misses:H + :uG group modifier */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CACHE_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + /* cycles:G + :uG group modifier */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong core.nr_members", evsel->core.nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 0); + + /* cache-misses:H + :uG group modifier */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CACHE_MISSES, "cache-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong group_idx", evsel__group_idx(evsel) == 1); + } + return TEST_OK; } static int test__leader_sample1(struct evlist *evlist) { - struct evsel *evsel, *leader; + struct evsel *evsel = NULL, *leader; - TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->core.nr_entries); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (3 * num_core_entries())); - /* cycles - sampling group leader */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - /* cache-misses - not sampling */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CACHE_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + /* cycles - sampling group leader */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - /* branch-misses - not sampling */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_BRANCH_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); - return 0; + /* cache-misses - not sampling */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CACHE_MISSES, "cache-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + /* branch-misses - not sampling */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_BRANCH_MISSES, "branch-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + } + return TEST_OK; } static int test__leader_sample2(struct evlist *evlist __maybe_unused) { - struct evsel *evsel, *leader; + struct evsel *evsel = NULL, *leader; - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (2 * num_core_entries())); - /* instructions - sampling group leader */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_INSTRUCTIONS == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - /* branch-misses - not sampling */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_BRANCH_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); - TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); - TEST_ASSERT_VAL("wrong exclude guest", evsel->core.attr.exclude_guest); - TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); - TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + /* instructions - sampling group leader */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_INSTRUCTIONS, "instructions"); + if (ret) + return ret; - return 0; + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + /* branch-misses - not sampling */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_BRANCH_MISSES, "branch-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->core.attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->core.attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + } + return TEST_OK; } static int test__checkevent_pinned_modifier(struct evlist *evlist) { + struct evsel *evsel = NULL; + + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == num_core_entries()); + + for (int i = 0; i < num_core_entries(); i++) { + evsel = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); + TEST_ASSERT_VAL("wrong pinned", evsel->core.attr.pinned); + } + return test__checkevent_symbolic_name(evlist); +} + +static int test__pinned_group(struct evlist *evlist) +{ + struct evsel *evsel = NULL, *leader; + + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == (3 * num_core_entries())); + + for (int i = 0; i < num_core_entries(); i++) { + int ret; + + /* cycles - group leader */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + /* TODO: The group modifier is not copied to the split group leader. */ + if (perf_pmus__num_core_pmus() == 1) + TEST_ASSERT_VAL("wrong pinned", evsel->core.attr.pinned); + + /* cache-misses - can not be pinned, but will go on with the leader */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CACHE_MISSES, "cache-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong pinned", !evsel->core.attr.pinned); + + /* branch-misses - ditto */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_BRANCH_MISSES, "branch-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong pinned", !evsel->core.attr.pinned); + } + return TEST_OK; +} + +static int test__checkevent_exclusive_modifier(struct evlist *evlist) +{ struct evsel *evsel = evlist__first(evlist); TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user); TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); TEST_ASSERT_VAL("wrong exclude_hv", evsel->core.attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", evsel->core.attr.precise_ip); - TEST_ASSERT_VAL("wrong pinned", evsel->core.attr.pinned); + TEST_ASSERT_VAL("wrong exclusive", evsel->core.attr.exclusive); return test__checkevent_symbolic_name(evlist); } -static int test__pinned_group(struct evlist *evlist) +static int test__exclusive_group(struct evlist *evlist) { - struct evsel *evsel, *leader; + struct evsel *evsel = NULL, *leader; - TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->core.nr_entries); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == 3 * num_core_entries()); - /* cycles - group leader */ - evsel = leader = evlist__first(evlist); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CPU_CYCLES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong group name", !evsel->group_name); - TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); - TEST_ASSERT_VAL("wrong pinned", evsel->core.attr.pinned); + for (int i = 0; i < num_core_entries(); i++) { + int ret; - /* cache-misses - can not be pinned, but will go on with the leader */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_CACHE_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong pinned", !evsel->core.attr.pinned); + /* cycles - group leader */ + evsel = leader = (i == 0 ? evlist__first(evlist) : evsel__next(evsel)); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + if (ret) + return ret; - /* branch-misses - ditto */ - evsel = evsel__next(evsel); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_HW_BRANCH_MISSES == evsel->core.attr.config); - TEST_ASSERT_VAL("wrong pinned", !evsel->core.attr.pinned); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader)); + /* TODO: The group modifier is not copied to the split group leader. */ + if (perf_pmus__num_core_pmus() == 1) + TEST_ASSERT_VAL("wrong exclusive", evsel->core.attr.exclusive); - return 0; -} + /* cache-misses - can not be pinned, but will go on with the leader */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_CACHE_MISSES, "cache-misses"); + if (ret) + return ret; + TEST_ASSERT_VAL("wrong exclusive", !evsel->core.attr.exclusive); + + /* branch-misses - ditto */ + evsel = evsel__next(evsel); + ret = assert_hw(&evsel->core, PERF_COUNT_HW_BRANCH_MISSES, "branch-misses"); + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong exclusive", !evsel->core.attr.exclusive); + } + return TEST_OK; +} static int test__checkevent_breakpoint_len(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 0)); TEST_ASSERT_VAL("wrong bp_type", (HW_BREAKPOINT_R | HW_BREAKPOINT_W) == evsel->core.attr.bp_type); TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_1 == evsel->core.attr.bp_len); - return 0; + return TEST_OK; } static int test__checkevent_breakpoint_len_w(struct evlist *evlist) @@ -1270,13 +1643,13 @@ static int test__checkevent_breakpoint_len_w(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0 == evsel->core.attr.config); + TEST_ASSERT_VAL("wrong config", test_config(evsel, 0)); TEST_ASSERT_VAL("wrong bp_type", HW_BREAKPOINT_W == evsel->core.attr.bp_type); TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_2 == evsel->core.attr.bp_len); - return 0; + return TEST_OK; } static int @@ -1296,64 +1669,81 @@ static int test__checkevent_precise_max_modifier(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); - TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries); + TEST_ASSERT_VAL("wrong number of entries", + evlist->core.nr_entries == 1 + num_core_entries()); TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", - PERF_COUNT_SW_TASK_CLOCK == evsel->core.attr.config); - return 0; + TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_SW_TASK_CLOCK)); + return TEST_OK; } static int test__checkevent_config_symbol(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); - TEST_ASSERT_VAL("wrong name setting", strcmp(evsel->name, "insn") == 0); - return 0; + TEST_ASSERT_VAL("wrong name setting", evsel__name_is(evsel, "insn")); + return TEST_OK; } static int test__checkevent_config_raw(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); - TEST_ASSERT_VAL("wrong name setting", strcmp(evsel->name, "rawpmu") == 0); - return 0; + TEST_ASSERT_VAL("wrong name setting", evsel__name_is(evsel, "rawpmu")); + return TEST_OK; } static int test__checkevent_config_num(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); - TEST_ASSERT_VAL("wrong name setting", strcmp(evsel->name, "numpmu") == 0); - return 0; + TEST_ASSERT_VAL("wrong name setting", evsel__name_is(evsel, "numpmu")); + return TEST_OK; } static int test__checkevent_config_cache(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); - TEST_ASSERT_VAL("wrong name setting", strcmp(evsel->name, "cachepmu") == 0); - return 0; + TEST_ASSERT_VAL("wrong name setting", evsel__name_is(evsel, "cachepmu")); + return test__checkevent_genhw(evlist); +} + +static bool test__pmu_cpu_valid(void) +{ + return !!perf_pmus__find("cpu"); +} + +static bool test__pmu_cpu_event_valid(void) +{ + struct perf_pmu *pmu = perf_pmus__find("cpu"); + + if (!pmu) + return false; + + return perf_pmu__has_format(pmu, "event"); } static bool test__intel_pt_valid(void) { - return !!perf_pmu__find("intel_pt"); + return !!perf_pmus__find("intel_pt"); } static int test__intel_pt(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); - TEST_ASSERT_VAL("wrong name setting", strcmp(evsel->name, "intel_pt//u") == 0); - return 0; + TEST_ASSERT_VAL("wrong name setting", evsel__name_is(evsel, "intel_pt//u")); + return TEST_OK; } static int test__checkevent_complex_name(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); - TEST_ASSERT_VAL("wrong complex name parsing", strcmp(evsel->name, "COMPLEX_CYCLES_NAME:orig=cycles,desc=chip-clock-ticks") == 0); - return 0; + TEST_ASSERT_VAL("wrong complex name parsing", + evsel__name_is(evsel, + "COMPLEX_CYCLES_NAME:orig=cycles,desc=chip-clock-ticks")); + return TEST_OK; } static int test__checkevent_raw_pmu(struct evlist *evlist) @@ -1362,28 +1752,56 @@ static int test__checkevent_raw_pmu(struct evlist *evlist) TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries); TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->core.attr.type); - TEST_ASSERT_VAL("wrong config", 0x1a == evsel->core.attr.config); - return 0; + TEST_ASSERT_VAL("wrong config", test_config(evsel, 0x1a)); + return TEST_OK; } static int test__sym_event_slash(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); + int ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + + if (ret) + return ret; - TEST_ASSERT_VAL("wrong type", evsel->core.attr.type == PERF_TYPE_HARDWARE); - TEST_ASSERT_VAL("wrong config", evsel->core.attr.config == PERF_COUNT_HW_CPU_CYCLES); TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel); - return 0; + return TEST_OK; } static int test__sym_event_dc(struct evlist *evlist) { struct evsel *evsel = evlist__first(evlist); + int ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + + if (ret) + return ret; - TEST_ASSERT_VAL("wrong type", evsel->core.attr.type == PERF_TYPE_HARDWARE); - TEST_ASSERT_VAL("wrong config", evsel->core.attr.config == PERF_COUNT_HW_CPU_CYCLES); TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user); - return 0; + return TEST_OK; +} + +static int test__term_equal_term(struct evlist *evlist) +{ + struct evsel *evsel = evlist__first(evlist); + int ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong name setting", strcmp(evsel->name, "name") == 0); + return TEST_OK; +} + +static int test__term_equal_legacy(struct evlist *evlist) +{ + struct evsel *evsel = evlist__first(evlist); + int ret = assert_hw(&evsel->core, PERF_COUNT_HW_CPU_CYCLES, "cycles"); + + if (ret) + return ret; + + TEST_ASSERT_VAL("wrong name setting", strcmp(evsel->name, "l1d") == 0); + return TEST_OK; } static int count_tracepoints(void) @@ -1442,401 +1860,703 @@ static int test__all_tracepoints(struct evlist *evlist) struct evlist_test { const char *name; - __u32 type; - const int id; bool (*valid)(void); int (*check)(struct evlist *evlist); }; -static struct evlist_test test__events[] = { +static const struct evlist_test test__events[] = { { .name = "syscalls:sys_enter_openat", .check = test__checkevent_tracepoint, - .id = 0, + /* 0 */ }, { .name = "syscalls:*", .check = test__checkevent_tracepoint_multi, - .id = 1, + /* 1 */ }, { .name = "r1a", .check = test__checkevent_raw, - .id = 2, + /* 2 */ }, { .name = "1:1", .check = test__checkevent_numeric, - .id = 3, + /* 3 */ }, { .name = "instructions", .check = test__checkevent_symbolic_name, - .id = 4, + /* 4 */ }, { .name = "cycles/period=100000,config2/", .check = test__checkevent_symbolic_name_config, - .id = 5, + /* 5 */ }, { .name = "faults", .check = test__checkevent_symbolic_alias, - .id = 6, + /* 6 */ }, { .name = "L1-dcache-load-miss", .check = test__checkevent_genhw, - .id = 7, + /* 7 */ }, { .name = "mem:0", .check = test__checkevent_breakpoint, - .id = 8, + /* 8 */ }, { .name = "mem:0:x", .check = test__checkevent_breakpoint_x, - .id = 9, + /* 9 */ }, { .name = "mem:0:r", .check = test__checkevent_breakpoint_r, - .id = 10, + /* 0 */ }, { .name = "mem:0:w", .check = test__checkevent_breakpoint_w, - .id = 11, + /* 1 */ }, { .name = "syscalls:sys_enter_openat:k", .check = test__checkevent_tracepoint_modifier, - .id = 12, + /* 2 */ }, { .name = "syscalls:*:u", .check = test__checkevent_tracepoint_multi_modifier, - .id = 13, + /* 3 */ }, { .name = "r1a:kp", .check = test__checkevent_raw_modifier, - .id = 14, + /* 4 */ }, { .name = "1:1:hp", .check = test__checkevent_numeric_modifier, - .id = 15, + /* 5 */ }, { .name = "instructions:h", .check = test__checkevent_symbolic_name_modifier, - .id = 16, + /* 6 */ }, { .name = "faults:u", .check = test__checkevent_symbolic_alias_modifier, - .id = 17, + /* 7 */ }, { .name = "L1-dcache-load-miss:kp", .check = test__checkevent_genhw_modifier, - .id = 18, + /* 8 */ }, { .name = "mem:0:u", .check = test__checkevent_breakpoint_modifier, - .id = 19, + /* 9 */ }, { .name = "mem:0:x:k", .check = test__checkevent_breakpoint_x_modifier, - .id = 20, + /* 0 */ }, { .name = "mem:0:r:hp", .check = test__checkevent_breakpoint_r_modifier, - .id = 21, + /* 1 */ }, { .name = "mem:0:w:up", .check = test__checkevent_breakpoint_w_modifier, - .id = 22, + /* 2 */ }, { .name = "r1,syscalls:sys_enter_openat:k,1:1:hp", .check = test__checkevent_list, - .id = 23, + /* 3 */ }, { .name = "instructions:G", .check = test__checkevent_exclude_host_modifier, - .id = 24, + /* 4 */ }, { .name = "instructions:H", .check = test__checkevent_exclude_guest_modifier, - .id = 25, + /* 5 */ }, { .name = "mem:0:rw", .check = test__checkevent_breakpoint_rw, - .id = 26, + /* 6 */ }, { .name = "mem:0:rw:kp", .check = test__checkevent_breakpoint_rw_modifier, - .id = 27, + /* 7 */ }, { .name = "{instructions:k,cycles:upp}", .check = test__group1, - .id = 28, + /* 8 */ }, { - .name = "{faults:k,cache-references}:u,cycles:k", + .name = "{faults:k,branches}:u,cycles:k", .check = test__group2, - .id = 29, + /* 9 */ }, { .name = "group1{syscalls:sys_enter_openat:H,cycles:kppp},group2{cycles,1:3}:G,instructions:u", .check = test__group3, - .id = 30, + /* 0 */ }, { .name = "{cycles:u,instructions:kp}:p", .check = test__group4, - .id = 31, + /* 1 */ }, { .name = "{cycles,instructions}:G,{cycles:G,instructions:G},cycles", .check = test__group5, - .id = 32, + /* 2 */ }, { .name = "*:*", .check = test__all_tracepoints, - .id = 33, + /* 3 */ }, { .name = "{cycles,cache-misses:G}:H", .check = test__group_gh1, - .id = 34, + /* 4 */ }, { .name = "{cycles,cache-misses:H}:G", .check = test__group_gh2, - .id = 35, + /* 5 */ }, { .name = "{cycles:G,cache-misses:H}:u", .check = test__group_gh3, - .id = 36, + /* 6 */ }, { .name = "{cycles:G,cache-misses:H}:uG", .check = test__group_gh4, - .id = 37, + /* 7 */ }, { .name = "{cycles,cache-misses,branch-misses}:S", .check = test__leader_sample1, - .id = 38, + /* 8 */ }, { .name = "{instructions,branch-misses}:Su", .check = test__leader_sample2, - .id = 39, + /* 9 */ }, { .name = "instructions:uDp", .check = test__checkevent_pinned_modifier, - .id = 40, + /* 0 */ }, { .name = "{cycles,cache-misses,branch-misses}:D", .check = test__pinned_group, - .id = 41, + /* 1 */ }, { .name = "mem:0/1", .check = test__checkevent_breakpoint_len, - .id = 42, + /* 2 */ }, { .name = "mem:0/2:w", .check = test__checkevent_breakpoint_len_w, - .id = 43, + /* 3 */ }, { .name = "mem:0/4:rw:u", .check = test__checkevent_breakpoint_len_rw_modifier, - .id = 44 + /* 4 */ }, #if defined(__s390x__) { .name = "kvm-s390:kvm_s390_create_vm", .check = test__checkevent_tracepoint, .valid = kvm_s390_create_vm_valid, - .id = 100, + /* 0 */ }, #endif { .name = "instructions:I", .check = test__checkevent_exclude_idle_modifier, - .id = 45, + /* 5 */ }, { .name = "instructions:kIG", .check = test__checkevent_exclude_idle_modifier_1, - .id = 46, + /* 6 */ }, { .name = "task-clock:P,cycles", .check = test__checkevent_precise_max_modifier, - .id = 47, + /* 7 */ }, { .name = "instructions/name=insn/", .check = test__checkevent_config_symbol, - .id = 48, + /* 8 */ }, { .name = "r1234/name=rawpmu/", .check = test__checkevent_config_raw, - .id = 49, + /* 9 */ }, { .name = "4:0x6530160/name=numpmu/", .check = test__checkevent_config_num, - .id = 50, + /* 0 */ }, { .name = "L1-dcache-misses/name=cachepmu/", .check = test__checkevent_config_cache, - .id = 51, + /* 1 */ }, { .name = "intel_pt//u", .valid = test__intel_pt_valid, .check = test__intel_pt, - .id = 52, + /* 2 */ }, { .name = "cycles/name='COMPLEX_CYCLES_NAME:orig=cycles,desc=chip-clock-ticks'/Duk", .check = test__checkevent_complex_name, - .id = 53 + /* 3 */ }, { .name = "cycles//u", .check = test__sym_event_slash, - .id = 54, + /* 4 */ }, { .name = "cycles:k", .check = test__sym_event_dc, - .id = 55, - } + /* 5 */ + }, + { + .name = "instructions:uep", + .check = test__checkevent_exclusive_modifier, + /* 6 */ + }, + { + .name = "{cycles,cache-misses,branch-misses}:e", + .check = test__exclusive_group, + /* 7 */ + }, + { + .name = "cycles/name=name/", + .check = test__term_equal_term, + /* 8 */ + }, + { + .name = "cycles/name=l1d/", + .check = test__term_equal_legacy, + /* 9 */ + }, + { + .name = "mem:0/name=breakpoint/", + .check = test__checkevent_breakpoint, + /* 0 */ + }, + { + .name = "mem:0:x/name=breakpoint/", + .check = test__checkevent_breakpoint_x, + /* 1 */ + }, + { + .name = "mem:0:r/name=breakpoint/", + .check = test__checkevent_breakpoint_r, + /* 2 */ + }, + { + .name = "mem:0:w/name=breakpoint/", + .check = test__checkevent_breakpoint_w, + /* 3 */ + }, + { + .name = "mem:0/name=breakpoint/u", + .check = test__checkevent_breakpoint_modifier_name, + /* 4 */ + }, + { + .name = "mem:0:x/name=breakpoint/k", + .check = test__checkevent_breakpoint_x_modifier_name, + /* 5 */ + }, + { + .name = "mem:0:r/name=breakpoint/hp", + .check = test__checkevent_breakpoint_r_modifier_name, + /* 6 */ + }, + { + .name = "mem:0:w/name=breakpoint/up", + .check = test__checkevent_breakpoint_w_modifier_name, + /* 7 */ + }, + { + .name = "mem:0:rw/name=breakpoint/", + .check = test__checkevent_breakpoint_rw, + /* 8 */ + }, + { + .name = "mem:0:rw/name=breakpoint/kp", + .check = test__checkevent_breakpoint_rw_modifier_name, + /* 9 */ + }, + { + .name = "mem:0/1/name=breakpoint/", + .check = test__checkevent_breakpoint_len, + /* 0 */ + }, + { + .name = "mem:0/2:w/name=breakpoint/", + .check = test__checkevent_breakpoint_len_w, + /* 1 */ + }, + { + .name = "mem:0/4:rw/name=breakpoint/u", + .check = test__checkevent_breakpoint_len_rw_modifier, + /* 2 */ + }, + { + .name = "mem:0/1/name=breakpoint1/,mem:0/4:rw/name=breakpoint2/", + .check = test__checkevent_breakpoint_2_events, + /* 3 */ + }, + { + .name = "9p:9p_client_req", + .check = test__checkevent_tracepoint, + /* 4 */ + }, }; -static struct evlist_test test__events_pmu[] = { +static const struct evlist_test test__events_pmu[] = { { - .name = "cpu/config=10,config1,config2=3,period=1000/u", + .name = "cpu/config=10,config1=1,config2=3,period=1000/u", + .valid = test__pmu_cpu_valid, .check = test__checkevent_pmu, - .id = 0, + /* 0 */ }, { .name = "cpu/config=1,name=krava/u,cpu/config=2/u", + .valid = test__pmu_cpu_valid, .check = test__checkevent_pmu_name, - .id = 1, + /* 1 */ }, { .name = "cpu/config=1,call-graph=fp,time,period=100000/,cpu/config=2,call-graph=no,time=0,period=2000/", + .valid = test__pmu_cpu_valid, .check = test__checkevent_pmu_partial_time_callgraph, - .id = 2, + /* 2 */ }, { .name = "cpu/name='COMPLEX_CYCLES_NAME:orig=cycles,desc=chip-clock-ticks',period=0x1,event=0x2/ukp", + .valid = test__pmu_cpu_event_valid, .check = test__checkevent_complex_name, - .id = 3, + /* 3 */ }, { .name = "software/r1a/", .check = test__checkevent_raw_pmu, - .id = 4, + /* 4 */ + }, + { + .name = "software/r0x1a/", + .check = test__checkevent_raw_pmu, + /* 5 */ + }, + { + .name = "cpu/L1-dcache-load-miss/", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_genhw, + /* 6 */ + }, + { + .name = "cpu/L1-dcache-load-miss/kp", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_genhw_modifier, + /* 7 */ + }, + { + .name = "cpu/L1-dcache-misses,name=cachepmu/", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_config_cache, + /* 8 */ + }, + { + .name = "cpu/instructions/", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_symbolic_name, + /* 9 */ + }, + { + .name = "cpu/cycles,period=100000,config2/", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_symbolic_name_config, + /* 0 */ + }, + { + .name = "cpu/instructions/h", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_symbolic_name_modifier, + /* 1 */ + }, + { + .name = "cpu/instructions/G", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_exclude_host_modifier, + /* 2 */ + }, + { + .name = "cpu/instructions/H", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_exclude_guest_modifier, + /* 3 */ + }, + { + .name = "{cpu/instructions/k,cpu/cycles/upp}", + .valid = test__pmu_cpu_valid, + .check = test__group1, + /* 4 */ + }, + { + .name = "{cpu/cycles/u,cpu/instructions/kp}:p", + .valid = test__pmu_cpu_valid, + .check = test__group4, + /* 5 */ + }, + { + .name = "{cpu/cycles/,cpu/cache-misses/G}:H", + .valid = test__pmu_cpu_valid, + .check = test__group_gh1, + /* 6 */ + }, + { + .name = "{cpu/cycles/,cpu/cache-misses/H}:G", + .valid = test__pmu_cpu_valid, + .check = test__group_gh2, + /* 7 */ + }, + { + .name = "{cpu/cycles/G,cpu/cache-misses/H}:u", + .valid = test__pmu_cpu_valid, + .check = test__group_gh3, + /* 8 */ + }, + { + .name = "{cpu/cycles/G,cpu/cache-misses/H}:uG", + .valid = test__pmu_cpu_valid, + .check = test__group_gh4, + /* 9 */ + }, + { + .name = "{cpu/cycles/,cpu/cache-misses/,cpu/branch-misses/}:S", + .valid = test__pmu_cpu_valid, + .check = test__leader_sample1, + /* 0 */ + }, + { + .name = "{cpu/instructions/,cpu/branch-misses/}:Su", + .valid = test__pmu_cpu_valid, + .check = test__leader_sample2, + /* 1 */ + }, + { + .name = "cpu/instructions/uDp", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_pinned_modifier, + /* 2 */ + }, + { + .name = "{cpu/cycles/,cpu/cache-misses/,cpu/branch-misses/}:D", + .valid = test__pmu_cpu_valid, + .check = test__pinned_group, + /* 3 */ + }, + { + .name = "cpu/instructions/I", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_exclude_idle_modifier, + /* 4 */ + }, + { + .name = "cpu/instructions/kIG", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_exclude_idle_modifier_1, + /* 5 */ + }, + { + .name = "cpu/cycles/u", + .valid = test__pmu_cpu_valid, + .check = test__sym_event_slash, + /* 6 */ + }, + { + .name = "cpu/cycles/k", + .valid = test__pmu_cpu_valid, + .check = test__sym_event_dc, + /* 7 */ + }, + { + .name = "cpu/instructions/uep", + .valid = test__pmu_cpu_valid, + .check = test__checkevent_exclusive_modifier, + /* 8 */ + }, + { + .name = "{cpu/cycles/,cpu/cache-misses/,cpu/branch-misses/}:e", + .valid = test__pmu_cpu_valid, + .check = test__exclusive_group, + /* 9 */ + }, + { + .name = "cpu/cycles,name=name/", + .valid = test__pmu_cpu_valid, + .check = test__term_equal_term, + /* 0 */ + }, + { + .name = "cpu/cycles,name=l1d/", + .valid = test__pmu_cpu_valid, + .check = test__term_equal_legacy, + /* 1 */ }, }; struct terms_test { const char *str; - __u32 type; - int (*check)(struct list_head *terms); + int (*check)(struct parse_events_terms *terms); }; -static struct terms_test test__terms[] = { +static const struct terms_test test__terms[] = { [0] = { - .str = "config=10,config1,config2=3,umask=1", + .str = "config=10,config1,config2=3,config3=4,umask=1,read,r0xead", .check = test__checkterms_simple, }, }; -static int test_event(struct evlist_test *e) +static int test_event(const struct evlist_test *e) { struct parse_events_error err; struct evlist *evlist; int ret; - bzero(&err, sizeof(err)); if (e->valid && !e->valid()) { - pr_debug("... SKIP"); - return 0; + pr_debug("... SKIP\n"); + return TEST_OK; } evlist = evlist__new(); - if (evlist == NULL) - return -ENOMEM; - - ret = parse_events(evlist, e->name, &err); + if (evlist == NULL) { + pr_err("Failed allocation"); + return TEST_FAIL; + } + parse_events_error__init(&err); + ret = __parse_events(evlist, e->name, /*pmu_filter=*/NULL, &err, /*fake_pmu=*/false, + /*warn_if_reordered=*/true, /*fake_tp=*/true); if (ret) { - pr_debug("failed to parse event '%s', err %d, str '%s'\n", - e->name, ret, err.str); - parse_events_print_error(&err, e->name); + pr_debug("failed to parse event '%s', err %d\n", e->name, ret); + parse_events_error__print(&err, e->name); + ret = TEST_FAIL; + if (parse_events_error__contains(&err, "can't access trace events")) + ret = TEST_SKIP; } else { ret = e->check(evlist); } + parse_events_error__exit(&err); + evlist__delete(evlist); + + return ret; +} + +static int test_event_fake_pmu(const char *str) +{ + struct parse_events_error err; + struct evlist *evlist; + int ret; + + evlist = evlist__new(); + if (!evlist) + return -ENOMEM; + + parse_events_error__init(&err); + ret = __parse_events(evlist, str, /*pmu_filter=*/NULL, &err, + /*fake_pmu=*/true, /*warn_if_reordered=*/true, + /*fake_tp=*/true); + if (ret) { + pr_debug("failed to parse event '%s', err %d\n", + str, ret); + parse_events_error__print(&err, str); + } + parse_events_error__exit(&err); evlist__delete(evlist); return ret; } -static int test_events(struct evlist_test *events, unsigned cnt) +static int combine_test_results(int existing, int latest) { - int ret1, ret2 = 0; - unsigned i; + if (existing == TEST_FAIL) + return TEST_FAIL; + if (existing == TEST_SKIP) + return latest == TEST_OK ? TEST_SKIP : latest; + return latest; +} + +static int test_events(const struct evlist_test *events, int cnt) +{ + int ret = TEST_OK; - for (i = 0; i < cnt; i++) { - struct evlist_test *e = &events[i]; + for (int i = 0; i < cnt; i++) { + const struct evlist_test *e = &events[i]; + int test_ret; - pr_debug("running test %d '%s'", e->id, e->name); - ret1 = test_event(e); - if (ret1) - ret2 = ret1; - pr_debug("\n"); + pr_debug("running test %d '%s'\n", i, e->name); + test_ret = test_event(e); + if (test_ret != TEST_OK) { + pr_debug("Event test failure: test %d '%s'", i, e->name); + ret = combine_test_results(ret, test_ret); + } } - return ret2; + return ret; +} + +static int test__events2(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + return test_events(test__events, ARRAY_SIZE(test__events)); } -static int test_term(struct terms_test *t) +static int test_term(const struct terms_test *t) { - struct list_head terms; + struct parse_events_terms terms; int ret; - INIT_LIST_HEAD(&terms); - ret = parse_events_terms(&terms, t->str); + parse_events_terms__init(&terms); + ret = parse_events_terms(&terms, t->str, /*input=*/ NULL); if (ret) { pr_debug("failed to parse terms '%s', err %d\n", t->str , ret); @@ -1844,18 +2564,17 @@ static int test_term(struct terms_test *t) } ret = t->check(&terms); - parse_events_terms__purge(&terms); + parse_events_terms__exit(&terms); return ret; } -static int test_terms(struct terms_test *terms, unsigned cnt) +static int test_terms(const struct terms_test *terms, int cnt) { int ret = 0; - unsigned i; - for (i = 0; i < cnt; i++) { - struct terms_test *t = &terms[i]; + for (int i = 0; i < cnt; i++) { + const struct terms_test *t = &terms[i]; pr_debug("running test %d '%s'\n", i, t->str); ret = test_term(t); @@ -1866,95 +2585,270 @@ static int test_terms(struct terms_test *terms, unsigned cnt) return ret; } -static int test_pmu(void) +static int test__terms2(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - struct stat st; - char path[PATH_MAX]; - int ret; + return test_terms(test__terms, ARRAY_SIZE(test__terms)); +} - snprintf(path, PATH_MAX, "%s/bus/event_source/devices/cpu/format/", - sysfs__mountpoint()); +static int test__pmu_events(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + struct perf_pmu *pmu = NULL; + int ret = TEST_OK; - ret = stat(path, &st); - if (ret) - pr_debug("omitting PMU cpu tests\n"); - return !ret; + while ((pmu = perf_pmus__scan(pmu)) != NULL) { + struct stat st; + char path[PATH_MAX]; + char pmu_event[PATH_MAX]; + char *buf = NULL; + FILE *file; + struct dirent *ent; + size_t len = 0; + DIR *dir; + int err; + int n; + + snprintf(path, PATH_MAX, "%s/bus/event_source/devices/%s/events/", + sysfs__mountpoint(), pmu->name); + + err = stat(path, &st); + if (err) { + pr_debug("skipping PMU %s events tests: %s\n", pmu->name, path); + continue; + } + + dir = opendir(path); + if (!dir) { + pr_debug("can't open pmu event dir: %s\n", path); + ret = combine_test_results(ret, TEST_SKIP); + continue; + } + + while ((ent = readdir(dir))) { + struct evlist_test e = { .name = NULL, }; + char name[2 * NAME_MAX + 1 + 12 + 3]; + int test_ret; + bool is_event_parameterized = 0; + + /* Names containing . are special and cannot be used directly */ + if (strchr(ent->d_name, '.')) + continue; + + /* exclude parameterized ones (name contains '?') */ + n = snprintf(pmu_event, sizeof(pmu_event), "%s%s", path, ent->d_name); + if (n >= PATH_MAX) { + pr_err("pmu event name crossed PATH_MAX(%d) size\n", PATH_MAX); + continue; + } + + file = fopen(pmu_event, "r"); + if (!file) { + pr_debug("can't open pmu event file for '%s'\n", ent->d_name); + ret = combine_test_results(ret, TEST_FAIL); + continue; + } + + if (getline(&buf, &len, file) < 0) { + pr_debug(" pmu event: %s is a null event\n", ent->d_name); + ret = combine_test_results(ret, TEST_FAIL); + fclose(file); + continue; + } + + if (strchr(buf, '?')) + is_event_parameterized = 1; + + free(buf); + buf = NULL; + fclose(file); + + if (is_event_parameterized == 1) { + pr_debug("skipping parameterized PMU event: %s which contains ?\n", pmu_event); + continue; + } + + snprintf(name, sizeof(name), "%s/event=%s/u", pmu->name, ent->d_name); + + e.name = name; + e.check = test__checkevent_pmu_events; + + test_ret = test_event(&e); + if (test_ret != TEST_OK) { + pr_debug("Test PMU event failed for '%s'", name); + ret = combine_test_results(ret, test_ret); + } + + if (!is_pmu_core(pmu->name)) + continue; + + /* + * Names containing '-' are recognized as prefixes and suffixes + * due to '-' being a legacy PMU separator. This fails when the + * prefix or suffix collides with an existing legacy token. For + * example, branch-brs has a prefix (branch) that collides with + * a PE_NAME_CACHE_TYPE token causing a parse error as a suffix + * isn't expected after this. As event names in the config + * slashes are allowed a '-' in the name we check this works + * above. + */ + if (strchr(ent->d_name, '-')) + continue; + + snprintf(name, sizeof(name), "%s:u,%s/event=%s/u", + ent->d_name, pmu->name, ent->d_name); + e.name = name; + e.check = test__checkevent_pmu_events_mix; + test_ret = test_event(&e); + if (test_ret != TEST_OK) { + pr_debug("Test PMU event failed for '%s'", name); + ret = combine_test_results(ret, test_ret); + } + } + + closedir(dir); + } + return ret; } -static int test_pmu_events(void) +static int test__pmu_events2(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + return test_events(test__events_pmu, ARRAY_SIZE(test__events_pmu)); +} + +static bool test_alias(char **event, char **alias) { - struct stat st; char path[PATH_MAX]; - struct dirent *ent; DIR *dir; - int ret; + struct dirent *dent; + const char *sysfs = sysfs__mountpoint(); + char buf[128]; + FILE *file; - snprintf(path, PATH_MAX, "%s/bus/event_source/devices/cpu/events/", - sysfs__mountpoint()); - - ret = stat(path, &st); - if (ret) { - pr_debug("omitting PMU cpu events tests\n"); - return 0; - } + if (!sysfs) + return false; + snprintf(path, PATH_MAX, "%s/bus/event_source/devices/", sysfs); dir = opendir(path); - if (!dir) { - pr_debug("can't open pmu event dir"); - return -1; - } + if (!dir) + return false; + + while ((dent = readdir(dir))) { + if (!strcmp(dent->d_name, ".") || + !strcmp(dent->d_name, "..")) + continue; - while (!ret && (ent = readdir(dir))) { - struct evlist_test e = { .id = 0, }; - char name[2 * NAME_MAX + 1 + 12 + 3]; + snprintf(path, PATH_MAX, "%s/bus/event_source/devices/%s/alias", + sysfs, dent->d_name); - /* Names containing . are special and cannot be used directly */ - if (strchr(ent->d_name, '.')) + if (!file_available(path)) continue; - snprintf(name, sizeof(name), "cpu/event=%s/u", ent->d_name); + file = fopen(path, "r"); + if (!file) + continue; - e.name = name; - e.check = test__checkevent_pmu_events; + if (!fgets(buf, sizeof(buf), file)) { + fclose(file); + continue; + } - ret = test_event(&e); - if (ret) - break; - snprintf(name, sizeof(name), "%s:u,cpu/event=%s/u", ent->d_name, ent->d_name); - e.name = name; - e.check = test__checkevent_pmu_events_mix; - ret = test_event(&e); + /* Remove the last '\n' */ + buf[strlen(buf) - 1] = 0; + + fclose(file); + *event = strdup(dent->d_name); + *alias = strdup(buf); + closedir(dir); + + if (*event == NULL || *alias == NULL) { + free(*event); + free(*alias); + return false; + } + + return true; } closedir(dir); - return ret; + return false; } -int test__parse_events(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__checkevent_pmu_events_alias(struct evlist *evlist) { - int ret1, ret2 = 0; + struct evsel *evsel1 = evlist__first(evlist); + struct evsel *evsel2 = evlist__last(evlist); -#define TEST_EVENTS(tests) \ -do { \ - ret1 = test_events(tests, ARRAY_SIZE(tests)); \ - if (!ret2) \ - ret2 = ret1; \ -} while (0) + TEST_ASSERT_VAL("wrong type", evsel1->core.attr.type == evsel2->core.attr.type); + TEST_ASSERT_VAL("wrong config", evsel1->core.attr.config == evsel2->core.attr.config); + return TEST_OK; +} - TEST_EVENTS(test__events); +static int test__pmu_events_alias(char *event, char *alias) +{ + struct evlist_test e = { .name = NULL, }; + char name[2 * NAME_MAX + 20]; - if (test_pmu()) - TEST_EVENTS(test__events_pmu); + snprintf(name, sizeof(name), "%s/event=1/,%s/event=1/", + event, alias); - if (test_pmu()) { - int ret = test_pmu_events(); - if (ret) - return ret; - } + e.name = name; + e.check = test__checkevent_pmu_events_alias; + return test_event(&e); +} - ret1 = test_terms(test__terms, ARRAY_SIZE(test__terms)); - if (!ret2) - ret2 = ret1; +static int test__alias(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + char *event, *alias; + int ret; - return ret2; + if (!test_alias(&event, &alias)) + return TEST_SKIP; + + ret = test__pmu_events_alias(event, alias); + + free(event); + free(alias); + return ret; } + +static int test__pmu_events_alias2(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + static const char events[][30] = { + "event-hyphen", + "event-two-hyph", + }; + int ret = TEST_OK; + + for (unsigned int i = 0; i < ARRAY_SIZE(events); i++) { + int test_ret = test_event_fake_pmu(&events[i][0]); + + if (test_ret != TEST_OK) { + pr_debug("check_parse_fake %s failed\n", &events[i][0]); + ret = combine_test_results(ret, test_ret); + } + } + + return ret; +} + +static struct test_case tests__parse_events[] = { + TEST_CASE_REASON("Test event parsing", + events2, + "permissions"), + TEST_CASE_REASON("Parsing of all PMU events from sysfs", + pmu_events, + "permissions"), + TEST_CASE_REASON("Parsing of given PMU events from sysfs", + pmu_events2, + "permissions"), + TEST_CASE_REASON("Parsing of aliased events from sysfs", alias, + "no aliases in sysfs"), + TEST_CASE("Parsing of aliased events", pmu_events_alias2), + TEST_CASE("Parsing of terms (event modifiers)", terms2), + { .name = NULL, } +}; + +struct test_suite suite__parse_events = { + .desc = "Parse event definition strings", + .test_cases = tests__parse_events, +}; diff --git a/tools/perf/tests/parse-metric.c b/tools/perf/tests/parse-metric.c new file mode 100644 index 000000000000..2c28fb50dc24 --- /dev/null +++ b/tools/perf/tests/parse-metric.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/compiler.h> +#include <string.h> +#include <perf/cpumap.h> +#include <perf/evlist.h> +#include "metricgroup.h" +#include "tests.h" +#include "pmu-events/pmu-events.h" +#include "evlist.h" +#include "rblist.h" +#include "debug.h" +#include "expr.h" +#include "stat.h" +#include "pmus.h" + +struct value { + const char *event; + u64 val; +}; + +static u64 find_value(const char *name, struct value *values) +{ + struct value *v = values; + + while (v->event) { + if (!strcmp(name, v->event)) + return v->val; + v++; + } + return 0; +} + +static void load_runtime_stat(struct evlist *evlist, struct value *vals) +{ + struct evsel *evsel; + u64 count; + + evlist__alloc_aggr_stats(evlist, 1); + evlist__for_each_entry(evlist, evsel) { + count = find_value(evsel->name, vals); + evsel->supported = true; + evsel->stats->aggr->counts.val = count; + if (evsel__name_is(evsel, "duration_time")) + update_stats(&walltime_nsecs_stats, count); + } +} + +static double compute_single(struct rblist *metric_events, struct evlist *evlist, + const char *name) +{ + struct metric_expr *mexp; + struct metric_event *me; + struct evsel *evsel; + + evlist__for_each_entry(evlist, evsel) { + me = metricgroup__lookup(metric_events, evsel, false); + if (me != NULL) { + list_for_each_entry (mexp, &me->head, nd) { + if (strcmp(mexp->metric_name, name)) + continue; + return test_generic_metric(mexp, 0); + } + } + } + return 0.; +} + +static int __compute_metric(const char *name, struct value *vals, + const char *name1, double *ratio1, + const char *name2, double *ratio2) +{ + struct rblist metric_events = { + .nr_entries = 0, + }; + const struct pmu_metrics_table *pme_test; + struct perf_cpu_map *cpus; + struct evlist *evlist; + int err; + + /* + * We need to prepare evlist for stat mode running on CPU 0 + * because that's where all the stats are going to be created. + */ + evlist = evlist__new(); + if (!evlist) + return -ENOMEM; + + cpus = perf_cpu_map__new("0"); + if (!cpus) { + evlist__delete(evlist); + return -ENOMEM; + } + + perf_evlist__set_maps(&evlist->core, cpus, NULL); + + /* Parse the metric into metric_events list. */ + pme_test = find_core_metrics_table("testarch", "testcpu"); + err = metricgroup__parse_groups_test(evlist, pme_test, name, + &metric_events); + if (err) + goto out; + + err = evlist__alloc_stats(/*config=*/NULL, evlist, /*alloc_raw=*/false); + if (err) + goto out; + + /* Load the runtime stats with given numbers for events. */ + load_runtime_stat(evlist, vals); + + /* And execute the metric */ + if (name1 && ratio1) + *ratio1 = compute_single(&metric_events, evlist, name1); + if (name2 && ratio2) + *ratio2 = compute_single(&metric_events, evlist, name2); + +out: + /* ... cleanup. */ + metricgroup__rblist_exit(&metric_events); + evlist__free_stats(evlist); + perf_cpu_map__put(cpus); + evlist__delete(evlist); + return err; +} + +static int compute_metric(const char *name, struct value *vals, double *ratio) +{ + return __compute_metric(name, vals, name, ratio, NULL, NULL); +} + +static int compute_metric_group(const char *name, struct value *vals, + const char *name1, double *ratio1, + const char *name2, double *ratio2) +{ + return __compute_metric(name, vals, name1, ratio1, name2, ratio2); +} + +static int test_ipc(void) +{ + double ratio; + struct value vals[] = { + { .event = "inst_retired.any", .val = 300 }, + { .event = "cpu_clk_unhalted.thread", .val = 200 }, + { .event = NULL, }, + }; + + TEST_ASSERT_VAL("failed to compute metric", + compute_metric("IPC", vals, &ratio) == 0); + + TEST_ASSERT_VAL("IPC failed, wrong ratio", + ratio == 1.5); + return 0; +} + +static int test_frontend(void) +{ + double ratio; + struct value vals[] = { + { .event = "idq_uops_not_delivered.core", .val = 300 }, + { .event = "cpu_clk_unhalted.thread", .val = 200 }, + { .event = "cpu_clk_unhalted.one_thread_active", .val = 400 }, + { .event = "cpu_clk_unhalted.ref_xclk", .val = 600 }, + { .event = NULL, }, + }; + + TEST_ASSERT_VAL("failed to compute metric", + compute_metric("Frontend_Bound_SMT", vals, &ratio) == 0); + + TEST_ASSERT_VAL("Frontend_Bound_SMT failed, wrong ratio", + ratio == 0.45); + return 0; +} + +static int test_cache_miss_cycles(void) +{ + double ratio; + struct value vals[] = { + { .event = "l1d-loads-misses", .val = 300 }, + { .event = "l1i-loads-misses", .val = 200 }, + { .event = "inst_retired.any", .val = 400 }, + { .event = NULL, }, + }; + + TEST_ASSERT_VAL("failed to compute metric", + compute_metric("cache_miss_cycles", vals, &ratio) == 0); + + TEST_ASSERT_VAL("cache_miss_cycles failed, wrong ratio", + ratio == 1.25); + return 0; +} + + +/* + * DCache_L2_All_Hits = l2_rqsts.demand_data_rd_hit + l2_rqsts.pf_hit + l2_rqsts.rfo_hi + * DCache_L2_All_Miss = max(l2_rqsts.all_demand_data_rd - l2_rqsts.demand_data_rd_hit, 0) + + * l2_rqsts.pf_miss + l2_rqsts.rfo_miss + * DCache_L2_All = dcache_l2_all_hits + dcache_l2_all_miss + * DCache_L2_Hits = d_ratio(dcache_l2_all_hits, dcache_l2_all) + * DCache_L2_Misses = d_ratio(dcache_l2_all_miss, dcache_l2_all) + * + * l2_rqsts.demand_data_rd_hit = 100 + * l2_rqsts.pf_hit = 200 + * l2_rqsts.rfo_hi = 300 + * l2_rqsts.all_demand_data_rd = 400 + * l2_rqsts.pf_miss = 500 + * l2_rqsts.rfo_miss = 600 + * + * DCache_L2_All_Hits = 600 + * DCache_L2_All_Miss = MAX(400 - 100, 0) + 500 + 600 = 1400 + * DCache_L2_All = 600 + 1400 = 2000 + * DCache_L2_Hits = 600 / 2000 = 0.3 + * DCache_L2_Misses = 1400 / 2000 = 0.7 + */ +static int test_dcache_l2(void) +{ + double ratio; + struct value vals[] = { + { .event = "l2_rqsts.demand_data_rd_hit", .val = 100 }, + { .event = "l2_rqsts.pf_hit", .val = 200 }, + { .event = "l2_rqsts.rfo_hit", .val = 300 }, + { .event = "l2_rqsts.all_demand_data_rd", .val = 400 }, + { .event = "l2_rqsts.pf_miss", .val = 500 }, + { .event = "l2_rqsts.rfo_miss", .val = 600 }, + { .event = NULL, }, + }; + + TEST_ASSERT_VAL("failed to compute metric", + compute_metric("DCache_L2_Hits", vals, &ratio) == 0); + + TEST_ASSERT_VAL("DCache_L2_Hits failed, wrong ratio", + ratio == 0.3); + + TEST_ASSERT_VAL("failed to compute metric", + compute_metric("DCache_L2_Misses", vals, &ratio) == 0); + + TEST_ASSERT_VAL("DCache_L2_Misses failed, wrong ratio", + ratio == 0.7); + return 0; +} + +static int test_recursion_fail(void) +{ + double ratio; + struct value vals[] = { + { .event = "inst_retired.any", .val = 300 }, + { .event = "cpu_clk_unhalted.thread", .val = 200 }, + { .event = NULL, }, + }; + + TEST_ASSERT_VAL("failed to find recursion", + compute_metric("M1", vals, &ratio) == -1); + + TEST_ASSERT_VAL("failed to find recursion", + compute_metric("M3", vals, &ratio) == -1); + return 0; +} + +static int test_memory_bandwidth(void) +{ + double ratio; + struct value vals[] = { + { .event = "l1d.replacement", .val = 4000000 }, + { .event = "duration_time", .val = 200000000 }, + { .event = NULL, }, + }; + + TEST_ASSERT_VAL("failed to compute metric", + compute_metric("L1D_Cache_Fill_BW", vals, &ratio) == 0); + TEST_ASSERT_VAL("L1D_Cache_Fill_BW, wrong ratio", + 1.28 == ratio); + + return 0; +} + +static int test_metric_group(void) +{ + double ratio1, ratio2; + struct value vals[] = { + { .event = "cpu_clk_unhalted.thread", .val = 200 }, + { .event = "l1d-loads-misses", .val = 300 }, + { .event = "l1i-loads-misses", .val = 200 }, + { .event = "inst_retired.any", .val = 400 }, + { .event = NULL, }, + }; + + TEST_ASSERT_VAL("failed to find recursion", + compute_metric_group("group1", vals, + "IPC", &ratio1, + "cache_miss_cycles", &ratio2) == 0); + + TEST_ASSERT_VAL("group IPC failed, wrong ratio", + ratio1 == 2.0); + + TEST_ASSERT_VAL("group cache_miss_cycles failed, wrong ratio", + ratio2 == 1.25); + return 0; +} + +static int test__parse_metric(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + TEST_ASSERT_VAL("IPC failed", test_ipc() == 0); + TEST_ASSERT_VAL("frontend failed", test_frontend() == 0); + TEST_ASSERT_VAL("DCache_L2 failed", test_dcache_l2() == 0); + TEST_ASSERT_VAL("recursion fail failed", test_recursion_fail() == 0); + TEST_ASSERT_VAL("Memory bandwidth", test_memory_bandwidth() == 0); + TEST_ASSERT_VAL("cache_miss_cycles failed", test_cache_miss_cycles() == 0); + TEST_ASSERT_VAL("test metric group", test_metric_group() == 0); + return 0; +} + +DEFINE_SUITE("Parse and process metrics", parse_metric); diff --git a/tools/perf/tests/parse-no-sample-id-all.c b/tools/perf/tests/parse-no-sample-id-all.c index adf3c9c4a416..50e68b7d43aa 100644 --- a/tools/perf/tests/parse-no-sample-id-all.c +++ b/tools/perf/tests/parse-no-sample-id-all.c @@ -8,10 +8,12 @@ #include "evlist.h" #include "header.h" #include "debug.h" +#include "util/sample.h" static int process_event(struct evlist **pevlist, union perf_event *event) { struct perf_sample sample; + int ret; if (event->header.type == PERF_RECORD_HEADER_ATTR) { if (perf_event__process_attr(NULL, event, pevlist)) { @@ -27,8 +29,11 @@ static int process_event(struct evlist **pevlist, union perf_event *event) if (!*pevlist) return -1; - if (perf_evlist__parse_sample(*pevlist, event, &sample)) { - pr_debug("perf_evlist__parse_sample failed\n"); + perf_sample__init(&sample, /*all=*/false); + ret = evlist__parse_sample(*pevlist, event, &sample); + perf_sample__exit(&sample); + if (ret) { + pr_debug("evlist__parse_sample failed\n"); return -1; } @@ -67,7 +72,8 @@ struct test_attr_event { * * Return: %0 on success, %-1 if the test fails. */ -int test__parse_no_sample_id_all(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__parse_no_sample_id_all(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { int err; @@ -103,3 +109,5 @@ int test__parse_no_sample_id_all(struct test *test __maybe_unused, int subtest _ return 0; } + +DEFINE_SUITE("Parse with no sample_id_all bit set", parse_no_sample_id_all); diff --git a/tools/perf/tests/pe-file-parsing.c b/tools/perf/tests/pe-file-parsing.c new file mode 100644 index 000000000000..fff58b220c07 --- /dev/null +++ b/tools/perf/tests/pe-file-parsing.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdbool.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <subcmd/exec-cmd.h> + +#include "debug.h" +#include "util/build-id.h" +#include "util/symbol.h" +#include "util/dso.h" + +#include "tests.h" + +#ifdef HAVE_LIBBFD_SUPPORT + +static int run_dir(const char *d) +{ + char filename[PATH_MAX]; + char debugfile[PATH_MAX]; + struct build_id bid; + char debuglink[PATH_MAX]; + char expect_build_id[] = { + 0x5a, 0x0f, 0xd8, 0x82, 0xb5, 0x30, 0x84, 0x22, + 0x4b, 0xa4, 0x7b, 0x62, 0x4c, 0x55, 0xa4, 0x69, + }; + char expect_debuglink[PATH_MAX] = "pe-file.exe.debug"; + struct dso *dso; + struct symbol *sym; + int ret; + size_t idx; + + scnprintf(filename, PATH_MAX, "%s/pe-file.exe", d); + ret = filename__read_build_id(filename, &bid); + TEST_ASSERT_VAL("Failed to read build_id", + ret == sizeof(expect_build_id)); + TEST_ASSERT_VAL("Wrong build_id", !memcmp(bid.data, expect_build_id, + sizeof(expect_build_id))); + + ret = filename__read_debuglink(filename, debuglink, PATH_MAX); + TEST_ASSERT_VAL("Failed to read debuglink", ret == 0); + TEST_ASSERT_VAL("Wrong debuglink", + !strcmp(debuglink, expect_debuglink)); + + scnprintf(debugfile, PATH_MAX, "%s/%s", d, debuglink); + ret = filename__read_build_id(debugfile, &bid); + TEST_ASSERT_VAL("Failed to read debug file build_id", + ret == sizeof(expect_build_id)); + TEST_ASSERT_VAL("Wrong build_id", !memcmp(bid.data, expect_build_id, + sizeof(expect_build_id))); + + dso = dso__new(filename); + TEST_ASSERT_VAL("Failed to get dso", dso); + + ret = dso__load_bfd_symbols(dso, debugfile); + TEST_ASSERT_VAL("Failed to load symbols", ret == 0); + + dso__sort_by_name(dso); + sym = dso__find_symbol_by_name(dso, "main", &idx); + TEST_ASSERT_VAL("Failed to find main", sym); + dso__delete(dso); + + return TEST_OK; +} + +static int test__pe_file_parsing(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + struct stat st; + char path_dir[PATH_MAX]; + + /* First try development tree tests. */ + if (!lstat("./tests", &st)) + return run_dir("./tests"); + + /* Then installed path. */ + snprintf(path_dir, PATH_MAX, "%s/tests", get_argv_exec_path()); + + if (!lstat(path_dir, &st)) + return run_dir(path_dir); + + return TEST_SKIP; +} + +#else + +static int test__pe_file_parsing(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + return TEST_SKIP; +} + +#endif + +DEFINE_SUITE("PE file support", pe_file_parsing); diff --git a/tools/perf/tests/pe-file.c b/tools/perf/tests/pe-file.c new file mode 100644 index 000000000000..eb3df5e9886f --- /dev/null +++ b/tools/perf/tests/pe-file.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 + +// pe-file.exe and pe-file.exe.debug built with; +// x86_64-w64-mingw32-gcc -o pe-file.exe pe-file.c +// -Wl,--file-alignment,4096 -Wl,--build-id +// x86_64-w64-mingw32-objcopy --only-keep-debug +// --compress-debug-sections pe-file.exe pe-file.exe.debug +// x86_64-w64-mingw32-objcopy --strip-debug +// --add-gnu-debuglink=pe-file.exe.debug pe-file.exe + +int main(int argc, char const *argv[]) +{ + return 0; +} diff --git a/tools/perf/tests/pe-file.exe b/tools/perf/tests/pe-file.exe Binary files differnew file mode 100644 index 000000000000..838a46dae724 --- /dev/null +++ b/tools/perf/tests/pe-file.exe diff --git a/tools/perf/tests/pe-file.exe.debug b/tools/perf/tests/pe-file.exe.debug Binary files differnew file mode 100644 index 000000000000..287d6718d6c9 --- /dev/null +++ b/tools/perf/tests/pe-file.exe.debug diff --git a/tools/perf/tests/perf-hooks.c b/tools/perf/tests/perf-hooks.c index dd865e0bea12..78cdeb89645e 100644 --- a/tools/perf/tests/perf-hooks.c +++ b/tools/perf/tests/perf-hooks.c @@ -26,7 +26,7 @@ static void the_hook(void *_hook_flags) raise(SIGSEGV); } -int test__perf_hooks(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__perf_hooks(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int hook_flags = 0; @@ -45,3 +45,5 @@ int test__perf_hooks(struct test *test __maybe_unused, int subtest __maybe_unuse return TEST_FAIL; return TEST_OK; } + +DEFINE_SUITE("perf hooks", perf_hooks); diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c index 83adfd846ccd..0958c7c8995f 100644 --- a/tools/perf/tests/perf-record.c +++ b/tools/perf/tests/perf-record.c @@ -2,17 +2,17 @@ #include <errno.h> #include <inttypes.h> #include <linux/string.h> -/* For the CLR_() macros */ -#include <pthread.h> #include <sched.h> #include <perf/mmap.h> +#include "event.h" #include "evlist.h" #include "evsel.h" #include "debug.h" #include "record.h" #include "tests.h" #include "util/mmap.h" +#include "util/sample.h" static int sched__get_first_possible_cpu(pid_t pid, cpu_set_t *maskp) { @@ -41,7 +41,7 @@ realloc: return cpu; } -int test__PERF_RECORD(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct record_opts opts = { .target = { @@ -53,7 +53,7 @@ int test__PERF_RECORD(struct test *test __maybe_unused, int subtest __maybe_unus }; cpu_set_t cpu_mask; size_t cpu_mask_size = sizeof(cpu_mask); - struct evlist *evlist = perf_evlist__new_dummy(); + struct evlist *evlist = evlist__new_dummy(); struct evsel *evsel; struct perf_sample sample; const char *cmd = "sleep"; @@ -70,8 +70,9 @@ int test__PERF_RECORD(struct test *test __maybe_unused, int subtest __maybe_unus int total_events = 0, nr_events[PERF_RECORD_MAX] = { 0, }; char sbuf[STRERR_BUFSIZE]; + perf_sample__init(&sample, /*all=*/false); if (evlist == NULL) /* Fallback for kernels lacking PERF_COUNT_SW_DUMMY */ - evlist = perf_evlist__new_default(); + evlist = evlist__new_default(); if (evlist == NULL) { pr_debug("Not enough memory to create evlist\n"); @@ -81,10 +82,10 @@ int test__PERF_RECORD(struct test *test __maybe_unused, int subtest __maybe_unus /* * 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 + * 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); + err = evlist__create_maps(evlist, &opts.target); if (err < 0) { pr_debug("Not enough memory to create thread/cpu maps\n"); goto out_delete_evlist; @@ -92,11 +93,11 @@ int test__PERF_RECORD(struct test *test __maybe_unused, int subtest __maybe_unus /* * 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 + * for 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.target, argv, false, NULL); + err = evlist__prepare_workload(evlist, &opts.target, argv, false, NULL); if (err < 0) { pr_debug("Couldn't run the workload!\n"); goto out_delete_evlist; @@ -109,7 +110,7 @@ int test__PERF_RECORD(struct test *test __maybe_unused, int subtest __maybe_unus evsel__set_sample_bit(evsel, CPU); evsel__set_sample_bit(evsel, TID); evsel__set_sample_bit(evsel, TIME); - perf_evlist__config(evlist, &opts, NULL); + evlist__config(evlist, &opts, NULL); err = sched__get_first_possible_cpu(evlist->workload.pid, &cpu_mask); if (err < 0) { @@ -161,7 +162,7 @@ int test__PERF_RECORD(struct test *test __maybe_unused, int subtest __maybe_unus /* * Now! */ - perf_evlist__start_workload(evlist); + evlist__start_workload(evlist); while (1) { int before = total_events; @@ -182,17 +183,17 @@ int test__PERF_RECORD(struct test *test __maybe_unused, int subtest __maybe_unus if (type < PERF_RECORD_MAX) nr_events[type]++; - err = perf_evlist__parse_sample(evlist, event, &sample); + err = evlist__parse_sample(evlist, event, &sample); if (err < 0) { if (verbose > 0) - perf_event__fprintf(event, stderr); + perf_event__fprintf(event, NULL, stderr); pr_debug("Couldn't parse sample\n"); goto out_delete_evlist; } if (verbose > 0) { pr_info("%" PRIu64" %d ", sample.time, sample.cpu); - perf_event__fprintf(event, stderr); + perf_event__fprintf(event, NULL, stderr); } if (prev_time > sample.time) { @@ -330,5 +331,22 @@ found_exit: out_delete_evlist: evlist__delete(evlist); out: - return (err < 0 || errs > 0) ? -1 : 0; + perf_sample__exit(&sample); + if (err == -EACCES) + return TEST_SKIP; + if (err < 0 || errs != 0) + return TEST_FAIL; + return TEST_OK; } + +static struct test_case tests__PERF_RECORD[] = { + TEST_CASE_REASON("PERF_RECORD_* events & perf_sample fields", + PERF_RECORD, + "permissions"), + { .name = NULL, } +}; + +struct test_suite suite__PERF_RECORD = { + .desc = "PERF_RECORD_* events & perf_sample fields", + .test_cases = tests__PERF_RECORD, +}; diff --git a/tools/perf/tests/perf-targz-src-pkg b/tools/perf/tests/perf-targz-src-pkg index fae26b1cf08f..b3075c168cb2 100755 --- a/tools/perf/tests/perf-targz-src-pkg +++ b/tools/perf/tests/perf-targz-src-pkg @@ -7,16 +7,17 @@ # be in such tarball, which sometimes gets broken when we move files around, # like when we made some files that were in tools/perf/ available to other tools/ # codebases by moving it to tools/include/, etc. +set -e PERF=$1 cd ${PERF}/../.. -make perf-targz-src-pkg > /dev/null +make perf-targz-src-pkg TARBALL=$(ls -rt perf-*.tar.gz) TMP_DEST=$(mktemp -d) tar xf ${TARBALL} -C $TMP_DEST rm -f ${TARBALL} cd - > /dev/null -make -C $TMP_DEST/perf*/tools/perf > /dev/null +make -C $TMP_DEST/perf*/tools/perf RC=$? rm -rf ${TMP_DEST} exit $RC diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c new file mode 100644 index 000000000000..d3e40fa5482c --- /dev/null +++ b/tools/perf/tests/perf-time-to-tsc.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <unistd.h> +#include <linux/types.h> +#include <sys/prctl.h> +#include <perf/cpumap.h> +#include <perf/evlist.h> +#include <perf/mmap.h> + +#include "debug.h" +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "record.h" +#include "tsc.h" +#include "mmap.h" +#include "tests.h" +#include "util/sample.h" + +/* + * Except x86_64/i386 and Arm64, other archs don't support TSC in perf. Just + * enable the test for x86_64/i386 and Arm64 archs. + */ +#if defined(__x86_64__) || defined(__i386__) || defined(__aarch64__) +#define TSC_IS_SUPPORTED 1 +#else +#define TSC_IS_SUPPORTED 0 +#endif + +#define CHECK__(x) { \ + while ((x) < 0) { \ + pr_debug(#x " failed!\n"); \ + goto out_err; \ + } \ +} + +#define CHECK_NOT_NULL__(x) { \ + while ((x) == NULL) { \ + pr_debug(#x " failed!\n"); \ + goto out_err; \ + } \ +} + +static int test__tsc_is_supported(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + if (!TSC_IS_SUPPORTED) { + pr_debug("Test not supported on this architecture\n"); + return TEST_SKIP; + } + + return TEST_OK; +} + +/** + * test__perf_time_to_tsc - test converting perf time to TSC. + * + * This function implements a test that checks that the conversion of perf time + * to and from TSC is consistent with the order of events. If the test passes + * %0 is returned, otherwise %-1 is returned. If TSC conversion is not + * supported then the test passes but " (not supported)" is printed. + */ +static int test__perf_time_to_tsc(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + struct record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .target = { + .uses_mmap = true, + }, + .sample_time = true, + }; + struct perf_thread_map *threads = NULL; + struct perf_cpu_map *cpus = NULL; + struct evlist *evlist = NULL; + struct evsel *evsel = NULL; + int err = TEST_FAIL, ret, i; + const char *comm1, *comm2; + struct perf_tsc_conversion tc; + struct perf_event_mmap_page *pc; + union perf_event *event; + u64 test_tsc, comm1_tsc, comm2_tsc; + u64 test_time, comm1_time = 0, comm2_time = 0; + struct mmap *md; + + + threads = thread_map__new(-1, getpid(), UINT_MAX); + CHECK_NOT_NULL__(threads); + + cpus = perf_cpu_map__new_online_cpus(); + CHECK_NOT_NULL__(cpus); + + evlist = evlist__new(); + CHECK_NOT_NULL__(evlist); + + perf_evlist__set_maps(&evlist->core, cpus, threads); + + CHECK__(parse_event(evlist, "cycles:u")); + + evlist__config(evlist, &opts, NULL); + + /* For hybrid "cycles:u", it creates two events */ + evlist__for_each_entry(evlist, evsel) { + evsel->core.attr.comm = 1; + evsel->core.attr.disabled = 1; + evsel->core.attr.enable_on_exec = 0; + } + + ret = evlist__open(evlist); + if (ret < 0) { + if (ret == -ENOENT) + err = TEST_SKIP; + else + pr_debug("evlist__open() failed\n"); + goto out_err; + } + + CHECK__(evlist__mmap(evlist, UINT_MAX)); + + pc = evlist->mmap[0].core.base; + ret = perf_read_tsc_conversion(pc, &tc); + if (ret) { + if (ret == -EOPNOTSUPP) { + pr_debug("perf_read_tsc_conversion is not supported in current kernel\n"); + err = TEST_SKIP; + } + goto out_err; + } + + evlist__enable(evlist); + + comm1 = "Test COMM 1"; + CHECK__(prctl(PR_SET_NAME, (unsigned long)comm1, 0, 0, 0)); + + test_tsc = rdtsc(); + + comm2 = "Test COMM 2"; + CHECK__(prctl(PR_SET_NAME, (unsigned long)comm2, 0, 0, 0)); + + evlist__disable(evlist); + + for (i = 0; i < evlist->core.nr_mmaps; i++) { + md = &evlist->mmap[i]; + if (perf_mmap__read_init(&md->core) < 0) + continue; + + while ((event = perf_mmap__read_event(&md->core)) != NULL) { + struct perf_sample sample; + + perf_sample__init(&sample, /*all=*/false); + if (event->header.type != PERF_RECORD_COMM || + (pid_t)event->comm.pid != getpid() || + (pid_t)event->comm.tid != getpid()) + goto next_event; + + if (strcmp(event->comm.comm, comm1) == 0) { + CHECK_NOT_NULL__(evsel = evlist__event2evsel(evlist, event)); + CHECK__(evsel__parse_sample(evsel, event, &sample)); + comm1_time = sample.time; + } + if (strcmp(event->comm.comm, comm2) == 0) { + CHECK_NOT_NULL__(evsel = evlist__event2evsel(evlist, event)); + CHECK__(evsel__parse_sample(evsel, event, &sample)); + comm2_time = sample.time; + } +next_event: + perf_mmap__consume(&md->core); + perf_sample__exit(&sample); + } + perf_mmap__read_done(&md->core); + } + + if (!comm1_time || !comm2_time) + goto out_err; + + test_time = tsc_to_perf_time(test_tsc, &tc); + comm1_tsc = perf_time_to_tsc(comm1_time, &tc); + comm2_tsc = perf_time_to_tsc(comm2_time, &tc); + + pr_debug("1st event perf time %"PRIu64" tsc %"PRIu64"\n", + comm1_time, comm1_tsc); + pr_debug("rdtsc time %"PRIu64" tsc %"PRIu64"\n", + test_time, test_tsc); + pr_debug("2nd event perf time %"PRIu64" tsc %"PRIu64"\n", + comm2_time, comm2_tsc); + + if (test_time <= comm1_time || + test_time >= comm2_time) + goto out_err; + + if (test_tsc <= comm1_tsc || + test_tsc >= comm2_tsc) + goto out_err; + + err = TEST_OK; + +out_err: + evlist__delete(evlist); + perf_cpu_map__put(cpus); + perf_thread_map__put(threads); + return err; +} + +static struct test_case time_to_tsc_tests[] = { + TEST_CASE_REASON("TSC support", tsc_is_supported, + "This architecture does not support"), + TEST_CASE_REASON("Perf time to TSC", perf_time_to_tsc, + "perf_read_tsc_conversion is not supported"), + { .name = NULL, } +}; + +struct test_suite suite__perf_time_to_tsc = { + .desc = "Convert perf time to TSC", + .test_cases = time_to_tsc_tests, +}; diff --git a/tools/perf/tests/pfm.c b/tools/perf/tests/pfm.c index 76a53126efdf..2e38dfa34b6c 100644 --- a/tools/perf/tests/pfm.c +++ b/tools/perf/tests/pfm.c @@ -12,27 +12,6 @@ #include <linux/kernel.h> #ifdef HAVE_LIBPFM -static int test__pfm_events(void); -static int test__pfm_group(void); -#endif - -static const struct { - int (*func)(void); - const char *desc; -} pfm_testcase_table[] = { -#ifdef HAVE_LIBPFM - { - .func = test__pfm_events, - .desc = "test of individual --pfm-events", - }, - { - .func = test__pfm_group, - .desc = "test groups of --pfm-events", - }, -#endif -}; - -#ifdef HAVE_LIBPFM static int count_pfm_events(struct perf_evlist *evlist) { struct perf_evsel *evsel; @@ -44,7 +23,8 @@ static int count_pfm_events(struct perf_evlist *evlist) return count; } -static int test__pfm_events(void) +static int test__pfm_events(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { struct evlist *evlist; struct option opt; @@ -96,7 +76,7 @@ static int test__pfm_events(void) count_pfm_events(&evlist->core), table[i].nr_events); TEST_ASSERT_EQUAL(table[i].events, - evlist->nr_groups, + evlist__nr_groups(evlist), 0); evlist__delete(evlist); @@ -104,7 +84,8 @@ static int test__pfm_events(void) return 0; } -static int test__pfm_group(void) +static int test__pfm_group(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { struct evlist *evlist; struct option opt; @@ -122,22 +103,22 @@ static int test__pfm_group(void) { .events = "{instructions}", .nr_events = 1, - .nr_groups = 1, + .nr_groups = 0, }, { .events = "{instructions},{}", .nr_events = 1, - .nr_groups = 1, + .nr_groups = 0, }, { .events = "{},{instructions}", - .nr_events = 0, + .nr_events = 1, .nr_groups = 0, }, { .events = "{instructions},{instructions}", .nr_events = 2, - .nr_groups = 2, + .nr_groups = 0, }, { .events = "{instructions,cycles},{instructions,cycles}", @@ -155,6 +136,16 @@ static int test__pfm_group(void) .nr_events = 3, .nr_groups = 1, }, + { + .events = "instructions}", + .nr_events = 1, + .nr_groups = 0, + }, + { + .events = "{{instructions}}", + .nr_events = 0, + .nr_groups = 0, + }, }; for (i = 0; i < ARRAY_SIZE(table); i++) { @@ -170,34 +161,34 @@ static int test__pfm_group(void) count_pfm_events(&evlist->core), table[i].nr_events); TEST_ASSERT_EQUAL(table[i].events, - evlist->nr_groups, + evlist__nr_groups(evlist), table[i].nr_groups); evlist__delete(evlist); } return 0; } -#endif - -const char *test__pfm_subtest_get_desc(int i) -{ - if (i < 0 || i >= (int)ARRAY_SIZE(pfm_testcase_table)) - return NULL; - return pfm_testcase_table[i].desc; -} - -int test__pfm_subtest_get_nr(void) +#else +static int test__pfm_events(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { - return (int)ARRAY_SIZE(pfm_testcase_table); + return TEST_SKIP; } -int test__pfm(struct test *test __maybe_unused, int i __maybe_unused) +static int test__pfm_group(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { -#ifdef HAVE_LIBPFM - if (i < 0 || i >= (int)ARRAY_SIZE(pfm_testcase_table)) - return TEST_FAIL; - return pfm_testcase_table[i].func(); -#else return TEST_SKIP; -#endif } +#endif + +static struct test_case pfm_tests[] = { + TEST_CASE_REASON("test of individual --pfm-events", pfm_events, "not compiled in"), + TEST_CASE_REASON("test groups of --pfm-events", pfm_group, "not compiled in"), + { .name = NULL, } +}; + +struct test_suite suite__pfm = { + .desc = "Test libpfm4 support", + .test_cases = pfm_tests, +}; diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c index ab64b4a4e284..815b40097428 100644 --- a/tools/perf/tests/pmu-events.c +++ b/tools/perf/tests/pmu-events.c @@ -2,6 +2,7 @@ #include "math.h" #include "parse-events.h" #include "pmu.h" +#include "pmus.h" #include "tests.h" #include <errno.h> #include <stdio.h> @@ -9,13 +10,19 @@ #include <linux/zalloc.h> #include "debug.h" #include "../pmu-events/pmu-events.h" +#include <perf/evlist.h> #include "util/evlist.h" #include "util/expr.h" +#include "util/hashmap.h" #include "util/parse-events.h" +#include "metricgroup.h" +#include "stat.h" struct perf_pmu_test_event { + /* used for matching against events from generated pmu-events.c */ struct pmu_event event; + /* used for matching against event aliases */ /* extra events for aliases */ const char *alias_str; @@ -25,99 +32,258 @@ struct perf_pmu_test_event { * be set in the alias. */ const char *alias_long_desc; + + /* PMU which we should match against */ + const char *matching_pmu; }; -static struct perf_pmu_test_event test_cpu_events[] = { - { - .event = { - .name = "bp_l1_btb_correct", - .event = "event=0x8a", - .desc = "L1 BTB Correction", - .topic = "branch", - }, - .alias_str = "event=0x8a", - .alias_long_desc = "L1 BTB Correction", +struct perf_pmu_test_pmu { + const char *pmu_name; + bool pmu_is_uncore; + const char *pmu_id; + struct perf_pmu_test_event const *aliases[10]; +}; + +static const struct perf_pmu_test_event bp_l1_btb_correct = { + .event = { + .pmu = "default_core", + .name = "bp_l1_btb_correct", + .event = "event=0x8a", + .desc = "L1 BTB Correction", + .topic = "branch", }, - { - .event = { - .name = "bp_l2_btb_correct", - .event = "event=0x8b", - .desc = "L2 BTB Correction", - .topic = "branch", - }, - .alias_str = "event=0x8b", - .alias_long_desc = "L2 BTB Correction", + .alias_str = "event=0x8a", + .alias_long_desc = "L1 BTB Correction", +}; + +static const struct perf_pmu_test_event bp_l2_btb_correct = { + .event = { + .pmu = "default_core", + .name = "bp_l2_btb_correct", + .event = "event=0x8b", + .desc = "L2 BTB Correction", + .topic = "branch", }, - { - .event = { - .name = "segment_reg_loads.any", - .event = "umask=0x80,period=200000,event=0x6", - .desc = "Number of segment register loads", - .topic = "other", - }, - .alias_str = "umask=0x80,(null)=0x30d40,event=0x6", - .alias_long_desc = "Number of segment register loads", + .alias_str = "event=0x8b", + .alias_long_desc = "L2 BTB Correction", +}; + +static const struct perf_pmu_test_event segment_reg_loads_any = { + .event = { + .pmu = "default_core", + .name = "segment_reg_loads.any", + .event = "event=6,period=200000,umask=0x80", + .desc = "Number of segment register loads", + .topic = "other", }, - { - .event = { - .name = "dispatch_blocked.any", - .event = "umask=0x20,period=200000,event=0x9", - .desc = "Memory cluster signals to block micro-op dispatch for any reason", - .topic = "other", - }, - .alias_str = "umask=0x20,(null)=0x30d40,event=0x9", - .alias_long_desc = "Memory cluster signals to block micro-op dispatch for any reason", + .alias_str = "event=0x6,period=0x30d40,umask=0x80", + .alias_long_desc = "Number of segment register loads", +}; + +static const struct perf_pmu_test_event dispatch_blocked_any = { + .event = { + .pmu = "default_core", + .name = "dispatch_blocked.any", + .event = "event=9,period=200000,umask=0x20", + .desc = "Memory cluster signals to block micro-op dispatch for any reason", + .topic = "other", }, - { - .event = { - .name = "eist_trans", - .event = "umask=0x0,period=200000,event=0x3a", - .desc = "Number of Enhanced Intel SpeedStep(R) Technology (EIST) transitions", - .topic = "other", - }, - .alias_str = "umask=0,(null)=0x30d40,event=0x3a", - .alias_long_desc = "Number of Enhanced Intel SpeedStep(R) Technology (EIST) transitions", + .alias_str = "event=0x9,period=0x30d40,umask=0x20", + .alias_long_desc = "Memory cluster signals to block micro-op dispatch for any reason", +}; + +static const struct perf_pmu_test_event eist_trans = { + .event = { + .pmu = "default_core", + .name = "eist_trans", + .event = "event=0x3a,period=200000", + .desc = "Number of Enhanced Intel SpeedStep(R) Technology (EIST) transitions", + .topic = "other", }, - { /* sentinel */ - .event = { - .name = NULL, - }, + .alias_str = "event=0x3a,period=0x30d40", + .alias_long_desc = "Number of Enhanced Intel SpeedStep(R) Technology (EIST) transitions", +}; + +static const struct perf_pmu_test_event l3_cache_rd = { + .event = { + .pmu = "default_core", + .name = "l3_cache_rd", + .event = "event=0x40", + .desc = "L3 cache access, read", + .long_desc = "Attributable Level 3 cache access, read", + .topic = "cache", }, + .alias_str = "event=0x40", + .alias_long_desc = "Attributable Level 3 cache access, read", }; -static struct perf_pmu_test_event test_uncore_events[] = { - { - .event = { - .name = "uncore_hisi_ddrc.flux_wcmd", - .event = "event=0x2", - .desc = "DDRC write commands. Unit: hisi_sccl,ddrc ", - .topic = "uncore", - .long_desc = "DDRC write commands", - .pmu = "hisi_sccl,ddrc", - }, - .alias_str = "event=0x2", - .alias_long_desc = "DDRC write commands", +static const struct perf_pmu_test_event *core_events[] = { + &bp_l1_btb_correct, + &bp_l2_btb_correct, + &segment_reg_loads_any, + &dispatch_blocked_any, + &eist_trans, + &l3_cache_rd, + NULL +}; + +static const struct perf_pmu_test_event uncore_hisi_ddrc_flux_wcmd = { + .event = { + .name = "uncore_hisi_ddrc.flux_wcmd", + .event = "event=2", + .desc = "DDRC write commands", + .topic = "uncore", + .long_desc = "DDRC write commands", + .pmu = "hisi_sccl,ddrc", }, - { - .event = { - .name = "unc_cbo_xsnp_response.miss_eviction", - .event = "umask=0x81,event=0x22", - .desc = "Unit: uncore_cbox A cross-core snoop resulted from L3 Eviction which misses in some processor core", - .topic = "uncore", - .long_desc = "A cross-core snoop resulted from L3 Eviction which misses in some processor core", - .pmu = "uncore_cbox", - }, - .alias_str = "umask=0x81,event=0x22", - .alias_long_desc = "A cross-core snoop resulted from L3 Eviction which misses in some processor core", + .alias_str = "event=0x2", + .alias_long_desc = "DDRC write commands", + .matching_pmu = "hisi_sccl1_ddrc2", +}; + +static const struct perf_pmu_test_event unc_cbo_xsnp_response_miss_eviction = { + .event = { + .name = "unc_cbo_xsnp_response.miss_eviction", + .event = "event=0x22,umask=0x81", + .desc = "A cross-core snoop resulted from L3 Eviction which misses in some processor core", + .topic = "uncore", + .long_desc = "A cross-core snoop resulted from L3 Eviction which misses in some processor core", + .pmu = "uncore_cbox", }, - { /* sentinel */ - .event = { - .name = NULL, - }, - } + .alias_str = "event=0x22,umask=0x81", + .alias_long_desc = "A cross-core snoop resulted from L3 Eviction which misses in some processor core", + .matching_pmu = "uncore_cbox_0", +}; + +static const struct perf_pmu_test_event uncore_hyphen = { + .event = { + .name = "event-hyphen", + .event = "event=0xe0", + .desc = "UNC_CBO_HYPHEN", + .topic = "uncore", + .long_desc = "UNC_CBO_HYPHEN", + .pmu = "uncore_cbox", + }, + .alias_str = "event=0xe0", + .alias_long_desc = "UNC_CBO_HYPHEN", + .matching_pmu = "uncore_cbox_0", +}; + +static const struct perf_pmu_test_event uncore_two_hyph = { + .event = { + .name = "event-two-hyph", + .event = "event=0xc0", + .desc = "UNC_CBO_TWO_HYPH", + .topic = "uncore", + .long_desc = "UNC_CBO_TWO_HYPH", + .pmu = "uncore_cbox", + }, + .alias_str = "event=0xc0", + .alias_long_desc = "UNC_CBO_TWO_HYPH", + .matching_pmu = "uncore_cbox_0", +}; + +static const struct perf_pmu_test_event uncore_hisi_l3c_rd_hit_cpipe = { + .event = { + .name = "uncore_hisi_l3c.rd_hit_cpipe", + .event = "event=7", + .desc = "Total read hits", + .topic = "uncore", + .long_desc = "Total read hits", + .pmu = "hisi_sccl,l3c", + }, + .alias_str = "event=0x7", + .alias_long_desc = "Total read hits", + .matching_pmu = "hisi_sccl3_l3c7", +}; + +static const struct perf_pmu_test_event uncore_imc_free_running_cache_miss = { + .event = { + .name = "uncore_imc_free_running.cache_miss", + .event = "event=0x12", + .desc = "Total cache misses", + .topic = "uncore", + .long_desc = "Total cache misses", + .pmu = "uncore_imc_free_running", + }, + .alias_str = "event=0x12", + .alias_long_desc = "Total cache misses", + .matching_pmu = "uncore_imc_free_running_0", +}; + +static const struct perf_pmu_test_event uncore_imc_cache_hits = { + .event = { + .name = "uncore_imc.cache_hits", + .event = "event=0x34", + .desc = "Total cache hits", + .topic = "uncore", + .long_desc = "Total cache hits", + .pmu = "uncore_imc", + }, + .alias_str = "event=0x34", + .alias_long_desc = "Total cache hits", + .matching_pmu = "uncore_imc_0", +}; + +static const struct perf_pmu_test_event *uncore_events[] = { + &uncore_hisi_ddrc_flux_wcmd, + &unc_cbo_xsnp_response_miss_eviction, + &uncore_hyphen, + &uncore_two_hyph, + &uncore_hisi_l3c_rd_hit_cpipe, + &uncore_imc_free_running_cache_miss, + &uncore_imc_cache_hits, + NULL +}; + +static const struct perf_pmu_test_event sys_ddr_pmu_write_cycles = { + .event = { + .name = "sys_ddr_pmu.write_cycles", + .event = "event=0x2b", + .desc = "ddr write-cycles event", + .topic = "uncore", + .pmu = "uncore_sys_ddr_pmu", + .compat = "v8", + }, + .alias_str = "event=0x2b", + .alias_long_desc = "ddr write-cycles event", + .matching_pmu = "uncore_sys_ddr_pmu0", }; -const int total_test_events_size = ARRAY_SIZE(test_uncore_events); +static const struct perf_pmu_test_event sys_ccn_pmu_read_cycles = { + .event = { + .name = "sys_ccn_pmu.read_cycles", + .event = "config=0x2c", + .desc = "ccn read-cycles event", + .topic = "uncore", + .pmu = "uncore_sys_ccn_pmu", + .compat = "0x01", + }, + .alias_str = "config=0x2c", + .alias_long_desc = "ccn read-cycles event", + .matching_pmu = "uncore_sys_ccn_pmu4", +}; + +static const struct perf_pmu_test_event sys_cmn_pmu_hnf_cache_miss = { + .event = { + .name = "sys_cmn_pmu.hnf_cache_miss", + .event = "eventid=1,type=5", + .desc = "Counts total cache misses in first lookup result (high priority)", + .topic = "uncore", + .pmu = "uncore_sys_cmn_pmu", + .compat = "(434|436|43c|43a).*", + }, + .alias_str = "eventid=0x1,type=0x5", + .alias_long_desc = "Counts total cache misses in first lookup result (high priority)", + .matching_pmu = "uncore_sys_cmn_pmu0", +}; + +static const struct perf_pmu_test_event *sys_events[] = { + &sys_ddr_pmu_write_cycles, + &sys_ccn_pmu_read_cycles, + &sys_cmn_pmu_hnf_cache_miss, + NULL +}; static bool is_same(const char *reference, const char *test) { @@ -133,247 +299,505 @@ static bool is_same(const char *reference, const char *test) return !strcmp(reference, test); } -static struct pmu_events_map *__test_pmu_get_events_map(void) +static int compare_pmu_events(const struct pmu_event *e1, const struct pmu_event *e2) { - struct pmu_events_map *map; + if (!is_same(e1->name, e2->name)) { + pr_debug2("testing event e1 %s: mismatched name string, %s vs %s\n", + e1->name, e1->name, e2->name); + return -1; + } + + if (!is_same(e1->compat, e2->compat)) { + pr_debug2("testing event e1 %s: mismatched compat string, %s vs %s\n", + e1->name, e1->compat, e2->compat); + return -1; + } + + if (!is_same(e1->event, e2->event)) { + pr_debug2("testing event e1 %s: mismatched event, %s vs %s\n", + e1->name, e1->event, e2->event); + return -1; + } + + if (!is_same(e1->desc, e2->desc)) { + pr_debug2("testing event e1 %s: mismatched desc, %s vs %s\n", + e1->name, e1->desc, e2->desc); + return -1; + } - for (map = &pmu_events_map[0]; map->cpuid; map++) { - if (!strcmp(map->cpuid, "testcpu")) - return map; + if (!is_same(e1->topic, e2->topic)) { + pr_debug2("testing event e1 %s: mismatched topic, %s vs %s\n", + e1->name, e1->topic, e2->topic); + return -1; + } + + if (!is_same(e1->long_desc, e2->long_desc)) { + pr_debug2("testing event e1 %s: mismatched long_desc, %s vs %s\n", + e1->name, e1->long_desc, e2->long_desc); + return -1; + } + + if (!is_same(e1->pmu, e2->pmu)) { + pr_debug2("testing event e1 %s: mismatched pmu string, %s vs %s\n", + e1->name, e1->pmu, e2->pmu); + return -1; + } + + if (!is_same(e1->unit, e2->unit)) { + pr_debug2("testing event e1 %s: mismatched unit, %s vs %s\n", + e1->name, e1->unit, e2->unit); + return -1; } - pr_err("could not find test events map\n"); + if (e1->perpkg != e2->perpkg) { + pr_debug2("testing event e1 %s: mismatched perpkg, %d vs %d\n", + e1->name, e1->perpkg, e2->perpkg); + return -1; + } - return NULL; + if (e1->deprecated != e2->deprecated) { + pr_debug2("testing event e1 %s: mismatched deprecated, %d vs %d\n", + e1->name, e1->deprecated, e2->deprecated); + return -1; + } + + return 0; } -/* Verify generated events from pmu-events.c is as expected */ -static int test_pmu_event_table(void) +static int compare_alias_to_test_event(struct pmu_event_info *alias, + struct perf_pmu_test_event const *test_event, + char const *pmu_name) { - struct pmu_events_map *map = __test_pmu_get_events_map(); - struct pmu_event *table; - int map_events = 0, expected_events; + struct pmu_event const *event = &test_event->event; - /* ignore 2x sentinels */ - expected_events = ARRAY_SIZE(test_cpu_events) + - ARRAY_SIZE(test_uncore_events) - 2; + /* An alias was found, ensure everything is in order */ + if (!is_same(alias->name, event->name)) { + pr_debug("testing aliases PMU %s: mismatched name, %s vs %s\n", + pmu_name, alias->name, event->name); + return -1; + } - if (!map) + if (!is_same(alias->desc, event->desc)) { + pr_debug("testing aliases PMU %s: mismatched desc, %s vs %s\n", + pmu_name, alias->desc, event->desc); return -1; + } - for (table = map->table; table->name; table++) { - struct perf_pmu_test_event *test; - struct pmu_event *te; - bool found = false; + if (!is_same(alias->long_desc, test_event->alias_long_desc)) { + pr_debug("testing aliases PMU %s: mismatched long_desc, %s vs %s\n", + pmu_name, alias->long_desc, + test_event->alias_long_desc); + return -1; + } - if (table->pmu) - test = &test_uncore_events[0]; - else - test = &test_cpu_events[0]; + if (!is_same(alias->topic, event->topic)) { + pr_debug("testing aliases PMU %s: mismatched topic, %s vs %s\n", + pmu_name, alias->topic, event->topic); + return -1; + } - te = &test->event; + if (!is_same(alias->str, test_event->alias_str)) { + pr_debug("testing aliases PMU %s: mismatched str, %s vs %s\n", + pmu_name, alias->str, test_event->alias_str); + return -1; + } - for (; te->name; test++, te = &test->event) { - if (strcmp(table->name, te->name)) - continue; - found = true; - map_events++; + if (!is_same(alias->long_desc, test_event->alias_long_desc)) { + pr_debug("testing aliases PMU %s: mismatched long desc, %s vs %s\n", + pmu_name, alias->str, test_event->alias_long_desc); + return -1; + } - if (!is_same(table->desc, te->desc)) { - pr_debug2("testing event table %s: mismatched desc, %s vs %s\n", - table->name, table->desc, te->desc); - return -1; - } + if (!is_same(alias->pmu_name, test_event->event.pmu) && + !is_same(alias->pmu_name, "default_core")) { + pr_debug("testing aliases PMU %s: mismatched pmu_name, %s vs %s\n", + pmu_name, alias->pmu_name, test_event->event.pmu); + return -1; + } - if (!is_same(table->topic, te->topic)) { - pr_debug2("testing event table %s: mismatched topic, %s vs %s\n", - table->name, table->topic, - te->topic); - return -1; - } + return 0; +} - if (!is_same(table->long_desc, te->long_desc)) { - pr_debug2("testing event table %s: mismatched long_desc, %s vs %s\n", - table->name, table->long_desc, - te->long_desc); - return -1; - } +static int test__pmu_event_table_core_callback(const struct pmu_event *pe, + const struct pmu_events_table *table __maybe_unused, + void *data) +{ + int *map_events = data; + struct perf_pmu_test_event const **test_event_table; + bool found = false; - if (!is_same(table->unit, te->unit)) { - pr_debug2("testing event table %s: mismatched unit, %s vs %s\n", - table->name, table->unit, - te->unit); - return -1; - } + if (strcmp(pe->pmu, "default_core")) + test_event_table = &uncore_events[0]; + else + test_event_table = &core_events[0]; - if (!is_same(table->perpkg, te->perpkg)) { - pr_debug2("testing event table %s: mismatched perpkg, %s vs %s\n", - table->name, table->perpkg, - te->perpkg); - return -1; - } + for (; *test_event_table; test_event_table++) { + struct perf_pmu_test_event const *test_event = *test_event_table; + struct pmu_event const *event = &test_event->event; - if (!is_same(table->metric_expr, te->metric_expr)) { - pr_debug2("testing event table %s: mismatched metric_expr, %s vs %s\n", - table->name, table->metric_expr, - te->metric_expr); - return -1; - } + if (strcmp(pe->name, event->name)) + continue; + found = true; + (*map_events)++; - if (!is_same(table->metric_name, te->metric_name)) { - pr_debug2("testing event table %s: mismatched metric_name, %s vs %s\n", - table->name, table->metric_name, - te->metric_name); - return -1; - } + if (compare_pmu_events(pe, event)) + return -1; - if (!is_same(table->deprecated, te->deprecated)) { - pr_debug2("testing event table %s: mismatched deprecated, %s vs %s\n", - table->name, table->deprecated, - te->deprecated); - return -1; - } + pr_debug("testing event table %s: pass\n", pe->name); + } + if (!found) { + pr_err("testing event table: could not find event %s\n", pe->name); + return -1; + } + return 0; +} - pr_debug("testing event table %s: pass\n", table->name); - } +static int test__pmu_event_table_sys_callback(const struct pmu_event *pe, + const struct pmu_events_table *table __maybe_unused, + void *data) +{ + int *map_events = data; + struct perf_pmu_test_event const **test_event_table; + bool found = false; - if (!found) { - pr_err("testing event table: could not find event %s\n", - table->name); - return -1; - } + test_event_table = &sys_events[0]; + + for (; *test_event_table; test_event_table++) { + struct perf_pmu_test_event const *test_event = *test_event_table; + struct pmu_event const *event = &test_event->event; + + if (strcmp(pe->name, event->name)) + continue; + found = true; + (*map_events)++; + + if (compare_pmu_events(pe, event)) + return TEST_FAIL; + + pr_debug("testing sys event table %s: pass\n", pe->name); } + if (!found) { + pr_debug("testing sys event table: could not find event %s\n", pe->name); + return TEST_FAIL; + } + return TEST_OK; +} + +/* Verify generated events from pmu-events.c are as expected */ +static int test__pmu_event_table(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + const struct pmu_events_table *sys_event_table = + find_sys_events_table("pmu_events__test_soc_sys"); + const struct pmu_events_table *table = find_core_events_table("testarch", "testcpu"); + int map_events = 0, expected_events, err; + + /* ignore 3x sentinels */ + expected_events = ARRAY_SIZE(core_events) + + ARRAY_SIZE(uncore_events) + + ARRAY_SIZE(sys_events) - 3; + + if (!table || !sys_event_table) + return -1; + + err = pmu_events_table__for_each_event(table, /*pmu=*/ NULL, + test__pmu_event_table_core_callback, + &map_events); + if (err) + return err; + + err = pmu_events_table__for_each_event(sys_event_table, /*pmu=*/ NULL, + test__pmu_event_table_sys_callback, + &map_events); + if (err) + return err; if (map_events != expected_events) { pr_err("testing event table: found %d, but expected %d\n", map_events, expected_events); - return -1; + return TEST_FAIL; } return 0; } -static struct perf_pmu_alias *find_alias(const char *test_event, struct list_head *aliases) -{ - struct perf_pmu_alias *alias; +struct test_core_pmu_event_aliases_cb_args { + struct perf_pmu_test_event const *test_event; + int *count; +}; - list_for_each_entry(alias, aliases, list) - if (!strcmp(test_event, alias->name)) - return alias; +static int test_core_pmu_event_aliases_cb(void *state, struct pmu_event_info *alias) +{ + struct test_core_pmu_event_aliases_cb_args *args = state; - return NULL; + if (compare_alias_to_test_event(alias, args->test_event, alias->pmu->name)) + return -1; + (*args->count)++; + pr_debug2("testing aliases core PMU %s: matched event %s\n", + alias->pmu_name, alias->name); + return 0; } /* Verify aliases are as expected */ -static int __test__pmu_event_aliases(char *pmu_name, int *count) +static int __test_core_pmu_event_aliases(const char *pmu_name, int *count) { - struct perf_pmu_test_event *test; - struct pmu_event *te; + struct perf_pmu_test_event const **test_event_table; struct perf_pmu *pmu; - LIST_HEAD(aliases); int res = 0; - bool use_uncore_table; - struct pmu_events_map *map = __test_pmu_get_events_map(); + const struct pmu_events_table *table = find_core_events_table("testarch", "testcpu"); - if (!map) + if (!table) return -1; - if (is_pmu_core(pmu_name)) { - test = &test_cpu_events[0]; - use_uncore_table = false; - } else { - test = &test_uncore_events[0]; - use_uncore_table = true; - } + test_event_table = &core_events[0]; pmu = zalloc(sizeof(*pmu)); if (!pmu) return -1; - pmu->name = pmu_name; + if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, pmu_name) != 0) { + perf_pmu__delete(pmu); + return -1; + } + pmu->is_core = true; - pmu_add_cpu_aliases_map(&aliases, pmu, map); + pmu->events_table = table; + pmu_add_cpu_aliases_table(pmu, table); + pmu->cpu_aliases_added = true; + pmu->sysfs_aliases_loaded = true; - for (te = &test->event; te->name; test++, te = &test->event) { - struct perf_pmu_alias *alias = find_alias(te->name, &aliases); + res = pmu_events_table__find_event(table, pmu, "bp_l1_btb_correct", NULL, NULL); + if (res != 0) { + pr_debug("Missing test event in test architecture"); + return res; + } + for (; *test_event_table; test_event_table++) { + struct perf_pmu_test_event test_event = **test_event_table; + struct pmu_event const *event = &test_event.event; + struct test_core_pmu_event_aliases_cb_args args = { + .test_event = &test_event, + .count = count, + }; + int err; + + test_event.event.pmu = pmu_name; + err = perf_pmu__find_event(pmu, event->name, &args, + test_core_pmu_event_aliases_cb); + if (err) + res = err; + } + perf_pmu__delete(pmu); - if (!alias) { - bool uncore_match = pmu_uncore_alias_match(pmu_name, - te->pmu); + return res; +} - if (use_uncore_table && !uncore_match) { - pr_debug3("testing aliases PMU %s: skip matching alias %s\n", - pmu_name, te->name); - continue; - } +static int __test_uncore_pmu_event_aliases(struct perf_pmu_test_pmu *test_pmu) +{ + int alias_count = 0, to_match_count = 0, matched_count = 0; + struct perf_pmu_test_event const **table; + struct perf_pmu *pmu; + const struct pmu_events_table *events_table; + int res = 0; - pr_debug2("testing aliases PMU %s: no alias, alias_table->name=%s\n", - pmu_name, te->name); - res = -1; - break; - } + events_table = find_core_events_table("testarch", "testcpu"); + if (!events_table) + return -1; - if (!is_same(alias->desc, te->desc)) { - pr_debug2("testing aliases PMU %s: mismatched desc, %s vs %s\n", - pmu_name, alias->desc, te->desc); - res = -1; - break; - } + pmu = zalloc(sizeof(*pmu)); + if (!pmu) + return -1; - if (!is_same(alias->long_desc, test->alias_long_desc)) { - pr_debug2("testing aliases PMU %s: mismatched long_desc, %s vs %s\n", - pmu_name, alias->long_desc, - test->alias_long_desc); - res = -1; - break; + if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, test_pmu->pmu_name) != 0) { + perf_pmu__delete(pmu); + return -1; + } + pmu->is_uncore = test_pmu->pmu_is_uncore; + if (test_pmu->pmu_id) { + pmu->id = strdup(test_pmu->pmu_id); + if (!pmu->id) { + perf_pmu__delete(pmu); + return -1; } + } + pmu->events_table = events_table; + pmu_add_cpu_aliases_table(pmu, events_table); + pmu->cpu_aliases_added = true; + pmu->sysfs_aliases_loaded = true; + pmu_add_sys_aliases(pmu); + + /* Count how many aliases we generated */ + alias_count = perf_pmu__num_events(pmu); + + /* Count how many aliases we expect from the known table */ + for (table = &test_pmu->aliases[0]; *table; table++) + to_match_count++; + + if (alias_count != to_match_count) { + pr_debug("testing aliases uncore PMU %s: mismatch expected aliases (%d) vs found (%d)\n", + pmu->name, to_match_count, alias_count); + perf_pmu__delete(pmu); + return -1; + } - if (!is_same(alias->str, test->alias_str)) { - pr_debug2("testing aliases PMU %s: mismatched str, %s vs %s\n", - pmu_name, alias->str, test->alias_str); - res = -1; - break; + for (table = &test_pmu->aliases[0]; *table; table++) { + struct perf_pmu_test_event test_event = **table; + struct pmu_event const *event = &test_event.event; + int err; + struct test_core_pmu_event_aliases_cb_args args = { + .test_event = &test_event, + .count = &matched_count, + }; + + if (strcmp(pmu->name, test_event.matching_pmu)) { + pr_debug("testing aliases uncore PMU %s: mismatched matching_pmu, %s vs %s\n", + pmu->name, test_event.matching_pmu, pmu->name); + perf_pmu__delete(pmu); + return -1; } - if (!is_same(alias->topic, te->topic)) { - pr_debug2("testing aliases PMU %s: mismatched topic, %s vs %s\n", - pmu_name, alias->topic, te->topic); - res = -1; - break; + err = perf_pmu__find_event(pmu, event->name, &args, + test_core_pmu_event_aliases_cb); + if (err) { + res = err; + pr_debug("testing aliases uncore PMU %s: could not match alias %s\n", + pmu->name, event->name); + perf_pmu__delete(pmu); + return -1; } - - (*count)++; - pr_debug2("testing aliases PMU %s: matched event %s\n", - pmu_name, alias->name); } - free(pmu); + if (alias_count != matched_count) { + pr_debug("testing aliases uncore PMU %s: mismatch found aliases (%d) vs matched (%d)\n", + pmu->name, matched_count, alias_count); + res = -1; + } + perf_pmu__delete(pmu); return res; } +static struct perf_pmu_test_pmu test_pmus[] = { + { + .pmu_name = "hisi_sccl1_ddrc2", + .pmu_is_uncore = 1, + .aliases = { + &uncore_hisi_ddrc_flux_wcmd, + }, + }, + { + .pmu_name = "uncore_cbox_0", + .pmu_is_uncore = 1, + .aliases = { + &unc_cbo_xsnp_response_miss_eviction, + &uncore_hyphen, + &uncore_two_hyph, + }, + }, + { + .pmu_name = "hisi_sccl3_l3c7", + .pmu_is_uncore = 1, + .aliases = { + &uncore_hisi_l3c_rd_hit_cpipe, + }, + }, + { + .pmu_name = "uncore_imc_free_running_0", + .pmu_is_uncore = 1, + .aliases = { + &uncore_imc_free_running_cache_miss, + }, + }, + { + .pmu_name = "uncore_imc_0", + .pmu_is_uncore = 1, + .aliases = { + &uncore_imc_cache_hits, + }, + }, + { + .pmu_name = "uncore_sys_ddr_pmu0", + .pmu_is_uncore = 1, + .pmu_id = "v8", + .aliases = { + &sys_ddr_pmu_write_cycles, + }, + }, + { + .pmu_name = "uncore_sys_ccn_pmu4", + .pmu_is_uncore = 1, + .pmu_id = "0x01", + .aliases = { + &sys_ccn_pmu_read_cycles, + }, + }, + { + .pmu_name = "uncore_sys_cmn_pmu0", + .pmu_is_uncore = 1, + .pmu_id = "43401", + .aliases = { + &sys_cmn_pmu_hnf_cache_miss, + }, + }, + { + .pmu_name = "uncore_sys_cmn_pmu0", + .pmu_is_uncore = 1, + .pmu_id = "43602", + .aliases = { + &sys_cmn_pmu_hnf_cache_miss, + }, + }, + { + .pmu_name = "uncore_sys_cmn_pmu0", + .pmu_is_uncore = 1, + .pmu_id = "43c03", + .aliases = { + &sys_cmn_pmu_hnf_cache_miss, + }, + }, + { + .pmu_name = "uncore_sys_cmn_pmu0", + .pmu_is_uncore = 1, + .pmu_id = "43a01", + .aliases = { + &sys_cmn_pmu_hnf_cache_miss, + }, + } +}; -static int test_aliases(void) +/* Test that aliases generated are as expected */ +static int test__aliases(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { struct perf_pmu *pmu = NULL; + unsigned long i; - while ((pmu = perf_pmu__scan(pmu)) != NULL) { + while ((pmu = perf_pmus__scan_core(pmu)) != NULL) { int count = 0; if (list_empty(&pmu->format)) { - pr_debug2("skipping testing PMU %s\n", pmu->name); + pr_debug2("skipping testing core PMU %s\n", pmu->name); continue; } - if (__test__pmu_event_aliases(pmu->name, &count)) { - pr_debug("testing PMU %s aliases: failed\n", pmu->name); + if (__test_core_pmu_event_aliases(pmu->name, &count)) { + pr_debug("testing core PMU %s aliases: failed\n", pmu->name); return -1; } - if (count == 0) - pr_debug3("testing PMU %s aliases: no events to match\n", + if (count == 0) { + pr_debug("testing core PMU %s aliases: no events to match\n", pmu->name); - else - pr_debug("testing PMU %s aliases: pass\n", pmu->name); + return -1; + } + + pr_debug("testing core PMU %s aliases: pass\n", pmu->name); + } + + for (i = 0; i < ARRAY_SIZE(test_pmus); i++) { + int res; + + res = __test_uncore_pmu_event_aliases(&test_pmus[i]); + if (res) + return res; } return 0; @@ -390,151 +814,290 @@ static bool is_number(const char *str) return errno == 0 && end_ptr != str; } -static int check_parse_id(const char *id, bool same_cpu, struct pmu_event *pe) +static int check_parse_id(const char *id, struct parse_events_error *error) { - struct parse_events_error error; struct evlist *evlist; int ret; + char *dup, *cur; /* Numbers are always valid. */ if (is_number(id)) return 0; evlist = evlist__new(); - memset(&error, 0, sizeof(error)); - ret = parse_events(evlist, id, &error); - if (ret && same_cpu) { - pr_warning("Parse event failed metric '%s' id '%s' expr '%s'\n", - pe->metric_name, id, pe->metric_expr); - pr_warning("Error string '%s' help '%s'\n", error.str, - error.help); - } else if (ret) { - pr_debug3("Parse event failed, but for an event that may not be supported by this CPU.\nid '%s' metric '%s' expr '%s'\n", - id, pe->metric_name, pe->metric_expr); - ret = 0; - } + if (!evlist) + return -ENOMEM; + + dup = strdup(id); + if (!dup) + return -ENOMEM; + + for (cur = strchr(dup, '@') ; cur; cur = strchr(++cur, '@')) + *cur = '/'; + + ret = __parse_events(evlist, dup, /*pmu_filter=*/NULL, error, /*fake_pmu=*/true, + /*warn_if_reordered=*/true, /*fake_tp=*/false); + free(dup); + evlist__delete(evlist); - free(error.str); - free(error.help); - free(error.first_str); - free(error.first_help); return ret; } -static void expr_failure(const char *msg, - const struct pmu_events_map *map, - const struct pmu_event *pe) +static int check_parse_fake(const char *id) { - pr_debug("%s for map %s %s %s\n", - msg, map->cpuid, map->version, map->type); - pr_debug("On metric %s\n", pe->metric_name); - pr_debug("On expression %s\n", pe->metric_expr); + struct parse_events_error error; + int ret; + + parse_events_error__init(&error); + ret = check_parse_id(id, &error); + parse_events_error__exit(&error); + return ret; } -static int test_parsing(void) +struct metric { + struct list_head list; + struct metric_ref metric_ref; +}; + +static int test__parsing_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table, + void *data) { - struct pmu_events_map *cpus_map = perf_pmu__find_map(NULL); - struct pmu_events_map *map; - struct pmu_event *pe; - int i, j, k; - int ret = 0; - struct expr_parse_ctx ctx; - double result; + int *failures = data; + int k; + struct evlist *evlist; + struct perf_cpu_map *cpus; + struct evsel *evsel; + struct rblist metric_events = { + .nr_entries = 0, + }; + int err = 0; + + if (!pm->metric_expr) + return 0; - i = 0; - for (;;) { - map = &pmu_events_map[i++]; - if (!map->table) - break; - j = 0; - for (;;) { - struct hashmap_entry *cur; - size_t bkt; - - pe = &map->table[j++]; - if (!pe->name && !pe->metric_group && !pe->metric_name) - break; - if (!pe->metric_expr) - continue; - expr__ctx_init(&ctx); - if (expr__find_other(pe->metric_expr, NULL, &ctx, 0) - < 0) { - expr_failure("Parse other failed", map, pe); - ret++; - continue; - } + pr_debug("Found metric '%s'\n", pm->metric_name); + (*failures)++; - /* - * Add all ids with a made up value. The value may - * trigger divide by zero when subtracted and so try to - * make them unique. - */ - k = 1; - hashmap__for_each_entry((&ctx.ids), cur, bkt) - expr__add_id(&ctx, strdup(cur->key), k++); - - hashmap__for_each_entry((&ctx.ids), cur, bkt) { - if (check_parse_id(cur->key, map == cpus_map, - pe)) - ret++; - } + /* + * We need to prepare evlist for stat mode running on CPU 0 + * because that's where all the stats are going to be created. + */ + evlist = evlist__new(); + if (!evlist) + return -ENOMEM; + + cpus = perf_cpu_map__new("0"); + if (!cpus) { + evlist__delete(evlist); + return -ENOMEM; + } - if (expr__parse(&result, &ctx, pe->metric_expr, 0)) { - expr_failure("Parse failed", map, pe); - ret++; + perf_evlist__set_maps(&evlist->core, cpus, NULL); + + err = metricgroup__parse_groups_test(evlist, table, pm->metric_name, &metric_events); + if (err) { + if (!strcmp(pm->metric_name, "M1") || !strcmp(pm->metric_name, "M2") || + !strcmp(pm->metric_name, "M3")) { + (*failures)--; + pr_debug("Expected broken metric %s skipping\n", pm->metric_name); + err = 0; + } + goto out_err; + } + + err = evlist__alloc_stats(/*config=*/NULL, evlist, /*alloc_raw=*/false); + if (err) + goto out_err; + /* + * Add all ids with a made up value. The value may trigger divide by + * zero when subtracted and so try to make them unique. + */ + k = 1; + evlist__alloc_aggr_stats(evlist, 1); + evlist__for_each_entry(evlist, evsel) { + evsel->stats->aggr->counts.val = k; + if (evsel__name_is(evsel, "duration_time")) + update_stats(&walltime_nsecs_stats, k); + k++; + } + evlist__for_each_entry(evlist, evsel) { + struct metric_event *me = metricgroup__lookup(&metric_events, evsel, false); + + if (me != NULL) { + struct metric_expr *mexp; + + list_for_each_entry (mexp, &me->head, nd) { + if (strcmp(mexp->metric_name, pm->metric_name)) + continue; + pr_debug("Result %f\n", test_generic_metric(mexp, 0)); + err = 0; + (*failures)--; + goto out_err; } - expr__ctx_clear(&ctx); } } - /* TODO: fail when not ok */ - return ret == 0 ? TEST_OK : TEST_SKIP; + pr_debug("Didn't find parsed metric %s", pm->metric_name); + err = 1; +out_err: + if (err) + pr_debug("Broken metric %s\n", pm->metric_name); + + /* ... cleanup. */ + metricgroup__rblist_exit(&metric_events); + evlist__free_stats(evlist); + perf_cpu_map__put(cpus); + evlist__delete(evlist); + return err; } -static const struct { - int (*func)(void); - const char *desc; -} pmu_events_testcase_table[] = { - { - .func = test_pmu_event_table, - .desc = "PMU event table sanity", - }, - { - .func = test_aliases, - .desc = "PMU event map aliases", - }, - { - .func = test_parsing, - .desc = "Parsing of PMU event table metrics", - }, +static int test__parsing(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int failures = 0; + + pmu_for_each_core_metric(test__parsing_callback, &failures); + pmu_for_each_sys_metric(test__parsing_callback, &failures); + + return failures == 0 ? TEST_OK : TEST_FAIL; +} + +struct test_metric { + const char *str; +}; + +static struct test_metric metrics[] = { + { "(unc_p_power_state_occupancy.cores_c0 / unc_p_clockticks) * 100." }, + { "imx8_ddr0@read\\-cycles@ * 4 * 4", }, + { "imx8_ddr0@axid\\-read\\,axi_mask\\=0xffff\\,axi_id\\=0x0000@ * 4", }, + { "(cstate_pkg@c2\\-residency@ / msr@tsc@) * 100", }, + { "(imx8_ddr0@read\\-cycles@ + imx8_ddr0@write\\-cycles@)", }, }; -const char *test__pmu_events_subtest_get_desc(int subtest) +static int metric_parse_fake(const char *metric_name, const char *str) { - if (subtest < 0 || - subtest >= (int)ARRAY_SIZE(pmu_events_testcase_table)) - return NULL; - return pmu_events_testcase_table[subtest].desc; + struct expr_parse_ctx *ctx; + struct hashmap_entry *cur; + double result; + int ret = -1; + size_t bkt; + int i; + + pr_debug("parsing '%s': '%s'\n", metric_name, str); + + ctx = expr__ctx_new(); + if (!ctx) { + pr_debug("expr__ctx_new failed"); + return TEST_FAIL; + } + ctx->sctx.is_test = true; + if (expr__find_ids(str, NULL, ctx) < 0) { + pr_err("expr__find_ids failed\n"); + return -1; + } + + /* + * Add all ids with a made up value. The value may + * trigger divide by zero when subtracted and so try to + * make them unique. + */ + i = 1; + hashmap__for_each_entry(ctx->ids, cur, bkt) + expr__add_id_val(ctx, strdup(cur->pkey), i++); + + hashmap__for_each_entry(ctx->ids, cur, bkt) { + if (check_parse_fake(cur->pkey)) { + pr_err("check_parse_fake failed\n"); + goto out; + } + } + + ret = 0; + if (expr__parse(&result, ctx, str)) { + /* + * Parsing failed, make numbers go from large to small which can + * resolve divide by zero issues. + */ + i = 1024; + hashmap__for_each_entry(ctx->ids, cur, bkt) + expr__add_id_val(ctx, strdup(cur->pkey), i--); + if (expr__parse(&result, ctx, str)) { + pr_err("expr__parse failed for %s\n", metric_name); + /* The following have hard to avoid divide by zero. */ + if (!strcmp(metric_name, "tma_clears_resteers") || + !strcmp(metric_name, "tma_mispredicts_resteers")) + ret = 0; + else + ret = -1; + } + } + +out: + expr__ctx_free(ctx); + return ret; } -const char *test__pmu_events_subtest_skip_reason(int subtest) +static int test__parsing_fake_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, + void *data __maybe_unused) { - if (subtest < 0 || - subtest >= (int)ARRAY_SIZE(pmu_events_testcase_table)) - return NULL; - if (pmu_events_testcase_table[subtest].func != test_parsing) - return NULL; - return "some metrics failed"; + return metric_parse_fake(pm->metric_name, pm->metric_expr); } -int test__pmu_events_subtest_get_nr(void) +/* + * Parse all the metrics for current architecture, or all defined cpus via the + * 'fake_pmu' in parse_events. + */ +static int test__parsing_fake(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { - return (int)ARRAY_SIZE(pmu_events_testcase_table); + int err = 0; + + for (size_t i = 0; i < ARRAY_SIZE(metrics); i++) { + err = metric_parse_fake("", metrics[i].str); + if (err) + return err; + } + + err = pmu_for_each_core_metric(test__parsing_fake_callback, NULL); + if (err) + return err; + + return pmu_for_each_sys_metric(test__parsing_fake_callback, NULL); } -int test__pmu_events(struct test *test __maybe_unused, int subtest) +static int test__parsing_threshold_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, + void *data __maybe_unused) { - if (subtest < 0 || - subtest >= (int)ARRAY_SIZE(pmu_events_testcase_table)) - return TEST_FAIL; - return pmu_events_testcase_table[subtest].func(); + if (!pm->metric_threshold) + return 0; + return metric_parse_fake(pm->metric_name, pm->metric_threshold); } + +static int test__parsing_threshold(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int err = 0; + + err = pmu_for_each_core_metric(test__parsing_threshold_callback, NULL); + if (err) + return err; + + return pmu_for_each_sys_metric(test__parsing_threshold_callback, NULL); +} + +static struct test_case pmu_events_tests[] = { + TEST_CASE("PMU event table sanity", pmu_event_table), + TEST_CASE("PMU event map aliases", aliases), + TEST_CASE_REASON("Parsing of PMU event table metrics", parsing, + "some metrics failed"), + TEST_CASE("Parsing of PMU event table metrics with fake PMUs", parsing_fake), + TEST_CASE("Parsing of metric thresholds with fake PMUs", parsing_threshold), + { .name = NULL, } +}; + +struct test_suite suite__pmu_events = { + .desc = "PMU JSON event tests", + .test_cases = pmu_events_tests, +}; diff --git a/tools/perf/tests/pmu.c b/tools/perf/tests/pmu.c index 5c11fe2b3040..4a9f8e090cf4 100644 --- a/tools/perf/tests/pmu.c +++ b/tools/perf/tests/pmu.c @@ -1,178 +1,549 @@ // SPDX-License-Identifier: GPL-2.0 +#include "evlist.h" +#include "evsel.h" #include "parse-events.h" #include "pmu.h" +#include "pmus.h" #include "tests.h" +#include "debug.h" +#include "fncache.h" +#include <api/fs/fs.h> +#include <ctype.h> +#include <dirent.h> #include <errno.h> +#include <fcntl.h> #include <stdio.h> -#include <linux/kernel.h> -#include <linux/limits.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", }, -}; +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> -/* 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, - }, -}; +/* Cleanup test PMU directory. */ +static int test_pmu_put(const char *dir, struct perf_pmu *pmu) +{ + char buf[PATH_MAX + 20]; + int ret; + + if (scnprintf(buf, sizeof(buf), "rm -fr %s", dir) < 0) { + pr_err("Failure to set up buffer for \"%s\"\n", dir); + return -EINVAL; + } + ret = system(buf); + if (ret) + pr_err("Failure to \"%s\"\n", buf); + + list_del(&pmu->list); + perf_pmu__delete(pmu); + return ret; +} /* - * Prepare format directory data, exported by kernel - * at /sys/bus/event_source/devices/<dev>/format. + * Prepare test PMU directory data, normally exported by kernel at + * /sys/bus/event_source/devices/<pmu>/. Give as input a buffer to hold the file + * path, the result is PMU loaded using that directory. */ -static char *test_format_dir_get(void) +static struct perf_pmu *test_pmu_get(char *dir, size_t sz) { - static char dir[PATH_MAX]; - unsigned int i; + /* Simulated format definitions. */ + const 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", }, + }; + const char *test_event = "krava01=15,krava02=170,krava03=1,krava11=27,krava12=1," + "krava13=2,krava21=119,krava22=11,krava23=2\n"; + + char name[PATH_MAX]; + int dirfd, file; + struct perf_pmu *pmu = NULL; + ssize_t len; - snprintf(dir, PATH_MAX, "/tmp/perf-pmu-test-format-XXXXXX"); - if (!mkdtemp(dir)) + /* Create equivalent of sysfs mount point. */ + scnprintf(dir, sz, "/tmp/perf-pmu-test-XXXXXX"); + if (!mkdtemp(dir)) { + pr_err("mkdtemp failed\n"); + dir[0] = '\0'; return NULL; + } + dirfd = open(dir, O_DIRECTORY); + if (dirfd < 0) { + pr_err("Failed to open test directory \"%s\"\n", dir); + goto err_out; + } - for (i = 0; i < ARRAY_SIZE(test_formats); i++) { - static char name[PATH_MAX]; - struct test_format *format = &test_formats[i]; - FILE *file; + /* Create the test PMU directory and give it a perf_event_attr type number. */ + if (mkdirat(dirfd, "perf-pmu-test", 0755) < 0) { + pr_err("Failed to mkdir PMU directory\n"); + goto err_out; + } + file = openat(dirfd, "perf-pmu-test/type", O_WRONLY | O_CREAT, 0600); + if (!file) { + pr_err("Failed to open for writing file \"type\"\n"); + goto err_out; + } + len = strlen("9999"); + if (write(file, "9999\n", len) < len) { + close(file); + pr_err("Failed to write to 'type' file\n"); + goto err_out; + } + close(file); - scnprintf(name, PATH_MAX, "%s/%s", dir, format->name); + /* Create format directory and files. */ + if (mkdirat(dirfd, "perf-pmu-test/format", 0755) < 0) { + pr_err("Failed to mkdir PMU format directory\n)"); + goto err_out; + } + for (size_t i = 0; i < ARRAY_SIZE(test_formats); i++) { + const struct test_format *format = &test_formats[i]; - file = fopen(name, "w"); - if (!file) - return NULL; + if (scnprintf(name, PATH_MAX, "perf-pmu-test/format/%s", format->name) < 0) { + pr_err("Failure to set up path for \"%s\"\n", format->name); + goto err_out; + } + file = openat(dirfd, name, O_WRONLY | O_CREAT, 0600); + if (!file) { + pr_err("Failed to open for writing file \"%s\"\n", name); + goto err_out; + } - if (1 != fwrite(format->value, strlen(format->value), 1, file)) - break; + if (write(file, format->value, strlen(format->value)) < 0) { + pr_err("Failed to write to file \"%s\"\n", name); + close(file); + goto err_out; + } + close(file); + } - fclose(file); + /* Create test event. */ + if (mkdirat(dirfd, "perf-pmu-test/events", 0755) < 0) { + pr_err("Failed to mkdir PMU events directory\n"); + goto err_out; + } + file = openat(dirfd, "perf-pmu-test/events/test-event", O_WRONLY | O_CREAT, 0600); + if (!file) { + pr_err("Failed to open for writing file \"type\"\n"); + goto err_out; } + len = strlen(test_event); + if (write(file, test_event, len) < len) { + close(file); + pr_err("Failed to write to 'test-event' file\n"); + goto err_out; + } + close(file); + + /* Make the PMU reading the files created above. */ + pmu = perf_pmus__add_test_pmu(dirfd, "perf-pmu-test"); + if (!pmu) + pr_err("Test PMU creation failed\n"); - return dir; +err_out: + if (!pmu) + test_pmu_put(dir, pmu); + if (dirfd >= 0) + close(dirfd); + return pmu; } -/* Cleanup format directory. */ -static int test_format_dir_put(char *dir) +static int test__pmu_format(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - char buf[PATH_MAX]; - snprintf(buf, PATH_MAX, "rm -f %s/*\n", dir); - if (system(buf)) - return -1; + char dir[PATH_MAX]; + struct perf_event_attr attr; + struct parse_events_terms terms; + int ret = TEST_FAIL; + struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir)); - snprintf(buf, PATH_MAX, "rmdir %s\n", dir); - return system(buf); + if (!pmu) + return TEST_FAIL; + + parse_events_terms__init(&terms); + if (parse_events_terms(&terms, + "krava01=15,krava02=170,krava03=1,krava11=27,krava12=1," + "krava13=2,krava21=119,krava22=11,krava23=2", + NULL)) { + pr_err("Term parsing failed\n"); + goto err_out; + } + + memset(&attr, 0, sizeof(attr)); + ret = perf_pmu__config_terms(pmu, &attr, &terms, /*zero=*/false, + /*apply_hardcoded=*/false, /*err=*/NULL); + if (ret) { + pr_err("perf_pmu__config_terms failed"); + goto err_out; + } + + if (attr.config != 0xc00000000002a823) { + pr_err("Unexpected config value %llx\n", attr.config); + goto err_out; + } + if (attr.config1 != 0x8000400000000145) { + pr_err("Unexpected config1 value %llx\n", attr.config1); + goto err_out; + } + if (attr.config2 != 0x0400000020041d07) { + pr_err("Unexpected config2 value %llx\n", attr.config2); + goto err_out; + } + + ret = TEST_OK; +err_out: + parse_events_terms__exit(&terms); + test_pmu_put(dir, pmu); + return ret; } -static struct list_head *test_terms_list(void) +static int test__pmu_events(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - static LIST_HEAD(terms); - unsigned int i; + char dir[PATH_MAX]; + struct parse_events_error err; + struct evlist *evlist; + struct evsel *evsel; + struct perf_event_attr *attr; + int ret = TEST_FAIL; + struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir)); + const char *event = "perf-pmu-test/test-event/"; - for (i = 0; i < ARRAY_SIZE(test_terms); i++) - list_add_tail(&test_terms[i].list, &terms); - return &terms; + if (!pmu) + return TEST_FAIL; + + evlist = evlist__new(); + if (evlist == NULL) { + pr_err("Failed allocation"); + goto err_out; + } + parse_events_error__init(&err); + ret = parse_events(evlist, event, &err); + if (ret) { + pr_debug("failed to parse event '%s', err %d\n", event, ret); + parse_events_error__print(&err, event); + if (parse_events_error__contains(&err, "can't access trace events")) + ret = TEST_SKIP; + goto err_out; + } + evsel = evlist__first(evlist); + attr = &evsel->core.attr; + if (attr->config != 0xc00000000002a823) { + pr_err("Unexpected config value %llx\n", attr->config); + goto err_out; + } + if (attr->config1 != 0x8000400000000145) { + pr_err("Unexpected config1 value %llx\n", attr->config1); + goto err_out; + } + if (attr->config2 != 0x0400000020041d07) { + pr_err("Unexpected config2 value %llx\n", attr->config2); + goto err_out; + } + + ret = TEST_OK; +err_out: + parse_events_error__exit(&err); + evlist__delete(evlist); + test_pmu_put(dir, pmu); + return ret; } -int test__pmu(struct test *test __maybe_unused, int subtest __maybe_unused) +static bool permitted_event_name(const char *name) { - char *format = test_format_dir_get(); - LIST_HEAD(formats); - struct list_head *terms = test_terms_list(); - int ret; + bool has_lower = false, has_upper = false; + __u64 config; - if (!format) - return -EINVAL; + for (size_t i = 0; i < strlen(name); i++) { + char c = name[i]; - do { - struct perf_event_attr attr; + if (islower(c)) { + if (has_upper) + goto check_legacy; + has_lower = true; + continue; + } + if (isupper(c)) { + if (has_lower) + goto check_legacy; + has_upper = true; + continue; + } + if (!isdigit(c) && c != '.' && c != '_' && c != '-') + goto check_legacy; + } + return true; +check_legacy: + /* + * If the event name matches a legacy cache name the legacy encoding + * will still be used. This isn't quite WAI as sysfs events should take + * priority, but this case happens on PowerPC and matches the behavior + * in older perf tools where legacy events were the priority. Be + * permissive and assume later PMU drivers will use all lower or upper + * case names. + */ + if (parse_events__decode_legacy_cache(name, /*extended_pmu_type=*/0, &config) == 0) { + pr_warning("sysfs event '%s' should be all lower/upper case, it will be matched using legacy encoding.", + name); + return true; + } + return false; +} + +static int test__pmu_event_names(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + char path[PATH_MAX]; + DIR *pmu_dir, *event_dir; + struct dirent *pmu_dent, *event_dent; + const char *sysfs = sysfs__mountpoint(); + int ret = TEST_OK; - memset(&attr, 0, sizeof(attr)); + if (!sysfs) { + pr_err("Sysfs not mounted\n"); + return TEST_FAIL; + } + + snprintf(path, sizeof(path), "%s/bus/event_source/devices/", sysfs); + pmu_dir = opendir(path); + if (!pmu_dir) { + pr_err("Error opening \"%s\"\n", path); + return TEST_FAIL; + } + while ((pmu_dent = readdir(pmu_dir))) { + if (!strcmp(pmu_dent->d_name, ".") || + !strcmp(pmu_dent->d_name, "..")) + continue; - ret = perf_pmu__format_parse(format, &formats); - if (ret) - break; + snprintf(path, sizeof(path), "%s/bus/event_source/devices/%s/type", + sysfs, pmu_dent->d_name); - ret = perf_pmu__config_terms("perf-pmu-test", &formats, &attr, - terms, false, NULL); - if (ret) - break; + /* Does it look like a PMU? */ + if (!file_available(path)) + continue; - ret = -EINVAL; + /* Process events. */ + snprintf(path, sizeof(path), "%s/bus/event_source/devices/%s/events", + sysfs, pmu_dent->d_name); - if (attr.config != 0xc00000000002a823) - break; - if (attr.config1 != 0x8000400000000145) - break; - if (attr.config2 != 0x0400000020041d07) - break; + event_dir = opendir(path); + if (!event_dir) { + pr_debug("Skipping as no event directory \"%s\"\n", path); + continue; + } + while ((event_dent = readdir(event_dir))) { + const char *event_name = event_dent->d_name; - ret = 0; - } while (0); + if (!strcmp(event_name, ".") || !strcmp(event_name, "..")) + continue; - test_format_dir_put(format); + if (!permitted_event_name(event_name)) { + pr_err("Invalid sysfs event name: %s/%s\n", + pmu_dent->d_name, event_name); + ret = TEST_FAIL; + } + } + closedir(event_dir); + } + closedir(pmu_dir); return ret; } + +static const char * const uncore_chas[] = { + "uncore_cha_0", + "uncore_cha_1", + "uncore_cha_2", + "uncore_cha_3", + "uncore_cha_4", + "uncore_cha_5", + "uncore_cha_6", + "uncore_cha_7", + "uncore_cha_8", + "uncore_cha_9", + "uncore_cha_10", + "uncore_cha_11", + "uncore_cha_12", + "uncore_cha_13", + "uncore_cha_14", + "uncore_cha_15", + "uncore_cha_16", + "uncore_cha_17", + "uncore_cha_18", + "uncore_cha_19", + "uncore_cha_20", + "uncore_cha_21", + "uncore_cha_22", + "uncore_cha_23", + "uncore_cha_24", + "uncore_cha_25", + "uncore_cha_26", + "uncore_cha_27", + "uncore_cha_28", + "uncore_cha_29", + "uncore_cha_30", + "uncore_cha_31", +}; + +static const char * const mrvl_ddrs[] = { + "mrvl_ddr_pmu_87e1b0000000", + "mrvl_ddr_pmu_87e1b1000000", + "mrvl_ddr_pmu_87e1b2000000", + "mrvl_ddr_pmu_87e1b3000000", + "mrvl_ddr_pmu_87e1b4000000", + "mrvl_ddr_pmu_87e1b5000000", + "mrvl_ddr_pmu_87e1b6000000", + "mrvl_ddr_pmu_87e1b7000000", + "mrvl_ddr_pmu_87e1b8000000", + "mrvl_ddr_pmu_87e1b9000000", + "mrvl_ddr_pmu_87e1ba000000", + "mrvl_ddr_pmu_87e1bb000000", + "mrvl_ddr_pmu_87e1bc000000", + "mrvl_ddr_pmu_87e1bd000000", + "mrvl_ddr_pmu_87e1be000000", + "mrvl_ddr_pmu_87e1bf000000", +}; + +static int test__name_len(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + TEST_ASSERT_VAL("cpu", pmu_name_len_no_suffix("cpu") == strlen("cpu")); + TEST_ASSERT_VAL("i915", pmu_name_len_no_suffix("i915") == strlen("i915")); + TEST_ASSERT_VAL("cpum_cf", pmu_name_len_no_suffix("cpum_cf") == strlen("cpum_cf")); + for (size_t i = 0; i < ARRAY_SIZE(uncore_chas); i++) { + TEST_ASSERT_VAL("Strips uncore_cha suffix", + pmu_name_len_no_suffix(uncore_chas[i]) == + strlen("uncore_cha")); + } + for (size_t i = 0; i < ARRAY_SIZE(mrvl_ddrs); i++) { + TEST_ASSERT_VAL("Strips mrvl_ddr_pmu suffix", + pmu_name_len_no_suffix(mrvl_ddrs[i]) == + strlen("mrvl_ddr_pmu")); + } + return TEST_OK; +} + +static int test__name_cmp(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + TEST_ASSERT_EQUAL("cpu", pmu_name_cmp("cpu", "cpu"), 0); + TEST_ASSERT_EQUAL("i915", pmu_name_cmp("i915", "i915"), 0); + TEST_ASSERT_EQUAL("cpum_cf", pmu_name_cmp("cpum_cf", "cpum_cf"), 0); + TEST_ASSERT_VAL("i915", pmu_name_cmp("cpu", "i915") < 0); + TEST_ASSERT_VAL("i915", pmu_name_cmp("i915", "cpu") > 0); + TEST_ASSERT_VAL("cpum_cf", pmu_name_cmp("cpum_cf", "cpum_ce") > 0); + TEST_ASSERT_VAL("cpum_cf", pmu_name_cmp("cpum_cf", "cpum_d0") < 0); + for (size_t i = 1; i < ARRAY_SIZE(uncore_chas); i++) { + TEST_ASSERT_VAL("uncore_cha suffixes ordered lt", + pmu_name_cmp(uncore_chas[i-1], uncore_chas[i]) < 0); + TEST_ASSERT_VAL("uncore_cha suffixes ordered gt", + pmu_name_cmp(uncore_chas[i], uncore_chas[i-1]) > 0); + } + for (size_t i = 1; i < ARRAY_SIZE(mrvl_ddrs); i++) { + TEST_ASSERT_VAL("mrvl_ddr_pmu suffixes ordered lt", + pmu_name_cmp(mrvl_ddrs[i-1], mrvl_ddrs[i]) < 0); + TEST_ASSERT_VAL("mrvl_ddr_pmu suffixes ordered gt", + pmu_name_cmp(mrvl_ddrs[i], mrvl_ddrs[i-1]) > 0); + } + return TEST_OK; +} + +/** + * Test perf_pmu__wildcard_match() that's used to search for a PMU given a name passed + * on the command line. The name that's passed may also be a filename type glob + * match. If the name does not match, perf_pmu__wildcard_match() attempts to match the + * alias of the PMU, if provided. + */ +static int test__pmu_match(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + struct perf_pmu test_pmu = { + .name = "pmuname", + }; + +#define TEST_PMU_MATCH(msg, to_match, expect) \ + TEST_ASSERT_EQUAL(msg, perf_pmu__wildcard_match(&test_pmu, to_match), expect) + + TEST_PMU_MATCH("Exact match", "pmuname", true); + TEST_PMU_MATCH("Longer token", "longertoken", false); + TEST_PMU_MATCH("Shorter token", "pmu", false); + + test_pmu.name = "pmuname_10"; + TEST_PMU_MATCH("Diff suffix_", "pmuname_2", false); + TEST_PMU_MATCH("Sub suffix_", "pmuname_1", true); + TEST_PMU_MATCH("Same suffix_", "pmuname_10", true); + TEST_PMU_MATCH("No suffix_", "pmuname", true); + TEST_PMU_MATCH("Underscore_", "pmuname_", true); + TEST_PMU_MATCH("Substring_", "pmuna", false); + + test_pmu.name = "pmuname_ab23"; + TEST_PMU_MATCH("Diff suffix hex_", "pmuname_2", false); + TEST_PMU_MATCH("Sub suffix hex_", "pmuname_ab", true); + TEST_PMU_MATCH("Same suffix hex_", "pmuname_ab23", true); + TEST_PMU_MATCH("No suffix hex_", "pmuname", true); + TEST_PMU_MATCH("Underscore hex_", "pmuname_", true); + TEST_PMU_MATCH("Substring hex_", "pmuna", false); + + test_pmu.name = "pmuname10"; + TEST_PMU_MATCH("Diff suffix", "pmuname2", false); + TEST_PMU_MATCH("Sub suffix", "pmuname1", true); + TEST_PMU_MATCH("Same suffix", "pmuname10", true); + TEST_PMU_MATCH("No suffix", "pmuname", true); + TEST_PMU_MATCH("Underscore", "pmuname_", false); + TEST_PMU_MATCH("Substring", "pmuna", false); + + test_pmu.name = "pmunameab23"; + TEST_PMU_MATCH("Diff suffix hex", "pmuname2", false); + TEST_PMU_MATCH("Sub suffix hex", "pmunameab", true); + TEST_PMU_MATCH("Same suffix hex", "pmunameab23", true); + TEST_PMU_MATCH("No suffix hex", "pmuname", true); + TEST_PMU_MATCH("Underscore hex", "pmuname_", false); + TEST_PMU_MATCH("Substring hex", "pmuna", false); + + /* + * 2 hex chars or less are not considered suffixes so it shouldn't be + * possible to wildcard by skipping the suffix. Therefore there are more + * false results here than above. + */ + test_pmu.name = "pmuname_a3"; + TEST_PMU_MATCH("Diff suffix 2 hex_", "pmuname_2", false); + /* + * This one should be false, but because pmuname_a3 ends in 3 which is + * decimal, it's not possible to determine if it's a short hex suffix or + * a normal decimal suffix following text. And we want to match on any + * length of decimal suffix. Run the test anyway and expect the wrong + * result. And slightly fuzzy matching shouldn't do too much harm. + */ + TEST_PMU_MATCH("Sub suffix 2 hex_", "pmuname_a", true); + TEST_PMU_MATCH("Same suffix 2 hex_", "pmuname_a3", true); + TEST_PMU_MATCH("No suffix 2 hex_", "pmuname", false); + TEST_PMU_MATCH("Underscore 2 hex_", "pmuname_", false); + TEST_PMU_MATCH("Substring 2 hex_", "pmuna", false); + + test_pmu.name = "pmuname_5"; + TEST_PMU_MATCH("Glob 1", "pmu*", true); + TEST_PMU_MATCH("Glob 2", "nomatch*", false); + TEST_PMU_MATCH("Seq 1", "pmuname_[12345]", true); + TEST_PMU_MATCH("Seq 2", "pmuname_[67890]", false); + TEST_PMU_MATCH("? 1", "pmuname_?", true); + TEST_PMU_MATCH("? 2", "pmuname_1?", false); + + return TEST_OK; +} + +static struct test_case tests__pmu[] = { + TEST_CASE("Parsing with PMU format directory", pmu_format), + TEST_CASE("Parsing with PMU event", pmu_events), + TEST_CASE("PMU event names", pmu_event_names), + TEST_CASE("PMU name combining", name_len), + TEST_CASE("PMU name comparison", name_cmp), + TEST_CASE("PMU cmdline match", pmu_match), + { .name = NULL, } +}; + +struct test_suite suite__pmu = { + .desc = "Sysfs PMU tests", + .test_cases = tests__pmu, +}; diff --git a/tools/perf/tests/python-use.c b/tools/perf/tests/python-use.c index 40ab72149ce1..0ebc22ac8d5b 100644 --- a/tools/perf/tests/python-use.c +++ b/tools/perf/tests/python-use.c @@ -9,16 +9,19 @@ #include "tests.h" #include "util/debug.h" -int test__python_use(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__python_use(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { char *cmd; int ret; - if (asprintf(&cmd, "echo \"import sys ; sys.path.append('%s'); import perf\" | %s %s", + if (asprintf(&cmd, "echo \"import sys ; sys.path.insert(0, '%s'); import perf\" | %s %s", PYTHONPATH, PYTHON, verbose > 0 ? "" : "2> /dev/null") < 0) return -1; + pr_debug("python usage test: \"%s\"\n", cmd); ret = system(cmd) ? -1 : 0; free(cmd); return ret; } + +DEFINE_SUITE("'import perf' in python", python_use); diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c index a0bdaf390ac8..72411580f869 100644 --- a/tools/perf/tests/sample-parsing.c +++ b/tools/perf/tests/sample-parsing.c @@ -13,6 +13,7 @@ #include "evsel.h" #include "debug.h" #include "util/synthetic-events.h" +#include "util/util.h" #include "tests.h" @@ -30,9 +31,18 @@ } \ } while (0) -static bool samples_same(const struct perf_sample *s1, - const struct perf_sample *s2, - u64 type, u64 read_format) +/* + * Hardcode the expected values for branch_entry flags. + * These are based on the input value (213) specified + * in branch_stack variable. + */ +#define BS_EXPECTED_BE 0xa000d00000000000 +#define BS_EXPECTED_LE 0x1aa00000000 +#define FLAG(s) s->branch_stack->entries[i].flags + +static bool samples_same(struct perf_sample *s1, + struct perf_sample *s2, + u64 type, u64 read_format, bool needs_swap) { size_t i; @@ -76,10 +86,15 @@ static bool samples_same(const struct perf_sample *s1, COMP(read.time_running); /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */ if (read_format & PERF_FORMAT_GROUP) { - for (i = 0; i < s1->read.group.nr; i++) - MCOMP(read.group.values[i]); + for (i = 0; i < s1->read.group.nr; i++) { + /* FIXME: check values without LOST */ + if (read_format & PERF_FORMAT_LOST) + MCOMP(read.group.values[i]); + } } else { COMP(read.one.id); + if (read_format & PERF_FORMAT_LOST) + COMP(read.one.lost); } } @@ -100,18 +115,26 @@ static bool samples_same(const struct perf_sample *s1, if (type & PERF_SAMPLE_BRANCH_STACK) { COMP(branch_stack->nr); COMP(branch_stack->hw_idx); - for (i = 0; i < s1->branch_stack->nr; i++) - MCOMP(branch_stack->entries[i]); + for (i = 0; i < s1->branch_stack->nr; i++) { + if (needs_swap) + return ((host_is_bigendian()) ? + (FLAG(s2).value == BS_EXPECTED_BE) : + (FLAG(s2).value == BS_EXPECTED_LE)); + else + MCOMP(branch_stack->entries[i]); + } } if (type & PERF_SAMPLE_REGS_USER) { - size_t sz = hweight_long(s1->user_regs.mask) * sizeof(u64); - - COMP(user_regs.mask); - COMP(user_regs.abi); - if (s1->user_regs.abi && - (!s1->user_regs.regs || !s2->user_regs.regs || - memcmp(s1->user_regs.regs, s2->user_regs.regs, sz))) { + struct regs_dump *s1_regs = perf_sample__user_regs(s1); + struct regs_dump *s2_regs = perf_sample__user_regs(s2); + size_t sz = hweight_long(s1_regs->mask) * sizeof(u64); + + COMP(user_regs->mask); + COMP(user_regs->abi); + if (s1_regs->abi && + (!s1_regs->regs || !s2_regs->regs || + memcmp(s1_regs->regs, s2_regs->regs, sz))) { pr_debug("Samples differ at 'user_regs'\n"); return false; } @@ -136,13 +159,15 @@ static bool samples_same(const struct perf_sample *s1, COMP(transaction); if (type & PERF_SAMPLE_REGS_INTR) { - size_t sz = hweight_long(s1->intr_regs.mask) * sizeof(u64); - - COMP(intr_regs.mask); - COMP(intr_regs.abi); - if (s1->intr_regs.abi && - (!s1->intr_regs.regs || !s2->intr_regs.regs || - memcmp(s1->intr_regs.regs, s2->intr_regs.regs, sz))) { + struct regs_dump *s1_regs = perf_sample__intr_regs(s1); + struct regs_dump *s2_regs = perf_sample__intr_regs(s2); + size_t sz = hweight_long(s1_regs->mask) * sizeof(u64); + + COMP(intr_regs->mask); + COMP(intr_regs->abi); + if (s1_regs->abi && + (!s1_regs->regs || !s2_regs->regs || + memcmp(s1_regs->regs, s2_regs->regs, sz))) { pr_debug("Samples differ at 'intr_regs'\n"); return false; } @@ -154,6 +179,12 @@ static bool samples_same(const struct perf_sample *s1, if (type & PERF_SAMPLE_CGROUP) COMP(cgroup); + if (type & PERF_SAMPLE_DATA_PAGE_SIZE) + COMP(data_page_size); + + if (type & PERF_SAMPLE_CODE_PAGE_SIZE) + COMP(code_page_size); + if (type & PERF_SAMPLE_AUX) { COMP(aux_sample.size); if (memcmp(s1->aux_sample.data, s2->aux_sample.data, @@ -193,9 +224,19 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format) .data = {1, -1ULL, 211, 212, 213}, }; u64 regs[64]; - const u64 raw_data[] = {0x123456780a0b0c0dULL, 0x1102030405060708ULL}; + const u32 raw_data[] = {0x12345678, 0x0a0b0c0d, 0x11020304, 0x05060708, 0 }; const u64 data[] = {0x2211443366558877ULL, 0, 0xaabbccddeeff4321ULL}; const u64 aux_data[] = {0xa55a, 0, 0xeeddee, 0x0282028202820282}; + struct regs_dump user_regs = { + .abi = PERF_SAMPLE_REGS_ABI_64, + .mask = sample_regs, + .regs = regs, + }; + struct regs_dump intr_regs = { + .abi = PERF_SAMPLE_REGS_ABI_64, + .mask = sample_regs, + .regs = regs, + }; struct perf_sample sample = { .ip = 101, .pid = 102, @@ -214,11 +255,7 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format) .callchain = &callchain.callchain, .no_hw_idx = false, .branch_stack = &branch_stack.branch_stack, - .user_regs = { - .abi = PERF_SAMPLE_REGS_ABI_64, - .mask = sample_regs, - .regs = regs, - }, + .user_regs = &user_regs, .user_stack = { .size = sizeof(data), .data = (void *)data, @@ -227,23 +264,23 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format) .time_enabled = 0x030a59d664fca7deULL, .time_running = 0x011b6ae553eb98edULL, }, - .intr_regs = { - .abi = PERF_SAMPLE_REGS_ABI_64, - .mask = sample_regs, - .regs = regs, - }, + .intr_regs = &intr_regs, .phys_addr = 113, .cgroup = 114, + .data_page_size = 115, + .code_page_size = 116, .aux_sample = { .size = sizeof(aux_data), .data = (void *)aux_data, }, }; - struct sample_read_value values[] = {{1, 5}, {9, 3}, {2, 7}, {6, 4},}; - struct perf_sample sample_out; + struct sample_read_value values[] = {{1, 5, 0}, {9, 3, 0}, {2, 7, 0}, {6, 4, 1},}; + struct perf_sample sample_out, sample_out_endian; size_t i, sz, bufsz; int err, ret = -1; + perf_sample__init(&sample_out, /*all=*/false); + perf_sample__init(&sample_out_endian, /*all=*/false); if (sample_type & PERF_SAMPLE_REGS_USER) evsel.core.attr.sample_regs_user = sample_regs; @@ -262,6 +299,7 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format) } else { sample.read.one.value = 0x08789faeb786aa87ULL; sample.read.one.id = 99; + sample.read.one.lost = 1; } sz = perf_event__sample_event_size(&sample, sample_type, read_format); @@ -305,15 +343,34 @@ static int do_test(u64 sample_type, u64 sample_regs, u64 read_format) goto out_free; } - if (!samples_same(&sample, &sample_out, sample_type, read_format)) { + if (!samples_same(&sample, &sample_out, sample_type, read_format, evsel.needs_swap)) { pr_debug("parsing failed for sample_type %#"PRIx64"\n", sample_type); goto out_free; } + if (sample_type == PERF_SAMPLE_BRANCH_STACK) { + evsel.needs_swap = true; + evsel.sample_size = __evsel__sample_size(sample_type); + err = evsel__parse_sample(&evsel, event, &sample_out_endian); + if (err) { + pr_debug("%s failed for sample_type %#"PRIx64", error %d\n", + "evsel__parse_sample", sample_type, err); + goto out_free; + } + + if (!samples_same(&sample, &sample_out_endian, sample_type, read_format, evsel.needs_swap)) { + pr_debug("parsing failed for sample_type %#"PRIx64"\n", + sample_type); + goto out_free; + } + } + ret = 0; out_free: free(event); + perf_sample__exit(&sample_out_endian); + perf_sample__exit(&sample_out); if (ret && read_format) pr_debug("read_format %#"PRIx64"\n", read_format); return ret; @@ -327,9 +384,9 @@ out_free: * checks sample format bits separately and together. If the test passes %0 is * returned, otherwise %-1 is returned. */ -int test__sample_parsing(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__sample_parsing(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - const u64 rf[] = {4, 5, 6, 7, 12, 13, 14, 15}; + const u64 rf[] = {4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 28, 29, 30, 31}; u64 sample_type; u64 sample_regs; size_t i; @@ -340,7 +397,7 @@ int test__sample_parsing(struct test *test __maybe_unused, int subtest __maybe_u * were added. Please actually update the test rather than just change * the condition below. */ - if (PERF_SAMPLE_MAX > PERF_SAMPLE_CGROUP << 1) { + if (PERF_SAMPLE_MAX > PERF_SAMPLE_WEIGHT_STRUCT << 1) { pr_debug("sample format has changed, some new PERF_SAMPLE_ bit was introduced - test needs updating\n"); return -1; } @@ -370,8 +427,12 @@ int test__sample_parsing(struct test *test __maybe_unused, int subtest __maybe_u return err; } - /* Test all sample format bits together */ - sample_type = PERF_SAMPLE_MAX - 1; + /* + * Test all sample format bits together + * Note: PERF_SAMPLE_WEIGHT and PERF_SAMPLE_WEIGHT_STRUCT cannot + * be set simultaneously. + */ + sample_type = (PERF_SAMPLE_MAX - 1) & ~PERF_SAMPLE_WEIGHT; sample_regs = 0x3fff; /* shared yb intr and user regs */ for (i = 0; i < ARRAY_SIZE(rf); i++) { err = do_test(sample_type, sample_regs, rf[i]); @@ -381,3 +442,5 @@ int test__sample_parsing(struct test *test __maybe_unused, int subtest __maybe_u return 0; } + +DEFINE_SUITE("Sample parsing", sample_parsing); diff --git a/tools/perf/tests/sdt.c b/tools/perf/tests/sdt.c index 60f0e9ee04fb..919712899251 100644 --- a/tools/perf/tests/sdt.c +++ b/tools/perf/tests/sdt.c @@ -28,16 +28,16 @@ static int target_function(void) static int build_id_cache__add_file(const char *filename) { char sbuild_id[SBUILD_ID_SIZE]; - u8 build_id[BUILD_ID_SIZE]; + struct build_id bid; int err; - err = filename__read_build_id(filename, &build_id, sizeof(build_id)); + err = filename__read_build_id(filename, &bid); if (err < 0) { pr_debug("Failed to read build id of %s\n", filename); return err; } - build_id__sprintf(build_id, sizeof(build_id), sbuild_id); + build_id__sprintf(&bid, sbuild_id); err = build_id_cache__add_s(sbuild_id, filename, NULL, false, false); if (err < 0) pr_debug("Failed to add build id cache of %s\n", filename); @@ -76,7 +76,7 @@ static int search_cached_probe(const char *target, return ret; } -int test__sdt_event(struct test *test __maybe_unused, int subtests __maybe_unused) +static int test__sdt_event(struct test_suite *test __maybe_unused, int subtests __maybe_unused) { int ret = TEST_FAIL; char __tempdir[] = "./test-buildid-XXXXXX"; @@ -114,9 +114,11 @@ error: return ret; } #else -int test__sdt_event(struct test *test __maybe_unused, int subtests __maybe_unused) +static int test__sdt_event(struct test_suite *test __maybe_unused, int subtests __maybe_unused) { pr_debug("Skip SDT event test because SDT support is not compiled\n"); return TEST_SKIP; } #endif + +DEFINE_SUITE("Probe SDT events", sdt_event); diff --git a/tools/perf/tests/shell/amd-ibs-swfilt.sh b/tools/perf/tests/shell/amd-ibs-swfilt.sh new file mode 100755 index 000000000000..83937aa687cc --- /dev/null +++ b/tools/perf/tests/shell/amd-ibs-swfilt.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# AMD IBS software filtering + +echo "check availability of IBS swfilt" + +# check if IBS PMU is available +if [ ! -d /sys/bus/event_source/devices/ibs_op ]; then + echo "[SKIP] IBS PMU does not exist" + exit 2 +fi + +# check if IBS PMU has swfilt format +if [ ! -f /sys/bus/event_source/devices/ibs_op/format/swfilt ]; then + echo "[SKIP] IBS PMU does not have swfilt" + exit 2 +fi + +echo "run perf record with modifier and swfilt" + +# setting any modifiers should fail +perf record -B -e ibs_op//u -o /dev/null true 2> /dev/null +if [ $? -eq 0 ]; then + echo "[FAIL] IBS PMU should not accept exclude_kernel" + exit 1 +fi + +# setting it with swfilt should be fine +perf record -B -e ibs_op/swfilt/u -o /dev/null true +if [ $? -ne 0 ]; then + echo "[FAIL] IBS op PMU cannot handle swfilt for exclude_kernel" + exit 1 +fi + +# setting it with swfilt=1 should be fine +perf record -B -e ibs_op/swfilt=1/k -o /dev/null true +if [ $? -ne 0 ]; then + echo "[FAIL] IBS op PMU cannot handle swfilt for exclude_user" + exit 1 +fi + +# check ibs_fetch PMU as well +perf record -B -e ibs_fetch/swfilt/u -o /dev/null true +if [ $? -ne 0 ]; then + echo "[FAIL] IBS fetch PMU cannot handle swfilt for exclude_kernel" + exit 1 +fi + +# check system wide recording +perf record -aB --synth=no -e ibs_op/swfilt/k -o /dev/null true +if [ $? -ne 0 ]; then + echo "[FAIL] IBS op PMU cannot handle swfilt in system-wide mode" + exit 1 +fi + +echo "check number of samples with swfilt" + +kernel_sample=$(perf record -e ibs_op/swfilt/u -o- true | perf script -i- -F misc | grep -c ^K) +if [ ${kernel_sample} -ne 0 ]; then + echo "[FAIL] unexpected kernel samples: " ${kernel_sample} + exit 1 +fi + +user_sample=$(perf record -e ibs_fetch/swfilt/k -o- true | perf script -i- -F misc | grep -c ^U) +if [ ${user_sample} -ne 0 ]; then + echo "[FAIL] unexpected user samples: " ${user_sample} + exit 1 +fi diff --git a/tools/perf/tests/shell/annotate.sh b/tools/perf/tests/shell/annotate.sh new file mode 100755 index 000000000000..16a1ccd06089 --- /dev/null +++ b/tools/perf/tests/shell/annotate.sh @@ -0,0 +1,112 @@ +#!/bin/bash +# perf annotate basic tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +shelldir=$(dirname "$0") + +# shellcheck source=lib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +testsym="noploop" + +skip_test_missing_symbol ${testsym} + +err=0 +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX) +testprog="perf test -w noploop" +# disassembly format: "percent : offset: instruction (operands ...)" +disasm_regex="[0-9]*\.[0-9]* *: *\w*: *\w*" + +cleanup() { + rm -rf "${perfdata}" "${perfout}" + rm -rf "${perfdata}".old + + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_basic() { + mode=$1 + echo "${mode} perf annotate test" + if [ "x${mode}" == "xBasic" ] + then + perf record -o "${perfdata}" ${testprog} 2> /dev/null + else + perf record -o - ${testprog} 2> /dev/null > "${perfdata}" + fi + if [ "x$?" != "x0" ] + then + echo "${mode} annotate [Failed: perf record]" + err=1 + return + fi + + # Generate the annotated output file + if [ "x${mode}" == "xBasic" ] + then + perf annotate --no-demangle -i "${perfdata}" --stdio 2> /dev/null > "${perfout}" + else + perf annotate --no-demangle -i - --stdio 2> /dev/null < "${perfdata}" > "${perfout}" + fi + + # check if it has the target symbol + if ! head -250 "${perfout}" | grep -q "${testsym}" + then + echo "${mode} annotate [Failed: missing target symbol]" + err=1 + return + fi + + # check if it has the disassembly lines + if ! head -250 "${perfout}" | grep -q "${disasm_regex}" + then + echo "${mode} annotate [Failed: missing disasm output from default disassembler]" + err=1 + return + fi + + # check again with a target symbol name + if [ "x${mode}" == "xBasic" ] + then + perf annotate --no-demangle -i "${perfdata}" "${testsym}" 2> /dev/null > "${perfout}" + else + perf annotate --no-demangle -i - "${testsym}" 2> /dev/null < "${perfdata}" > "${perfout}" + fi + + if ! head -250 "${perfout}"| grep -q -m 3 "${disasm_regex}" + then + echo "${mode} annotate [Failed: missing disasm output when specifying the target symbol]" + err=1 + return + fi + + # check one more with external objdump tool (forced by --objdump option) + if [ "x${mode}" == "xBasic" ] + then + perf annotate --no-demangle -i "${perfdata}" --objdump=objdump 2> /dev/null > "${perfout}" + else + perf annotate --no-demangle -i - "${testsym}" 2> /dev/null < "${perfdata}" > "${perfout}" + fi + if ! head -250 "${perfout}" | grep -q -m 3 "${disasm_regex}" + then + echo "${mode} annotate [Failed: missing disasm output from non default disassembler (using --objdump)]" + err=1 + return + fi + echo "${mode} annotate test [Success]" +} + +test_basic Basic +test_basic Pipe + +cleanup +exit $err diff --git a/tools/perf/tests/shell/attr.sh b/tools/perf/tests/shell/attr.sh new file mode 100755 index 000000000000..5a4e43b2471d --- /dev/null +++ b/tools/perf/tests/shell/attr.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Perf attribute expectations test +# SPDX-License-Identifier: GPL-2.0 + +err=0 + +cleanup() { + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +shelldir=$(dirname "$0") +perf_path=$(which perf) +python "${shelldir}"/lib/attr.py -d "${shelldir}"/attr -v -p "$perf_path" +cleanup +exit $err diff --git a/tools/perf/tests/attr/README b/tools/perf/tests/shell/attr/README index 430024f618f1..67c4ca76b85d 100644 --- a/tools/perf/tests/attr/README +++ b/tools/perf/tests/shell/attr/README @@ -45,20 +45,27 @@ Following tests are defined (with perf commands): perf record -d kill (test-record-data) perf record -F 100 kill (test-record-freq) perf record -g kill (test-record-graph-default) + perf record -g kill (test-record-graph-default-aarch64) perf record --call-graph dwarf kill (test-record-graph-dwarf) perf record --call-graph fp kill (test-record-graph-fp) - perf record --group -e cycles,instructions kill (test-record-group) + perf record --call-graph fp kill (test-record-graph-fp-aarch64) perf record -e '{cycles,instructions}' kill (test-record-group1) + perf record -e '{cycles/period=1/,instructions/period=2/}:S' kill (test-record-group2) + perf record -e '{cycles,cache-misses}:S' kill (test-record-group-sampling1) + perf record -c 10000 -e '{cycles,cache-misses}:S' kill (test-record-group-sampling2) perf record -D kill (test-record-no-delay) perf record -i kill (test-record-no-inherit) perf record -n kill (test-record-no-samples) perf record -c 100 -P kill (test-record-period) + perf record -c 1 --pfm-events=cycles:period=2 (test-record-pfm-period) perf record -R kill (test-record-raw) + perf record -c 2 -e arm_spe_0// -- kill (test-record-spe-period) + perf record -e arm_spe_0/period=3/ -- kill (test-record-spe-period-term) + perf record -e arm_spe_0/pa_enable=1/ -- kill (test-record-spe-physical-address) perf stat -e cycles kill (test-stat-basic) perf stat kill (test-stat-default) perf stat -d kill (test-stat-detailed-1) perf stat -dd kill (test-stat-detailed-2) perf stat -ddd kill (test-stat-detailed-3) - perf stat --group -e cycles,instructions kill (test-stat-group) perf stat -e '{cycles,instructions}' kill (test-stat-group1) perf stat -i -e cycles kill (test-stat-no-inherit) diff --git a/tools/perf/tests/attr/base-record b/tools/perf/tests/shell/attr/base-record index 645009c08b3c..b44e4e6e4443 100644 --- a/tools/perf/tests/attr/base-record +++ b/tools/perf/tests/shell/attr/base-record @@ -5,18 +5,18 @@ group_fd=-1 flags=0|8 cpu=* type=0|1 -size=120 -config=0 +size=136 +config=0|1 sample_period=* sample_type=263 -read_format=0|4 +read_format=0|4|20 disabled=1 inherit=1 pinned=0 exclusive=0 exclude_user=0 exclude_kernel=0|1 -exclude_hv=0 +exclude_hv=0|1 exclude_idle=0 mmap=1 comm=1 diff --git a/tools/perf/tests/shell/attr/base-record-spe b/tools/perf/tests/shell/attr/base-record-spe new file mode 100644 index 000000000000..08fa96b59240 --- /dev/null +++ b/tools/perf/tests/shell/attr/base-record-spe @@ -0,0 +1,40 @@ +[event] +fd=* +group_fd=-1 +flags=* +cpu=* +type=* +size=* +config=* +sample_period=* +sample_type=* +read_format=* +disabled=* +inherit=* +pinned=* +exclusive=* +exclude_user=* +exclude_kernel=* +exclude_hv=* +exclude_idle=* +mmap=* +comm=* +freq=* +inherit_stat=* +enable_on_exec=* +task=* +watermark=* +precise_ip=* +mmap_data=* +sample_id_all=* +exclude_host=* +exclude_guest=* +exclude_callchain_kernel=* +exclude_callchain_user=* +wakeup_events=* +bp_type=* +config1=* +config2=* +branch_sample_type=* +sample_regs_user=* +sample_stack_user=* diff --git a/tools/perf/tests/attr/base-stat b/tools/perf/tests/shell/attr/base-stat index b0f42c34882e..fccd8ec4d1b0 100644 --- a/tools/perf/tests/attr/base-stat +++ b/tools/perf/tests/shell/attr/base-stat @@ -5,7 +5,7 @@ group_fd=-1 flags=0|8 cpu=* type=0 -size=120 +size=136 config=0 sample_period=0 sample_type=65536 @@ -16,7 +16,7 @@ pinned=0 exclusive=0 exclude_user=0 exclude_kernel=0|1 -exclude_hv=0 +exclude_hv=0|1 exclude_idle=0 mmap=0 comm=0 diff --git a/tools/perf/tests/attr/system-wide-dummy b/tools/perf/tests/shell/attr/system-wide-dummy index eba723cc0d38..a1e1d6a263bf 100644 --- a/tools/perf/tests/attr/system-wide-dummy +++ b/tools/perf/tests/shell/attr/system-wide-dummy @@ -7,23 +7,25 @@ cpu=* pid=-1 flags=8 type=1 -size=120 +size=136 config=9 -sample_period=4000 -sample_type=455 -read_format=4 +sample_period=1 +# PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | +# PERF_SAMPLE_CPU | PERF_SAMPLE_IDENTIFIER +sample_type=65671 +read_format=4|20 # Event will be enabled right away. disabled=0 inherit=1 pinned=0 exclusive=0 exclude_user=0 -exclude_kernel=0 -exclude_hv=0 +exclude_kernel=1 +exclude_hv=1 exclude_idle=0 mmap=1 comm=1 -freq=1 +freq=0 inherit_stat=0 enable_on_exec=0 task=1 @@ -32,7 +34,7 @@ precise_ip=0 mmap_data=0 sample_id_all=1 exclude_host=0 -exclude_guest=0 +exclude_guest=1 exclude_callchain_kernel=0 exclude_callchain_user=0 mmap2=1 diff --git a/tools/perf/tests/attr/test-record-C0 b/tools/perf/tests/shell/attr/test-record-C0 index 317730b906dd..1049ac8b52f2 100644 --- a/tools/perf/tests/attr/test-record-C0 +++ b/tools/perf/tests/shell/attr/test-record-C0 @@ -10,13 +10,15 @@ cpu=0 enable_on_exec=0 # PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | -# PERF_SAMPLE_ID | PERF_SAMPLE_PERIOD +# PERF_SAMPLE_PERIOD | PERF_SAMPLE_IDENTIFIER # + PERF_SAMPLE_CPU added by -C 0 -sample_type=455 +sample_type=65927 # Dummy event handles mmaps, comm and task. mmap=0 comm=0 task=0 +inherit=0 [event:system-wide-dummy] +inherit=0 diff --git a/tools/perf/tests/attr/test-record-basic b/tools/perf/tests/shell/attr/test-record-basic index b0ca42a5ecc9..b0ca42a5ecc9 100644 --- a/tools/perf/tests/attr/test-record-basic +++ b/tools/perf/tests/shell/attr/test-record-basic diff --git a/tools/perf/tests/attr/test-record-branch-any b/tools/perf/tests/shell/attr/test-record-branch-any index 1a99b3ce6b89..1a99b3ce6b89 100644 --- a/tools/perf/tests/attr/test-record-branch-any +++ b/tools/perf/tests/shell/attr/test-record-branch-any diff --git a/tools/perf/tests/attr/test-record-branch-filter-any b/tools/perf/tests/shell/attr/test-record-branch-filter-any index 709768b508c6..709768b508c6 100644 --- a/tools/perf/tests/attr/test-record-branch-filter-any +++ b/tools/perf/tests/shell/attr/test-record-branch-filter-any diff --git a/tools/perf/tests/attr/test-record-branch-filter-any_call b/tools/perf/tests/shell/attr/test-record-branch-filter-any_call index f943221f7825..f943221f7825 100644 --- a/tools/perf/tests/attr/test-record-branch-filter-any_call +++ b/tools/perf/tests/shell/attr/test-record-branch-filter-any_call diff --git a/tools/perf/tests/attr/test-record-branch-filter-any_ret b/tools/perf/tests/shell/attr/test-record-branch-filter-any_ret index fd4f5b4154a9..fd4f5b4154a9 100644 --- a/tools/perf/tests/attr/test-record-branch-filter-any_ret +++ b/tools/perf/tests/shell/attr/test-record-branch-filter-any_ret diff --git a/tools/perf/tests/attr/test-record-branch-filter-hv b/tools/perf/tests/shell/attr/test-record-branch-filter-hv index 4e52d685ebe1..4e52d685ebe1 100644 --- a/tools/perf/tests/attr/test-record-branch-filter-hv +++ b/tools/perf/tests/shell/attr/test-record-branch-filter-hv diff --git a/tools/perf/tests/attr/test-record-branch-filter-ind_call b/tools/perf/tests/shell/attr/test-record-branch-filter-ind_call index e08c6ab3796e..e08c6ab3796e 100644 --- a/tools/perf/tests/attr/test-record-branch-filter-ind_call +++ b/tools/perf/tests/shell/attr/test-record-branch-filter-ind_call diff --git a/tools/perf/tests/attr/test-record-branch-filter-k b/tools/perf/tests/shell/attr/test-record-branch-filter-k index b4b98f84fc2f..b4b98f84fc2f 100644 --- a/tools/perf/tests/attr/test-record-branch-filter-k +++ b/tools/perf/tests/shell/attr/test-record-branch-filter-k diff --git a/tools/perf/tests/attr/test-record-branch-filter-u b/tools/perf/tests/shell/attr/test-record-branch-filter-u index fb9610edbb0d..fb9610edbb0d 100644 --- a/tools/perf/tests/attr/test-record-branch-filter-u +++ b/tools/perf/tests/shell/attr/test-record-branch-filter-u diff --git a/tools/perf/tests/attr/test-record-count b/tools/perf/tests/shell/attr/test-record-count index 5e9b9019d786..5e9b9019d786 100644 --- a/tools/perf/tests/attr/test-record-count +++ b/tools/perf/tests/shell/attr/test-record-count diff --git a/tools/perf/tests/attr/test-record-data b/tools/perf/tests/shell/attr/test-record-data index a99bb13149c2..a99bb13149c2 100644 --- a/tools/perf/tests/attr/test-record-data +++ b/tools/perf/tests/shell/attr/test-record-data diff --git a/tools/perf/tests/shell/attr/test-record-dummy-C0 b/tools/perf/tests/shell/attr/test-record-dummy-C0 new file mode 100644 index 000000000000..91499405fff4 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-dummy-C0 @@ -0,0 +1,55 @@ +[config] +command = record +args = --no-bpf-event -e dummy -C 0 kill >/dev/null 2>&1 +ret = 1 + +[event] +fd=1 +group_fd=-1 +cpu=0 +pid=-1 +flags=8 +type=1 +size=136 +config=9 +sample_period=4000 +# PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | +# PERF_SAMPLE_PERIOD +# + PERF_SAMPLE_CPU added by -C 0 +sample_type=391 +read_format=4|20 +disabled=0 +inherit=0 +pinned=0 +exclusive=0 +exclude_user=0 +exclude_kernel=0 +exclude_hv=0 +exclude_idle=0 +mmap=1 +comm=1 +freq=1 +inherit_stat=0 +enable_on_exec=0 +task=1 +watermark=0 +precise_ip=0 +mmap_data=0 +sample_id_all=1 +exclude_host=0 +exclude_guest=0 +exclude_callchain_kernel=0 +exclude_callchain_user=0 +mmap2=1 +comm_exec=1 +context_switch=0 +write_backward=0 +namespaces=0 +use_clockid=0 +wakeup_events=0 +bp_type=0 +config1=0 +config2=0 +branch_sample_type=0 +sample_regs_user=0 +sample_stack_user=0 diff --git a/tools/perf/tests/attr/test-record-freq b/tools/perf/tests/shell/attr/test-record-freq index 89e29f6b2ae0..89e29f6b2ae0 100644 --- a/tools/perf/tests/attr/test-record-freq +++ b/tools/perf/tests/shell/attr/test-record-freq diff --git a/tools/perf/tests/attr/test-record-graph-default b/tools/perf/tests/shell/attr/test-record-graph-default index 5d8234d50845..f0a18b4ea4f5 100644 --- a/tools/perf/tests/attr/test-record-graph-default +++ b/tools/perf/tests/shell/attr/test-record-graph-default @@ -2,6 +2,8 @@ command = record args = --no-bpf-event -g kill >/dev/null 2>&1 ret = 1 +# arm64 enables registers in the default mode (fp) +arch = !aarch64 [event:base-record] sample_type=295 diff --git a/tools/perf/tests/shell/attr/test-record-graph-default-aarch64 b/tools/perf/tests/shell/attr/test-record-graph-default-aarch64 new file mode 100644 index 000000000000..e98d62efb6f7 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-graph-default-aarch64 @@ -0,0 +1,9 @@ +[config] +command = record +args = --no-bpf-event -g kill >/dev/null 2>&1 +ret = 1 +arch = aarch64 + +[event:base-record] +sample_type=4391 +sample_regs_user=1073741824 diff --git a/tools/perf/tests/attr/test-record-graph-dwarf b/tools/perf/tests/shell/attr/test-record-graph-dwarf index ae92061d611d..ae92061d611d 100644 --- a/tools/perf/tests/attr/test-record-graph-dwarf +++ b/tools/perf/tests/shell/attr/test-record-graph-dwarf diff --git a/tools/perf/tests/attr/test-record-graph-fp b/tools/perf/tests/shell/attr/test-record-graph-fp index 5630521c0b0f..a6e60e839205 100644 --- a/tools/perf/tests/attr/test-record-graph-fp +++ b/tools/perf/tests/shell/attr/test-record-graph-fp @@ -2,6 +2,8 @@ command = record args = --no-bpf-event --call-graph fp kill >/dev/null 2>&1 ret = 1 +# arm64 enables registers in fp mode +arch = !aarch64 [event:base-record] sample_type=295 diff --git a/tools/perf/tests/shell/attr/test-record-graph-fp-aarch64 b/tools/perf/tests/shell/attr/test-record-graph-fp-aarch64 new file mode 100644 index 000000000000..cbeea9971285 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-graph-fp-aarch64 @@ -0,0 +1,9 @@ +[config] +command = record +args = --no-bpf-event --call-graph fp kill >/dev/null 2>&1 +ret = 1 +arch = aarch64 + +[event:base-record] +sample_type=4391 +sample_regs_user=1073741824 diff --git a/tools/perf/tests/attr/test-record-group-sampling b/tools/perf/tests/shell/attr/test-record-group-sampling index 300b9f7e6d69..86a940d7895d 100644 --- a/tools/perf/tests/attr/test-record-group-sampling +++ b/tools/perf/tests/shell/attr/test-record-group-sampling @@ -2,12 +2,13 @@ command = record args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 ret = 1 +kernel_until = 6.12 [event-1:base-record] fd=1 group_fd=-1 sample_type=343 -read_format=12 +read_format=12|28 inherit=0 [event-2:base-record] @@ -18,11 +19,11 @@ group_fd=1 type=0 config=3 -# default | PERF_SAMPLE_READ +# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD sample_type=343 -# PERF_FORMAT_ID | PERF_FORMAT_GROUP -read_format=12 +# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST +read_format=12|28 task=0 mmap=0 comm=0 diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling1 b/tools/perf/tests/shell/attr/test-record-group-sampling1 new file mode 100644 index 000000000000..4748ab7bf684 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-group-sampling1 @@ -0,0 +1,50 @@ +[config] +command = record +args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 +ret = 1 +kernel_since = 6.12 + +[event-1:base-record] +fd=1 +group_fd=-1 + +# cycles +type=0 +config=0 + +# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD +sample_type=343 + +# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING +read_format=28|31 +task=1 +mmap=1 +comm=1 +enable_on_exec=1 +disabled=1 + +# inherit is enabled for group sampling +inherit=1 + +[event-2:base-record] +fd=2 +group_fd=1 + +# cache-misses +type=0 +config=3 + +# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD +sample_type=343 + +# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING +read_format=28|31 +task=0 +mmap=0 +comm=0 +enable_on_exec=0 +disabled=0 +freq=0 + +# inherit is enabled for group sampling +inherit=1 diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling2 b/tools/perf/tests/shell/attr/test-record-group-sampling2 new file mode 100644 index 000000000000..e0432244a0eb --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-group-sampling2 @@ -0,0 +1,61 @@ +[config] +command = record +args = --no-bpf-event -c 10000 -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 +ret = 1 +kernel_since = 6.12 + +[event-1:base-record] +fd=1 +group_fd=-1 + +# cycles +type=0 +config=0 + +# default | PERF_SAMPLE_READ +sample_type=87 + +# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING +read_format=28|31 +task=1 +mmap=1 +comm=1 +enable_on_exec=1 +disabled=1 + +# inherit is enabled for group sampling +inherit=1 + +# sampling disabled +sample_freq=0 +sample_period=10000 +freq=0 +write_backward=0 + +[event-2:base-record] +fd=2 +group_fd=1 + +# cache-misses +type=0 +config=3 + +# default | PERF_SAMPLE_READ +sample_type=87 + +# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING +read_format=28|31 +task=0 +mmap=0 +comm=0 +enable_on_exec=0 +disabled=0 + +# inherit is enabled for group sampling +inherit=1 + +# sampling disabled +sample_freq=0 +sample_period=0 +freq=0 +write_backward=0 diff --git a/tools/perf/tests/attr/test-record-group1 b/tools/perf/tests/shell/attr/test-record-group1 index 3ffe246e0228..eeb1db392bc9 100644 --- a/tools/perf/tests/attr/test-record-group1 +++ b/tools/perf/tests/shell/attr/test-record-group1 @@ -7,7 +7,7 @@ ret = 1 fd=1 group_fd=-1 sample_type=327 -read_format=4 +read_format=4|20 [event-2:base-record] fd=2 @@ -15,7 +15,7 @@ group_fd=1 type=0 config=1 sample_type=327 -read_format=4 +read_format=4|20 mmap=0 comm=0 task=0 diff --git a/tools/perf/tests/shell/attr/test-record-group2 b/tools/perf/tests/shell/attr/test-record-group2 new file mode 100644 index 000000000000..891d41a7bddf --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-group2 @@ -0,0 +1,30 @@ +[config] +command = record +args = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1 +ret = 1 +kernel_until = 6.12 + +[event-1:base-record] +fd=1 +group_fd=-1 +config=0|1 +sample_period=1234000 +sample_type=87 +read_format=12|28 +inherit=0 +freq=0 + +[event-2:base-record] +fd=2 +group_fd=1 +config=0|1 +sample_period=6789000 +sample_type=87 +read_format=12|28 +disabled=0 +inherit=0 +mmap=0 +comm=0 +freq=0 +enable_on_exec=0 +task=0 diff --git a/tools/perf/tests/shell/attr/test-record-group3 b/tools/perf/tests/shell/attr/test-record-group3 new file mode 100644 index 000000000000..249be884959e --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-group3 @@ -0,0 +1,31 @@ +[config] +command = record +args = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1 +ret = 1 +kernel_since = 6.12 + +[event-1:base-record] +fd=1 +group_fd=-1 +config=0|1 +sample_period=1234000 +sample_type=87 +read_format=28|31 +disabled=1 +inherit=1 +freq=0 + +[event-2:base-record] +fd=2 +group_fd=1 +config=0|1 +sample_period=6789000 +sample_type=87 +read_format=28|31 +disabled=0 +inherit=1 +mmap=0 +comm=0 +freq=0 +enable_on_exec=0 +task=0 diff --git a/tools/perf/tests/attr/test-record-no-buffering b/tools/perf/tests/shell/attr/test-record-no-buffering index 583dcbb078ba..583dcbb078ba 100644 --- a/tools/perf/tests/attr/test-record-no-buffering +++ b/tools/perf/tests/shell/attr/test-record-no-buffering diff --git a/tools/perf/tests/attr/test-record-no-inherit b/tools/perf/tests/shell/attr/test-record-no-inherit index 15d1dc162e1c..15d1dc162e1c 100644 --- a/tools/perf/tests/attr/test-record-no-inherit +++ b/tools/perf/tests/shell/attr/test-record-no-inherit diff --git a/tools/perf/tests/attr/test-record-no-samples b/tools/perf/tests/shell/attr/test-record-no-samples index 596fbd6d5a2c..596fbd6d5a2c 100644 --- a/tools/perf/tests/attr/test-record-no-samples +++ b/tools/perf/tests/shell/attr/test-record-no-samples diff --git a/tools/perf/tests/attr/test-record-period b/tools/perf/tests/shell/attr/test-record-period index 119101154c5e..119101154c5e 100644 --- a/tools/perf/tests/attr/test-record-period +++ b/tools/perf/tests/shell/attr/test-record-period diff --git a/tools/perf/tests/shell/attr/test-record-pfm-period b/tools/perf/tests/shell/attr/test-record-pfm-period new file mode 100644 index 000000000000..368f5b814094 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-pfm-period @@ -0,0 +1,9 @@ +[config] +command = record +args = --no-bpf-event -c 10000 --pfm-events=cycles:period=77777 kill >/dev/null 2>&1 +ret = 1 + +[event:base-record] +sample_period=77777 +sample_type=7 +freq=0 diff --git a/tools/perf/tests/attr/test-record-raw b/tools/perf/tests/shell/attr/test-record-raw index 13a5f7860c78..13a5f7860c78 100644 --- a/tools/perf/tests/attr/test-record-raw +++ b/tools/perf/tests/shell/attr/test-record-raw diff --git a/tools/perf/tests/shell/attr/test-record-spe-period b/tools/perf/tests/shell/attr/test-record-spe-period new file mode 100644 index 000000000000..75f8c9cd8e3f --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-spe-period @@ -0,0 +1,12 @@ +[config] +command = record +args = --no-bpf-event -c 2 -e arm_spe_0// -- kill >/dev/null 2>&1 +ret = 1 +arch = aarch64 + +[event-10:base-record-spe] +sample_period=2 +freq=0 + +# dummy event +[event-1:base-record-spe] diff --git a/tools/perf/tests/shell/attr/test-record-spe-period-term b/tools/perf/tests/shell/attr/test-record-spe-period-term new file mode 100644 index 000000000000..8f60a4fec657 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-spe-period-term @@ -0,0 +1,12 @@ +[config] +command = record +args = --no-bpf-event -e arm_spe_0/period=3/ -- kill >/dev/null 2>&1 +ret = 1 +arch = aarch64 + +[event-10:base-record-spe] +sample_period=3 +freq=0 + +# dummy event +[event-1:base-record-spe] diff --git a/tools/perf/tests/shell/attr/test-record-spe-physical-address b/tools/perf/tests/shell/attr/test-record-spe-physical-address new file mode 100644 index 000000000000..7ebcf5012ce3 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-spe-physical-address @@ -0,0 +1,12 @@ +[config] +command = record +args = --no-bpf-event -e arm_spe_0/pa_enable=1/ -- kill >/dev/null 2>&1 +ret = 1 +arch = aarch64 + +[event-10:base-record-spe] +# 622727 is the decimal of IP|TID|TIME|CPU|IDENTIFIER|DATA_SRC|PHYS_ADDR +sample_type=622727 + +# dummy event +[event-1:base-record-spe]
\ No newline at end of file diff --git a/tools/perf/tests/shell/attr/test-record-user-regs-no-sve-aarch64 b/tools/perf/tests/shell/attr/test-record-user-regs-no-sve-aarch64 new file mode 100644 index 000000000000..bed765450ca9 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-user-regs-no-sve-aarch64 @@ -0,0 +1,9 @@ +# Test that asking for VG fails if the system doesn't support SVE. This +# applies both before and after the feature was added in 6.1 +[config] +command = record +args = --no-bpf-event --user-regs=vg kill >/dev/null 2>&1 +ret = 129 +test_ret = true +arch = aarch64 +auxv = auxv["AT_HWCAP"] & 0x400000 == 0 diff --git a/tools/perf/tests/shell/attr/test-record-user-regs-old-sve-aarch64 b/tools/perf/tests/shell/attr/test-record-user-regs-old-sve-aarch64 new file mode 100644 index 000000000000..15ebfc3418e3 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-user-regs-old-sve-aarch64 @@ -0,0 +1,10 @@ +# Test that asking for VG always fails on old kernels because it was +# added in 6.1. This applies to systems that either support or don't +# support SVE. +[config] +command = record +args = --no-bpf-event --user-regs=vg kill >/dev/null 2>&1 +ret = 129 +test_ret = true +arch = aarch64 +kernel_until = 6.1 diff --git a/tools/perf/tests/shell/attr/test-record-user-regs-sve-aarch64 b/tools/perf/tests/shell/attr/test-record-user-regs-sve-aarch64 new file mode 100644 index 000000000000..a65113cd7311 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-record-user-regs-sve-aarch64 @@ -0,0 +1,14 @@ +# Test that asking for VG works if the system has SVE and after the +# feature was added in 6.1 +[config] +command = record +args = --no-bpf-event --user-regs=vg kill >/dev/null 2>&1 +ret = 1 +test_ret = true +arch = aarch64 +auxv = auxv["AT_HWCAP"] & 0x400000 == 0x400000 +kernel_since = 6.1 + +[event:base-record] +sample_type=4359 +sample_regs_user=70368744177664 diff --git a/tools/perf/tests/attr/test-stat-C0 b/tools/perf/tests/shell/attr/test-stat-C0 index a2c76d10b2bb..a2c76d10b2bb 100644 --- a/tools/perf/tests/attr/test-stat-C0 +++ b/tools/perf/tests/shell/attr/test-stat-C0 diff --git a/tools/perf/tests/attr/test-stat-basic b/tools/perf/tests/shell/attr/test-stat-basic index 69867d049fda..69867d049fda 100644 --- a/tools/perf/tests/attr/test-stat-basic +++ b/tools/perf/tests/shell/attr/test-stat-basic diff --git a/tools/perf/tests/shell/attr/test-stat-default b/tools/perf/tests/shell/attr/test-stat-default new file mode 100644 index 000000000000..e47fb4944679 --- /dev/null +++ b/tools/perf/tests/shell/attr/test-stat-default @@ -0,0 +1,229 @@ +[config] +command = stat +args = kill >/dev/null 2>&1 +ret = 1 + +# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK +[event1:base-stat] +fd=1 +type=1 +config=1 + +# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES +[event2:base-stat] +fd=2 +type=1 +config=3 + +# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS +[event3:base-stat] +fd=3 +type=1 +config=4 + +# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS +[event4:base-stat] +fd=4 +type=1 +config=2 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES +[event5:base-stat] +fd=5 +type=0 +config=0 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND +[event6:base-stat] +fd=6 +type=0 +config=7 +optional=1 +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND +[event7:base-stat] +fd=7 +type=0 +config=8 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS +[event8:base-stat] +fd=8 +type=0 +config=1 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS +[event9:base-stat] +fd=9 +type=0 +config=4 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES +[event10:base-stat] +fd=10 +type=0 +config=5 +optional=1 + +# PERF_TYPE_RAW / slots (0x400) +[event11:base-stat] +fd=11 +group_fd=-1 +type=4 +config=1024 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-retiring (0x8000) +[event12:base-stat] +fd=12 +group_fd=11 +type=4 +config=32768 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-bad-spec (0x8100) +[event13:base-stat] +fd=13 +group_fd=11 +type=4 +config=33024 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-fe-bound (0x8200) +[event14:base-stat] +fd=14 +group_fd=11 +type=4 +config=33280 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-be-bound (0x8300) +[event15:base-stat] +fd=15 +group_fd=11 +type=4 +config=33536 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-heavy-ops (0x8400) +[event16:base-stat] +fd=16 +group_fd=11 +type=4 +config=33792 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-br-mispredict (0x8500) +[event17:base-stat] +fd=17 +group_fd=11 +type=4 +config=34048 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-fetch-lat (0x8600) +[event18:base-stat] +fd=18 +group_fd=11 +type=4 +config=34304 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-mem-bound (0x8700) +[event19:base-stat] +fd=19 +group_fd=11 +type=4 +config=34560 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING +[event20:base-stat] +fd=20 +type=4 +config=4109 +optional=1 + +# PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ +[event21:base-stat] +fd=21 +type=4 +config=17039629 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD +[event22:base-stat] +fd=22 +type=4 +config=60 +optional=1 + +# PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY +[event23:base-stat] +fd=23 +type=4 +config=2097421 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK +[event24:base-stat] +fd=24 +type=4 +config=316 +optional=1 + +# PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE +[event25:base-stat] +fd=25 +type=4 +config=412 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE +[event26:base-stat] +fd=26 +type=4 +config=572 +optional=1 + +# PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS +[event27:base-stat] +fd=27 +type=4 +config=706 +optional=1 + +# PERF_TYPE_RAW / UOPS_ISSUED.ANY +[event28:base-stat] +fd=28 +type=4 +config=270 +optional=1 diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-1 b/tools/perf/tests/shell/attr/test-stat-detailed-1 new file mode 100644 index 000000000000..3d500d3e0c5c --- /dev/null +++ b/tools/perf/tests/shell/attr/test-stat-detailed-1 @@ -0,0 +1,271 @@ +[config] +command = stat +args = -d kill >/dev/null 2>&1 +ret = 1 + + +# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK +[event1:base-stat] +fd=1 +type=1 +config=1 + +# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES +[event2:base-stat] +fd=2 +type=1 +config=3 + +# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS +[event3:base-stat] +fd=3 +type=1 +config=4 + +# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS +[event4:base-stat] +fd=4 +type=1 +config=2 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES +[event5:base-stat] +fd=5 +type=0 +config=0 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND +[event6:base-stat] +fd=6 +type=0 +config=7 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND +[event7:base-stat] +fd=7 +type=0 +config=8 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS +[event8:base-stat] +fd=8 +type=0 +config=1 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS +[event9:base-stat] +fd=9 +type=0 +config=4 +optional=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES +[event10:base-stat] +fd=10 +type=0 +config=5 +optional=1 + +# PERF_TYPE_RAW / slots (0x400) +[event11:base-stat] +fd=11 +group_fd=-1 +type=4 +config=1024 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-retiring (0x8000) +[event12:base-stat] +fd=12 +group_fd=11 +type=4 +config=32768 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-bad-spec (0x8100) +[event13:base-stat] +fd=13 +group_fd=11 +type=4 +config=33024 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-fe-bound (0x8200) +[event14:base-stat] +fd=14 +group_fd=11 +type=4 +config=33280 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-be-bound (0x8300) +[event15:base-stat] +fd=15 +group_fd=11 +type=4 +config=33536 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-heavy-ops (0x8400) +[event16:base-stat] +fd=16 +group_fd=11 +type=4 +config=33792 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-br-mispredict (0x8500) +[event17:base-stat] +fd=17 +group_fd=11 +type=4 +config=34048 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-fetch-lat (0x8600) +[event18:base-stat] +fd=18 +group_fd=11 +type=4 +config=34304 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-mem-bound (0x8700) +[event19:base-stat] +fd=19 +group_fd=11 +type=4 +config=34560 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING +[event20:base-stat] +fd=20 +type=4 +config=4109 +optional=1 + +# PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ +[event21:base-stat] +fd=21 +type=4 +config=17039629 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD +[event22:base-stat] +fd=22 +type=4 +config=60 +optional=1 + +# PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY +[event23:base-stat] +fd=23 +type=4 +config=2097421 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK +[event24:base-stat] +fd=24 +type=4 +config=316 +optional=1 + +# PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE +[event25:base-stat] +fd=25 +type=4 +config=412 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE +[event26:base-stat] +fd=26 +type=4 +config=572 +optional=1 + +# PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS +[event27:base-stat] +fd=27 +type=4 +config=706 +optional=1 + +# PERF_TYPE_RAW / UOPS_ISSUED.ANY +[event28:base-stat] +fd=28 +type=4 +config=270 +optional=1 + +# PERF_TYPE_HW_CACHE / +# PERF_COUNT_HW_CACHE_L1D << 0 | +# (PERF_COUNT_HW_CACHE_OP_READ << 8) | +# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) +[event29:base-stat] +fd=29 +type=3 +config=0 +optional=1 + +# PERF_TYPE_HW_CACHE / +# PERF_COUNT_HW_CACHE_L1D << 0 | +# (PERF_COUNT_HW_CACHE_OP_READ << 8) | +# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) +[event30:base-stat] +fd=30 +type=3 +config=65536 +optional=1 + +# PERF_TYPE_HW_CACHE / +# PERF_COUNT_HW_CACHE_LL << 0 | +# (PERF_COUNT_HW_CACHE_OP_READ << 8) | +# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) +[event31:base-stat] +fd=31 +type=3 +config=2 +optional=1 + +# PERF_TYPE_HW_CACHE, +# PERF_COUNT_HW_CACHE_LL << 0 | +# (PERF_COUNT_HW_CACHE_OP_READ << 8) | +# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) +[event32:base-stat] +fd=32 +type=3 +config=65538 +optional=1 diff --git a/tools/perf/tests/attr/test-stat-detailed-2 b/tools/perf/tests/shell/attr/test-stat-detailed-2 index 4fca9f1bfbf8..01777a63752f 100644 --- a/tools/perf/tests/attr/test-stat-detailed-2 +++ b/tools/perf/tests/shell/attr/test-stat-detailed-2 @@ -70,12 +70,172 @@ type=0 config=5 optional=1 +# PERF_TYPE_RAW / slots (0x400) +[event11:base-stat] +fd=11 +group_fd=-1 +type=4 +config=1024 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-retiring (0x8000) +[event12:base-stat] +fd=12 +group_fd=11 +type=4 +config=32768 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-bad-spec (0x8100) +[event13:base-stat] +fd=13 +group_fd=11 +type=4 +config=33024 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-fe-bound (0x8200) +[event14:base-stat] +fd=14 +group_fd=11 +type=4 +config=33280 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-be-bound (0x8300) +[event15:base-stat] +fd=15 +group_fd=11 +type=4 +config=33536 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-heavy-ops (0x8400) +[event16:base-stat] +fd=16 +group_fd=11 +type=4 +config=33792 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-br-mispredict (0x8500) +[event17:base-stat] +fd=17 +group_fd=11 +type=4 +config=34048 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-fetch-lat (0x8600) +[event18:base-stat] +fd=18 +group_fd=11 +type=4 +config=34304 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-mem-bound (0x8700) +[event19:base-stat] +fd=19 +group_fd=11 +type=4 +config=34560 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING +[event20:base-stat] +fd=20 +type=4 +config=4109 +optional=1 + +# PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ +[event21:base-stat] +fd=21 +type=4 +config=17039629 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD +[event22:base-stat] +fd=22 +type=4 +config=60 +optional=1 + +# PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY +[event23:base-stat] +fd=23 +type=4 +config=2097421 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK +[event24:base-stat] +fd=24 +type=4 +config=316 +optional=1 + +# PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE +[event25:base-stat] +fd=25 +type=4 +config=412 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE +[event26:base-stat] +fd=26 +type=4 +config=572 +optional=1 + +# PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS +[event27:base-stat] +fd=27 +type=4 +config=706 +optional=1 + +# PERF_TYPE_RAW / UOPS_ISSUED.ANY +[event28:base-stat] +fd=28 +type=4 +config=270 +optional=1 + # PERF_TYPE_HW_CACHE / # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event11:base-stat] -fd=11 +[event29:base-stat] +fd=29 type=3 config=0 optional=1 @@ -84,8 +244,8 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event12:base-stat] -fd=12 +[event30:base-stat] +fd=30 type=3 config=65536 optional=1 @@ -94,8 +254,8 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event13:base-stat] -fd=13 +[event31:base-stat] +fd=31 type=3 config=2 optional=1 @@ -104,8 +264,8 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event14:base-stat] -fd=14 +[event32:base-stat] +fd=32 type=3 config=65538 optional=1 @@ -114,8 +274,8 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event15:base-stat] -fd=15 +[event33:base-stat] +fd=33 type=3 config=1 optional=1 @@ -124,8 +284,8 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event16:base-stat] -fd=16 +[event34:base-stat] +fd=34 type=3 config=65537 optional=1 @@ -134,8 +294,8 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event17:base-stat] -fd=17 +[event35:base-stat] +fd=35 type=3 config=3 optional=1 @@ -144,8 +304,8 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event18:base-stat] -fd=18 +[event36:base-stat] +fd=36 type=3 config=65539 optional=1 @@ -154,8 +314,8 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event19:base-stat] -fd=19 +[event37:base-stat] +fd=37 type=3 config=4 optional=1 @@ -164,8 +324,8 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event20:base-stat] -fd=20 +[event38:base-stat] +fd=38 type=3 config=65540 optional=1 diff --git a/tools/perf/tests/attr/test-stat-detailed-3 b/tools/perf/tests/shell/attr/test-stat-detailed-3 index 4bb58e1c82a6..8400abd7e1e4 100644 --- a/tools/perf/tests/attr/test-stat-detailed-3 +++ b/tools/perf/tests/shell/attr/test-stat-detailed-3 @@ -70,12 +70,172 @@ type=0 config=5 optional=1 +# PERF_TYPE_RAW / slots (0x400) +[event11:base-stat] +fd=11 +group_fd=-1 +type=4 +config=1024 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-retiring (0x8000) +[event12:base-stat] +fd=12 +group_fd=11 +type=4 +config=32768 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-bad-spec (0x8100) +[event13:base-stat] +fd=13 +group_fd=11 +type=4 +config=33024 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-fe-bound (0x8200) +[event14:base-stat] +fd=14 +group_fd=11 +type=4 +config=33280 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-be-bound (0x8300) +[event15:base-stat] +fd=15 +group_fd=11 +type=4 +config=33536 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-heavy-ops (0x8400) +[event16:base-stat] +fd=16 +group_fd=11 +type=4 +config=33792 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-br-mispredict (0x8500) +[event17:base-stat] +fd=17 +group_fd=11 +type=4 +config=34048 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-fetch-lat (0x8600) +[event18:base-stat] +fd=18 +group_fd=11 +type=4 +config=34304 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / topdown-mem-bound (0x8700) +[event19:base-stat] +fd=19 +group_fd=11 +type=4 +config=34560 +disabled=0 +enable_on_exec=0 +read_format=15 +optional=1 + +# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING +[event20:base-stat] +fd=20 +type=4 +config=4109 +optional=1 + +# PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/ +[event21:base-stat] +fd=21 +type=4 +config=17039629 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD +[event22:base-stat] +fd=22 +type=4 +config=60 +optional=1 + +# PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY +[event23:base-stat] +fd=23 +type=4 +config=2097421 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK +[event24:base-stat] +fd=24 +type=4 +config=316 +optional=1 + +# PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE +[event25:base-stat] +fd=25 +type=4 +config=412 +optional=1 + +# PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE +[event26:base-stat] +fd=26 +type=4 +config=572 +optional=1 + +# PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS +[event27:base-stat] +fd=27 +type=4 +config=706 +optional=1 + +# PERF_TYPE_RAW / UOPS_ISSUED.ANY +[event28:base-stat] +fd=28 +type=4 +config=270 +optional=1 + # PERF_TYPE_HW_CACHE / # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event11:base-stat] -fd=11 +[event29:base-stat] +fd=29 type=3 config=0 optional=1 @@ -84,8 +244,8 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event12:base-stat] -fd=12 +[event30:base-stat] +fd=30 type=3 config=65536 optional=1 @@ -94,8 +254,8 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event13:base-stat] -fd=13 +[event31:base-stat] +fd=31 type=3 config=2 optional=1 @@ -104,8 +264,8 @@ optional=1 # PERF_COUNT_HW_CACHE_LL << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event14:base-stat] -fd=14 +[event32:base-stat] +fd=32 type=3 config=65538 optional=1 @@ -114,8 +274,8 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event15:base-stat] -fd=15 +[event33:base-stat] +fd=33 type=3 config=1 optional=1 @@ -124,8 +284,8 @@ optional=1 # PERF_COUNT_HW_CACHE_L1I << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event16:base-stat] -fd=16 +[event34:base-stat] +fd=34 type=3 config=65537 optional=1 @@ -134,8 +294,8 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event17:base-stat] -fd=17 +[event35:base-stat] +fd=35 type=3 config=3 optional=1 @@ -144,8 +304,8 @@ optional=1 # PERF_COUNT_HW_CACHE_DTLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event18:base-stat] -fd=18 +[event36:base-stat] +fd=36 type=3 config=65539 optional=1 @@ -154,8 +314,8 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event19:base-stat] -fd=19 +[event37:base-stat] +fd=37 type=3 config=4 optional=1 @@ -164,8 +324,8 @@ optional=1 # PERF_COUNT_HW_CACHE_ITLB << 0 | # (PERF_COUNT_HW_CACHE_OP_READ << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event20:base-stat] -fd=20 +[event38:base-stat] +fd=38 type=3 config=65540 optional=1 @@ -174,8 +334,8 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | # (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) -[event21:base-stat] -fd=21 +[event39:base-stat] +fd=39 type=3 config=512 optional=1 @@ -184,8 +344,8 @@ optional=1 # PERF_COUNT_HW_CACHE_L1D << 0 | # (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) | # (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) -[event22:base-stat] -fd=22 +[event40:base-stat] +fd=40 type=3 config=66048 optional=1 diff --git a/tools/perf/tests/attr/test-stat-group1 b/tools/perf/tests/shell/attr/test-stat-group1 index 1746751123dc..1746751123dc 100644 --- a/tools/perf/tests/attr/test-stat-group1 +++ b/tools/perf/tests/shell/attr/test-stat-group1 diff --git a/tools/perf/tests/attr/test-stat-no-inherit b/tools/perf/tests/shell/attr/test-stat-no-inherit index 924fbb9300d1..924fbb9300d1 100644 --- a/tools/perf/tests/attr/test-stat-no-inherit +++ b/tools/perf/tests/shell/attr/test-stat-no-inherit diff --git a/tools/perf/tests/shell/base_probe/test_adding_blacklisted.sh b/tools/perf/tests/shell/base_probe/test_adding_blacklisted.sh new file mode 100755 index 000000000000..8226449ac5c3 --- /dev/null +++ b/tools/perf/tests/shell/base_probe/test_adding_blacklisted.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# perf_probe :: Reject blacklisted probes (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +# +# test_adding_blacklisted of perf_probe test +# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> +# Author: Michael Petlan <mpetlan@redhat.com> +# +# Description: +# +# Blacklisted functions should not be added successfully as probes, +# they must be skipped. +# + +# include working environment +. ../common/init.sh + +TEST_RESULT=0 + +# skip if not supported +BLACKFUNC_LIST=`head -n 5 /sys/kernel/debug/kprobes/blacklist 2> /dev/null | cut -f2` +if [ -z "$BLACKFUNC_LIST" ]; then + print_overall_skipped + exit 2 +fi + +# try to find vmlinux with DWARF debug info +VMLINUX_FILE=$(perf probe -v random_probe |& grep "Using.*for symbols" | sed -r 's/^Using (.*) for symbols$/\1/') + +# remove all previously added probes +clear_all_probes + + +### adding blacklisted function +REGEX_SCOPE_FAIL="Failed to find scope of probe point" +REGEX_SKIP_MESSAGE=" is blacklisted function, skip it\." +REGEX_NOT_FOUND_MESSAGE="Probe point \'$RE_EVENT\' not found." +REGEX_ERROR_MESSAGE="Error: Failed to add events." +REGEX_INVALID_ARGUMENT="Failed to write event: Invalid argument" +REGEX_SYMBOL_FAIL="Failed to find symbol at $RE_ADDRESS" +REGEX_OUT_SECTION="$RE_EVENT is out of \.\w+, skip it" +REGEX_MISSING_DECL_LINE="A function DIE doesn't have decl_line. Maybe broken DWARF?" + +BLACKFUNC="" +SKIP_DWARF=0 + +for BLACKFUNC in $BLACKFUNC_LIST; do + echo "Probing $BLACKFUNC" + + # functions from blacklist should be skipped by perf probe + ! $CMD_PERF probe $BLACKFUNC > $LOGS_DIR/adding_blacklisted.log 2> $LOGS_DIR/adding_blacklisted.err + PERF_EXIT_CODE=$? + + # check for bad DWARF polluting the result + ../common/check_all_patterns_found.pl "$REGEX_MISSING_DECL_LINE" >/dev/null < $LOGS_DIR/adding_blacklisted.err + + if [ $? -eq 0 ]; then + SKIP_DWARF=1 + echo "Result polluted by broken DWARF, trying another probe" + + # confirm that the broken DWARF comes from assembler + if [ -n "$VMLINUX_FILE" ]; then + readelf -wi "$VMLINUX_FILE" | + awk -v probe="$BLACKFUNC" '/DW_AT_language/ { comp_lang = $0 } + $0 ~ probe { if (comp_lang) { print comp_lang }; exit }' | + grep -q "MIPS assembler" + + CHECK_EXIT_CODE=$? + if [ $CHECK_EXIT_CODE -ne 0 ]; then + SKIP_DWARF=0 # broken DWARF while available + break + fi + fi + else + ../common/check_all_lines_matched.pl "$REGEX_SKIP_MESSAGE" "$REGEX_NOT_FOUND_MESSAGE" "$REGEX_ERROR_MESSAGE" "$REGEX_SCOPE_FAIL" "$REGEX_INVALID_ARGUMENT" "$REGEX_SYMBOL_FAIL" "$REGEX_OUT_SECTION" < $LOGS_DIR/adding_blacklisted.err + CHECK_EXIT_CODE=$? + + SKIP_DWARF=0 + break + fi +done + +if [ $SKIP_DWARF -eq 1 ]; then + print_testcase_skipped "adding blacklisted function $BLACKFUNC" +else + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "adding blacklisted function $BLACKFUNC" + (( TEST_RESULT += $? )) +fi + +### listing not-added probe + +# blacklisted probes should NOT appear in perf-list output +$CMD_PERF list probe:\* > $LOGS_DIR/adding_blacklisted_list.log +PERF_EXIT_CODE=$? + +../common/check_all_lines_matched.pl "$RE_LINE_EMPTY" "List of pre-defined events" "Metric Groups:" < $LOGS_DIR/adding_blacklisted_list.log +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "listing blacklisted probe (should NOT be listed)" +(( TEST_RESULT += $? )) + + +# print overall results +print_overall_results "$TEST_RESULT" +exit $? diff --git a/tools/perf/tests/shell/base_probe/test_adding_kernel.sh b/tools/perf/tests/shell/base_probe/test_adding_kernel.sh new file mode 100755 index 000000000000..df288cf90cd6 --- /dev/null +++ b/tools/perf/tests/shell/base_probe/test_adding_kernel.sh @@ -0,0 +1,301 @@ +#!/bin/bash +# perf_probe :: Add probes, list and remove them (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +# +# test_adding_kernel of perf_probe test +# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> +# Author: Michael Petlan <mpetlan@redhat.com> +# +# Description: +# +# This test tests adding of probes, their correct listing +# and removing. +# + +# include working environment +. ../common/init.sh + +TEST_RESULT=0 + +# shellcheck source=lib/probe_vfs_getname.sh +. "$(dirname "$0")/../lib/probe_vfs_getname.sh" + +TEST_PROBE=${TEST_PROBE:-"inode_permission"} + +# set NO_DEBUGINFO to skip testcase if debuginfo is not present +# skip_if_no_debuginfo returns 2 if debuginfo is not present +skip_if_no_debuginfo +if [ $? -eq 2 ]; then + NO_DEBUGINFO=1 +fi + +check_kprobes_available +if [ $? -ne 0 ]; then + print_overall_skipped + exit 2 +fi + + +### basic probe adding + +for opt in "" "-a" "--add"; do + clear_all_probes + $CMD_PERF probe $opt $TEST_PROBE 2> $LOGS_DIR/adding_kernel_add$opt.err + PERF_EXIT_CODE=$? + + ../common/check_all_patterns_found.pl "Added new events?:" "probe:$TEST_PROBE" "on $TEST_PROBE" < $LOGS_DIR/adding_kernel_add$opt.err + CHECK_EXIT_CODE=$? + + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "adding probe $TEST_PROBE :: $opt" + (( TEST_RESULT += $? )) +done + + +### listing added probe :: perf list + +# any added probes should appear in perf-list output +$CMD_PERF list probe:\* > $LOGS_DIR/adding_kernel_list.log +PERF_EXIT_CODE=$? + +../common/check_all_lines_matched.pl "$RE_LINE_EMPTY" "List of pre-defined events" "probe:${TEST_PROBE}(?:_\d+)?\s+\[Tracepoint event\]" "Metric Groups:" < $LOGS_DIR/adding_kernel_list.log +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "listing added probe :: perf list" +(( TEST_RESULT += $? )) + + +### listing added probe :: perf probe -l + +# '-l' should list all the added probes as well +$CMD_PERF probe -l > $LOGS_DIR/adding_kernel_list-l.log +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "\s*probe:${TEST_PROBE}(?:_\d+)?\s+\(on ${TEST_PROBE}(?:[:\+]$RE_NUMBER_HEX)?@.+\)" < $LOGS_DIR/adding_kernel_list-l.log +CHECK_EXIT_CODE=$? + +if [ $NO_DEBUGINFO ] ; then + print_testcase_skipped $NO_DEBUGINFO $NO_DEBUGINFO "Skipped due to missing debuginfo" +else + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "listing added probe :: perf probe -l" +fi + +(( TEST_RESULT += $? )) + + +### using added probe + +$CMD_PERF stat -e probe:$TEST_PROBE\* -o $LOGS_DIR/adding_kernel_using_probe.log -- cat /proc/uptime > /dev/null +PERF_EXIT_CODE=$? + +REGEX_STAT_HEADER="\s*Performance counter stats for \'cat /proc/uptime\':" +REGEX_STAT_VALUES="\s*\d+\s+probe:$TEST_PROBE" +# the value should be greater than 1 +REGEX_STAT_VALUE_NONZERO="\s*[1-9][0-9]*\s+probe:$TEST_PROBE" +REGEX_STAT_TIME="\s*$RE_NUMBER\s+seconds (?:time elapsed|user|sys)" +../common/check_all_lines_matched.pl "$REGEX_STAT_HEADER" "$REGEX_STAT_VALUES" "$REGEX_STAT_TIME" "$RE_LINE_COMMENT" "$RE_LINE_EMPTY" < $LOGS_DIR/adding_kernel_using_probe.log +CHECK_EXIT_CODE=$? +../common/check_all_patterns_found.pl "$REGEX_STAT_HEADER" "$REGEX_STAT_VALUE_NONZERO" "$REGEX_STAT_TIME" < $LOGS_DIR/adding_kernel_using_probe.log +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "using added probe" +(( TEST_RESULT += $? )) + + +### removing added probe + +# '-d' should remove the probe +$CMD_PERF probe -d $TEST_PROBE\* 2> $LOGS_DIR/adding_kernel_removing.err +PERF_EXIT_CODE=$? + +../common/check_all_lines_matched.pl "Removed event: probe:$TEST_PROBE" < $LOGS_DIR/adding_kernel_removing.err +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "deleting added probe" +(( TEST_RESULT += $? )) + + +### listing removed probe + +# removed probes should NOT appear in perf-list output +$CMD_PERF list probe:\* > $LOGS_DIR/adding_kernel_list_removed.log +PERF_EXIT_CODE=$? + +../common/check_all_lines_matched.pl "$RE_LINE_EMPTY" "List of pre-defined events" "Metric Groups:" < $LOGS_DIR/adding_kernel_list_removed.log +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "listing removed probe (should NOT be listed)" +(( TEST_RESULT += $? )) + + +### dry run + +# the '-n' switch should run it in dry mode +$CMD_PERF probe -n --add $TEST_PROBE 2> $LOGS_DIR/adding_kernel_dryrun.err +PERF_EXIT_CODE=$? + +# check for the output (should be the same as usual) +../common/check_all_patterns_found.pl "Added new events?:" "probe:$TEST_PROBE" "on $TEST_PROBE" < $LOGS_DIR/adding_kernel_dryrun.err +CHECK_EXIT_CODE=$? + +# check that no probe was added in real +! ( $CMD_PERF probe -l | grep "probe:$TEST_PROBE" ) +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "dry run :: adding probe" +(( TEST_RESULT += $? )) + + +### force-adding probes + +# when using '--force' a probe should be added even if it is already there +$CMD_PERF probe --add $TEST_PROBE 2> $LOGS_DIR/adding_kernel_forceadd_01.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "Added new events?:" "probe:$TEST_PROBE" "on $TEST_PROBE" < $LOGS_DIR/adding_kernel_forceadd_01.err +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "force-adding probes :: first probe adding" +(( TEST_RESULT += $? )) + +# adding existing probe without '--force' should fail +! $CMD_PERF probe --add $TEST_PROBE 2> $LOGS_DIR/adding_kernel_forceadd_02.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "Error: event \"$TEST_PROBE\" already exists." "Error: Failed to add events." < $LOGS_DIR/adding_kernel_forceadd_02.err +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "force-adding probes :: second probe adding (without force)" +(( TEST_RESULT += $? )) + +# adding existing probe with '--force' should pass +NO_OF_PROBES=`$CMD_PERF probe -l $TEST_PROBE| wc -l` +$CMD_PERF probe --force --add $TEST_PROBE 2> $LOGS_DIR/adding_kernel_forceadd_03.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "Added new events?:" "probe:${TEST_PROBE}_${NO_OF_PROBES}" "on $TEST_PROBE" < $LOGS_DIR/adding_kernel_forceadd_03.err +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "force-adding probes :: second probe adding (with force)" +(( TEST_RESULT += $? )) + + +### using doubled probe + +# since they are the same, they should produce the same results +$CMD_PERF stat -e probe:$TEST_PROBE -e probe:${TEST_PROBE}_${NO_OF_PROBES} -x';' -o $LOGS_DIR/adding_kernel_using_two.log -- bash -c 'cat /proc/cpuinfo > /dev/null' +PERF_EXIT_CODE=$? + +REGEX_LINE="$RE_NUMBER;+probe:${TEST_PROBE}_?(?:$NO_OF_PROBES)?;$RE_NUMBER;$RE_NUMBER" +../common/check_all_lines_matched.pl "$REGEX_LINE" "$RE_LINE_EMPTY" "$RE_LINE_COMMENT" < $LOGS_DIR/adding_kernel_using_two.log +CHECK_EXIT_CODE=$? + +VALUE_1=`grep "$TEST_PROBE;" $LOGS_DIR/adding_kernel_using_two.log | awk -F';' '{print $1}'` +VALUE_2=`grep "${TEST_PROBE}_${NO_OF_PROBES};" $LOGS_DIR/adding_kernel_using_two.log | awk -F';' '{print $1}'` + +test $VALUE_1 -eq $VALUE_2 +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "using doubled probe" + + +### removing multiple probes + +# using wildcards should remove all matching probes +$CMD_PERF probe --del \* 2> $LOGS_DIR/adding_kernel_removing_wildcard.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "Removed event: probe:$TEST_PROBE" "Removed event: probe:${TEST_PROBE}_1" < $LOGS_DIR/adding_kernel_removing_wildcard.err +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "removing multiple probes" +(( TEST_RESULT += $? )) + + +### wildcard adding support + +$CMD_PERF probe -nf --max-probes=512 -a 'vfs_* $params' 2> $LOGS_DIR/adding_kernel_adding_wildcard.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "probe:vfs_mknod" "probe:vfs_create" "probe:vfs_rmdir" "probe:vfs_link" "probe:vfs_write" < $LOGS_DIR/adding_kernel_adding_wildcard.err +CHECK_EXIT_CODE=$? + +if [ $NO_DEBUGINFO ] ; then + print_testcase_skipped $NO_DEBUGINFO $NO_DEBUGINFO "Skipped due to missing debuginfo" +else + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "wildcard adding support" +fi + +(( TEST_RESULT += $? )) + + +### non-existing variable + +# perf probe should survive a non-existing variable probing attempt +{ $CMD_PERF probe 'vfs_read somenonexistingrandomstuffwhichisalsoprettylongorevenlongertoexceed64' ; } 2> $LOGS_DIR/adding_kernel_nonexisting.err +PERF_EXIT_CODE=$? + +# the exitcode should not be 0 or segfault +test $PERF_EXIT_CODE -ne 139 -a $PERF_EXIT_CODE -ne 0 +PERF_EXIT_CODE=$? + +# check that the error message is reasonable +../common/check_all_patterns_found.pl "Failed to find" "somenonexistingrandomstuffwhichisalsoprettylongorevenlongertoexceed64" < $LOGS_DIR/adding_kernel_nonexisting.err +CHECK_EXIT_CODE=$? +../common/check_all_patterns_found.pl "in this function|at this address" "Error" "Failed to add events" < $LOGS_DIR/adding_kernel_nonexisting.err +(( CHECK_EXIT_CODE += $? )) +../common/check_all_lines_matched.pl "Failed to find" "Error" "Probe point .+ not found" "optimized out" "Use.+\-\-range option to show.+location range" < $LOGS_DIR/adding_kernel_nonexisting.err +(( CHECK_EXIT_CODE += $? )) +../common/check_no_patterns_found.pl "$RE_SEGFAULT" < $LOGS_DIR/adding_kernel_nonexisting.err +(( CHECK_EXIT_CODE += $? )) + +if [ $NO_DEBUGINFO ]; then + print_testcase_skipped $NO_DEBUGINFO $NO_DEBUGINFO "Skipped due to missing debuginfo" +else + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "non-existing variable" +fi + +(( TEST_RESULT += $? )) + + +### function with return value + +# adding probe with return value +$CMD_PERF probe --add "$TEST_PROBE%return \$retval" 2> $LOGS_DIR/adding_kernel_func_retval_add.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "Added new events?:" "probe:$TEST_PROBE" "on $TEST_PROBE%return with \\\$retval" < $LOGS_DIR/adding_kernel_func_retval_add.err +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "function with retval :: add" +(( TEST_RESULT += $? )) + +# recording some data +$CMD_PERF record -e probe:$TEST_PROBE\* -o $CURRENT_TEST_DIR/perf.data -- cat /proc/cpuinfo > /dev/null 2> $LOGS_DIR/adding_kernel_func_retval_record.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "$RE_LINE_RECORD1" "$RE_LINE_RECORD2" < $LOGS_DIR/adding_kernel_func_retval_record.err +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "function with retval :: record" +(( TEST_RESULT += $? )) + +# perf script should report the function calls with the correct arg values +$CMD_PERF script -i $CURRENT_TEST_DIR/perf.data > $LOGS_DIR/adding_kernel_func_retval_script.log +PERF_EXIT_CODE=$? + +REGEX_SCRIPT_LINE="\s*cat\s+$RE_NUMBER\s+\[$RE_NUMBER\]\s+$RE_NUMBER:\s+probe:$TEST_PROBE\w*:\s+\($RE_NUMBER_HEX\s+<\-\s+$RE_NUMBER_HEX\)\s+arg1=$RE_NUMBER_HEX" +../common/check_all_lines_matched.pl "$REGEX_SCRIPT_LINE" < $LOGS_DIR/adding_kernel_func_retval_script.log +CHECK_EXIT_CODE=$? +../common/check_all_patterns_found.pl "$REGEX_SCRIPT_LINE" < $LOGS_DIR/adding_kernel_func_retval_script.log +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "function argument probing :: script" +(( TEST_RESULT += $? )) + + +clear_all_probes + +# print overall results +print_overall_results "$TEST_RESULT" +exit $? diff --git a/tools/perf/tests/shell/base_probe/test_basic.sh b/tools/perf/tests/shell/base_probe/test_basic.sh new file mode 100755 index 000000000000..9d8b5afbeddd --- /dev/null +++ b/tools/perf/tests/shell/base_probe/test_basic.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# perf_probe :: Basic perf probe functionality (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +# +# test_basic of perf_probe test +# Author: Michael Petlan <mpetlan@redhat.com> +# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> +# +# Description: +# +# This test tests basic functionality of perf probe command. +# + +# include working environment +. ../common/init.sh + +TEST_RESULT=0 + +if ! check_kprobes_available; then + print_overall_skipped + exit 2 +fi + + +### help message + +if [ "$PARAM_GENERAL_HELP_TEXT_CHECK" = "y" ]; then + # test that a help message is shown and looks reasonable + $CMD_PERF probe --help > $LOGS_DIR/basic_helpmsg.log 2> $LOGS_DIR/basic_helpmsg.err + PERF_EXIT_CODE=$? + + ../common/check_all_patterns_found.pl "PERF-PROBE" "NAME" "SYNOPSIS" "DESCRIPTION" "OPTIONS" "PROBE\s+SYNTAX" "PROBE\s+ARGUMENT" "LINE\s+SYNTAX" < $LOGS_DIR/basic_helpmsg.log + CHECK_EXIT_CODE=$? + ../common/check_all_patterns_found.pl "LAZY\s+MATCHING" "FILTER\s+PATTERN" "EXAMPLES" "SEE\s+ALSO" < $LOGS_DIR/basic_helpmsg.log + (( CHECK_EXIT_CODE += $? )) + ../common/check_all_patterns_found.pl "vmlinux" "module=" "source=" "verbose" "quiet" "add=" "del=" "list.*EVENT" "line=" "vars=" "externs" < $LOGS_DIR/basic_helpmsg.log + (( CHECK_EXIT_CODE += $? )) + ../common/check_all_patterns_found.pl "no-inlines" "funcs.*FILTER" "filter=FILTER" "force" "dry-run" "max-probes" "exec=" "demangle-kernel" < $LOGS_DIR/basic_helpmsg.log + (( CHECK_EXIT_CODE += $? )) + ../common/check_no_patterns_found.pl "No manual entry for" < $LOGS_DIR/basic_helpmsg.err + (( CHECK_EXIT_CODE += $? )) + + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "help message" + (( TEST_RESULT += $? )) +else + print_testcase_skipped "help message" +fi + + +### usage message + +# without any args perf-probe should print usage +$CMD_PERF probe 2> $LOGS_DIR/basic_usage.log > /dev/null + +../common/check_all_patterns_found.pl "[Uu]sage" "perf probe" "verbose" "quiet" "add" "del" "force" "line" "vars" "externs" "range" < $LOGS_DIR/basic_usage.log +CHECK_EXIT_CODE=$? + +print_results 0 $CHECK_EXIT_CODE "usage message" +(( TEST_RESULT += $? )) + + +### quiet switch + +# '--quiet' should mute all output +$CMD_PERF probe --quiet --add vfs_read > $LOGS_DIR/basic_quiet01.log 2> $LOGS_DIR/basic_quiet01.err +PERF_EXIT_CODE=$? +$CMD_PERF probe --quiet --del vfs_read > $LOGS_DIR/basic_quiet03.log 2> $LOGS_DIR/basic_quiet02.err +(( PERF_EXIT_CODE += $? )) + +test "`cat $LOGS_DIR/basic_quiet*log $LOGS_DIR/basic_quiet*err | wc -l`" -eq 0 +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "quiet switch" +(( TEST_RESULT += $? )) + + +# print overall results +print_overall_results "$TEST_RESULT" +exit $? diff --git a/tools/perf/tests/shell/base_probe/test_invalid_options.sh b/tools/perf/tests/shell/base_probe/test_invalid_options.sh new file mode 100755 index 000000000000..92f7254eb32a --- /dev/null +++ b/tools/perf/tests/shell/base_probe/test_invalid_options.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# perf_probe :: Reject invalid options (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +# +# test_invalid_options of perf_probe test +# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> +# Author: Michael Petlan <mpetlan@redhat.com> +# +# Description: +# +# This test checks whether the invalid and incompatible options are reported +# + +# include working environment +. ../common/init.sh + +TEST_RESULT=0 + +if ! check_kprobes_available; then + print_overall_skipped + exit 2 +fi + +# Check for presence of DWARF +$CMD_PERF check feature -q dwarf +[ $? -ne 0 ] && HINT_FAIL="Some of the tests need DWARF to run" + +### missing argument + +# some options require an argument +for opt in '-a' '-d' '-L' '-V'; do + ! $CMD_PERF probe $opt 2> $LOGS_DIR/invalid_options_missing_argument$opt.err + PERF_EXIT_CODE=$? + + ../common/check_all_patterns_found.pl "Error: switch .* requires a value" < $LOGS_DIR/invalid_options_missing_argument$opt.err + CHECK_EXIT_CODE=$? + + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "missing argument for $opt" + (( TEST_RESULT += $? )) +done + + +### unnecessary argument + +# some options may omit the argument +for opt in '-F' '-l'; do + $CMD_PERF probe -F > /dev/null 2> $LOGS_DIR/invalid_options_unnecessary_argument$opt.err + PERF_EXIT_CODE=$? + + test ! -s $LOGS_DIR/invalid_options_unnecessary_argument$opt.err + CHECK_EXIT_CODE=$? + + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "unnecessary argument for $opt" + (( TEST_RESULT += $? )) +done + + +### mutually exclusive options + +# some options are mutually exclusive +test -e $LOGS_DIR/invalid_options_mutually_exclusive.log && rm -f $LOGS_DIR/invalid_options_mutually_exclusive.log +for opt in '-a xxx -d xxx' '-a xxx -L foo' '-a xxx -V foo' '-a xxx -l' '-a xxx -F' \ + '-d xxx -L foo' '-d xxx -V foo' '-d xxx -l' '-d xxx -F' \ + '-L foo -V bar' '-L foo -l' '-L foo -F' '-V foo -l' '-V foo -F' '-l -F'; do + ! $CMD_PERF probe $opt > /dev/null 2> $LOGS_DIR/aux.log + PERF_EXIT_CODE=$? + + ../common/check_all_patterns_found.pl "Error: switch .+ cannot be used with switch .+" < $LOGS_DIR/aux.log + CHECK_EXIT_CODE=$? + + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "mutually exclusive options :: $opt" + (( TEST_RESULT += $? )) + + # gather the logs + cat $LOGS_DIR/aux.log | grep "Error" >> $LOGS_DIR/invalid_options_mutually_exclusive.log +done + + +# print overall results +print_overall_results "$TEST_RESULT" $HINT_FAIL +exit $? diff --git a/tools/perf/tests/shell/base_probe/test_line_semantics.sh b/tools/perf/tests/shell/base_probe/test_line_semantics.sh new file mode 100755 index 000000000000..20435b6bf6bc --- /dev/null +++ b/tools/perf/tests/shell/base_probe/test_line_semantics.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# perf_probe :: Check patterns for line semantics (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +# +# test_line_semantics of perf_probe test +# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com> +# Author: Michael Petlan <mpetlan@redhat.com> +# +# Description: +# +# This test checks whether the semantic errors of line option's +# arguments are properly reported. +# + +# include working environment +. ../common/init.sh + +TEST_RESULT=0 + +if ! check_kprobes_available; then + print_overall_skipped + exit 2 +fi + +# Check for presence of DWARF +$CMD_PERF check feature -q dwarf +[ $? -ne 0 ] && HINT_FAIL="Some of the tests need DWARF to run" + +### acceptable --line descriptions + +# testing acceptance of valid patterns for the '--line' option +VALID_PATTERNS="func func:10 func:0-10 func:2+10 func@source.c func@source.c:1 source.c:1 source.c:1+1 source.c:1-10" +for desc in $VALID_PATTERNS; do + ! ( $CMD_PERF probe --line $desc 2>&1 | grep -q "Semantic error" ) + CHECK_EXIT_CODE=$? + + print_results 0 $CHECK_EXIT_CODE "acceptable descriptions :: $desc" + (( TEST_RESULT += $? )) +done + + +### unacceptable --line descriptions + +# testing handling of invalid patterns for the '--line' option +INVALID_PATTERNS="func:foo func:1-foo func:1+foo func;lazy\*pattern" +for desc in $INVALID_PATTERNS; do + $CMD_PERF probe --line $desc 2>&1 | grep -q "Semantic error" + CHECK_EXIT_CODE=$? + + print_results 0 $CHECK_EXIT_CODE "unacceptable descriptions :: $desc" + (( TEST_RESULT += $? )) +done + + +# print overall results +print_overall_results "$TEST_RESULT" $HINT_FAIL +exit $? diff --git a/tools/perf/tests/shell/base_report/setup.sh b/tools/perf/tests/shell/base_report/setup.sh new file mode 100755 index 000000000000..8634e7e0dda6 --- /dev/null +++ b/tools/perf/tests/shell/base_report/setup.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# perftool-testsuite :: perf_report +# SPDX-License-Identifier: GPL-2.0 + +# +# setup.sh of perf report test +# Author: Michael Petlan <mpetlan@redhat.com> +# +# Description: +# +# We need some sample data for perf-report testing +# +# + +# include working environment +. ../common/init.sh + +TEST_RESULT=0 + +test -d "$HEADER_TAR_DIR" || mkdir -p "$HEADER_TAR_DIR" + +SW_EVENT="cpu-clock" + +$CMD_PERF record -asdg -e $SW_EVENT -o $CURRENT_TEST_DIR/perf.data -- $CMD_LONGER_SLEEP 2> $LOGS_DIR/setup.log +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "$RE_LINE_RECORD1" "$RE_LINE_RECORD2" < $LOGS_DIR/setup.log +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "prepare the perf.data file" +(( TEST_RESULT += $? )) + +# Some minimal parallel workload. +$CMD_PERF record --latency -o $CURRENT_TEST_DIR/perf.data.1 bash -c "for i in {1..100} ; do cat /proc/cpuinfo 1> /dev/null & done; sleep 1" 2> $LOGS_DIR/setup-latency.log +PERF_EXIT_CODE=$? + +echo ================== +cat $LOGS_DIR/setup-latency.log +echo ================== + +../common/check_all_patterns_found.pl "$RE_LINE_RECORD1" "$RE_LINE_RECORD2" < $LOGS_DIR/setup-latency.log +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "prepare the perf.data.1 file" +(( TEST_RESULT += $? )) + +print_overall_results $TEST_RESULT +exit $? diff --git a/tools/perf/tests/shell/base_report/stderr-whitelist.txt b/tools/perf/tests/shell/base_report/stderr-whitelist.txt new file mode 100644 index 000000000000..e3341401b47c --- /dev/null +++ b/tools/perf/tests/shell/base_report/stderr-whitelist.txt @@ -0,0 +1,5 @@ +no symbols found in .*, maybe install a debug package +was updated .*is prelink enabled.+ Restart the long running apps that use it +Warning: +\d+ out of order events recorded. +detected invalid bpf_prog_info diff --git a/tools/perf/tests/shell/base_report/test_basic.sh b/tools/perf/tests/shell/base_report/test_basic.sh new file mode 100755 index 000000000000..adfd8713b8f8 --- /dev/null +++ b/tools/perf/tests/shell/base_report/test_basic.sh @@ -0,0 +1,242 @@ +#!/bin/bash +# perf_report :: Basic perf report options (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +# +# test_basic of perf_report test +# Author: Michael Petlan <mpetlan@redhat.com> +# +# Description: +# +# This test tests basic functionality of perf report command. +# +# + +# include working environment +. ../common/init.sh + +TEST_RESULT=0 + + +### help message + +if [ "$PARAM_GENERAL_HELP_TEXT_CHECK" = "y" ]; then + # test that a help message is shown and looks reasonable + $CMD_PERF report --help > $LOGS_DIR/basic_helpmsg.log 2> $LOGS_DIR/basic_helpmsg.err + PERF_EXIT_CODE=$? + + ../common/check_all_patterns_found.pl "PERF-REPORT" "NAME" "SYNOPSIS" "DESCRIPTION" "OPTIONS" "OVERHEAD\s+CALCULATION" "SEE ALSO" < $LOGS_DIR/basic_helpmsg.log + CHECK_EXIT_CODE=$? + ../common/check_all_patterns_found.pl "input" "verbose" "show-nr-samples" "show-cpu-utilization" "threads" "comms" "pid" "tid" "dsos" "symbols" "symbol-filter" < $LOGS_DIR/basic_helpmsg.log + (( CHECK_EXIT_CODE += $? )) + ../common/check_all_patterns_found.pl "hide-unresolved" "sort" "fields" "parent" "exclude-other" "column-widths" "field-separator" "dump-raw-trace" "children" < $LOGS_DIR/basic_helpmsg.log + (( CHECK_EXIT_CODE += $? )) + ../common/check_all_patterns_found.pl "call-graph" "max-stack" "inverted" "ignore-callees" "pretty" "stdio" "tui" "gtk" "vmlinux" "kallsyms" "modules" < $LOGS_DIR/basic_helpmsg.log + (( CHECK_EXIT_CODE += $? )) + ../common/check_all_patterns_found.pl "force" "symfs" "cpu" "disassembler-style" "source" "asm-raw" "show-total-period" "show-info" "branch-stack" "group" < $LOGS_DIR/basic_helpmsg.log + (( CHECK_EXIT_CODE += $? )) + ../common/check_all_patterns_found.pl "branch-history" "objdump" "demangle" "percent-limit" "percentage" "header" "itrace" "full-source-path" "show-ref-call-graph" < $LOGS_DIR/basic_helpmsg.log + (( CHECK_EXIT_CODE += $? )) + ../common/check_no_patterns_found.pl "No manual entry for" < $LOGS_DIR/basic_helpmsg.err + (( CHECK_EXIT_CODE += $? )) + + print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "help message" + (( TEST_RESULT += $? )) +else + print_testcase_skipped "help message" +fi + + +### basic execution + +# test that perf report is even working +$CMD_PERF report -i $CURRENT_TEST_DIR/perf.data --stdio > $LOGS_DIR/basic_basic.log 2> $LOGS_DIR/basic_basic.err +PERF_EXIT_CODE=$? + +REGEX_LOST_SAMPLES_INFO="#\s*Total Lost Samples:\s+$RE_NUMBER" +REGEX_SAMPLES_INFO="#\s*Samples:\s+(?:$RE_NUMBER)\w?\s+of\s+event\s+'$RE_EVENT_ANY'" +REGEX_LINES_HEADER="#\s*Children\s+Self\s+Command\s+Shared Object\s+Symbol" +REGEX_LINES="\s*$RE_NUMBER%\s+$RE_NUMBER%\s+\S+\s+\[kernel\.(?:vmlinux)|(?:kallsyms)\]\s+\[[k\.]\]\s+\w+" +../common/check_all_patterns_found.pl "$REGEX_LOST_SAMPLES_INFO" "$REGEX_SAMPLES_INFO" "$REGEX_LINES_HEADER" "$REGEX_LINES" < $LOGS_DIR/basic_basic.log +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/basic_basic.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "basic execution" +(( TEST_RESULT += $? )) + + +### number of samples + +# '--show-nr-samples' should show number of samples for each symbol +$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data --show-nr-samples > $LOGS_DIR/basic_nrsamples.log 2> $LOGS_DIR/basic_nrsamples.err +PERF_EXIT_CODE=$? + +REGEX_LINES_HEADER="#\s*Children\s+Self\s+Samples\s+Command\s+Shared Object\s+Symbol" +REGEX_LINES="\s*$RE_NUMBER%\s+$RE_NUMBER%\s+$RE_NUMBER\s+\S+\s+\[kernel\.(?:vmlinux)|(?:kallsyms)\]\s+\[[k\.]\]\s+\w+" +../common/check_all_patterns_found.pl "$REGEX_LINES_HEADER" "$REGEX_LINES" < $LOGS_DIR/basic_nrsamples.log +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/basic_nrsamples.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "number of samples" +(( TEST_RESULT += $? )) + + +### header + +# '--header' and '--header-only' should show perf report header +$CMD_PERF report -i $CURRENT_TEST_DIR/perf.data --stdio --header-only > $LOGS_DIR/basic_header.log +PERF_EXIT_CODE=$? + +REGEX_LINE_TIMESTAMP="#\s+captured on\s*:\s*$RE_DATE_TIME" +REGEX_LINE_HOSTNAME="#\s+hostname\s*:\s*$MY_HOSTNAME" +REGEX_LINE_KERNEL="#\s+os release\s*:\s*${MY_KERNEL_VERSION//+/\\+}" +REGEX_LINE_PERF="#\s+perf version\s*:\s*" +REGEX_LINE_ARCH="#\s+arch\s*:\s*$MY_ARCH" +REGEX_LINE_CPUS_ONLINE="#\s+nrcpus online\s*:\s*$MY_CPUS_ONLINE" +REGEX_LINE_CPUS_AVAIL="#\s+nrcpus avail\s*:\s*$MY_CPUS_AVAILABLE" +# disable precise check for "nrcpus avail" in BASIC runmode +test $PERFTOOL_TESTSUITE_RUNMODE -lt $RUNMODE_STANDARD && REGEX_LINE_CPUS_AVAIL="#\s+nrcpus avail\s*:\s*$RE_NUMBER" +../common/check_all_patterns_found.pl "$REGEX_LINE_TIMESTAMP" "$REGEX_LINE_HOSTNAME" "$REGEX_LINE_KERNEL" "$REGEX_LINE_PERF" "$REGEX_LINE_ARCH" "$REGEX_LINE_CPUS_ONLINE" "$REGEX_LINE_CPUS_AVAIL" < $LOGS_DIR/basic_header.log +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "header" +(( TEST_RESULT += $? )) + +# '--header' and '--header-only' should use creation time +OLD_TIMESTAMP=`$CMD_PERF report --stdio --header-only -i $CURRENT_TEST_DIR/perf.data | grep "captured on"` +PERF_EXIT_CODE=$? + +( tar -C $CURRENT_TEST_DIR -c perf.data | xz > $CURRENT_TEST_DIR/perf.data.tar.xz ; xzcat $CURRENT_TEST_DIR/perf.data.tar.xz | tar x -C $HEADER_TAR_DIR ) +(( PERF_EXIT_CODE += $? )) + +NEW_TIMESTAMP=`$CMD_PERF report --stdio --header-only -i $HEADER_TAR_DIR/perf.data | grep "captured on"` +(( PERF_EXIT_CODE += $? )) + +test "$OLD_TIMESTAMP" = "$NEW_TIMESTAMP" +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "header timestamp" +(( TEST_RESULT += $? )) + + +### show CPU utilization + +# '--showcpuutilization' should show percentage for both system and userspace mode +$CMD_PERF report -i $CURRENT_TEST_DIR/perf.data --stdio --showcpuutilization > $LOGS_DIR/basic_cpuut.log 2> $LOGS_DIR/basic_cpuut.err +PERF_EXIT_CODE=$? + +REGEX_LINES_HEADER="#\s*Children\s+Self\s+sys\s+usr\s+Command\s+Shared Object\s+Symbol" +REGEX_LINES="\s*$RE_NUMBER%\s+$RE_NUMBER%\s+$RE_NUMBER%\s+$RE_NUMBER%\s+\S+\s+\[kernel\.(?:vmlinux)|(?:kallsyms)\]\s+\[[k\.]\]\s+\w+" +../common/check_all_patterns_found.pl "$REGEX_LINES_HEADER" "$REGEX_LINES" < $LOGS_DIR/basic_cpuut.log +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/basic_cpuut.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "show CPU utilization" +(( TEST_RESULT += $? )) + + +### pid + +# '--pid=' should limit the output for a process with the given pid only +$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data --pid=1 > $LOGS_DIR/basic_pid.log 2> $LOGS_DIR/basic_pid.err +PERF_EXIT_CODE=$? + +grep -P -v '^#' $LOGS_DIR/basic_pid.log | grep -P '\s+[\d\.]+%' | ../common/check_all_lines_matched.pl "systemd|init" +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/basic_pid.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "pid" +(( TEST_RESULT += $? )) + + +### non-existing symbol + +# '--symbols' should show only the given symbols +$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data --symbols=dummynonexistingsymbol > $LOGS_DIR/basic_symbols.log 2> $LOGS_DIR/basic_symbols.err +PERF_EXIT_CODE=$? + +../common/check_all_lines_matched.pl "$RE_LINE_EMPTY" "$RE_LINE_COMMENT" < $LOGS_DIR/basic_symbols.log +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/basic_symbols.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "non-existing symbol" +(( TEST_RESULT += $? )) + + +### symbol filter + +# '--symbol-filter' should filter symbols based on substrings +$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data --symbol-filter=map > $LOGS_DIR/basic_symbolfilter.log 2> $LOGS_DIR/basic_symbolfilter.err +PERF_EXIT_CODE=$? + +grep -P -v '^#' $LOGS_DIR/basic_symbolfilter.log | grep -P '\s+[\d\.]+%' | ../common/check_all_lines_matched.pl "\[[k\.]\]\s+.*map" +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/basic_symbolfilter.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "symbol filter" +(( TEST_RESULT += $? )) + + +### latency and parallelism + +# Record with --latency should record with context switches. +$CMD_PERF report -i $CURRENT_TEST_DIR/perf.data.1 --stdio --header-only > $LOGS_DIR/latency_header.log +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl ", context_switch = 1, " < $LOGS_DIR/latency_header.log +CHECK_EXIT_CODE=$? + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "latency header" +(( TEST_RESULT += $? )) + + +# The default report for latency profile should show Overhead and Latency fields (in that order). +$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data.1 > $LOGS_DIR/latency_default.log 2> $LOGS_DIR/latency_default.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "# Overhead Latency Command" < $LOGS_DIR/latency_default.log +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/latency_default.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "default report for latency profile" +(( TEST_RESULT += $? )) + + +# The latency report for latency profile should show Latency and Overhead fields (in that order). +$CMD_PERF report --latency --stdio -i $CURRENT_TEST_DIR/perf.data.1 > $LOGS_DIR/latency_latency.log 2> $LOGS_DIR/latency_latency.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "# Latency Overhead Command" < $LOGS_DIR/latency_latency.log +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/latency_latency.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "latency report for latency profile" +(( TEST_RESULT += $? )) + + +# Ensure parallelism histogram with parallelism filter does not fail/crash. +$CMD_PERF report --hierarchy --sort latency,parallelism,comm,symbol --parallelism=1,2 --stdio -i $CURRENT_TEST_DIR/perf.data.1 > $LOGS_DIR/parallelism_hierarchy.log 2> $LOGS_DIR/parallelism_hierarchy.err +PERF_EXIT_CODE=$? + +../common/check_all_patterns_found.pl "# Latency Parallelism / Command / Symbol" < $LOGS_DIR/parallelism_hierarchy.log +CHECK_EXIT_CODE=$? +../common/check_errors_whitelisted.pl "stderr-whitelist.txt" < $LOGS_DIR/parallelism_hierarchy.err +(( CHECK_EXIT_CODE += $? )) + +print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "parallelism histogram" +(( TEST_RESULT += $? )) + + +# TODO: $CMD_PERF report -n --showcpuutilization -TUxDg 2> 01.log + +# print overall results +print_overall_results "$TEST_RESULT" +exit $? diff --git a/tools/perf/tests/shell/buildid.sh b/tools/perf/tests/shell/buildid.sh new file mode 100755 index 000000000000..3383ca3399d4 --- /dev/null +++ b/tools/perf/tests/shell/buildid.sh @@ -0,0 +1,175 @@ +#!/bin/sh +# build id cache operations +# SPDX-License-Identifier: GPL-2.0 + +# skip if there's no readelf +if ! [ -x "$(command -v readelf)" ]; then + echo "failed: no readelf, install binutils" + exit 2 +fi + +# skip if there's no compiler +if ! [ -x "$(command -v cc)" ]; then + echo "failed: no compiler, install gcc" + exit 2 +fi + +# check what we need to test windows binaries +add_pe=1 +run_pe=1 +if ! perf version --build-options | grep -q 'libbfd: .* on '; then + echo "WARNING: perf not built with libbfd. PE binaries will not be tested." + add_pe=0 + run_pe=0 +fi +if ! which wine > /dev/null; then + echo "WARNING: wine not found. PE binaries will not be run." + run_pe=0 +fi + +# set up wine +if [ ${run_pe} -eq 1 ]; then + wineprefix=$(mktemp -d /tmp/perf.wineprefix.XXX) + export WINEPREFIX=${wineprefix} + # clear display variables to prevent wine from popping up dialogs + unset DISPLAY + unset WAYLAND_DISPLAY +fi + +ex_md5=$(mktemp /tmp/perf.ex.MD5.XXX) +ex_sha1=$(mktemp /tmp/perf.ex.SHA1.XXX) +ex_pe=$(dirname $0)/../pe-file.exe + +echo 'int main(void) { return 0; }' | cc -Wl,--build-id=sha1 -o ${ex_sha1} -x c - +echo 'int main(void) { return 0; }' | cc -Wl,--build-id=md5 -o ${ex_md5} -x c - + +echo "test binaries: ${ex_sha1} ${ex_md5} ${ex_pe}" + +check() +{ + case $1 in + *.exe) + # We don't have a tool that can pull a nicely formatted build-id out of + # a PE file, but we can extract the whole section with objcopy and + # format it ourselves. The .buildid section is a Debug Directory + # containing a CodeView entry: + # https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#debug-directory-image-only + # https://github.com/dotnet/runtime/blob/da94c022576a5c3bbc0e896f006565905eb137f9/docs/design/specs/PE-COFF.md + # The build-id starts at byte 33 and must be rearranged into a GUID. + id=`objcopy -O binary --only-section=.buildid $1 /dev/stdout | \ + cut -c 33-48 | hexdump -ve '/1 "%02x"' | \ + sed 's@^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(.*\)0a$@\4\3\2\1\6\5\8\7\9@'` + ;; + *) + id=`readelf -n ${1} 2>/dev/null | grep 'Build ID' | awk '{print $3}'` + ;; + esac + echo "build id: ${id}" + + id_file=${id#??} + id_dir=${id%$id_file} + link=$build_id_dir/.build-id/$id_dir/$id_file + echo "link: ${link}" + + if [ ! -h $link ]; then + echo "failed: link ${link} does not exist" + exit 1 + fi + + file=${build_id_dir}/.build-id/$id_dir/`readlink ${link}`/elf + echo "file: ${file}" + + # Check for file permission of original file + # in case of pe-file.exe file + echo $1 | grep ".exe" + if [ $? -eq 0 ]; then + if [ -x $1 ] && [ ! -x $file ]; then + echo "failed: file ${file} executable does not exist" + exit 1 + fi + + if [ ! -x $file ] && [ ! -e $file ]; then + echo "failed: file ${file} does not exist" + exit 1 + fi + elif [ ! -x $file ]; then + echo "failed: file ${file} does not exist" + exit 1 + fi + + diff ${file} ${1} + if [ $? -ne 0 ]; then + echo "failed: ${file} do not match" + exit 1 + fi + + ${perf} buildid-cache -l | grep ${id} + if [ $? -ne 0 ]; then + echo "failed: ${id} is not reported by \"perf buildid-cache -l\"" + exit 1 + fi + + echo "OK for ${1}" +} + +test_add() +{ + build_id_dir=$(mktemp -d /tmp/perf.debug.XXX) + perf="perf --buildid-dir ${build_id_dir}" + + ${perf} buildid-cache -v -a ${1} + if [ $? -ne 0 ]; then + echo "failed: add ${1} to build id cache" + exit 1 + fi + + check ${1} + + rm -rf ${build_id_dir} +} + +test_record() +{ + data=$(mktemp /tmp/perf.data.XXX) + build_id_dir=$(mktemp -d /tmp/perf.debug.XXX) + log_out=$(mktemp /tmp/perf.log.out.XXX) + log_err=$(mktemp /tmp/perf.log.err.XXX) + perf="perf --buildid-dir ${build_id_dir}" + + echo "running: perf record $*" + ${perf} record --buildid-all -o ${data} "$@" 1>${log_out} 2>${log_err} + if [ $? -ne 0 ]; then + echo "failed: record $*" + echo "see log: ${log_err}" + exit 1 + fi + + args="$*" + check ${args##* } + + rm -f ${log_out} ${log_err} + rm -rf ${build_id_dir} + rm -rf ${data} +} + +# add binaries manual via perf buildid-cache -a +test_add ${ex_sha1} +test_add ${ex_md5} +if [ ${add_pe} -eq 1 ]; then + test_add ${ex_pe} +fi + +# add binaries via perf record post processing +test_record ${ex_sha1} +test_record ${ex_md5} +if [ ${run_pe} -eq 1 ]; then + test_record wine ${ex_pe} +fi + +# cleanup +rm ${ex_sha1} ${ex_md5} +if [ ${run_pe} -eq 1 ]; then + rm -r ${wineprefix} +fi + +exit 0 diff --git a/tools/perf/tests/shell/common/check_all_lines_matched.pl b/tools/perf/tests/shell/common/check_all_lines_matched.pl new file mode 100755 index 000000000000..fded48959a3f --- /dev/null +++ b/tools/perf/tests/shell/common/check_all_lines_matched.pl @@ -0,0 +1,39 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: GPL-2.0 + +@regexps = @ARGV; + +$max_printed_lines = 20; +$max_printed_lines = $ENV{TESTLOG_ERR_MSG_MAX_LINES} if (defined $ENV{TESTLOG_ERR_MSG_MAX_LINES}); + +$quiet = 1; +$quiet = 0 if (defined $ENV{TESTLOG_VERBOSITY} && $ENV{TESTLOG_VERBOSITY} ge 2); + +$passed = 1; +$lines_printed = 0; + +while (<STDIN>) +{ + s/\n//; + + $line_matched = 0; + for $r (@regexps) + { + if (/$r/) + { + $line_matched = 1; + last; + } + } + + unless ($line_matched) + { + if ($lines_printed++ < $max_printed_lines) + { + print "Line did not match any pattern: \"$_\"\n" unless $quiet; + } + $passed = 0; + } +} + +exit ($passed == 0); diff --git a/tools/perf/tests/shell/common/check_all_patterns_found.pl b/tools/perf/tests/shell/common/check_all_patterns_found.pl new file mode 100755 index 000000000000..11bdf1d3460a --- /dev/null +++ b/tools/perf/tests/shell/common/check_all_patterns_found.pl @@ -0,0 +1,34 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: GPL-2.0 + +@regexps = @ARGV; + +$quiet = 1; +$quiet = 0 if (defined $ENV{TESTLOG_VERBOSITY} && $ENV{TESTLOG_VERBOSITY} ge 2); + +%found = (); +$passed = 1; + +while (<STDIN>) +{ + s/\n//; + + for $r (@regexps) + { + if (/$r/) + { + $found{$r} = 1; # FIXME: maybe add counters -- how many times was the regexp matched + } + } +} + +for $r (@regexps) +{ + unless (exists $found{$r}) + { + print "Regexp not found: \"$r\"\n" unless $quiet; + $passed = 0; + } +} + +exit ($passed == 0); diff --git a/tools/perf/tests/shell/common/check_errors_whitelisted.pl b/tools/perf/tests/shell/common/check_errors_whitelisted.pl new file mode 100755 index 000000000000..c57d355dd76e --- /dev/null +++ b/tools/perf/tests/shell/common/check_errors_whitelisted.pl @@ -0,0 +1,51 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: GPL-2.0 + +$whitelist_file = shift; + +if (defined $whitelist_file) +{ + open (INFILE, $whitelist_file) or die "Checker error: Unable to open the whitelist file: $whitelist_file\n"; + @regexps = <INFILE>; + close INFILE or die "Checker error: Unable to close the whitelist file: $whitelist_file\n"; +} +else +{ + @regexps = (); +} + +$max_printed_lines = 20; +$max_printed_lines = $ENV{TESTLOG_ERR_MSG_MAX_LINES} if (defined $ENV{TESTLOG_ERR_MSG_MAX_LINES}); + +$quiet = 1; +$quiet = 0 if (defined $ENV{TESTLOG_VERBOSITY} && $ENV{TESTLOG_VERBOSITY} ge 2); + +$passed = 1; +$lines_printed = 0; + +while (<STDIN>) +{ + s/\n//; + + $line_matched = 0; + for $r (@regexps) + { + chomp $r; + if (/$r/) + { + $line_matched = 1; + last; + } + } + + unless ($line_matched) + { + if ($lines_printed++ < $max_printed_lines) + { + print "Line did not match any pattern: \"$_\"\n" unless $quiet; + } + $passed = 0; + } +} + +exit ($passed == 0); diff --git a/tools/perf/tests/shell/common/check_no_patterns_found.pl b/tools/perf/tests/shell/common/check_no_patterns_found.pl new file mode 100755 index 000000000000..770999e87a5f --- /dev/null +++ b/tools/perf/tests/shell/common/check_no_patterns_found.pl @@ -0,0 +1,34 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: GPL-2.0 + +@regexps = @ARGV; + +$quiet = 1; +$quiet = 0 if (defined $ENV{TESTLOG_VERBOSITY} && $ENV{TESTLOG_VERBOSITY} ge 2); + +%found = (); +$passed = 1; + +while (<STDIN>) +{ + s/\n//; + + for $r (@regexps) + { + if (/$r/) + { + $found{$r} = 1; + } + } +} + +for $r (@regexps) +{ + if (exists $found{$r}) + { + print "Regexp found: \"$r\"\n" unless $quiet; + $passed = 0; + } +} + +exit ($passed == 0); diff --git a/tools/perf/tests/shell/common/init.sh b/tools/perf/tests/shell/common/init.sh new file mode 100644 index 000000000000..26c7525651e0 --- /dev/null +++ b/tools/perf/tests/shell/common/init.sh @@ -0,0 +1,143 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# init.sh +# Author: Michael Petlan <mpetlan@redhat.com> +# +# Description: +# +# This file should be used for initialization of basic functions +# for checking, reporting results etc. +# +# + + +. ../common/settings.sh +. ../common/patterns.sh + +THIS_TEST_NAME=`basename $0 .sh` + +_echo() +{ + test "$TESTLOG_VERBOSITY" -ne 0 && echo -e "$@" +} + +print_results() +{ + PERF_RETVAL="$1"; shift + CHECK_RETVAL="$1"; shift + FAILURE_REASON="" + TASK_COMMENT="$*" + if [ $PERF_RETVAL -eq 0 ] && [ $CHECK_RETVAL -eq 0 ]; then + _echo "$MPASS-- [ PASS ] --$MEND $TEST_NAME :: $THIS_TEST_NAME :: $TASK_COMMENT" + return 0 + else + if [ $PERF_RETVAL -ne 0 ]; then + FAILURE_REASON="command exitcode" + fi + if [ $CHECK_RETVAL -ne 0 ]; then + test -n "$FAILURE_REASON" && FAILURE_REASON="$FAILURE_REASON + " + FAILURE_REASON="$FAILURE_REASON""output regexp parsing" + fi + _echo "$MFAIL-- [ FAIL ] --$MEND $TEST_NAME :: $THIS_TEST_NAME :: $TASK_COMMENT ($FAILURE_REASON)" + return 1 + fi +} + +print_overall_results() +{ + RETVAL="$1"; shift + TASK_COMMENT="$*" + test -n "$TASK_COMMENT" && TASK_COMMENT=":: $TASK_COMMENT" + + if [ $RETVAL -eq 0 ]; then + _echo "$MALLPASS## [ PASS ] ##$MEND $TEST_NAME :: $THIS_TEST_NAME SUMMARY" + else + _echo "$MALLFAIL## [ FAIL ] ##$MEND $TEST_NAME :: $THIS_TEST_NAME SUMMARY :: $RETVAL failures found $TASK_COMMENT" + fi + return $RETVAL +} + +print_testcase_skipped() +{ + TASK_COMMENT="$*" + _echo "$MSKIP-- [ SKIP ] --$MEND $TEST_NAME :: $THIS_TEST_NAME :: $TASK_COMMENT :: testcase skipped" + return 0 +} + +print_overall_skipped() +{ + _echo "$MSKIP## [ SKIP ] ##$MEND $TEST_NAME :: $THIS_TEST_NAME :: testcase skipped" + return 0 +} + +print_warning() +{ + WARN_COMMENT="$*" + _echo "$MWARN-- [ WARN ] --$MEND $TEST_NAME :: $THIS_TEST_NAME :: $WARN_COMMENT" + return 0 +} + +# this function should skip a testcase if the testsuite is not run in +# a runmode that fits the testcase --> if the suite runs in BASIC mode +# all STANDARD and EXPERIMENTAL testcases will be skipped; if the suite +# runs in STANDARD mode, all EXPERIMENTAL testcases will be skipped and +# if the suite runs in EXPERIMENTAL mode, nothing is skipped +consider_skipping() +{ + TESTCASE_RUNMODE="$1" + # the runmode of a testcase needs to be at least the current suite's runmode + if [ $PERFTOOL_TESTSUITE_RUNMODE -lt $TESTCASE_RUNMODE ]; then + print_overall_skipped + exit 2 + fi +} + +detect_baremetal() +{ + # return values: + # 0 = bare metal + # 1 = virtualization detected + # 2 = unknown state + VIRT=`systemd-detect-virt 2>/dev/null` + test $? -eq 127 && return 2 + test "$VIRT" = "none" +} + +detect_intel() +{ + # return values: + # 0 = is Intel + # 1 = is not Intel or unknown + grep "vendor_id" < /proc/cpuinfo | grep -q "GenuineIntel" +} + +detect_amd() +{ + # return values: + # 0 = is AMD + # 1 = is not AMD or unknown + grep "vendor_id" < /proc/cpuinfo | grep -q "AMD" +} + +# base probe utility +check_kprobes_available() +{ + test -e /sys/kernel/debug/tracing/kprobe_events +} + +check_uprobes_available() +{ + test -e /sys/kernel/debug/tracing/uprobe_events +} + +clear_all_probes() +{ + echo 0 > /sys/kernel/debug/tracing/events/enable + check_kprobes_available && echo > /sys/kernel/debug/tracing/kprobe_events + check_uprobes_available && echo > /sys/kernel/debug/tracing/uprobe_events +} + +check_sdt_support() +{ + $CMD_PERF list sdt | grep sdt > /dev/null 2> /dev/null +} diff --git a/tools/perf/tests/shell/common/patterns.sh b/tools/perf/tests/shell/common/patterns.sh new file mode 100644 index 000000000000..21dab25c7b7f --- /dev/null +++ b/tools/perf/tests/shell/common/patterns.sh @@ -0,0 +1,268 @@ +# SPDX-License-Identifier: GPL-2.0 + +export RE_NUMBER="[0-9\.]+" +# Number +# Examples: +# 123.456 + + +export RE_NUMBER_HEX="[0-9A-Fa-f]+" +# Hexadecimal number +# Examples: +# 1234 +# a58d +# aBcD +# deadbeef + + +export RE_DATE_YYYYMMDD="[0-9]{4}-(?:(?:01|03|05|07|08|10|12)-(?:[0-2][0-9]|3[0-1])|02-[0-2][0-9]|(?:(?:04|06|09|11)-(?:[0-2][0-9]|30)))" +# Date in YYYY-MM-DD form +# Examples: +# 1990-02-29 +# 0015-07-31 +# 2456-12-31 +#! 2012-13-01 +#! 1963-09-31 + + +export RE_TIME="(?:[0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]" +# Time +# Examples: +# 15:12:27 +# 23:59:59 +#! 24:00:00 +#! 11:25:60 +#! 17:60:15 + + +export RE_DATE_TIME="\w+\s+\w+\s+$RE_NUMBER\s+$RE_TIME\s+$RE_NUMBER" +# Time and date +# Examples: +# Wed Feb 12 10:46:26 2020 +# Mon Mar 2 13:27:06 2020 +#! St úno 12 10:57:21 CET 2020 +#! Po úno 14 15:17:32 2010 + + +export RE_ADDRESS="0x$RE_NUMBER_HEX" +# Memory address +# Examples: +# 0x123abc +# 0xffffffff9abe8ae8 +# 0x0 + + +export RE_ADDRESS_NOT_NULL="0x[0-9A-Fa-f]*[1-9A-Fa-f]+[0-9A-Fa-f]*" +# Memory address (not NULL) +# Examples: +# 0xffffffff9abe8ae8 +#! 0x0 +#! 0x0000000000000000 + +export RE_PROCESS_PID="[^\/]+\/\d+" +# A process with PID +# Example: +# sleep/4102 +# test_overhead./866185 +# in:imjournal/1096 +# random#$& test/866607 + +export RE_EVENT_ANY="[\w\-\:\/_=,]+" +# Name of any event (universal) +# Examples: +# cpu-cycles +# cpu/event=12,umask=34/ +# r41e1 +# nfs:nfs_getattr_enter + + +export RE_EVENT="[\w\-:_]+" +# Name of an usual event +# Examples: +# cpu-cycles + + +export RE_EVENT_RAW="r$RE_NUMBER_HEX" +# Specification of a raw event +# Examples: +# r41e1 +# r1a + + +export RE_EVENT_CPU="cpu/(\w+=$RE_NUMBER_HEX,?)+/p*" +# Specification of a CPU event +# Examples: +# cpu/event=12,umask=34/pp + + +export RE_EVENT_UNCORE="uncore/[\w_]+/" +# Specification of an uncore event +# Examples: +# uncore/qhl_request_local_reads/ + + +export RE_EVENT_SUBSYSTEM="[\w\-]+:[\w\-]+" +# Name of an event from subsystem +# Examples: +# ext4:ext4_ordered_write_end +# sched:sched_switch + + +export RE_FILE_NAME="[\w\+\.-]+" +# A filename +# Examples: +# libstdc++.so.6 +#! some/path + + +export RE_PATH_ABSOLUTE="(?:\/$RE_FILE_NAME)+" +# A full filepath +# Examples: +# /usr/lib64/somelib.so.5.4.0 +# /lib/modules/4.3.0-rc5/kernel/fs/xfs/xfs.ko +# /usr/bin/mv +#! some/relative/path +#! ./some/relative/path + + +export RE_PATH="(?:$RE_FILE_NAME)?$RE_PATH_ABSOLUTE" +# A filepath +# Examples: +# /usr/lib64/somelib.so.5.4.0 +# /lib/modules/4.3.0-rc5/kernel/fs/xfs/xfs.ko +# ./.emacs +# src/fs/file.c + + +export RE_DSO="(?:$RE_PATH_ABSOLUTE(?: \(deleted\))?|\[kernel\.kallsyms\]|\[unknown\]|\[vdso\]|\[kernel\.vmlinux\][\.\w]*)" +# A DSO name in various result tables +# Examples: +# /usr/lib64/somelib.so.5.4.0 +# /usr/bin/somebinart (deleted) +# /lib/modules/4.3.0-rc5/kernel/fs/xfs/xfs.ko +# [kernel.kallsyms] +# [kernel.vmlinux] +# [vdso] +# [unknown] + + +export RE_LINE_COMMENT="^#.*" +# A comment line +# Examples: +# # Started on Thu Sep 10 11:43:00 2015 + + +export RE_LINE_EMPTY="^\s*$" +# An empty line with possible whitespaces +# Examples: +# + + +export RE_LINE_RECORD1="^\[\s+perf\s+record:\s+Woken up $RE_NUMBER times? to write data\s+\].*$" +# The first line of perf-record "OK" output +# Examples: +# [ perf record: Woken up 1 times to write data ] + + +export RE_LINE_RECORD2="^\[\s+perf\s+record:\s+Captured and wrote $RE_NUMBER\s*MB\s+(?:[\w\+\.-]*(?:$RE_PATH)?\/)?perf\.data(?:\.\d+)?\s*\(~?$RE_NUMBER samples\)\s+\].*$" +# The second line of perf-record "OK" output +# Examples: +# [ perf record: Captured and wrote 0.405 MB perf.data (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB perf.data (~109 samples) ] +# [ perf record: Captured and wrote 0.405 MB /some/temp/dir/perf.data (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB ./perf.data (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB ./perf.data.3 (109 samples) ] + + +export RE_LINE_RECORD2_TOLERANT="^\[\s+perf\s+record:\s+Captured and wrote $RE_NUMBER\s*MB\s+(?:[\w\+\.-]*(?:$RE_PATH)?\/)?perf\.data(?:\.\d+)?\s*(?:\(~?$RE_NUMBER samples\))?\s+\].*$" +# The second line of perf-record "OK" output, even no samples is OK here +# Examples: +# [ perf record: Captured and wrote 0.405 MB perf.data (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB perf.data (~109 samples) ] +# [ perf record: Captured and wrote 0.405 MB /some/temp/dir/perf.data (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB ./perf.data (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB ./perf.data.3 (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB perf.data ] + + +export RE_LINE_RECORD2_TOLERANT_FILENAME="^\[\s+perf\s+record:\s+Captured and wrote $RE_NUMBER\s*MB\s+(?:[\w\+\.-]*(?:$RE_PATH)?\/)?perf\w*\.data(?:\.\d+)?\s*\(~?$RE_NUMBER samples\)\s+\].*$" +# The second line of perf-record "OK" output +# Examples: +# [ perf record: Captured and wrote 0.405 MB perf.data (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB perf_ls.data (~109 samples) ] +# [ perf record: Captured and wrote 0.405 MB perf_aNyCaSe.data (109 samples) ] +# [ perf record: Captured and wrote 0.405 MB ./perfdata.data.3 (109 samples) ] +#! [ perf record: Captured and wrote 0.405 MB /some/temp/dir/my_own.data (109 samples) ] +#! [ perf record: Captured and wrote 0.405 MB ./UPPERCASE.data (109 samples) ] +#! [ perf record: Captured and wrote 0.405 MB ./aNyKiNDoF.data.3 (109 samples) ] +#! [ perf record: Captured and wrote 0.405 MB perf.data ] + + +export RE_LINE_TRACE_FULL="^\s*$RE_NUMBER\s*\(\s*$RE_NUMBER\s*ms\s*\):\s*$RE_PROCESS_PID\s+.*\)\s+=\s+(:?\-?$RE_NUMBER|0x$RE_NUMBER_HEX).*$" +# A line of perf-trace output +# Examples: +# 0.115 ( 0.005 ms): sleep/4102 open(filename: 0xd09e2ab2, flags: CLOEXEC ) = 3 +# 0.157 ( 0.005 ms): sleep/4102 mmap(len: 3932736, prot: EXEC|READ, flags: PRIVATE|DENYWRITE, fd: 3 ) = 0x7f89d0605000 +#! 0.115 ( 0.005 ms): sleep/4102 open(filename: 0xd09e2ab2, flags: CLOEXEC ) = + +export RE_LINE_TRACE_ONE_PROC="^\s*$RE_NUMBER\s*\(\s*$RE_NUMBER\s*ms\s*\):\s*\w+\(.*\)\s+=\s+(?:\-?$RE_NUMBER|0x$RE_NUMBER_HEX).*$" +# A line of perf-trace output +# Examples: +# 0.115 ( 0.005 ms): open(filename: 0xd09e2ab2, flags: CLOEXEC ) = 3 +# 0.157 ( 0.005 ms): mmap(len: 3932736, prot: EXEC|READ, flags: PRIVATE|DENYWRITE, fd: 3 ) = 0x7f89d0605000 +#! 0.115 ( 0.005 ms): open(filename: 0xd09e2ab2, flags: CLOEXEC ) = + +export RE_LINE_TRACE_CONTINUED="^\s*(:?$RE_NUMBER|\?)\s*\(\s*($RE_NUMBER\s*ms\s*)?\):\s*($RE_PROCESS_PID\s*)?\.\.\.\s*\[continued\]:\s+\w+\(\).*\s+=\s+(?:\-?$RE_NUMBER|0x$RE_NUMBER_HEX).*$" +# A line of perf-trace output +# Examples: +# 0.000 ( 0.000 ms): ... [continued]: nanosleep()) = 0 +# 0.000 ( 0.000 ms): ... [continued]: nanosleep()) = 0x00000000 +# ? ( ): packagekitd/94838 ... [continued]: poll()) = 0 (Timeout) +#! 0.000 ( 0.000 ms): ... [continued]: nanosleep()) = + +export RE_LINE_TRACE_UNFINISHED="^\s*$RE_NUMBER\s*\(\s*\):\s*$RE_PROCESS_PID\s+.*\)\s+\.\.\.\s*$" +# A line of perf-trace output +# Examples: +# 901.040 ( ): in:imjournal/1096 ppoll(ufds: 0x7f701a5adb70, nfds: 1, tsp: 0x7f701a5adaf0, sigsetsize: 8) ... +# 613.727 ( ): gmain/1099 poll(ufds: 0x56248f6b64b0, nfds: 2, timeout_msecs: 3996) ... + +export RE_LINE_TRACE_SUMMARY_HEADER="\s*syscall\s+calls\s+(?:errors\s+)?total\s+min\s+avg\s+max\s+stddev" +# A header of a perf-trace summary table +# Example: +# syscall calls total min avg max stddev +# syscall calls errors total min avg max stddev + + +export RE_LINE_TRACE_SUMMARY_CONTENT="^\s*\w+\s+(?:$RE_NUMBER\s+){5,6}$RE_NUMBER%" +# A line of a perf-trace summary table +# Example: +# open 3 0.017 0.005 0.006 0.007 10.90% +# openat 2 0 0.017 0.008 0.009 0.010 12.29% + + +export RE_LINE_REPORT_CONTENT="^\s+$RE_NUMBER%\s+\w+\s+\S+\s+\S+\s+\S+" # FIXME +# A line from typicap perf report --stdio output +# Example: +# 100.00% sleep [kernel.vmlinux] [k] syscall_return_slowpath + + +export RE_TASK="\s+[\w~\/ \.\+:#-]+(?:\[-1(?:\/\d+)?\]|\[\d+(?:\/\d+)?\])" +# A name of a task used for perf sched timehist -s +# Example: +# sleep[62755] +# runtest.sh[62762] +# gmain[705/682] +# xfsaild/dm-0[495] +# kworker/u8:1-ev[62714] +# :-1[-1/62756] +# :-1[-1] +# :-1[62756] + + +export RE_SEGFAULT=".*(?:Segmentation\sfault|SIGSEGV|\score\s|dumped|segfault).*" +# Possible variations of the segfault message +# Example: +# /bin/bash: line 1: 32 Segmentation fault timeout 15s +# Segmentation fault (core dumped) +# Program terminated with signal SIGSEGV +#! WARNING: 12323431 isn't a 'cpu_core', please use a CPU list in the 'cpu_core' range (0-15) diff --git a/tools/perf/tests/shell/common/settings.sh b/tools/perf/tests/shell/common/settings.sh new file mode 100644 index 000000000000..cba1b338f96f --- /dev/null +++ b/tools/perf/tests/shell/common/settings.sh @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# settings.sh +# Author: Michael Petlan <mpetlan@redhat.com> +# +# Description: +# +# This file contains global settings for the whole testsuite. +# Its purpose is to make it easier when it is necessary i.e. to +# change the usual sample command which is used in all of the tests +# in many files. +# +# This file is intended to be sourced in the tests. +# + +#### which perf to use in the testing +export CMD_PERF=${CMD_PERF:-`which perf`} + +#### basic programs examinated by perf +export CMD_BASIC_SLEEP="sleep 0.1" +export CMD_QUICK_SLEEP="sleep 0.01" +export CMD_LONGER_SLEEP="sleep 2" +export CMD_DOUBLE_LONGER_SLEEP="sleep 4" +export CMD_VERY_LONG_SLEEP="sleep 30" +export CMD_SIMPLE="true" + +#### testsuite run mode +# define constants: +export RUNMODE_BASIC=0 +export RUNMODE_STANDARD=1 +export RUNMODE_EXPERIMENTAL=2 +# default runmode is STANDARD +export PERFTOOL_TESTSUITE_RUNMODE=${PERFTOOL_TESTSUITE_RUNMODE:-$RUNMODE_STANDARD} + +#### common settings +export TESTLOG_VERBOSITY=${TESTLOG_VERBOSITY:-2} +export TESTLOG_FORCE_COLOR=${TESTLOG_FORCE_COLOR:-n} +export TESTLOG_ERR_MSG_MAX_LINES=${TESTLOG_ERR_MSG_MAX_LINES:-20} +export TESTLOG_CLEAN=${TESTLOG_CLEAN:-y} + +#### other environment-related settings +export TEST_IGNORE_MISSING_PMU=${TEST_IGNORE_MISSING_PMU:-n} + +#### clear locale +export LC_ALL=C + +#### colors +if [ -t 1 ] || [ "$TESTLOG_FORCE_COLOR" = "yes" ]; then + export MPASS="\e[32m" + export MALLPASS="\e[1;32m" + export MFAIL="\e[31m" + export MALLFAIL="\e[1;31m" + export MWARN="\e[1;35m" + export MSKIP="\e[33m" + export MHIGH="\e[1;33m" + export MEND="\e[m" +else + export MPASS="" + export MALLPASS="" + export MFAIL="" + export MALLFAIL="" + export MWARN="" + export MSKIP="" + export MHIGH="" + export MEND="" +fi + +### general info +DIR_PATH=`dirname "$(readlink -e "$0")"` + +TEST_NAME=`basename $DIR_PATH | sed 's/base/perf/'`; export TEST_NAME +MY_ARCH=`arch`; export MY_ARCH + +# storing logs and temporary files variables +if [ -n "$PERFSUITE_RUN_DIR" ]; then + # when $PERFSUITE_RUN_DIR is set to something, all the logs and temp files will be placed there + # --> the $PERFSUITE_RUN_DIR/perf_something/examples and $PERFSUITE_RUN_DIR/perf_something/logs + # dirs will be used for that + PERFSUITE_RUN_DIR=`readlink -f $PERFSUITE_RUN_DIR`; export PERFSUITE_RUN_DIR + export CURRENT_TEST_DIR="$PERFSUITE_RUN_DIR/$TEST_NAME" + export MAKE_TARGET_DIR="$CURRENT_TEST_DIR/examples" + export LOGS_DIR="$CURRENT_TEST_DIR/logs" + export HEADER_TAR_DIR="$CURRENT_TEST_DIR/header_tar" + test -d "$CURRENT_TEST_DIR" || mkdir -p "$CURRENT_TEST_DIR" + test -d "$LOGS_DIR" || mkdir -p "$LOGS_DIR" +else + # when $PERFSUITE_RUN_DIR is not set, logs will be placed here + export CURRENT_TEST_DIR="." + export LOGS_DIR="." + export HEADER_TAR_DIR="./header_tar" +fi + + +#### test parametrization +if [ ! -d ./common ]; then + # set parameters based on runmode + if [ -f ../common/parametrization.$PERFTOOL_TESTSUITE_RUNMODE.sh ]; then + # shellcheck source=/dev/null + . ../common/parametrization.$PERFTOOL_TESTSUITE_RUNMODE.sh + fi + # if some parameters haven't been set until now, set them to default + if [ -f ../common/parametrization.sh ]; then + . ../common/parametrization.sh + fi +fi diff --git a/tools/perf/tests/shell/coresight/Makefile b/tools/perf/tests/shell/coresight/Makefile new file mode 100644 index 000000000000..fa08fd9a5991 --- /dev/null +++ b/tools/perf/tests/shell/coresight/Makefile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 +include ../../../../../tools/scripts/Makefile.include +include ../../../../../tools/scripts/Makefile.arch +include ../../../../../tools/scripts/utilities.mak + +SUBDIRS = \ + asm_pure_loop \ + memcpy_thread \ + thread_loop \ + unroll_loop_thread + +all: $(SUBDIRS) +$(SUBDIRS): + @$(MAKE) -C $@ >/dev/null + +INSTALLDIRS = $(SUBDIRS:%=install-%) + +install-tests: $(INSTALLDIRS) +$(INSTALLDIRS): + @$(MAKE) -C $(@:install-%=%) install-tests >/dev/null + +CLEANDIRS = $(SUBDIRS:%=clean-%) + +clean: $(CLEANDIRS) +$(CLEANDIRS): + $(call QUIET_CLEAN, test-$(@:clean-%=%)) $(MAKE) -C $(@:clean-%=%) clean >/dev/null + +.PHONY: all clean $(SUBDIRS) $(CLEANDIRS) $(INSTALLDIRS) diff --git a/tools/perf/tests/shell/coresight/Makefile.miniconfig b/tools/perf/tests/shell/coresight/Makefile.miniconfig new file mode 100644 index 000000000000..5f72a9cb43f3 --- /dev/null +++ b/tools/perf/tests/shell/coresight/Makefile.miniconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +ifndef DESTDIR +prefix ?= $(HOME) +endif + +DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) +INSTALL = install +INSTDIR_SUB = tests/shell/coresight + +include ../../../../../scripts/Makefile.include +include ../../../../../scripts/Makefile.arch +include ../../../../../scripts/utilities.mak diff --git a/tools/perf/tests/shell/coresight/asm_pure_loop.sh b/tools/perf/tests/shell/coresight/asm_pure_loop.sh new file mode 100755 index 000000000000..c63bc8c73e26 --- /dev/null +++ b/tools/perf/tests/shell/coresight/asm_pure_loop.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e +# CoreSight / ASM Pure Loop (exclusive) + +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +TEST="asm_pure_loop" + +# shellcheck source=../lib/coresight.sh +. "$(dirname $0)"/../lib/coresight.sh + +ARGS="" +DATV="out" +# shellcheck disable=SC2153 +DATA="$DATD/perf-$TEST-$DATV.data" + +perf record $PERFRECOPT -o "$DATA" "$BIN" $ARGS + +perf_dump_aux_verify "$DATA" 10 10 10 + +err=$? +exit $err diff --git a/tools/perf/tests/shell/coresight/asm_pure_loop/.gitignore b/tools/perf/tests/shell/coresight/asm_pure_loop/.gitignore new file mode 100644 index 000000000000..468673ac32e8 --- /dev/null +++ b/tools/perf/tests/shell/coresight/asm_pure_loop/.gitignore @@ -0,0 +1 @@ +asm_pure_loop diff --git a/tools/perf/tests/shell/coresight/asm_pure_loop/Makefile b/tools/perf/tests/shell/coresight/asm_pure_loop/Makefile new file mode 100644 index 000000000000..206849e92bc9 --- /dev/null +++ b/tools/perf/tests/shell/coresight/asm_pure_loop/Makefile @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +include ../Makefile.miniconfig + +# Binary to produce +BIN=asm_pure_loop +# Any linking/libraries needed for the binary - empty if none needed +LIB= + +all: $(BIN) + +$(BIN): $(BIN).S +ifdef CORESIGHT +ifeq ($(ARCH),arm64) +# Build line - this is raw asm with no libc to have an always exact binary + $(Q)$(CC) $(BIN).S -nostdlib -static -o $(BIN) $(LIB) +endif +endif + +install-tests: all +ifdef CORESIGHT +ifeq ($(ARCH),arm64) +# Install the test tool in the right place + $(call QUIET_INSTALL, tests) \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)'; \ + $(INSTALL) $(BIN) '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)/$(BIN)' +endif +endif + +clean: + $(Q)$(RM) -f $(BIN) + +.PHONY: all clean install-tests diff --git a/tools/perf/tests/shell/coresight/asm_pure_loop/asm_pure_loop.S b/tools/perf/tests/shell/coresight/asm_pure_loop/asm_pure_loop.S new file mode 100644 index 000000000000..577760046772 --- /dev/null +++ b/tools/perf/tests/shell/coresight/asm_pure_loop/asm_pure_loop.S @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Tamas Zsoldos <tamas.zsoldos@arm.com>, 2021 */ + +.globl _start +_start: + mov x0, 0x0000ffff + mov x1, xzr +loop: + nop + nop + cbnz x1, noskip + nop + nop + adrp x2, skip + add x2, x2, :lo12:skip + br x2 + nop + nop +noskip: + nop + nop +skip: + sub x0, x0, 1 + cbnz x0, loop + + mov x0, #0 + mov x8, #93 // __NR_exit syscall + svc #0 + +.section .note.GNU-stack, "", @progbits diff --git a/tools/perf/tests/shell/coresight/memcpy_thread/.gitignore b/tools/perf/tests/shell/coresight/memcpy_thread/.gitignore new file mode 100644 index 000000000000..f8217e56091e --- /dev/null +++ b/tools/perf/tests/shell/coresight/memcpy_thread/.gitignore @@ -0,0 +1 @@ +memcpy_thread diff --git a/tools/perf/tests/shell/coresight/memcpy_thread/Makefile b/tools/perf/tests/shell/coresight/memcpy_thread/Makefile new file mode 100644 index 000000000000..2db637eb2c26 --- /dev/null +++ b/tools/perf/tests/shell/coresight/memcpy_thread/Makefile @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 +include ../Makefile.miniconfig + +# Binary to produce +BIN=memcpy_thread +# Any linking/libraries needed for the binary - empty if none needed +LIB=-pthread + +all: $(BIN) + +$(BIN): $(BIN).c +ifdef CORESIGHT +ifeq ($(ARCH),arm64) +# Build line + $(Q)$(CC) $(BIN).c -o $(BIN) $(LIB) +endif +endif + +install-tests: all +ifdef CORESIGHT +ifeq ($(ARCH),arm64) +# Install the test tool in the right place + $(call QUIET_INSTALL, tests) \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)'; \ + $(INSTALL) $(BIN) '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)/$(BIN)' +endif +endif + +clean: + $(Q)$(RM) -f $(BIN) + +.PHONY: all clean install-tests diff --git a/tools/perf/tests/shell/coresight/memcpy_thread/memcpy_thread.c b/tools/perf/tests/shell/coresight/memcpy_thread/memcpy_thread.c new file mode 100644 index 000000000000..5f886cd09e6b --- /dev/null +++ b/tools/perf/tests/shell/coresight/memcpy_thread/memcpy_thread.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 +// Carsten Haitzler <carsten.haitzler@arm.com>, 2021 +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <pthread.h> + +struct args { + unsigned long loops; + unsigned long size; + pthread_t th; + void *ret; +}; + +static void *thrfn(void *arg) +{ + struct args *a = arg; + unsigned long i, len = a->loops; + unsigned char *src, *dst; + + src = malloc(a->size * 1024); + dst = malloc(a->size * 1024); + if ((!src) || (!dst)) { + printf("ERR: Can't allocate memory\n"); + exit(1); + } + for (i = 0; i < len; i++) + memcpy(dst, src, a->size * 1024); +} + +static pthread_t new_thr(void *(*fn) (void *arg), void *arg) +{ + pthread_t t; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_create(&t, &attr, fn, arg); + return t; +} + +int main(int argc, char **argv) +{ + unsigned long i, len, size, thr; + struct args args[256]; + long long v; + + if (argc < 4) { + printf("ERR: %s [copysize Kb] [numthreads] [numloops (hundreds)]\n", argv[0]); + exit(1); + } + + v = atoll(argv[1]); + if ((v < 1) || (v > (1024 * 1024))) { + printf("ERR: max memory 1GB (1048576 KB)\n"); + exit(1); + } + size = v; + thr = atol(argv[2]); + if ((thr < 1) || (thr > 256)) { + printf("ERR: threads 1-256\n"); + exit(1); + } + v = atoll(argv[3]); + if ((v < 1) || (v > 40000000000ll)) { + printf("ERR: loops 1-40000000000 (hundreds)\n"); + exit(1); + } + len = v * 100; + for (i = 0; i < thr; i++) { + args[i].loops = len; + args[i].size = size; + args[i].th = new_thr(thrfn, &(args[i])); + } + for (i = 0; i < thr; i++) + pthread_join(args[i].th, &(args[i].ret)); + return 0; +} diff --git a/tools/perf/tests/shell/coresight/memcpy_thread_16k_10.sh b/tools/perf/tests/shell/coresight/memcpy_thread_16k_10.sh new file mode 100755 index 000000000000..8e29630957c8 --- /dev/null +++ b/tools/perf/tests/shell/coresight/memcpy_thread_16k_10.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e +# CoreSight / Memcpy 16k 10 Threads (exclusive) + +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +TEST="memcpy_thread" + +# shellcheck source=../lib/coresight.sh +. "$(dirname $0)"/../lib/coresight.sh + +ARGS="16 10 1" +DATV="16k_10" +# shellcheck disable=SC2153 +DATA="$DATD/perf-$TEST-$DATV.data" + +perf record $PERFRECOPT -o "$DATA" "$BIN" $ARGS + +perf_dump_aux_verify "$DATA" 10 10 10 + +err=$? +exit $err diff --git a/tools/perf/tests/shell/coresight/thread_loop/.gitignore b/tools/perf/tests/shell/coresight/thread_loop/.gitignore new file mode 100644 index 000000000000..6d4c33eaa9e8 --- /dev/null +++ b/tools/perf/tests/shell/coresight/thread_loop/.gitignore @@ -0,0 +1 @@ +thread_loop diff --git a/tools/perf/tests/shell/coresight/thread_loop/Makefile b/tools/perf/tests/shell/coresight/thread_loop/Makefile new file mode 100644 index 000000000000..ea846c038e7a --- /dev/null +++ b/tools/perf/tests/shell/coresight/thread_loop/Makefile @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 +include ../Makefile.miniconfig + +# Binary to produce +BIN=thread_loop +# Any linking/libraries needed for the binary - empty if none needed +LIB=-pthread + +all: $(BIN) + +$(BIN): $(BIN).c +ifdef CORESIGHT +ifeq ($(ARCH),arm64) +# Build line + $(Q)$(CC) $(BIN).c -o $(BIN) $(LIB) +endif +endif + +install-tests: all +ifdef CORESIGHT +ifeq ($(ARCH),arm64) +# Install the test tool in the right place + $(call QUIET_INSTALL, tests) \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)'; \ + $(INSTALL) $(BIN) '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)/$(BIN)' +endif +endif + +clean: + $(Q)$(RM) -f $(BIN) + +.PHONY: all clean install-tests diff --git a/tools/perf/tests/shell/coresight/thread_loop/thread_loop.c b/tools/perf/tests/shell/coresight/thread_loop/thread_loop.c new file mode 100644 index 000000000000..e05a559253ca --- /dev/null +++ b/tools/perf/tests/shell/coresight/thread_loop/thread_loop.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +// Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +// define this for gettid() +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <pthread.h> +#include <sys/syscall.h> +#ifndef SYS_gettid +// gettid is 178 on arm64 +# define SYS_gettid 178 +#endif +#define gettid() syscall(SYS_gettid) + +struct args { + unsigned int loops; + pthread_t th; + void *ret; +}; + +static void *thrfn(void *arg) +{ + struct args *a = arg; + int i = 0, len = a->loops; + + if (getenv("SHOW_TID")) { + unsigned long long tid = gettid(); + + printf("%llu\n", tid); + } + asm volatile( + "loop:\n" + "add %[i], %[i], #1\n" + "cmp %[i], %[len]\n" + "blt loop\n" + : /* out */ + : /* in */ [i] "r" (i), [len] "r" (len) + : /* clobber */ + ); + return (void *)(long)i; +} + +static pthread_t new_thr(void *(*fn) (void *arg), void *arg) +{ + pthread_t t; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_create(&t, &attr, fn, arg); + return t; +} + +int main(int argc, char **argv) +{ + unsigned int i, len, thr; + struct args args[256]; + + if (argc < 3) { + printf("ERR: %s [numthreads] [numloops (millions)]\n", argv[0]); + exit(1); + } + + thr = atoi(argv[1]); + if ((thr < 1) || (thr > 256)) { + printf("ERR: threads 1-256\n"); + exit(1); + } + len = atoi(argv[2]); + if ((len < 1) || (len > 4000)) { + printf("ERR: max loops 4000 (millions)\n"); + exit(1); + } + len *= 1000000; + for (i = 0; i < thr; i++) { + args[i].loops = len; + args[i].th = new_thr(thrfn, &(args[i])); + } + for (i = 0; i < thr; i++) + pthread_join(args[i].th, &(args[i].ret)); + return 0; +} diff --git a/tools/perf/tests/shell/coresight/thread_loop_check_tid_10.sh b/tools/perf/tests/shell/coresight/thread_loop_check_tid_10.sh new file mode 100755 index 000000000000..0c4c82a1c8e1 --- /dev/null +++ b/tools/perf/tests/shell/coresight/thread_loop_check_tid_10.sh @@ -0,0 +1,23 @@ +#!/bin/sh -e +# CoreSight / Thread Loop 10 Threads - Check TID (exclusive) + +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +TEST="thread_loop" + +# shellcheck source=../lib/coresight.sh +. "$(dirname $0)"/../lib/coresight.sh + +ARGS="10 1" +DATV="check-tid-10th" +# shellcheck disable=SC2153 +DATA="$DATD/perf-$TEST-$DATV.data" +STDO="$DATD/perf-$TEST-$DATV.stdout" + +SHOW_TID=1 perf record -s $PERFRECOPT -o "$DATA" "$BIN" $ARGS > $STDO + +perf_dump_aux_tid_verify "$DATA" "$STDO" + +err=$? +exit $err diff --git a/tools/perf/tests/shell/coresight/thread_loop_check_tid_2.sh b/tools/perf/tests/shell/coresight/thread_loop_check_tid_2.sh new file mode 100755 index 000000000000..d3aea9fc6ced --- /dev/null +++ b/tools/perf/tests/shell/coresight/thread_loop_check_tid_2.sh @@ -0,0 +1,23 @@ +#!/bin/sh -e +# CoreSight / Thread Loop 2 Threads - Check TID (exclusive) + +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +TEST="thread_loop" + +# shellcheck source=../lib/coresight.sh +. "$(dirname $0)"/../lib/coresight.sh + +ARGS="2 20" +DATV="check-tid-2th" +# shellcheck disable=SC2153 +DATA="$DATD/perf-$TEST-$DATV.data" +STDO="$DATD/perf-$TEST-$DATV.stdout" + +SHOW_TID=1 perf record -s $PERFRECOPT -o "$DATA" "$BIN" $ARGS > $STDO + +perf_dump_aux_tid_verify "$DATA" "$STDO" + +err=$? +exit $err diff --git a/tools/perf/tests/shell/coresight/unroll_loop_thread/.gitignore b/tools/perf/tests/shell/coresight/unroll_loop_thread/.gitignore new file mode 100644 index 000000000000..2cb4e996dbf3 --- /dev/null +++ b/tools/perf/tests/shell/coresight/unroll_loop_thread/.gitignore @@ -0,0 +1 @@ +unroll_loop_thread diff --git a/tools/perf/tests/shell/coresight/unroll_loop_thread/Makefile b/tools/perf/tests/shell/coresight/unroll_loop_thread/Makefile new file mode 100644 index 000000000000..6264c4e3abd1 --- /dev/null +++ b/tools/perf/tests/shell/coresight/unroll_loop_thread/Makefile @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 +include ../Makefile.miniconfig + +# Binary to produce +BIN=unroll_loop_thread +# Any linking/libraries needed for the binary - empty if none needed +LIB=-pthread + +all: $(BIN) + +$(BIN): $(BIN).c +ifdef CORESIGHT +ifeq ($(ARCH),arm64) +# Build line + $(Q)$(CC) $(BIN).c -o $(BIN) $(LIB) +endif +endif + +install-tests: all +ifdef CORESIGHT +ifeq ($(ARCH),arm64) +# Install the test tool in the right place + $(call QUIET_INSTALL, tests) \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)'; \ + $(INSTALL) $(BIN) '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)/$(BIN)' +endif +endif + +clean: + $(Q)$(RM) -f $(BIN) + +.PHONY: all clean install-tests diff --git a/tools/perf/tests/shell/coresight/unroll_loop_thread/unroll_loop_thread.c b/tools/perf/tests/shell/coresight/unroll_loop_thread/unroll_loop_thread.c new file mode 100644 index 000000000000..0fc7bf1a25af --- /dev/null +++ b/tools/perf/tests/shell/coresight/unroll_loop_thread/unroll_loop_thread.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +// Carsten Haitzler <carsten.haitzler@arm.com>, 2021 +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <pthread.h> + +struct args { + pthread_t th; + unsigned int in; + void *ret; +}; + +static void *thrfn(void *arg) +{ + struct args *a = arg; + unsigned int i, in = a->in; + + for (i = 0; i < 10000; i++) { + asm volatile ( +// force an unroll of thia add instruction so we can test long runs of code +#define SNIP1 "add %[in], %[in], #1\n" +// 10 +#define SNIP2 SNIP1 SNIP1 SNIP1 SNIP1 SNIP1 SNIP1 SNIP1 SNIP1 SNIP1 SNIP1 +// 100 +#define SNIP3 SNIP2 SNIP2 SNIP2 SNIP2 SNIP2 SNIP2 SNIP2 SNIP2 SNIP2 SNIP2 +// 1000 +#define SNIP4 SNIP3 SNIP3 SNIP3 SNIP3 SNIP3 SNIP3 SNIP3 SNIP3 SNIP3 SNIP3 +// 10000 +#define SNIP5 SNIP4 SNIP4 SNIP4 SNIP4 SNIP4 SNIP4 SNIP4 SNIP4 SNIP4 SNIP4 +// 100000 + SNIP5 SNIP5 SNIP5 SNIP5 SNIP5 SNIP5 SNIP5 SNIP5 SNIP5 SNIP5 + : /* out */ + : /* in */ [in] "r" (in) + : /* clobber */ + ); + } +} + +static pthread_t new_thr(void *(*fn) (void *arg), void *arg) +{ + pthread_t t; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_create(&t, &attr, fn, arg); + return t; +} + +int main(int argc, char **argv) +{ + unsigned int i, thr; + struct args args[256]; + + if (argc < 2) { + printf("ERR: %s [numthreads]\n", argv[0]); + exit(1); + } + + thr = atoi(argv[1]); + if ((thr > 256) || (thr < 1)) { + printf("ERR: threads 1-256\n"); + exit(1); + } + for (i = 0; i < thr; i++) { + args[i].in = rand(); + args[i].th = new_thr(thrfn, &(args[i])); + } + for (i = 0; i < thr; i++) + pthread_join(args[i].th, &(args[i].ret)); + return 0; +} diff --git a/tools/perf/tests/shell/coresight/unroll_loop_thread_10.sh b/tools/perf/tests/shell/coresight/unroll_loop_thread_10.sh new file mode 100755 index 000000000000..7429d3a2ae43 --- /dev/null +++ b/tools/perf/tests/shell/coresight/unroll_loop_thread_10.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e +# CoreSight / Unroll Loop Thread 10 (exclusive) + +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +TEST="unroll_loop_thread" + +# shellcheck source=../lib/coresight.sh +. "$(dirname $0)"/../lib/coresight.sh + +ARGS="10" +DATV="10" +# shellcheck disable=SC2153 +DATA="$DATD/perf-$TEST-$DATV.data" + +perf record $PERFRECOPT -o "$DATA" "$BIN" $ARGS + +perf_dump_aux_verify "$DATA" 10 10 10 + +err=$? +exit $err diff --git a/tools/perf/tests/shell/daemon.sh b/tools/perf/tests/shell/daemon.sh new file mode 100755 index 000000000000..e5fa8d6f9eb1 --- /dev/null +++ b/tools/perf/tests/shell/daemon.sh @@ -0,0 +1,538 @@ +#!/bin/bash +# daemon operations +# SPDX-License-Identifier: GPL-2.0 + +check_line_first() +{ + local line=$1 + local name=$2 + local base=$3 + local output=$4 + local lock=$5 + local up=$6 + + local line_name + line_name=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $2 }'` + local line_base + line_base=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $3 }'` + local line_output + line_output=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $4 }'` + local line_lock + line_lock=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $5 }'` + local line_up + line_up=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $6 }'` + + if [ "${name}" != "${line_name}" ]; then + echo "FAILED: wrong name" + error=1 + fi + + if [ "${base}" != "${line_base}" ]; then + echo "FAILED: wrong base" + error=1 + fi + + if [ "${output}" != "${line_output}" ]; then + echo "FAILED: wrong output" + error=1 + fi + + if [ "${lock}" != "${line_lock}" ]; then + echo "FAILED: wrong lock" + error=1 + fi + + if [ "${up}" != "${line_up}" ]; then + echo "FAILED: wrong up" + error=1 + fi +} + +check_line_other() +{ + local line=$1 + local name=$2 + local run=$3 + local base=$4 + local output=$5 + local control=$6 + local ack=$7 + local up=$8 + + local line_name + line_name=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $2 }'` + local line_run + line_run=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $3 }'` + local line_base + line_base=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $4 }'` + local line_output + line_output=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $5 }'` + local line_control + line_control=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $6 }'` + local line_ack + line_ack=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $7 }'` + local line_up + line_up=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $8 }'` + + if [ "${name}" != "${line_name}" ]; then + echo "FAILED: wrong name" + error=1 + fi + + if [ "${run}" != "${line_run}" ]; then + echo "FAILED: wrong run" + error=1 + fi + + if [ "${base}" != "${line_base}" ]; then + echo "FAILED: wrong base" + error=1 + fi + + if [ "${output}" != "${line_output}" ]; then + echo "FAILED: wrong output" + error=1 + fi + + if [ "${control}" != "${line_control}" ]; then + echo "FAILED: wrong control" + error=1 + fi + + if [ "${ack}" != "${line_ack}" ]; then + echo "FAILED: wrong ack" + error=1 + fi + + if [ "${up}" != "${line_up}" ]; then + echo "FAILED: wrong up" + error=1 + fi +} + +daemon_exit() +{ + local config=$1 + + local line + line=`perf daemon --config ${config} -x: | head -1` + local pid + pid=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $1 }'` + + # Reset trap handler. + trap - SIGINT SIGTERM + + # stop daemon + perf daemon stop --config ${config} + + # ... and wait for the pid to go away + tail --pid=${pid} -f /dev/null +} + +daemon_start() +{ + local config=$1 + local session=$2 + + perf daemon start --config ${config} + + # Clean up daemon if interrupted. + trap 'echo "FAILED: Signal caught"; daemon_exit "${config}"; exit 1' SIGINT SIGTERM + + # wait for the session to ping + local state="FAIL" + local retries=0 + while [ "${state}" != "OK" ]; do + state=`perf daemon ping --config ${config} --session ${session} | awk '{ print $1 }'` + sleep 0.05 + retries=$((${retries} +1)) + if [ ${retries} -ge 600 ]; then + echo "FAILED: Timeout waiting for daemon to ping" + daemon_exit ${config} + exit 1 + fi + done +} + +test_list() +{ + echo "test daemon list" + + local config + config=$(mktemp /tmp/perf.daemon.config.XXX) + local base + base=$(mktemp -d /tmp/perf.daemon.base.XXX) + + cat <<EOF > ${config} +[daemon] +base=BASE + +[session-size] +run = -e cpu-clock -m 1 sleep 10 + +[session-time] +run = -e task-clock -m 1 sleep 10 +EOF + + sed -i -e "s|BASE|${base}|" ${config} + + # start daemon + daemon_start ${config} size + + # check first line + # pid:daemon:base:base/output:base/lock + local line + line=`perf daemon --config ${config} -x: | head -1` + check_line_first ${line} daemon ${base} ${base}/output ${base}/lock "0" + + # check 1st session + # pid:size:-e cpu-clock:base/size:base/size/output:base/size/control:base/size/ack:0 + local line + line=`perf daemon --config ${config} -x: | head -2 | tail -1` + check_line_other "${line}" size "-e cpu-clock -m 1 sleep 10" ${base}/session-size \ + ${base}/session-size/output ${base}/session-size/control \ + ${base}/session-size/ack "0" + + # check 2nd session + # pid:time:-e task-clock:base/time:base/time/output:base/time/control:base/time/ack:0 + local line + line=`perf daemon --config ${config} -x: | head -3 | tail -1` + check_line_other "${line}" time "-e task-clock -m 1 sleep 10" ${base}/session-time \ + ${base}/session-time/output ${base}/session-time/control \ + ${base}/session-time/ack "0" + + # stop daemon + daemon_exit ${config} + + rm -rf ${base} + rm -f ${config} +} + +test_reconfig() +{ + echo "test daemon reconfig" + + local config + config=$(mktemp /tmp/perf.daemon.config.XXX) + local base + base=$(mktemp -d /tmp/perf.daemon.base.XXX) + + # prepare config + cat <<EOF > ${config} +[daemon] +base=BASE + +[session-size] +run = -e cpu-clock -m 1 sleep 10 + +[session-time] +run = -e task-clock -m 1 sleep 10 +EOF + + sed -i -e "s|BASE|${base}|" ${config} + + # start daemon + daemon_start ${config} size + + # check 2nd session + # pid:time:-e task-clock:base/time:base/time/output:base/time/control:base/time/ack:0 + local line + line=`perf daemon --config ${config} -x: | head -3 | tail -1` + check_line_other "${line}" time "-e task-clock -m 1 sleep 10" ${base}/session-time \ + ${base}/session-time/output ${base}/session-time/control ${base}/session-time/ack "0" + local pid + pid=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $1 }'` + + # prepare new config + local config_new=${config}.new + cat <<EOF > ${config_new} +[daemon] +base=BASE + +[session-size] +run = -e cpu-clock -m 1 sleep 10 + +[session-time] +run = -e cpu-clock -m 1 sleep 10 +EOF + + # TEST 1 - change config + + sed -i -e "s|BASE|${base}|" ${config_new} + cp ${config_new} ${config} + + # wait for old session to finish + tail --pid=${pid} -f /dev/null + + # wait for new one to start + local state="FAIL" + while [ "${state}" != "OK" ]; do + state=`perf daemon ping --config ${config} --session time | awk '{ print $1 }'` + done + + # check reconfigured 2nd session + # pid:time:-e task-clock:base/time:base/time/output:base/time/control:base/time/ack:0 + local line + line=`perf daemon --config ${config} -x: | head -3 | tail -1` + check_line_other "${line}" time "-e cpu-clock -m 1 sleep 10" ${base}/session-time \ + ${base}/session-time/output ${base}/session-time/control ${base}/session-time/ack "0" + + # TEST 2 - empty config + + local config_empty=${config}.empty + cat <<EOF > ${config_empty} +[daemon] +base=BASE +EOF + + # change config + sed -i -e "s|BASE|${base}|" ${config_empty} + cp ${config_empty} ${config} + + # wait for sessions to finish + local state="OK" + while [ "${state}" != "FAIL" ]; do + state=`perf daemon ping --config ${config} --session time | awk '{ print $1 }'` + done + + local state="OK" + while [ "${state}" != "FAIL" ]; do + state=`perf daemon ping --config ${config} --session size | awk '{ print $1 }'` + done + + local one + one=`perf daemon --config ${config} -x: | wc -l` + + if [ ${one} -ne "1" ]; then + echo "FAILED: wrong list output" + error=1 + fi + + # TEST 3 - config again + + cp ${config_new} ${config} + + # wait for size to start + local state="FAIL" + while [ "${state}" != "OK" ]; do + state=`perf daemon ping --config ${config} --session size | awk '{ print $1 }'` + done + + # wait for time to start + local state="FAIL" + while [ "${state}" != "OK" ]; do + state=`perf daemon ping --config ${config} --session time | awk '{ print $1 }'` + done + + # stop daemon + daemon_exit ${config} + + rm -rf ${base} + rm -f ${config} + rm -f ${config_new} + rm -f ${config_empty} +} + +test_stop() +{ + echo "test daemon stop" + + local config + config=$(mktemp /tmp/perf.daemon.config.XXX) + local base + base=$(mktemp -d /tmp/perf.daemon.base.XXX) + + # prepare config + cat <<EOF > ${config} +[daemon] +base=BASE + +[session-size] +run = -e cpu-clock -m 1 sleep 10 + +[session-time] +run = -e task-clock -m 1 sleep 10 +EOF + + sed -i -e "s|BASE|${base}|" ${config} + + # start daemon + daemon_start ${config} size + + local pid_size + pid_size=`perf daemon --config ${config} -x: | head -2 | tail -1 | + awk 'BEGIN { FS = ":" } ; { print $1 }'` + local pid_time + pid_time=`perf daemon --config ${config} -x: | head -3 | tail -1 | + awk 'BEGIN { FS = ":" } ; { print $1 }'` + + # check that sessions are running + if [ ! -d "/proc/${pid_size}" ]; then + echo "FAILED: session size not up" + fi + + if [ ! -d "/proc/${pid_time}" ]; then + echo "FAILED: session time not up" + fi + + # stop daemon + daemon_exit ${config} + + # check that sessions are gone + if [ -d "/proc/${pid_size}" ]; then + echo "FAILED: session size still up" + fi + + if [ -d "/proc/${pid_time}" ]; then + echo "FAILED: session time still up" + fi + + rm -rf ${base} + rm -f ${config} +} + +test_signal() +{ + echo "test daemon signal" + + local config + config=$(mktemp /tmp/perf.daemon.config.XXX) + local base + base=$(mktemp -d /tmp/perf.daemon.base.XXX) + + # prepare config + cat <<EOF > ${config} +[daemon] +base=BASE + +[session-test] +run = -e cpu-clock --switch-output -m 1 sleep 10 +EOF + + sed -i -e "s|BASE|${base}|" ${config} + + # start daemon + daemon_start ${config} test + + # send 2 signals then exit. Do this in a loop watching the number of + # files to avoid races. If the loop retries more than 600 times then + # give up. + local retries=0 + local signals=0 + local success=0 + while [ ${retries} -lt 600 ] && [ ${success} -eq 0 ]; do + local files + files=`ls ${base}/session-test/*perf.data* 2> /dev/null | wc -l` + if [ ${signals} -eq 0 ]; then + perf daemon signal --config ${config} --session test + signals=1 + elif [ ${signals} -eq 1 ] && [ $files -ge 1 ]; then + perf daemon signal --config ${config} + signals=2 + elif [ ${signals} -eq 2 ] && [ $files -ge 2 ]; then + daemon_exit ${config} + signals=3 + elif [ ${signals} -eq 3 ] && [ $files -ge 3 ]; then + success=1 + fi + retries=$((${retries} +1)) + done + if [ ${success} -eq 0 ]; then + error=1 + echo "FAILED: perf data no generated" + fi + + rm -rf ${base} + rm -f ${config} +} + +test_ping() +{ + echo "test daemon ping" + + local config + config=$(mktemp /tmp/perf.daemon.config.XXX) + local base + base=$(mktemp -d /tmp/perf.daemon.base.XXX) + + # prepare config + cat <<EOF > ${config} +[daemon] +base=BASE + +[session-size] +run = -e cpu-clock -m 1 sleep 10 + +[session-time] +run = -e task-clock -m 1 sleep 10 +EOF + + sed -i -e "s|BASE|${base}|" ${config} + + # start daemon + daemon_start ${config} size + + size=`perf daemon ping --config ${config} --session size | awk '{ print $1 }'` + type=`perf daemon ping --config ${config} --session time | awk '{ print $1 }'` + + if [ ${size} != "OK" ] || [ ${type} != "OK" ]; then + error=1 + echo "FAILED: daemon ping failed" + fi + + # stop daemon + daemon_exit ${config} + + rm -rf ${base} + rm -f ${config} +} + +test_lock() +{ + echo "test daemon lock" + + local config + config=$(mktemp /tmp/perf.daemon.config.XXX) + local base + base=$(mktemp -d /tmp/perf.daemon.base.XXX) + + # prepare config + cat <<EOF > ${config} +[daemon] +base=BASE + +[session-size] +run = -e cpu-clock -m 1 sleep 10 +EOF + + sed -i -e "s|BASE|${base}|" ${config} + + # start daemon + daemon_start ${config} size + + # start second daemon over the same config/base + failed=`perf daemon start --config ${config} 2>&1 | awk '{ print $1 }'` + + # check that we failed properly + if [ ${failed} != "failed:" ]; then + error=1 + echo "FAILED: daemon lock failed" + fi + + # stop daemon + daemon_exit ${config} + + rm -rf ${base} + rm -f ${config} +} + +error=0 + +test_list +test_reconfig +test_stop +test_signal +test_ping +test_lock + +exit ${error} diff --git a/tools/perf/tests/shell/diff.sh b/tools/perf/tests/shell/diff.sh new file mode 100755 index 000000000000..e05a5dc49479 --- /dev/null +++ b/tools/perf/tests/shell/diff.sh @@ -0,0 +1,108 @@ +#!/bin/sh +# perf diff tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +err=0 +perfdata1=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +perfdata2=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +perfdata3=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +testprog="perf test -w thloop" + +shelldir=$(dirname "$0") +# shellcheck source=lib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +testsym="test_loop" + +skip_test_missing_symbol ${testsym} + +cleanup() { + rm -rf "${perfdata1}" + rm -rf "${perfdata1}".old + rm -rf "${perfdata2}" + rm -rf "${perfdata2}".old + rm -rf "${perfdata3}" + rm -rf "${perfdata3}".old + + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +make_data() { + file="$1" + if ! perf record -o "${file}" ${testprog} 2> /dev/null + then + echo "Workload record [Failed record]" >&2 + echo 1 + return + fi + if ! perf report -i "${file}" -q | grep -q "${testsym}" + then + echo "Workload record [Failed missing output]" >&2 + echo 1 + return + fi + echo 0 +} + +test_two_files() { + echo "Basic two file diff test" + err=$(make_data "${perfdata1}") + if [ "$err" != 0 ] + then + return + fi + err=$(make_data "${perfdata2}") + if [ "$err" != 0 ] + then + return + fi + + if ! perf diff "${perfdata1}" "${perfdata2}" | grep -q "${testsym}" + then + echo "Basic two file diff test [Failed diff]" + err=1 + return + fi + echo "Basic two file diff test [Success]" +} + +test_three_files() { + echo "Basic three file diff test" + err=$(make_data "${perfdata1}") + if [ "$err" != 0 ] + then + return + fi + err=$(make_data "${perfdata2}") + if [ "$err" != 0 ] + then + return + fi + err=$(make_data "${perfdata3}") + if [ $err != 0 ] + then + return + fi + + if ! perf diff "${perfdata1}" "${perfdata2}" "${perfdata3}" | grep -q "${testsym}" + then + echo "Basic three file diff test [Failed diff]" + err=1 + return + fi + echo "Basic three file diff test [Success]" +} + +test_two_files +test_three_files + +cleanup +exit $err diff --git a/tools/perf/tests/shell/ftrace.sh b/tools/perf/tests/shell/ftrace.sh new file mode 100755 index 000000000000..c243731d2fbf --- /dev/null +++ b/tools/perf/tests/shell/ftrace.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# perf ftrace tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +# perf ftrace commands only works for root +if [ "$(id -u)" != 0 ]; then + echo "perf ftrace test [Skipped: no permission]" + exit 2 +fi + +output=$(mktemp /tmp/__perf_test.ftrace.XXXXXX) + +cleanup() { + rm -f "${output}" + + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +# this will be set in test_ftrace_trace() +target_function= + +test_ftrace_list() { + echo "perf ftrace list test" + perf ftrace -F > "${output}" + # this will be used in test_ftrace_trace() + sleep_functions=$(grep 'sys_.*sleep$' "${output}") + echo "syscalls for sleep:" + echo "${sleep_functions}" + echo "perf ftrace list test [Success]" +} + +test_ftrace_trace() { + echo "perf ftrace trace test" + perf ftrace trace --graph-opts depth=5 sleep 0.1 > "${output}" + # it should have some function name contains 'sleep' + grep "^#" "${output}" + grep -F 'sleep()' "${output}" + # find actual syscall function name + for FN in ${sleep_functions}; do + if grep -q "${FN}" "${output}"; then + target_function="${FN}" + echo "perf ftrace trace test [Success]" + return + fi + done + + echo "perf ftrace trace test [Failure: sleep syscall not found]" + exit 1 +} + +test_ftrace_latency() { + echo "perf ftrace latency test" + echo "target function: ${target_function}" + perf ftrace latency -T "${target_function}" sleep 0.1 > "${output}" + grep "^#" "${output}" + grep "###" "${output}" + echo "perf ftrace latency test [Success]" +} + +test_ftrace_profile() { + echo "perf ftrace profile test" + perf ftrace profile --graph-opts depth=5 sleep 0.1 > "${output}" + grep ^# "${output}" + time_re="[[:space:]]+1[[:digit:]]{5}\.[[:digit:]]{3}" + # 100283.000 100283.000 100283.000 1 __x64_sys_clock_nanosleep + # Check for one *clock_nanosleep line with a Count of just 1 that takes a bit more than 0.1 seconds + # Strip the _x64_sys part to work with other architectures + grep -E "^${time_re}${time_re}${time_re}[[:space:]]+1[[:space:]]+.*clock_nanosleep" "${output}" + echo "perf ftrace profile test [Success]" +} + +test_ftrace_list +test_ftrace_trace +test_ftrace_latency +test_ftrace_profile + +cleanup +exit 0 diff --git a/tools/perf/tests/attr.py b/tools/perf/tests/shell/lib/attr.py index cb39ac46bc73..bfccc727d9b2 100644 --- a/tools/perf/tests/attr.py +++ b/tools/perf/tests/shell/lib/attr.py @@ -1,19 +1,16 @@ # SPDX-License-Identifier: GPL-2.0 -from __future__ import print_function - +import configparser import os import sys import glob import optparse +import platform import tempfile import logging +import re import shutil - -try: - import configparser -except ImportError: - import ConfigParser as configparser +import subprocess def data_equal(a, b): # Allow multiple values in assignment separated by '|' @@ -123,23 +120,33 @@ class Event(dict): if not data_equal(self[t], other[t]): log.warning("expected %s=%s, got %s" % (t, self[t], other[t])) +def parse_version(version): + if not version: + return None + return [int(v) for v in version.split(".")[0:2]] + # Test file description needs to have following sections: # [config] # - just single instance in file # - needs to specify: # 'command' - perf command name # 'args' - special command arguments -# 'ret' - expected command return value (0 by default) +# 'ret' - Skip test if Perf doesn't exit with this value (0 by default) +# 'test_ret'- If set to 'true', fail test instead of skipping for 'ret' argument # 'arch' - architecture specific test (optional) # comma separated list, ! at the beginning # negates it. -# +# 'auxv' - Truthy statement that is evaled in the scope of the auxv map. When false, +# the test is skipped. For example 'auxv["AT_HWCAP"] == 10'. (optional) +# 'kernel_since' - Inclusive kernel version from which the test will start running. Only the +# first two values are supported, for example "6.1" (optional) +# 'kernel_until' - Exclusive kernel version from which the test will stop running. (optional) # [eventX:base] # - one or multiple instances in file # - expected values assignments class Test(object): def __init__(self, path, options): - parser = configparser.SafeConfigParser() + parser = configparser.ConfigParser() parser.read(path) log.warning("running '%s'" % path) @@ -155,12 +162,17 @@ class Test(object): except: self.ret = 0 + self.test_ret = parser.getboolean('config', 'test_ret', fallback=False) + try: self.arch = parser.get('config', 'arch') log.warning("test limitation '%s'" % self.arch) except: self.arch = '' + self.auxv = parser.get('config', 'auxv', fallback=None) + self.kernel_since = parse_version(parser.get('config', 'kernel_since', fallback=None)) + self.kernel_until = parse_version(parser.get('config', 'kernel_until', fallback=None)) self.expect = {} self.result = {} log.debug(" loading expected events"); @@ -172,7 +184,38 @@ class Test(object): else: return True - def skip_test(self, myarch): + def skip_test_kernel_since(self): + if not self.kernel_since: + return False + return not self.kernel_since <= parse_version(platform.release()) + + def skip_test_kernel_until(self): + if not self.kernel_until: + return False + return not parse_version(platform.release()) < self.kernel_until + + def skip_test_auxv(self): + def new_auxv(a, pattern): + items = list(filter(None, pattern.split(a))) + # AT_HWCAP is hex but doesn't have a prefix, so special case it + if items[0] == "AT_HWCAP": + value = int(items[-1], 16) + else: + try: + value = int(items[-1], 0) + except: + value = items[-1] + return (items[0], value) + + if not self.auxv: + return False + auxv = subprocess.check_output("LD_SHOW_AUXV=1 sleep 0", shell=True) \ + .decode(sys.stdout.encoding) + pattern = re.compile(r"[: ]+") + auxv = dict([new_auxv(a, pattern) for a in auxv.splitlines()]) + return not eval(self.auxv) + + def skip_test_arch(self, myarch): # If architecture not set always run test if self.arch == '': # log.warning("test for arch %s is ok" % myarch) @@ -197,8 +240,25 @@ class Test(object): return False return True + def restore_sample_rate(self, value=10000): + try: + # Check value of sample_rate + with open("/proc/sys/kernel/perf_event_max_sample_rate", "r") as fIn: + curr_value = fIn.readline() + # If too low restore to reasonable value + if not curr_value or int(curr_value) < int(value): + with open("/proc/sys/kernel/perf_event_max_sample_rate", "w") as fOut: + fOut.write(str(value)) + + except IOError as e: + log.warning("couldn't restore sample_rate value: I/O error %s" % e) + except ValueError as e: + log.warning("couldn't restore sample_rate value: Value error %s" % e) + except TypeError as e: + log.warning("couldn't restore sample_rate value: Type error %s" % e) + def load_events(self, path, events): - parser_event = configparser.SafeConfigParser() + parser_event = configparser.ConfigParser() parser_event.read(path) # The event record section header contains 'event' word, @@ -212,7 +272,7 @@ class Test(object): # Read parent event if there's any if (':' in section): base = section[section.index(':') + 1:] - parser_base = configparser.SafeConfigParser() + parser_base = configparser.ConfigParser() parser_base.read(self.test_dir + '/' + base) base_items = parser_base.items('event') @@ -222,9 +282,19 @@ class Test(object): def run_cmd(self, tempdir): junk1, junk2, junk3, junk4, myarch = (os.uname()) - if self.skip_test(myarch): + if self.skip_test_arch(myarch): raise Notest(self, myarch) + if self.skip_test_auxv(): + raise Notest(self, "auxv skip") + + if self.skip_test_kernel_since(): + raise Notest(self, "old kernel skip") + + if self.skip_test_kernel_until(): + raise Notest(self, "new kernel skip") + + self.restore_sample_rate() cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir, self.perf, self.command, tempdir, self.args) ret = os.WEXITSTATUS(os.system(cmd)) @@ -232,7 +302,10 @@ class Test(object): log.info(" '%s' ret '%s', expected '%s'" % (cmd, str(ret), str(self.ret))) if not data_equal(str(ret), str(self.ret)): - raise Unsup(self) + if self.test_ret: + raise Fail(self, "Perf exit code failure") + else: + raise Unsup(self) def compare(self, expect, result): match = {} diff --git a/tools/perf/tests/shell/lib/coresight.sh b/tools/perf/tests/shell/lib/coresight.sh new file mode 100644 index 000000000000..184d62e7e5bd --- /dev/null +++ b/tools/perf/tests/shell/lib/coresight.sh @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: GPL-2.0 +# Carsten Haitzler <carsten.haitzler@arm.com>, 2021 + +# This is sourced from a driver script so no need for #!/bin... etc. at the +# top - the assumption below is that it runs as part of sourcing after the +# test sets up some basic env vars to say what it is. + +# This currently works with ETMv4 / ETF not any other packet types at thi +# point. This will need changes if that changes. + +# perf record options for the perf tests to use +PERFRECMEM="-m ,16M" +PERFRECOPT="$PERFRECMEM -e cs_etm//u" + +TOOLS=$(dirname $0) +DIR="$TOOLS/$TEST" +BIN="$DIR/$TEST" +# If the test tool/binary does not exist and is executable then skip the test +if ! test -x "$BIN"; then exit 2; fi +# If CoreSight is not available, skip the test +perf list pmu | grep -q cs_etm || exit 2 +DATD="." +# If the data dir env is set then make the data dir use that instead of ./ +if test -n "$PERF_TEST_CORESIGHT_DATADIR"; then + DATD="$PERF_TEST_CORESIGHT_DATADIR"; +fi +# If the stat dir env is set then make the data dir use that instead of ./ +STATD="." +if test -n "$PERF_TEST_CORESIGHT_STATDIR"; then + STATD="$PERF_TEST_CORESIGHT_STATDIR"; +fi + +# Called if the test fails - error code 1 +err() { + echo "$1" + exit 1 +} + +# Check that some statistics from our perf +check_val_min() { + STATF="$4" + if test "$2" -lt "$3"; then + echo ", FAILED" >> "$STATF" + err "Sanity check number of $1 is too low ($2 < $3)" + fi +} + +perf_dump_aux_verify() { + # Some basic checking that the AUX chunk contains some sensible data + # to see that we are recording something and at least a minimum + # amount of it. We should almost always see Fn packets in just about + # anything but certainly we will see some trace info and async + # packets + DUMP="$DATD/perf-tmp-aux-dump.txt" + perf report --stdio --dump -i "$1" | \ + grep -o -e I_ATOM_F -e I_ASYNC -e I_TRACE_INFO > "$DUMP" + # Simply count how many of these packets we find to see that we are + # producing a reasonable amount of data - exact checks are not sane + # as this is a lossy process where we may lose some blocks and the + # compiler may produce different code depending on the compiler and + # optimization options, so this is rough just to see if we're + # either missing almost all the data or all of it + ATOM_FX_NUM=$(grep -c I_ATOM_F "$DUMP") + ASYNC_NUM=$(grep -c I_ASYNC "$DUMP") + TRACE_INFO_NUM=$(grep -c I_TRACE_INFO "$DUMP") + rm -f "$DUMP" + + # Arguments provide minimums for a pass + CHECK_FX_MIN="$2" + CHECK_ASYNC_MIN="$3" + CHECK_TRACE_INFO_MIN="$4" + + # Write out statistics, so over time you can track results to see if + # there is a pattern - for example we have less "noisy" results that + # produce more consistent amounts of data each run, to see if over + # time any techinques to minimize data loss are having an effect or + # not + STATF="$STATD/stats-$TEST-$DATV.csv" + if ! test -f "$STATF"; then + echo "ATOM Fx Count, Minimum, ASYNC Count, Minimum, TRACE INFO Count, Minimum" > "$STATF" + fi + echo -n "$ATOM_FX_NUM, $CHECK_FX_MIN, $ASYNC_NUM, $CHECK_ASYNC_MIN, $TRACE_INFO_NUM, $CHECK_TRACE_INFO_MIN" >> "$STATF" + + # Actually check to see if we passed or failed. + check_val_min "ATOM_FX" "$ATOM_FX_NUM" "$CHECK_FX_MIN" "$STATF" + check_val_min "ASYNC" "$ASYNC_NUM" "$CHECK_ASYNC_MIN" "$STATF" + check_val_min "TRACE_INFO" "$TRACE_INFO_NUM" "$CHECK_TRACE_INFO_MIN" "$STATF" + echo ", Ok" >> "$STATF" +} + +perf_dump_aux_tid_verify() { + # Specifically crafted test will produce a list of Tread ID's to + # stdout that need to be checked to see that they have had trace + # info collected in AUX blocks in the perf data. This will go + # through all the TID's that are listed as CID=0xabcdef and see + # that all the Thread IDs the test tool reports are in the perf + # data AUX chunks + + # The TID test tools will print a TID per stdout line that are being + # tested + TIDS=$(cat "$2") + # Scan the perf report to find the TIDs that are actually CID in hex + # and build a list of the ones found + FOUND_TIDS=$(perf report --stdio --dump -i "$1" | \ + grep -o "CID=0x[0-9a-z]\+" | sed 's/CID=//g' | \ + uniq | sort | uniq) + # No CID=xxx found - maybe your kernel is reporting these as + # VMID=xxx so look there + if test -z "$FOUND_TIDS"; then + FOUND_TIDS=$(perf report --stdio --dump -i "$1" | \ + grep -o "VMID=0x[0-9a-z]\+" | sed 's/VMID=//g' | \ + uniq | sort | uniq) + fi + + # Iterate over the list of TIDs that the test says it has and find + # them in the TIDs found in the perf report + MISSING="" + for TID2 in $TIDS; do + FOUND="" + for TIDHEX in $FOUND_TIDS; do + TID=$(printf "%i" $TIDHEX) + if test "$TID" -eq "$TID2"; then + FOUND="y" + break + fi + done + if test -z "$FOUND"; then + MISSING="$MISSING $TID" + fi + done + if test -n "$MISSING"; then + err "Thread IDs $MISSING not found in perf AUX data" + fi +} diff --git a/tools/perf/tests/shell/lib/perf_has_symbol.sh b/tools/perf/tests/shell/lib/perf_has_symbol.sh new file mode 100644 index 000000000000..561c93b75d77 --- /dev/null +++ b/tools/perf/tests/shell/lib/perf_has_symbol.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +perf_has_symbol() +{ + if perf test -vv -F "Symbols" 2>&1 | grep "[[:space:]]$1$"; then + echo "perf does have symbol '$1'" + return 0 + fi + echo "perf does not have symbol '$1'" + return 1 +} + +skip_test_missing_symbol() +{ + if ! perf_has_symbol "$1" ; then + echo "perf is missing symbols - skipping test" + exit 2 + fi + return 0 +} diff --git a/tools/perf/tests/shell/lib/perf_json_output_lint.py b/tools/perf/tests/shell/lib/perf_json_output_lint.py new file mode 100644 index 000000000000..9e772a89ce38 --- /dev/null +++ b/tools/perf/tests/shell/lib/perf_json_output_lint.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +# Basic sanity check of perf JSON output as specified in the man page. + +import argparse +import sys +import json + +ap = argparse.ArgumentParser() +ap.add_argument('--no-args', action='store_true') +ap.add_argument('--interval', action='store_true') +ap.add_argument('--system-wide-no-aggr', action='store_true') +ap.add_argument('--system-wide', action='store_true') +ap.add_argument('--event', action='store_true') +ap.add_argument('--per-core', action='store_true') +ap.add_argument('--per-thread', action='store_true') +ap.add_argument('--per-cache', action='store_true') +ap.add_argument('--per-cluster', action='store_true') +ap.add_argument('--per-die', action='store_true') +ap.add_argument('--per-node', action='store_true') +ap.add_argument('--per-socket', action='store_true') +ap.add_argument('--metric-only', action='store_true') +ap.add_argument('--file', type=argparse.FileType('r'), default=sys.stdin) +args = ap.parse_args() + +Lines = args.file.readlines() + +def isfloat(num): + try: + float(num) + return True + except ValueError: + return False + + +def isint(num): + try: + int(num) + return True + except ValueError: + return False + +def is_counter_value(num): + return isfloat(num) or num == '<not counted>' or num == '<not supported>' + +def check_json_output(expected_items): + checks = { + 'aggregate-number': lambda x: isfloat(x), + 'core': lambda x: True, + 'counter-value': lambda x: is_counter_value(x), + 'cgroup': lambda x: True, + 'cpu': lambda x: isint(x), + 'cache': lambda x: True, + 'cluster': lambda x: True, + 'die': lambda x: True, + 'event': lambda x: True, + 'event-runtime': lambda x: isfloat(x), + 'interval': lambda x: isfloat(x), + 'metric-unit': lambda x: True, + 'metric-value': lambda x: isfloat(x), + 'metric-threshold': lambda x: x in ['unknown', 'good', 'less good', 'nearly bad', 'bad'], + 'metricgroup': lambda x: True, + 'node': lambda x: True, + 'pcnt-running': lambda x: isfloat(x), + 'socket': lambda x: True, + 'thread': lambda x: True, + 'unit': lambda x: True, + 'insn per cycle': lambda x: isfloat(x), + 'GHz': lambda x: True, # FIXME: it seems unintended for --metric-only + } + input = '[\n' + ','.join(Lines) + '\n]' + for item in json.loads(input): + if expected_items != -1: + count = len(item) + if count not in expected_items and count >= 1 and count <= 7 and 'metric-value' in item: + # Events that generate >1 metric may have isolated metric + # values and possibly other prefixes like interval, core, + # aggregate-number, or event-runtime/pcnt-running from multiplexing. + pass + elif count not in expected_items and count >= 1 and count <= 5 and 'metricgroup' in item: + pass + elif count - 1 in expected_items and 'metric-threshold' in item: + pass + elif count in expected_items and 'insn per cycle' in item: + pass + elif count not in expected_items: + raise RuntimeError(f'wrong number of fields. counted {count} expected {expected_items}' + f' in \'{item}\'') + for key, value in item.items(): + if key not in checks: + raise RuntimeError(f'Unexpected key: key={key} value={value}') + if not checks[key](value): + raise RuntimeError(f'Check failed for: key={key} value={value}') + + +try: + if args.no_args or args.system_wide or args.event: + expected_items = [5, 7] + elif args.interval or args.per_thread or args.system_wide_no_aggr: + expected_items = [6, 8] + elif args.per_core or args.per_socket or args.per_node or args.per_die or args.per_cluster or args.per_cache: + expected_items = [7, 9] + elif args.metric_only: + expected_items = [1, 2] + else: + # If no option is specified, don't check the number of items. + expected_items = -1 + check_json_output(expected_items) +except: + print('Test failed for input:\n' + '\n'.join(Lines)) + raise diff --git a/tools/perf/tests/shell/lib/perf_metric_validation.py b/tools/perf/tests/shell/lib/perf_metric_validation.py new file mode 100644 index 000000000000..dea8ef1977bf --- /dev/null +++ b/tools/perf/tests/shell/lib/perf_metric_validation.py @@ -0,0 +1,603 @@ +# SPDX-License-Identifier: GPL-2.0 +import re +import csv +import json +import argparse +from pathlib import Path +import subprocess + + +class TestError: + def __init__(self, metric: list[str], wl: str, value: list[float], low: float, up=float('nan'), description=str()): + self.metric: list = metric # multiple metrics in relationship type tests + self.workloads = [wl] # multiple workloads possible + self.collectedValue: list = value + self.valueLowBound = low + self.valueUpBound = up + self.description = description + + def __repr__(self) -> str: + if len(self.metric) > 1: + return "\nMetric Relationship Error: \tThe collected value of metric {0}\n\ + \tis {1} in workload(s): {2} \n\ + \tbut expected value range is [{3}, {4}]\n\ + \tRelationship rule description: \'{5}\'".format(self.metric, self.collectedValue, self.workloads, + self.valueLowBound, self.valueUpBound, self.description) + elif len(self.collectedValue) == 0: + return "\nNo Metric Value Error: \tMetric {0} returns with no value \n\ + \tworkload(s): {1}".format(self.metric, self.workloads) + else: + return "\nWrong Metric Value Error: \tThe collected value of metric {0}\n\ + \tis {1} in workload(s): {2}\n\ + \tbut expected value range is [{3}, {4}]"\ + .format(self.metric, self.collectedValue, self.workloads, + self.valueLowBound, self.valueUpBound) + + +class Validator: + def __init__(self, rulefname, reportfname='', t=5, debug=False, datafname='', fullrulefname='', + workload='true', metrics='', cputype='cpu'): + self.rulefname = rulefname + self.reportfname = reportfname + self.rules = None + self.collectlist: str = metrics + self.metrics = self.__set_metrics(metrics) + self.skiplist = set() + self.tolerance = t + self.cputype = cputype + + self.workloads = [x for x in workload.split(",") if x] + self.wlidx = 0 # idx of current workloads + self.allresults = dict() # metric results of all workload + self.alltotalcnt = dict() + self.allpassedcnt = dict() + + self.results = dict() # metric results of current workload + # vars for test pass/failure statistics + # metrics with no results or negative results, neg result counts failed tests + self.ignoremetrics = set() + self.totalcnt = 0 + self.passedcnt = 0 + # vars for errors + self.errlist = list() + + # vars for Rule Generator + self.pctgmetrics = set() # Percentage rule + + # vars for debug + self.datafname = datafname + self.debug = debug + self.fullrulefname = fullrulefname + + def __set_metrics(self, metrics=''): + if metrics != '': + return set(metrics.split(",")) + else: + return set() + + def read_json(self, filename: str) -> dict: + try: + with open(Path(filename).resolve(), "r") as f: + data = json.loads(f.read()) + except OSError as e: + print(f"Error when reading file {e}") + sys.exit() + + return data + + def json_dump(self, data, output_file): + parent = Path(output_file).parent + if not parent.exists(): + parent.mkdir(parents=True) + + with open(output_file, "w+") as output_file: + json.dump(data, + output_file, + ensure_ascii=True, + indent=4) + + def get_results(self, idx: int = 0): + return self.results.get(idx) + + def get_bounds(self, lb, ub, error, alias={}, ridx: int = 0) -> list: + """ + Get bounds and tolerance from lb, ub, and error. + If missing lb, use 0.0; missing ub, use float('inf); missing error, use self.tolerance. + + @param lb: str/float, lower bound + @param ub: str/float, upper bound + @param error: float/str, error tolerance + @returns: lower bound, return inf if the lower bound is a metric value and is not collected + upper bound, return -1 if the upper bound is a metric value and is not collected + tolerance, denormalized base on upper bound value + """ + # init ubv and lbv to invalid values + def get_bound_value(bound, initval, ridx): + val = initval + if isinstance(bound, int) or isinstance(bound, float): + val = bound + elif isinstance(bound, str): + if bound == '': + val = float("inf") + elif bound in alias: + vall = self.get_value(alias[ub], ridx) + if vall: + val = vall[0] + elif bound.replace('.', '1').isdigit(): + val = float(bound) + else: + print("Wrong bound: {0}".format(bound)) + else: + print("Wrong bound: {0}".format(bound)) + return val + + ubv = get_bound_value(ub, -1, ridx) + lbv = get_bound_value(lb, float('inf'), ridx) + t = get_bound_value(error, self.tolerance, ridx) + + # denormalize error threshold + denormerr = t * ubv / 100 if ubv != 100 and ubv > 0 else t + + return lbv, ubv, denormerr + + def get_value(self, name: str, ridx: int = 0) -> list: + """ + Get value of the metric from self.results. + If result of this metric is not provided, the metric name will be added into self.ignoremetics. + All future test(s) on this metric will fail. + + @param name: name of the metric + @returns: list with value found in self.results; list is empty when value is not found. + """ + results = [] + data = self.results[ridx] if ridx in self.results else self.results[0] + if name not in self.ignoremetrics: + if name in data: + results.append(data[name]) + elif name.replace('.', '1').isdigit(): + results.append(float(name)) + else: + self.ignoremetrics.add(name) + return results + + def check_bound(self, val, lb, ub, err): + return True if val <= ub + err and val >= lb - err else False + + # Positive Value Sanity check + def pos_val_test(self): + """ + Check if metrics value are non-negative. + One metric is counted as one test. + Failure: when metric value is negative or not provided. + Metrics with negative value will be added into self.ignoremetrics. + """ + negmetric = dict() + pcnt = 0 + tcnt = 0 + rerun = list() + results = self.get_results() + if not results: + return + for name, val in results.items(): + if val < 0: + negmetric[name] = val + rerun.append(name) + else: + pcnt += 1 + tcnt += 1 + # The first round collect_perf() run these metrics with simple workload + # "true". We give metrics a second chance with a longer workload if less + # than 20 metrics failed positive test. + if len(rerun) > 0 and len(rerun) < 20: + second_results = dict() + self.second_test(rerun, second_results) + for name, val in second_results.items(): + if name not in negmetric: + continue + if val >= 0: + del negmetric[name] + pcnt += 1 + + if len(negmetric.keys()): + self.ignoremetrics.update(negmetric.keys()) + self.errlist.extend( + [TestError([m], self.workloads[self.wlidx], negmetric[m], 0) for m in negmetric.keys()]) + + return + + def evaluate_formula(self, formula: str, alias: dict, ridx: int = 0): + """ + Evaluate the value of formula. + + @param formula: the formula to be evaluated + @param alias: the dict has alias to metric name mapping + @returns: value of the formula is success; -1 if the one or more metric value not provided + """ + stack = [] + b = 0 + errs = [] + sign = "+" + f = str() + + # TODO: support parenthesis? + for i in range(len(formula)): + if i+1 == len(formula) or formula[i] in ('+', '-', '*', '/'): + s = alias[formula[b:i]] if i + \ + 1 < len(formula) else alias[formula[b:]] + v = self.get_value(s, ridx) + if not v: + errs.append(s) + else: + f = f + "{0}(={1:.4f})".format(s, v[0]) + if sign == "*": + stack[-1] = stack[-1] * v + elif sign == "/": + stack[-1] = stack[-1] / v + elif sign == '-': + stack.append(-v[0]) + else: + stack.append(v[0]) + if i + 1 < len(formula): + sign = formula[i] + f += sign + b = i + 1 + + if len(errs) > 0: + return -1, "Metric value missing: "+','.join(errs) + + val = sum(stack) + return val, f + + # Relationships Tests + def relationship_test(self, rule: dict): + """ + Validate if the metrics follow the required relationship in the rule. + eg. lower_bound <= eval(formula)<= upper_bound + One rule is counted as ont test. + Failure: when one or more metric result(s) not provided, or when formula evaluated outside of upper/lower bounds. + + @param rule: dict with metric name(+alias), formula, and required upper and lower bounds. + """ + alias = dict() + for m in rule['Metrics']: + alias[m['Alias']] = m['Name'] + lbv, ubv, t = self.get_bounds( + rule['RangeLower'], rule['RangeUpper'], rule['ErrorThreshold'], alias, ridx=rule['RuleIndex']) + val, f = self.evaluate_formula( + rule['Formula'], alias, ridx=rule['RuleIndex']) + + lb = rule['RangeLower'] + ub = rule['RangeUpper'] + if isinstance(lb, str): + if lb in alias: + lb = alias[lb] + if isinstance(ub, str): + if ub in alias: + ub = alias[ub] + + if val == -1: + self.errlist.append(TestError([m['Name'] for m in rule['Metrics']], self.workloads[self.wlidx], [], + lb, ub, rule['Description'])) + elif not self.check_bound(val, lbv, ubv, t): + self.errlist.append(TestError([m['Name'] for m in rule['Metrics']], self.workloads[self.wlidx], [val], + lb, ub, rule['Description'])) + else: + self.passedcnt += 1 + self.totalcnt += 1 + + return + + # Single Metric Test + def single_test(self, rule: dict): + """ + Validate if the metrics are in the required value range. + eg. lower_bound <= metrics_value <= upper_bound + One metric is counted as one test in this type of test. + One rule may include one or more metrics. + Failure: when the metric value not provided or the value is outside the bounds. + This test updates self.total_cnt. + + @param rule: dict with metrics to validate and the value range requirement + """ + lbv, ubv, t = self.get_bounds( + rule['RangeLower'], rule['RangeUpper'], rule['ErrorThreshold']) + metrics = rule['Metrics'] + passcnt = 0 + totalcnt = 0 + failures = dict() + rerun = list() + for m in metrics: + totalcnt += 1 + result = self.get_value(m['Name']) + if len(result) > 0 and self.check_bound(result[0], lbv, ubv, t) or m['Name'] in self.skiplist: + passcnt += 1 + else: + failures[m['Name']] = result + rerun.append(m['Name']) + + if len(rerun) > 0 and len(rerun) < 20: + second_results = dict() + self.second_test(rerun, second_results) + for name, val in second_results.items(): + if name not in failures: + continue + if self.check_bound(val, lbv, ubv, t): + passcnt += 1 + del failures[name] + else: + failures[name] = [val] + self.results[0][name] = val + + self.totalcnt += totalcnt + self.passedcnt += passcnt + if len(failures.keys()) != 0: + self.errlist.extend([TestError([name], self.workloads[self.wlidx], val, + rule['RangeLower'], rule['RangeUpper']) for name, val in failures.items()]) + + return + + def create_report(self): + """ + Create final report and write into a JSON file. + """ + print(self.errlist) + + if self.debug: + allres = [{"Workload": self.workloads[i], "Results": self.allresults[i]} + for i in range(0, len(self.workloads))] + self.json_dump(allres, self.datafname) + + def check_rule(self, testtype, metric_list): + """ + Check if the rule uses metric(s) that not exist in current platform. + + @param metric_list: list of metrics from the rule. + @return: False when find one metric out in Metric file. (This rule should not skipped.) + True when all metrics used in the rule are found in Metric file. + """ + if testtype == "RelationshipTest": + for m in metric_list: + if m['Name'] not in self.metrics: + return False + return True + + # Start of Collector and Converter + def convert(self, data: list, metricvalues: dict): + """ + Convert collected metric data from the -j output to dict of {metric_name:value}. + """ + for json_string in data: + try: + result = json.loads(json_string) + if "metric-unit" in result and result["metric-unit"] != "(null)" and result["metric-unit"] != "": + name = result["metric-unit"].split(" ")[1] if len(result["metric-unit"].split(" ")) > 1 \ + else result["metric-unit"] + metricvalues[name.lower()] = float(result["metric-value"]) + except ValueError as error: + continue + return + + def _run_perf(self, metric, workload: str): + tool = 'perf' + command = [tool, 'stat', '--cputype', self.cputype, '-j', '-M', f"{metric}", "-a"] + wl = workload.split() + command.extend(wl) + print(" ".join(command)) + cmd = subprocess.run(command, stderr=subprocess.PIPE, encoding='utf-8') + data = [x+'}' for x in cmd.stderr.split('}\n') if x] + if data[0][0] != '{': + data[0] = data[0][data[0].find('{'):] + return data + + def collect_perf(self, workload: str): + """ + Collect metric data with "perf stat -M" on given workload with -a and -j. + """ + self.results = dict() + print(f"Starting perf collection") + print(f"Long workload: {workload}") + collectlist = dict() + if self.collectlist != "": + collectlist[0] = {x for x in self.collectlist.split(",")} + else: + collectlist[0] = set(list(self.metrics)) + # Create metric set for relationship rules + for rule in self.rules: + if rule["TestType"] == "RelationshipTest": + metrics = [m["Name"] for m in rule["Metrics"]] + if not any(m not in collectlist[0] for m in metrics): + collectlist[rule["RuleIndex"]] = [ + ",".join(list(set(metrics)))] + + for idx, metrics in collectlist.items(): + if idx == 0: + wl = "true" + else: + wl = workload + for metric in metrics: + data = self._run_perf(metric, wl) + if idx not in self.results: + self.results[idx] = dict() + self.convert(data, self.results[idx]) + return + + def second_test(self, collectlist, second_results): + workload = self.workloads[self.wlidx] + for metric in collectlist: + data = self._run_perf(metric, workload) + self.convert(data, second_results) + + # End of Collector and Converter + + # Start of Rule Generator + def parse_perf_metrics(self): + """ + Read and parse perf metric file: + 1) find metrics with '1%' or '100%' as ScaleUnit for Percent check + 2) create metric name list + """ + command = ['perf', 'list', '-j', '--details', 'metrics'] + cmd = subprocess.run(command, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, encoding='utf-8') + try: + data = json.loads(cmd.stdout) + for m in data: + if 'MetricName' not in m: + print("Warning: no metric name") + continue + if 'Unit' in m and m['Unit'] != self.cputype: + continue + name = m['MetricName'].lower() + self.metrics.add(name) + if 'ScaleUnit' in m and (m['ScaleUnit'] == '1%' or m['ScaleUnit'] == '100%'): + self.pctgmetrics.add(name.lower()) + except ValueError as error: + print(f"Error when parsing metric data") + sys.exit() + + return + + def remove_unsupported_rules(self, rules): + new_rules = [] + for rule in rules: + add_rule = True + for m in rule["Metrics"]: + if m["Name"] in self.skiplist or m["Name"] not in self.metrics: + add_rule = False + break + if add_rule: + new_rules.append(rule) + return new_rules + + def create_rules(self): + """ + Create full rules which includes: + 1) All the rules from the "relationshi_rules" file + 2) SingleMetric rule for all the 'percent' metrics + + Reindex all the rules to avoid repeated RuleIndex + """ + data = self.read_json(self.rulefname) + rules = data['RelationshipRules'] + self.skiplist = set([name.lower() for name in data['SkipList']]) + self.rules = self.remove_unsupported_rules(rules) + pctgrule = {'RuleIndex': 0, + 'TestType': 'SingleMetricTest', + 'RangeLower': '0', + 'RangeUpper': '100', + 'ErrorThreshold': self.tolerance, + 'Description': 'Metrics in percent unit have value with in [0, 100]', + 'Metrics': [{'Name': m.lower()} for m in self.pctgmetrics]} + self.rules.append(pctgrule) + + # Re-index all rules to avoid repeated RuleIndex + idx = 1 + for r in self.rules: + r['RuleIndex'] = idx + idx += 1 + + if self.debug: + # TODO: need to test and generate file name correctly + data = {'RelationshipRules': self.rules, 'SupportedMetrics': [ + {"MetricName": name} for name in self.metrics]} + self.json_dump(data, self.fullrulefname) + + return + # End of Rule Generator + + def _storewldata(self, key): + ''' + Store all the data of one workload into the corresponding data structure for all workloads. + @param key: key to the dictionaries (index of self.workloads). + ''' + self.allresults[key] = self.results + self.alltotalcnt[key] = self.totalcnt + self.allpassedcnt[key] = self.passedcnt + + # Initialize data structures before data validation of each workload + def _init_data(self): + + testtypes = ['PositiveValueTest', + 'RelationshipTest', 'SingleMetricTest'] + self.results = dict() + self.ignoremetrics = set() + self.errlist = list() + self.totalcnt = 0 + self.passedcnt = 0 + + def test(self): + ''' + The real entry point of the test framework. + This function loads the validation rule JSON file and Standard Metric file to create rules for + testing and namemap dictionaries. + It also reads in result JSON file for testing. + + In the test process, it passes through each rule and launch correct test function bases on the + 'TestType' field of the rule. + + The final report is written into a JSON file. + ''' + if not self.collectlist: + self.parse_perf_metrics() + if not self.metrics: + print("No metric found for testing") + return 0 + self.create_rules() + for i in range(0, len(self.workloads)): + self.wlidx = i + self._init_data() + self.collect_perf(self.workloads[i]) + # Run positive value test + self.pos_val_test() + for r in self.rules: + # skip rules that uses metrics not exist in this platform + testtype = r['TestType'] + if not self.check_rule(testtype, r['Metrics']): + continue + if testtype == 'RelationshipTest': + self.relationship_test(r) + elif testtype == 'SingleMetricTest': + self.single_test(r) + else: + print("Unsupported Test Type: ", testtype) + print("Workload: ", self.workloads[i]) + print("Total Test Count: ", self.totalcnt) + print("Passed Test Count: ", self.passedcnt) + self._storewldata(i) + self.create_report() + return len(self.errlist) > 0 +# End of Class Validator + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Launch metric value validation") + + parser.add_argument( + "-rule", help="Base validation rule file", required=True) + parser.add_argument( + "-output_dir", help="Path for validator output file, report file", required=True) + parser.add_argument("-debug", help="Debug run, save intermediate data to files", + action="store_true", default=False) + parser.add_argument( + "-wl", help="Workload to run while data collection", default="true") + parser.add_argument("-m", help="Metric list to validate", default="") + parser.add_argument("-cputype", help="Only test metrics for the given CPU/PMU type", + default="cpu") + args = parser.parse_args() + outpath = Path(args.output_dir) + reportf = Path.joinpath(outpath, 'perf_report.json') + fullrule = Path.joinpath(outpath, 'full_rule.json') + datafile = Path.joinpath(outpath, 'perf_data.json') + + validator = Validator(args.rule, reportf, debug=args.debug, + datafname=datafile, fullrulefname=fullrule, workload=args.wl, + metrics=args.m, cputype=args.cputype) + ret = validator.test() + + return ret + + +if __name__ == "__main__": + import sys + sys.exit(main()) diff --git a/tools/perf/tests/shell/lib/perf_metric_validation_rules.json b/tools/perf/tests/shell/lib/perf_metric_validation_rules.json new file mode 100644 index 000000000000..eb6f59e018b7 --- /dev/null +++ b/tools/perf/tests/shell/lib/perf_metric_validation_rules.json @@ -0,0 +1,398 @@ +{ + "SkipList": [ + "tsx_aborted_cycles", + "tsx_transactional_cycles", + "C2_Pkg_Residency", + "C6_Pkg_Residency", + "C1_Core_Residency", + "C6_Core_Residency", + "tma_false_sharing", + "tma_remote_cache", + "tma_contested_accesses" + ], + "RelationshipRules": [ + { + "RuleIndex": 1, + "Formula": "a+b", + "TestType": "RelationshipTest", + "RangeLower": "c", + "RangeUpper": "c", + "ErrorThreshold": 5.0, + "Description": "Intel(R) Optane(TM) Persistent Memory(PMEM) bandwidth total includes Intel(R) Optane(TM) Persistent Memory(PMEM) read bandwidth and Intel(R) Optane(TM) Persistent Memory(PMEM) write bandwidth", + "Metrics": [ + { + "Name": "pmem_memory_bandwidth_read", + "Alias": "a" + }, + { + "Name": "pmem_memory_bandwidth_write", + "Alias": "b" + }, + { + "Name": "pmem_memory_bandwidth_total", + "Alias": "c" + } + ] + }, + { + "RuleIndex": 2, + "Formula": "a+b", + "TestType": "RelationshipTest", + "RangeLower": "c", + "RangeUpper": "c", + "ErrorThreshold": 5.0, + "Description": "DDR memory bandwidth total includes DDR memory read bandwidth and DDR memory write bandwidth", + "Metrics": [ + { + "Name": "memory_bandwidth_read", + "Alias": "a" + }, + { + "Name": "memory_bandwidth_write", + "Alias": "b" + }, + { + "Name": "memory_bandwidth_total", + "Alias": "c" + } + ] + }, + { + "RuleIndex": 3, + "Formula": "a+b", + "TestType": "RelationshipTest", + "RangeLower": "100", + "RangeUpper": "100", + "ErrorThreshold": 5.0, + "Description": "Total memory read accesses includes memory reads from last level cache (LLC) addressed to local DRAM and memory reads from the last level cache (LLC) addressed to remote DRAM.", + "Metrics": [ + { + "Name": "numa_reads_addressed_to_local_dram", + "Alias": "a" + }, + { + "Name": "numa_reads_addressed_to_remote_dram", + "Alias": "b" + } + ] + }, + { + "RuleIndex": 4, + "Formula": "a", + "TestType": "SingleMetricTest", + "RangeLower": "0.125", + "RangeUpper": "", + "ErrorThreshold": "", + "Description": "", + "Metrics": [ + { + "Name": "cpi", + "Alias": "a" + } + ] + }, + { + "RuleIndex": 5, + "Formula": "", + "TestType": "SingleMetricTest", + "RangeLower": "0", + "RangeUpper": "1", + "ErrorThreshold": 5.0, + "Description": "Ratio values should be within value range [0,1)", + "Metrics": [ + { + "Name": "loads_per_instr", + "Alias": "" + }, + { + "Name": "stores_per_instr", + "Alias": "" + }, + { + "Name": "l1d_mpi", + "Alias": "" + }, + { + "Name": "l1d_demand_data_read_hits_per_instr", + "Alias": "" + }, + { + "Name": "l1_i_code_read_misses_with_prefetches_per_instr", + "Alias": "" + }, + { + "Name": "l2_demand_data_read_hits_per_instr", + "Alias": "" + }, + { + "Name": "l2_mpi", + "Alias": "" + }, + { + "Name": "l2_demand_data_read_mpi", + "Alias": "" + }, + { + "Name": "l2_demand_code_mpi", + "Alias": "" + } + ] + }, + { + "RuleIndex": 6, + "Formula": "a+b+c+d", + "TestType": "RelationshipTest", + "RangeLower": "100", + "RangeUpper": "100", + "ErrorThreshold": 5.0, + "Description": "Sum of TMA level 1 metrics should be 100%", + "Metrics": [ + { + "Name": "tma_frontend_bound", + "Alias": "a" + }, + { + "Name": "tma_bad_speculation", + "Alias": "b" + }, + { + "Name": "tma_backend_bound", + "Alias": "c" + }, + { + "Name": "tma_retiring", + "Alias": "d" + } + ] + }, + { + "RuleIndex": 7, + "Formula": "a+b", + "TestType": "RelationshipTest", + "RangeLower": "c", + "RangeUpper": "c", + "ErrorThreshold": 5.0, + "Description": "Sum of the level 2 children should equal level 1 parent", + "Metrics": [ + { + "Name": "tma_fetch_latency", + "Alias": "a" + }, + { + "Name": "tma_fetch_bandwidth", + "Alias": "b" + }, + { + "Name": "tma_frontend_bound", + "Alias": "c" + } + ] + }, + { + "RuleIndex": 8, + "Formula": "a+b", + "TestType": "RelationshipTest", + "RangeLower": "c", + "RangeUpper": "c", + "ErrorThreshold": 5.0, + "Description": "Sum of the level 2 children should equal level 1 parent", + "Metrics": [ + { + "Name": "tma_branch_mispredicts", + "Alias": "a" + }, + { + "Name": "tma_machine_clears", + "Alias": "b" + }, + { + "Name": "tma_bad_speculation", + "Alias": "c" + } + ] + }, + { + "RuleIndex": 9, + "Formula": "a+b", + "TestType": "RelationshipTest", + "RangeLower": "c", + "RangeUpper": "c", + "ErrorThreshold": 5.0, + "Description": "Sum of the level 2 children should equal level 1 parent", + "Metrics": [ + { + "Name": "tma_memory_bound", + "Alias": "a" + }, + { + "Name": "tma_core_bound", + "Alias": "b" + }, + { + "Name": "tma_backend_bound", + "Alias": "c" + } + ] + }, + { + "RuleIndex": 10, + "Formula": "a+b", + "TestType": "RelationshipTest", + "RangeLower": "c", + "RangeUpper": "c", + "ErrorThreshold": 5.0, + "Description": "Sum of the level 2 children should equal level 1 parent", + "Metrics": [ + { + "Name": "tma_light_operations", + "Alias": "a" + }, + { + "Name": "tma_heavy_operations", + "Alias": "b" + }, + { + "Name": "tma_retiring", + "Alias": "c" + } + ] + }, + { + "RuleIndex": 11, + "Formula": "a+b+c", + "TestType": "RelationshipTest", + "RangeLower": "100", + "RangeUpper": "100", + "ErrorThreshold": 5.0, + "Description": "The all_requests includes the memory_page_empty, memory_page_misses, and memory_page_hits equals.", + "Metrics": [ + { + "Name": "memory_page_empty_vs_all_requests", + "Alias": "a" + }, + { + "Name": "memory_page_misses_vs_all_requests", + "Alias": "b" + }, + { + "Name": "memory_page_hits_vs_all_requests", + "Alias": "c" + } + ] + }, + { + "RuleIndex": 12, + "Formula": "a-b", + "TestType": "RelationshipTest", + "RangeLower": "0", + "RangeUpper": "", + "ErrorThreshold": 5.0, + "Description": "CPU utilization in kernel mode should always be <= cpu utilization", + "Metrics": [ + { + "Name": "cpu_utilization", + "Alias": "a" + }, + { + "Name": "cpu_utilization_in_kernel_mode", + "Alias": "b" + } + ] + }, + { + "RuleIndex": 13, + "Formula": "a-b", + "TestType": "RelationshipTest", + "RangeLower": "0", + "RangeUpper": "", + "ErrorThreshold": 5.0, + "Description": "Total L2 misses per instruction should be >= L2 demand data read misses per instruction", + "Metrics": [ + { + "Name": "l2_mpi", + "Alias": "a" + }, + { + "Name": "l2_demand_data_read_mpi", + "Alias": "b" + } + ] + }, + { + "RuleIndex": 14, + "Formula": "a-b", + "TestType": "RelationshipTest", + "RangeLower": "0", + "RangeUpper": "", + "ErrorThreshold": 5.0, + "Description": "Total L2 misses per instruction should be >= L2 demand code misses per instruction", + "Metrics": [ + { + "Name": "l2_mpi", + "Alias": "a" + }, + { + "Name": "l2_demand_code_mpi", + "Alias": "b" + } + ] + }, + { + "RuleIndex": 15, + "Formula": "b+c+d", + "TestType": "RelationshipTest", + "RangeLower": "a", + "RangeUpper": "a", + "ErrorThreshold": 5.0, + "Description": "L3 data read, rfo, code misses per instruction equals total L3 misses per instruction.", + "Metrics": [ + { + "Name": "llc_mpi", + "Alias": "a" + }, + { + "Name": "llc_data_read_mpi_demand_plus_prefetch", + "Alias": "b" + }, + { + "Name": "llc_rfo_read_mpi_demand_plus_prefetch", + "Alias": "c" + }, + { + "Name": "llc_code_read_mpi_demand_plus_prefetch", + "Alias": "d" + } + ] + }, + { + "RuleIndex": 16, + "Formula": "a", + "TestType": "SingleMetricTest", + "RangeLower": "0", + "RangeUpper": "8", + "ErrorThreshold": 0.0, + "Description": "Setting generous range for allowable frequencies", + "Metrics": [ + { + "Name": "uncore_freq", + "Alias": "a" + } + ] + }, + { + "RuleIndex": 17, + "Formula": "a", + "TestType": "SingleMetricTest", + "RangeLower": "0", + "RangeUpper": "8", + "ErrorThreshold": 0.0, + "Description": "Setting generous range for allowable frequencies", + "Metrics": [ + { + "Name": "cpu_operating_frequency", + "Alias": "a" + } + ] + } + ] +}
\ No newline at end of file diff --git a/tools/perf/tests/shell/lib/probe.sh b/tools/perf/tests/shell/lib/probe.sh index 51e3f60baba0..5aa6e2ec5734 100644 --- a/tools/perf/tests/shell/lib/probe.sh +++ b/tools/perf/tests/shell/lib/probe.sh @@ -1,3 +1,4 @@ +#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017 diff --git a/tools/perf/tests/shell/lib/probe_vfs_getname.sh b/tools/perf/tests/shell/lib/probe_vfs_getname.sh index c2cc42daf924..58debce9ab42 100644 --- a/tools/perf/tests/shell/lib/probe_vfs_getname.sh +++ b/tools/perf/tests/shell/lib/probe_vfs_getname.sh @@ -1,3 +1,4 @@ +#!/bin/sh # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017 perf probe -l 2>&1 | grep -q probe:vfs_getname @@ -10,15 +11,42 @@ cleanup_probe_vfs_getname() { } add_probe_vfs_getname() { - local verbose=$1 + add_probe_verbose=$1 if [ $had_vfs_getname -eq 1 ] ; then - line=$(perf probe -L getname_flags 2>&1 | egrep 'result.*=.*filename;' | sed -r 's/[[:space:]]+([[:digit:]]+)[[:space:]]+result->uptr.*/\1/') + result_initname_re="[[:space:]]+([[:digit:]]+)[[:space:]]+initname.*" + line=$(perf probe -L getname_flags 2>&1 | grep -E "$result_initname_re" | sed -r "s/$result_initname_re/\1/") + + # Search the old regular expressions so that this will + # pass on older kernels as well. + if [ -z "$line" ] ; then + result_filename_re="[[:space:]]+([[:digit:]]+)[[:space:]]+result->uptr.*" + line=$(perf probe -L getname_flags 2>&1 | grep -E "$result_filename_re" | sed -r "s/$result_filename_re/\1/") + fi + + if [ -z "$line" ] ; then + result_aname_re="[[:space:]]+([[:digit:]]+)[[:space:]]+result->aname = NULL;" + line=$(perf probe -L getname_flags 2>&1 | grep -E "$result_aname_re" | sed -r "s/$result_aname_re/\1/") + fi + + if [ -z "$line" ] ; then + echo "Could not find probeable line" + return 2 + fi + perf probe -q "vfs_getname=getname_flags:${line} pathname=result->name:string" || \ - perf probe $verbose "vfs_getname=getname_flags:${line} pathname=filename:ustring" + perf probe $add_probe_verbose "vfs_getname=getname_flags:${line} pathname=filename:ustring" || return 1 fi } skip_if_no_debuginfo() { - add_probe_vfs_getname -v 2>&1 | egrep -q "^(Failed to find the path for kernel|Debuginfo-analysis is not supported)" && return 2 + add_probe_vfs_getname -v 2>&1 | grep -E -q "^(Failed to find the path for the kernel|Debuginfo-analysis is not supported)|(file has no debug information)" && return 2 return 1 } + +# check if perf is compiled with libtraceevent support +skip_no_probe_record_support() { + if [ $had_vfs_getname -eq 1 ] ; then + perf check feature -q libtraceevent && return 1 + return 2 + fi +} diff --git a/tools/perf/tests/shell/lib/setup_python.sh b/tools/perf/tests/shell/lib/setup_python.sh new file mode 100644 index 000000000000..c2fce1793538 --- /dev/null +++ b/tools/perf/tests/shell/lib/setup_python.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +if [ "x$PYTHON" = "x" ] +then + python3 --version >/dev/null 2>&1 && PYTHON=python3 +fi +if [ "x$PYTHON" = "x" ] +then + python --version >/dev/null 2>&1 && PYTHON=python +fi +if [ "x$PYTHON" = "x" ] +then + echo Skipping test, python not detected please set environment variable PYTHON. + exit 2 +fi diff --git a/tools/perf/tests/shell/lib/stat_output.sh b/tools/perf/tests/shell/lib/stat_output.sh new file mode 100644 index 000000000000..c2ec7881ec1d --- /dev/null +++ b/tools/perf/tests/shell/lib/stat_output.sh @@ -0,0 +1,195 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# Return true if perf_event_paranoid is > $1 and not running as root. +function ParanoidAndNotRoot() +{ + [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ] +} + +# $1 name $2 extra_opt +check_no_args() +{ + echo -n "Checking $1 output: no args " + perf stat $2 true + commachecker --no-args + echo "[Success]" +} + +check_system_wide() +{ + echo -n "Checking $1 output: system wide " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat -a $2 true + commachecker --system-wide + echo "[Success]" +} + +check_system_wide_no_aggr() +{ + echo -n "Checking $1 output: system wide no aggregation " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat -A -a --no-merge $2 true + commachecker --system-wide-no-aggr + echo "[Success]" +} + +check_interval() +{ + echo -n "Checking $1 output: interval " + perf stat -I 1000 $2 true + commachecker --interval + echo "[Success]" +} + +check_event() +{ + echo -n "Checking $1 output: event " + perf stat -e cpu-clock $2 true + commachecker --event + echo "[Success]" +} + +check_per_core() +{ + echo -n "Checking $1 output: per core " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat --per-core -a $2 true + commachecker --per-core + echo "[Success]" +} + +check_per_thread() +{ + echo -n "Checking $1 output: per thread " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat --per-thread -p $$ $2 true + commachecker --per-thread + echo "[Success]" +} + +check_per_cache_instance() +{ + echo -n "Checking $1 output: per cache instance " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat --per-cache -a $2 true + commachecker --per-cache + echo "[Success]" +} + +check_per_cluster() +{ + echo -n "Checking $1 output: per cluster " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat --per-cluster -a $2 true + echo "[Success]" +} + +check_per_die() +{ + echo -n "Checking $1 output: per die " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat --per-die -a $2 true + commachecker --per-die + echo "[Success]" +} + +check_per_node() +{ + echo -n "Checking $1 output: per node " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat --per-node -a $2 true + commachecker --per-node + echo "[Success]" +} + +check_per_socket() +{ + echo -n "Checking $1 output: per socket " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoid and not root" + return + fi + perf stat --per-socket -a $2 true + commachecker --per-socket + echo "[Success]" +} + +check_metric_only() +{ + echo -n "Checking $1 output: metric only " + if [ "$(uname -m)" = "s390x" ] && ! grep '^facilities' /proc/cpuinfo | grep -qw 67 + then + echo "[Skip] CPU-measurement counter facility not installed" + return + fi + perf stat --metric-only $2 -e instructions,cycles true + commachecker --metric-only + echo "[Success]" +} + +# The perf stat options for per-socket, per-core, per-die +# and -A ( no_aggr mode ) uses the info fetched from this +# directory: "/sys/devices/system/cpu/cpu*/topology". For +# example, socket value is fetched from "physical_package_id" +# file in topology directory. +# Reference: cpu__get_topology_int in util/cpumap.c +# If the platform doesn't expose topology information, values +# will be set to -1. For example, incase of pSeries platform +# of powerpc, value for "physical_package_id" is restricted +# and set to -1. Check here validates the socket-id read from +# topology file before proceeding further + +FILE_LOC="/sys/devices/system/cpu/cpu*/topology/" +FILE_NAME="physical_package_id" + +function check_for_topology() +{ + if ! ParanoidAndNotRoot 0 + then + socket_file=`ls $FILE_LOC/$FILE_NAME | head -n 1` + [ -z $socket_file ] && { + echo 0 + return + } + socket_id=`cat $socket_file` + [ $socket_id == -1 ] && { + echo 1 + return + } + fi + echo 0 +} diff --git a/tools/perf/tests/shell/lib/waiting.sh b/tools/perf/tests/shell/lib/waiting.sh new file mode 100644 index 000000000000..bdd5a7c71591 --- /dev/null +++ b/tools/perf/tests/shell/lib/waiting.sh @@ -0,0 +1,78 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +tenths=date\ +%s%1N + +# Wait for PID $1 to have $2 number of threads started +# Time out after $3 tenths of a second or 5 seconds if $3 is "" +wait_for_threads() +{ + tm_out=$3 ; [ -n "${tm_out}" ] || tm_out=50 + start_time=$($tenths) + while [ -e "/proc/$1/task" ] ; do + th_cnt=$(find "/proc/$1/task" -mindepth 1 -maxdepth 1 -printf x | wc -c) + if [ "${th_cnt}" -ge "$2" ] ; then + return 0 + fi + # Wait at most tm_out tenths of a second + if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then + echo "PID $1 does not have $2 threads" + return 1 + fi + done + return 1 +} + +# Wait for perf record -vvv 2>$2 with PID $1 to start by looking at file $2 +# It depends on capturing perf record debug message "perf record has started" +# Time out after $3 tenths of a second or 5 seconds if $3 is "" +wait_for_perf_to_start() +{ + tm_out=$3 ; [ -n "${tm_out}" ] || tm_out=50 + echo "Waiting for \"perf record has started\" message" + start_time=$($tenths) + while [ -e "/proc/$1" ] ; do + if grep -q "perf record has started" "$2" ; then + echo OK + break + fi + # Wait at most tm_out tenths of a second + if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then + echo "perf recording did not start" + return 1 + fi + done + return 0 +} + +# Wait for process PID %1 to exit +# Time out after $2 tenths of a second or 5 seconds if $2 is "" +wait_for_process_to_exit() +{ + tm_out=$2 ; [ -n "${tm_out}" ] || tm_out=50 + start_time=$($tenths) + while [ -e "/proc/$1" ] ; do + # Wait at most tm_out tenths of a second + if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then + echo "PID $1 did not exit as expected" + return 1 + fi + done + return 0 +} + +# Check if PID $1 is still running after $2 tenths of a second +# or 0.3 seconds if $2 is "" +is_running() +{ + tm_out=$2 ; [ -n "${tm_out}" ] || tm_out=3 + start_time=$($tenths) + while [ -e "/proc/$1" ] ; do + # Check for at least tm_out tenths of a second + if [ $(($($tenths) - start_time)) -gt $tm_out ] ; then + return 0 + fi + done + echo "PID $1 exited prematurely" + return 1 +} diff --git a/tools/perf/tests/shell/list.sh b/tools/perf/tests/shell/list.sh new file mode 100755 index 000000000000..76a9846cff22 --- /dev/null +++ b/tools/perf/tests/shell/list.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# perf list tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +list_output=$(mktemp /tmp/__perf_test.list_output.json.XXXXX) + +cleanup() { + rm -f "${list_output}" + + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_list_json() { + echo "Json output test" + # Generate perf list json output into list_output file. + perf list -j -o "${list_output}" + # Validate the json using python, redirect the json copy to /dev/null as + # otherwise the test may block writing to stdout. + $PYTHON -m json.tool "${list_output}" /dev/null + echo "Json output test [Success]" +} + +test_list_json +cleanup +exit 0 diff --git a/tools/perf/tests/shell/lock_contention.sh b/tools/perf/tests/shell/lock_contention.sh new file mode 100755 index 000000000000..30d195d4c62f --- /dev/null +++ b/tools/perf/tests/shell/lock_contention.sh @@ -0,0 +1,336 @@ +#!/bin/sh +# kernel lock contention analysis test +# SPDX-License-Identifier: GPL-2.0 + +set -e + +err=0 +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +result=$(mktemp /tmp/__perf_test.result.XXXXX) + +cleanup() { + rm -f ${perfdata} + rm -f ${result} + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit ${err} +} +trap trap_cleanup EXIT TERM INT + +check() { + if [ "$(id -u)" != 0 ]; then + echo "[Skip] No root permission" + err=2 + exit + fi + + if ! perf list tracepoint | grep -q lock:contention_begin; then + echo "[Skip] No lock contention tracepoints" + err=2 + exit + fi + + # shellcheck disable=SC2046 + if [ `nproc` -lt 4 ]; then + echo "[Skip] Low number of CPUs (`nproc`), lock event cannot be triggered certainly" + err=2 + exit + fi +} + +test_record() +{ + echo "Testing perf lock record and perf lock contention" + perf lock record -o ${perfdata} -- perf bench sched messaging > /dev/null 2>&1 + # the output goes to the stderr and we expect only 1 output (-E 1) + perf lock contention -i ${perfdata} -E 1 -q 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] Recorded result count is not 1:" "$(cat "${result}" | wc -l)" + err=1 + exit + fi +} + +test_bpf() +{ + echo "Testing perf lock contention --use-bpf" + + if ! perf lock con -b true > /dev/null 2>&1 ; then + echo "[Skip] No BPF support" + return + fi + + # the perf lock contention output goes to the stderr + perf lock con -a -b -E 1 -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] BPF result count is not 1:" "$(cat "${result}" | wc -l)" + err=1 + exit + fi +} + +test_record_concurrent() +{ + echo "Testing perf lock record and perf lock contention at the same time" + perf lock record -o- -- perf bench sched messaging 2> /dev/null | \ + perf lock contention -i- -E 1 -q 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] Recorded result count is not 1:" "$(cat "${result}" | wc -l)" + err=1 + exit + fi +} + +test_aggr_task() +{ + echo "Testing perf lock contention --threads" + perf lock contention -i ${perfdata} -t -E 1 -q 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] Recorded result count is not 1:" "$(cat "${result}" | wc -l)" + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + return + fi + + # the perf lock contention output goes to the stderr + perf lock con -a -b -t -E 1 -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] BPF result count is not 1:" "$(cat "${result}" | wc -l)" + err=1 + exit + fi +} + +test_aggr_addr() +{ + echo "Testing perf lock contention --lock-addr" + perf lock contention -i ${perfdata} -l -E 1 -q 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] Recorded result count is not 1:" "$(cat "${result}" | wc -l)" + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + return + fi + + # the perf lock contention output goes to the stderr + perf lock con -a -b -l -E 1 -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] BPF result count is not 1:" "$(cat "${result}" | wc -l)" + err=1 + exit + fi +} + +test_aggr_cgroup() +{ + echo "Testing perf lock contention --lock-cgroup" + + if ! perf lock con -b true > /dev/null 2>&1 ; then + echo "[Skip] No BPF support" + return + fi + + # the perf lock contention output goes to the stderr + perf lock con -a -b -g -E 1 -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] BPF result count is not 1:" "$(cat "${result}" | wc -l)" + err=1 + exit + fi +} + +test_type_filter() +{ + echo "Testing perf lock contention --type-filter (w/ spinlock)" + perf lock contention -i ${perfdata} -Y spinlock -q 2> ${result} + if [ "$(grep -c -v spinlock "${result}")" != "0" ]; then + echo "[Fail] Recorded result should not have non-spinlocks:" "$(cat "${result}")" + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + return + fi + + perf lock con -a -b -Y spinlock -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(grep -c -v spinlock "${result}")" != "0" ]; then + echo "[Fail] BPF result should not have non-spinlocks:" "$(cat "${result}")" + err=1 + exit + fi +} + +test_lock_filter() +{ + echo "Testing perf lock contention --lock-filter (w/ tasklist_lock)" + perf lock contention -i ${perfdata} -l -q 2> ${result} + if [ "$(grep -c tasklist_lock "${result}")" != "1" ]; then + echo "[Skip] Could not find 'tasklist_lock'" + return + fi + + perf lock contention -i ${perfdata} -L tasklist_lock -q 2> ${result} + + # find out the type of tasklist_lock + test_lock_filter_type=$(head -1 "${result}" | awk '{ print $8 }' | sed -e 's/:.*//') + + if [ "$(grep -c -v "${test_lock_filter_type}" "${result}")" != "0" ]; then + echo "[Fail] Recorded result should not have non-${test_lock_filter_type} locks:" "$(cat "${result}")" + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + return + fi + + perf lock con -a -b -L tasklist_lock -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(grep -c -v "${test_lock_filter_type}" "${result}")" != "0" ]; then + echo "[Fail] BPF result should not have non-${test_lock_filter_type} locks:" "$(cat "${result}")" + err=1 + exit + fi +} + +test_stack_filter() +{ + echo "Testing perf lock contention --callstack-filter (w/ unix_stream)" + perf lock contention -i ${perfdata} -v -q 2> ${result} + if [ "$(grep -c unix_stream "${result}")" = "0" ]; then + echo "[Skip] Could not find 'unix_stream'" + return + fi + + perf lock contention -i ${perfdata} -E 1 -S unix_stream -q 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] Recorded result should have a lock from unix_stream:" "$(cat "${result}")" + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + return + fi + + perf lock con -a -b -S unix_stream -E 1 -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] BPF result should have a lock from unix_stream:" "$(cat "${result}")" + err=1 + exit + fi +} + +test_aggr_task_stack_filter() +{ + echo "Testing perf lock contention --callstack-filter with task aggregation" + perf lock contention -i ${perfdata} -v -q 2> ${result} + if [ "$(grep -c unix_stream "${result}")" = "0" ]; then + echo "[Skip] Could not find 'unix_stream'" + return + fi + + perf lock contention -i ${perfdata} -t -E 1 -S unix_stream -q 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] Recorded result should have a task from unix_stream:" "$(cat "${result}")" + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + return + fi + + perf lock con -a -b -t -S unix_stream -E 1 -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] BPF result should have a task from unix_stream:" "$(cat "${result}")" + err=1 + exit + fi +} +test_cgroup_filter() +{ + echo "Testing perf lock contention --cgroup-filter" + + if ! perf lock con -b true > /dev/null 2>&1 ; then + echo "[Skip] No BPF support" + return + fi + + perf lock con -a -b -g -E 1 -F wait_total -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] BPF result should have a cgroup result:" "$(cat "${result}")" + err=1 + exit + fi + + cgroup=$(cat "${result}" | awk '{ print $3 }') + perf lock con -a -b -g -E 1 -G "${cgroup}" -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ "$(cat "${result}" | wc -l)" != "1" ]; then + echo "[Fail] BPF result should have a result with cgroup filter:" "$(cat "${cgroup}")" + err=1 + exit + fi +} + + +test_csv_output() +{ + echo "Testing perf lock contention CSV output" + perf lock contention -i ${perfdata} -E 1 -x , --output ${result} + # count the number of commas in the header + # it should have 5: contended, total-wait, max-wait, avg-wait, type, caller + header=$(grep "# output:" ${result} | tr -d -c , | wc -c) + if [ "${header}" != "5" ]; then + echo "[Fail] Recorded result does not have enough output columns: ${header} != 5" + err=1 + exit + fi + # count the number of commas in the output + output=$(grep -v "^#" ${result} | tr -d -c , | wc -c) + if [ "${header}" != "${output}" ]; then + echo "[Fail] Recorded result does not match the number of commas: ${header} != ${output}" + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + echo "[Skip] No BPF support" + return + fi + + # the perf lock contention output goes to the stderr + perf lock con -a -b -E 1 -x , --output ${result} -- perf bench sched messaging > /dev/null 2>&1 + output=$(grep -v "^#" ${result} | tr -d -c , | wc -c) + if [ "${header}" != "${output}" ]; then + echo "[Fail] BPF result does not match the number of commas: ${header} != ${output}" + err=1 + exit + fi +} + +check + +test_record +test_bpf +test_record_concurrent +test_aggr_task +test_aggr_addr +test_aggr_cgroup +test_type_filter +test_lock_filter +test_stack_filter +test_aggr_task_stack_filter +test_cgroup_filter +test_csv_output + +exit ${err} diff --git a/tools/perf/tests/shell/perf-report-hierarchy.sh b/tools/perf/tests/shell/perf-report-hierarchy.sh new file mode 100755 index 000000000000..02e3b6aee4ed --- /dev/null +++ b/tools/perf/tests/shell/perf-report-hierarchy.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# perf report --hierarchy +# SPDX-License-Identifier: GPL-2.0 +# Arnaldo Carvalho de Melo <acme@redhat.com> + +set -e + +temp_dir=$(mktemp -d /tmp/perf-test-report.XXXXXXXXXX) + +cleanup() +{ + trap - EXIT TERM INT + sane=$(echo "${temp_dir}" | cut -b 1-21) + if [ "${sane}" = "/tmp/perf-test-report" ] ; then + echo "--- Cleaning up ---" + rm -rf "${temp_dir:?}/"* + rmdir "${temp_dir}" + fi +} + +trap_cleanup() +{ + cleanup + exit 1 +} + +trap trap_cleanup EXIT TERM INT + +test_report_hierarchy() +{ + echo "perf report --hierarchy" + + perf_data="${temp_dir}/perf-report-hierarchy-perf.data" + perf record -o "${perf_data}" uname + perf report --hierarchy -i "${perf_data}" > /dev/null + echo "perf report --hierarchy test [Success]" +} + +test_report_hierarchy + +cleanup + +exit 0 diff --git a/tools/perf/tests/shell/perftool-testsuite_probe.sh b/tools/perf/tests/shell/perftool-testsuite_probe.sh new file mode 100755 index 000000000000..3863df16c19b --- /dev/null +++ b/tools/perf/tests/shell/perftool-testsuite_probe.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# perftool-testsuite_probe (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +[ "$(id -u)" = 0 ] || exit 2 +test -d "$(dirname "$0")/base_probe" || exit 2 +cd "$(dirname "$0")/base_probe" || exit 2 +status=0 + +PERFSUITE_RUN_DIR=$(mktemp -d /tmp/"$(basename "$0" .sh)".XXX) +export PERFSUITE_RUN_DIR + +for testcase in setup.sh test_*; do # skip setup.sh if not present or not executable + test -x "$testcase" || continue + ./"$testcase" + (( status += $? )) +done + +if ! [ "$PERFTEST_KEEP_LOGS" = "y" ]; then + rm -rf "$PERFSUITE_RUN_DIR" +fi + +test $status -ne 0 && exit 1 +exit 0 diff --git a/tools/perf/tests/shell/perftool-testsuite_report.sh b/tools/perf/tests/shell/perftool-testsuite_report.sh new file mode 100755 index 000000000000..a8cf75b4e77e --- /dev/null +++ b/tools/perf/tests/shell/perftool-testsuite_report.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# perftool-testsuite_report (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +test -d "$(dirname "$0")/base_report" || exit 2 +cd "$(dirname "$0")/base_report" || exit 2 +status=0 + +PERFSUITE_RUN_DIR=$(mktemp -d /tmp/"$(basename "$0" .sh)".XXX) +export PERFSUITE_RUN_DIR + +for testcase in setup.sh test_*; do # skip setup.sh if not present or not executable + test -x "$testcase" || continue + ./"$testcase" + (( status += $? )) +done + +if ! [ "$PERFTEST_KEEP_LOGS" = "y" ]; then + rm -rf "$PERFSUITE_RUN_DIR" +fi + +test $status -ne 0 && exit 1 +exit 0 diff --git a/tools/perf/tests/shell/pipe_test.sh b/tools/perf/tests/shell/pipe_test.sh new file mode 100755 index 000000000000..e459aa99a951 --- /dev/null +++ b/tools/perf/tests/shell/pipe_test.sh @@ -0,0 +1,127 @@ +#!/bin/bash +# perf pipe recording and injection test +# SPDX-License-Identifier: GPL-2.0 + +shelldir=$(dirname "$0") +# shellcheck source=lib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +sym="noploop" + +skip_test_missing_symbol ${sym} + +data=$(mktemp /tmp/perf.data.XXXXXX) +data2=$(mktemp /tmp/perf.data2.XXXXXX) +prog="perf test -w noploop" +[ "$(uname -m)" = "s390x" ] && prog="$prog 3" +err=0 + +set -e + +cleanup() { + rm -rf "${data}" + rm -rf "${data}".old + rm -rf "${data2}" + rm -rf "${data2}".old + + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_record_report() { + echo + echo "Record+report pipe test" + + task="perf" + if ! perf record -e task-clock:u -o - ${prog} | perf report -i - --task | grep -q ${task} + then + echo "Record+report pipe test [Failed - cannot find the test file in the perf report #1]" + err=1 + return + fi + + if ! perf record -g -e task-clock:u -o - ${prog} | perf report -i - --task | grep -q ${task} + then + echo "Record+report pipe test [Failed - cannot find the test file in the perf report #2]" + err=1 + return + fi + + perf record -g -e task-clock:u -o - ${prog} > ${data} + if ! perf report -i ${data} --task | grep -q ${task} + then + echo "Record+report pipe test [Failed - cannot find the test file in the perf report #3]" + err=1 + return + fi + + echo "Record+report pipe test [Success]" +} + +test_inject_bids() { + inject_opt=$1 + + echo + echo "Inject ${inject_opt} build-ids test" + + if ! perf record -e task-clock:u -o - ${prog} | perf inject ${inject_opt}| perf report -i - | grep -q ${sym} + then + echo "Inject build-ids test [Failed - cannot find noploop function in pipe #1]" + err=1 + return + fi + + if ! perf record -g -e task-clock:u -o - ${prog} | perf inject ${inject_opt} | perf report -i - | grep -q ${sym} + then + echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #2]" + err=1 + return + fi + + perf record -e task-clock:u -o - ${prog} | perf inject ${inject_opt} -o ${data} + if ! perf report -i ${data} | grep -q ${sym}; then + echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #3]" + err=1 + return + fi + + perf record -e task-clock:u -o ${data} ${prog} + if ! perf inject ${inject_opt} -i ${data} | perf report -i - | grep -q ${sym}; then + echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #4]" + err=1 + return + fi + + perf record -e task-clock:u -o - ${prog} > ${data} + if ! perf inject ${inject_opt} -i ${data} | perf report -i - | grep -q ${sym}; then + echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #5]" + err=1 + return + fi + + perf record -e task-clock:u -o - ${prog} > ${data} + perf inject ${inject_opt} -i ${data} -o ${data2} + if ! perf report -i ${data2} | grep -q ${sym}; then + echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #6]" + err=1 + return + fi + + echo "Inject ${inject_opt} build-ids test [Success]" +} + +test_record_report +test_inject_bids -B +test_inject_bids -b +test_inject_bids --buildid-all +test_inject_bids --mmap2-buildid-all + +cleanup +exit $err + diff --git a/tools/perf/tests/shell/probe_vfs_getname.sh b/tools/perf/tests/shell/probe_vfs_getname.sh index 5d1b63d3f3e1..0f52654c914a 100755 --- a/tools/perf/tests/shell/probe_vfs_getname.sh +++ b/tools/perf/tests/shell/probe_vfs_getname.sh @@ -1,16 +1,25 @@ #!/bin/sh -# Add vfs_getname probe to get syscall args filenames +# Add vfs_getname probe to get syscall args filenames (exclusive) # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017 -. $(dirname $0)/lib/probe.sh +# shellcheck source=lib/probe.sh +. "$(dirname $0)"/lib/probe.sh skip_if_no_perf_probe || exit 2 +[ "$(id -u)" = 0 ] || exit 2 -. $(dirname $0)/lib/probe_vfs_getname.sh +# shellcheck source=lib/probe_vfs_getname.sh +. "$(dirname $0)"/lib/probe_vfs_getname.sh -add_probe_vfs_getname || skip_if_no_debuginfo +add_probe_vfs_getname err=$? + +if [ $err -eq 1 ] ; then + skip_if_no_debuginfo + err=$? +fi + cleanup_probe_vfs_getname exit $err diff --git a/tools/perf/tests/shell/record+probe_libc_inet_pton.sh b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh index f12a4e217968..c4bab5b5cc59 100755 --- a/tools/perf/tests/shell/record+probe_libc_inet_pton.sh +++ b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh @@ -1,5 +1,5 @@ #!/bin/sh -# probe libc's inet_pton & backtrace it with ping +# probe libc's inet_pton & backtrace it with ping (exclusive) # Installs a probe on libc's inet_pton function, that will use uprobes, # then use 'perf trace' on a ping to localhost asking for just one packet @@ -10,10 +10,13 @@ # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017 -. $(dirname $0)/lib/probe.sh +# shellcheck source=lib/probe.sh +. "$(dirname "$0")/lib/probe.sh" +# shellcheck source=lib/probe_vfs_getname.sh +. "$(dirname "$0")/lib/probe_vfs_getname.sh" libc=$(grep -w libc /proc/self/maps | head -1 | sed -r 's/.*[[:space:]](\/.*)/\1/g') -nm -Dg $libc 2>/dev/null | fgrep -q inet_pton || exit 254 +nm -Dg $libc 2>/dev/null | grep -F -q inet_pton || exit 254 event_pattern='probe_libc:inet_pton(\_[[:digit:]]+)?' @@ -22,7 +25,7 @@ add_libc_inet_pton_event() { event_name=$(perf probe -f -x $libc -a inet_pton 2>&1 | tail -n +2 | head -n -5 | \ grep -P -o "$event_pattern(?=[[:space:]]\(on inet_pton in $libc\))") - if [ $? -ne 0 -o -z "$event_name" ] ; then + if [ $? -ne 0 ] || [ -z "$event_name" ] ; then printf "FAIL: could not add event\n" return 1 fi @@ -37,36 +40,52 @@ trace_libc_inet_pton_backtrace() { case "$(uname -m)" in s390x) eventattr='call-graph=dwarf,max-stack=4' - echo "gaih_inet.*\+0x[[:xdigit:]]+[[:space:]]\($libc|inlined\)$" >> $expected - echo "(__GI_)?getaddrinfo\+0x[[:xdigit:]]+[[:space:]]\($libc|inlined\)$" >> $expected - echo "main\+0x[[:xdigit:]]+[[:space:]]\(.*/bin/ping.*\)$" >> $expected - ;; - ppc64|ppc64le) - eventattr='max-stack=4' - echo "gaih_inet.*\+0x[[:xdigit:]]+[[:space:]]\($libc\)$" >> $expected - echo "getaddrinfo\+0x[[:xdigit:]]+[[:space:]]\($libc\)$" >> $expected - echo ".*(\+0x[[:xdigit:]]+|\[unknown\])[[:space:]]\(.*/bin/ping.*\)$" >> $expected + echo "((__GI_)?getaddrinfo|text_to_binary_address)\+0x[[:xdigit:]]+[[:space:]]\($libc|inlined\)$" >> $expected + echo "(gaih_inet|main)\+0x[[:xdigit:]]+[[:space:]]\(inlined|.*/bin/ping.*\)$" >> $expected ;; *) - eventattr='max-stack=3' - echo "getaddrinfo\+0x[[:xdigit:]]+[[:space:]]\($libc\)$" >> $expected + eventattr='max-stack=4' echo ".*(\+0x[[:xdigit:]]+|\[unknown\])[[:space:]]\(.*/bin/ping.*\)$" >> $expected ;; esac perf_data=`mktemp -u /tmp/perf.data.XXX` perf_script=`mktemp -u /tmp/perf.script.XXX` + + # Check presence of libtraceevent support to run perf record + skip_no_probe_record_support "$event_name/$eventattr/" + if [ $? -eq 2 ]; then + echo "WARN: Skipping test trace_libc_inet_pton_backtrace. No libtraceevent support." + return 2 + fi + perf record -e $event_name/$eventattr/ -o $perf_data ping -6 -c 1 ::1 > /dev/null 2>&1 - perf script -i $perf_data > $perf_script + # check if perf data file got created in above step. + if [ ! -e $perf_data ]; then + printf "FAIL: perf record failed to create \"%s\" \n" "$perf_data" + return 1 + fi + perf script -i $perf_data | tac | grep -m1 ^ping -B9 | tac > $perf_script - exec 3<$perf_script exec 4<$expected - while read line <&3 && read -r pattern <&4; do + while read -r pattern <&4; do + echo "Pattern: $pattern" [ -z "$pattern" ] && break - echo $line - echo "$line" | egrep -q "$pattern" - if [ $? -ne 0 ] ; then - printf "FAIL: expected backtrace entry \"%s\" got \"%s\"\n" "$pattern" "$line" + + found=0 + + # Search lines in the perf script result + exec 3<$perf_script + while read line <&3; do + [ -z "$line" ] && break + echo " Matching: $line" + ! echo "$line" | grep -E -q "$pattern" + found=$? + [ $found -eq 1 ] && break + done + + if [ $found -ne 1 ] ; then + printf "FAIL: Didn't find the expected backtrace entry \"%s\"\n" "$pattern" return 1 fi done @@ -85,7 +104,8 @@ delete_libc_inet_pton_event() { } # Check for IPv6 interface existence -ip a sh lo | fgrep -q inet6 || exit 2 +ip a sh lo | grep -F -q inet6 || exit 2 +[ "$(id -u)" = 0 ] || exit 2 skip_if_no_perf_probe && \ add_libc_inet_pton_event && \ diff --git a/tools/perf/tests/shell/record+script_probe_vfs_getname.sh b/tools/perf/tests/shell/record+script_probe_vfs_getname.sh index 54030c18bfc2..1ad252f0d36e 100755 --- a/tools/perf/tests/shell/record+script_probe_vfs_getname.sh +++ b/tools/perf/tests/shell/record+script_probe_vfs_getname.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Use vfs_getname probe to get syscall args filenames +# Use vfs_getname probe to get syscall args filenames (exclusive) # Uses the 'perf test shell' library to add probe:vfs_getname to the system # then use it with 'perf record' using 'touch' to write to a temp file, then @@ -9,32 +9,47 @@ # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017 -. $(dirname $0)/lib/probe.sh +# shellcheck source=lib/probe.sh +. "$(dirname "$0")/lib/probe.sh" skip_if_no_perf_probe || exit 2 +[ "$(id -u)" = 0 ] || exit 2 -. $(dirname $0)/lib/probe_vfs_getname.sh - -perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) -file=$(mktemp /tmp/temporary_file.XXXXX) +# shellcheck source=lib/probe_vfs_getname.sh +. "$(dirname "$0")/lib/probe_vfs_getname.sh" record_open_file() { echo "Recording open file:" - perf record -o ${perfdata} -e probe:vfs_getname touch $file + # Check presence of libtraceevent support to run perf record + skip_no_probe_record_support "probe:vfs_getname*" + if [ $? -eq 2 ]; then + echo "WARN: Skipping test record_open_file. No libtraceevent support" + return 2 + fi + perf record -o ${perfdata} -e probe:vfs_getname\* touch $file } perf_script_filenames() { echo "Looking at perf.data file for vfs_getname records for the file we touched:" perf script -i ${perfdata} | \ - egrep " +touch +[0-9]+ +\[[0-9]+\] +[0-9]+\.[0-9]+: +probe:vfs_getname: +\([[:xdigit:]]+\) +pathname=\"${file}\"" + grep -E " +touch +[0-9]+ +\[[0-9]+\] +[0-9]+\.[0-9]+: +probe:vfs_getname[_0-9]*: +\([[:xdigit:]]+\) +pathname=\"${file}\"" } -add_probe_vfs_getname || skip_if_no_debuginfo +add_probe_vfs_getname err=$? + +if [ $err -eq 1 ] ; then + skip_if_no_debuginfo + err=$? +fi + if [ $err -ne 0 ] ; then exit $err fi +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +file=$(mktemp /tmp/temporary_file.XXXXX) + record_open_file && perf_script_filenames err=$? rm -f ${perfdata} diff --git a/tools/perf/tests/shell/record+zstd_comp_decomp.sh b/tools/perf/tests/shell/record+zstd_comp_decomp.sh index 63a91ec473bb..8929046e9057 100755 --- a/tools/perf/tests/shell/record+zstd_comp_decomp.sh +++ b/tools/perf/tests/shell/record+zstd_comp_decomp.sh @@ -12,25 +12,26 @@ skip_if_no_z_record() { collect_z_record() { echo "Collecting compressed record file:" - $perf_tool record -o $trace_file -g -z -F 5000 -- \ + [ "$(uname -m)" != s390x ] && gflag='-g' + $perf_tool record -o "$trace_file" $gflag -z -F 5000 -- \ dd count=500 if=/dev/urandom of=/dev/null } check_compressed_stats() { echo "Checking compressed events stats:" - $perf_tool report -i $trace_file --header --stats | \ + $perf_tool report -i "$trace_file" --header --stats | \ grep -E "(# compressed : Zstd,)|(COMPRESSED events:)" } check_compressed_output() { - $perf_tool inject -i $trace_file -o $trace_file.decomp && - $perf_tool report -i $trace_file --stdio | head -n -3 > $trace_file.comp.output && - $perf_tool report -i $trace_file.decomp --stdio | head -n -3 > $trace_file.decomp.output && - diff $trace_file.comp.output $trace_file.decomp.output + $perf_tool inject -i "$trace_file" -o "$trace_file.decomp" && + $perf_tool report -i "$trace_file" --stdio -F comm,dso,sym | head -n -3 > "$trace_file.comp.output" && + $perf_tool report -i "$trace_file.decomp" --stdio -F comm,dso,sym | head -n -3 > "$trace_file.decomp.output" && + diff "$trace_file.comp.output" "$trace_file.decomp.output" } skip_if_no_z_record || exit 2 collect_z_record && check_compressed_stats && check_compressed_output err=$? -rm -f $trace_file* +rm -f "$trace_file*" exit $err diff --git a/tools/perf/tests/shell/record.sh b/tools/perf/tests/shell/record.sh new file mode 100755 index 000000000000..587f62e34414 --- /dev/null +++ b/tools/perf/tests/shell/record.sh @@ -0,0 +1,356 @@ +#!/bin/bash +# perf record tests (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +set -e + +shelldir=$(dirname "$0") +# shellcheck source=lib/waiting.sh +. "${shelldir}"/lib/waiting.sh + +# shellcheck source=lib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +testsym="test_loop" + +skip_test_missing_symbol ${testsym} + +err=0 +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +script_output=$(mktemp /tmp/__perf_test.perf.data.XXXXX.script) +testprog="perf test -w thloop" +cpu_pmu_dir="/sys/bus/event_source/devices/cpu*" +br_cntr_file="/caps/branch_counter_nr" +br_cntr_output="branch stack counters" +br_cntr_script_output="br_cntr: A" + +default_fd_limit=$(ulimit -Sn) +# With option --threads=cpu the number of open file descriptors should be +# equal to sum of: nmb_cpus * nmb_events (2+dummy), +# nmb_threads for perf.data.n (equal to nmb_cpus) and +# 2*nmb_cpus of pipes = 4*nmb_cpus (each pipe has 2 ends) +# All together it needs 8*nmb_cpus file descriptors plus some are also used +# outside of testing, thus raising the limit to 16*nmb_cpus +min_fd_limit=$(($(getconf _NPROCESSORS_ONLN) * 16)) + +cleanup() { + rm -f "${perfdata}" + rm -f "${perfdata}".old + rm -f "${script_output}" + + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_per_thread() { + echo "Basic --per-thread mode test" + if ! perf record -o /dev/null --quiet ${testprog} 2> /dev/null + then + echo "Per-thread record [Skipped event not supported]" + return + fi + if ! perf record --per-thread -o "${perfdata}" ${testprog} 2> /dev/null + then + echo "Per-thread record [Failed record]" + err=1 + return + fi + if ! perf report -i "${perfdata}" -q | grep -q "${testsym}" + then + echo "Per-thread record [Failed missing output]" + err=1 + return + fi + + # run the test program in background (for 30 seconds) + ${testprog} 30 & + TESTPID=$! + + rm -f "${perfdata}" + + wait_for_threads ${TESTPID} 2 + perf record -p "${TESTPID}" --per-thread -o "${perfdata}" sleep 1 2> /dev/null + kill ${TESTPID} + + if [ ! -e "${perfdata}" ] + then + echo "Per-thread record [Failed record -p]" + err=1 + return + fi + if ! perf report -i "${perfdata}" -q | grep -q "${testsym}" + then + echo "Per-thread record [Failed -p missing output]" + err=1 + return + fi + + echo "Basic --per-thread mode test [Success]" +} + +test_register_capture() { + echo "Register capture test" + if ! perf list pmu | grep -q 'br_inst_retired.near_call' + then + echo "Register capture test [Skipped missing event]" + return + fi + if ! perf record --intr-regs=\? 2>&1 | grep -q 'available registers: AX BX CX DX SI DI BP SP IP FLAGS CS SS R8 R9 R10 R11 R12 R13 R14 R15' + then + echo "Register capture test [Skipped missing registers]" + return + fi + if ! perf record -o - --intr-regs=di,r8,dx,cx -e br_inst_retired.near_call \ + -c 1000 --per-thread ${testprog} 2> /dev/null \ + | perf script -F ip,sym,iregs -i - 2> /dev/null \ + | grep -q "DI:" + then + echo "Register capture test [Failed missing output]" + err=1 + return + fi + echo "Register capture test [Success]" +} + +test_system_wide() { + echo "Basic --system-wide mode test" + if ! perf record -aB --synth=no -o "${perfdata}" ${testprog} 2> /dev/null + then + echo "System-wide record [Skipped not supported]" + return + fi + if ! perf report -i "${perfdata}" -q | grep -q "${testsym}" + then + echo "System-wide record [Failed missing output]" + err=1 + return + fi + if ! perf record -aB --synth=no -e cpu-clock,cs --threads=cpu \ + -o "${perfdata}" ${testprog} 2> /dev/null + then + echo "System-wide record [Failed record --threads option]" + err=1 + return + fi + if ! perf report -i "${perfdata}" -q | grep -q "${testsym}" + then + echo "System-wide record [Failed --threads missing output]" + err=1 + return + fi + echo "Basic --system-wide mode test [Success]" +} + +test_workload() { + echo "Basic target workload test" + if ! perf record -o "${perfdata}" ${testprog} 2> /dev/null + then + echo "Workload record [Failed record]" + err=1 + return + fi + if ! perf report -i "${perfdata}" -q | grep -q "${testsym}" + then + echo "Workload record [Failed missing output]" + err=1 + return + fi + if ! perf record -e cpu-clock,cs --threads=package \ + -o "${perfdata}" ${testprog} 2> /dev/null + then + echo "Workload record [Failed record --threads option]" + err=1 + return + fi + if ! perf report -i "${perfdata}" -q | grep -q "${testsym}" + then + echo "Workload record [Failed --threads missing output]" + err=1 + return + fi + echo "Basic target workload test [Success]" +} + +test_branch_counter() { + echo "Branch counter test" + # Check if the branch counter feature is supported + for dir in $cpu_pmu_dir + do + if [ ! -e "$dir$br_cntr_file" ] + then + echo "branch counter feature not supported on all core PMUs ($dir) [Skipped]" + return + fi + done + if ! perf record -o "${perfdata}" -e "{branches:p,instructions}" -j any,counter ${testprog} 2> /dev/null + then + echo "Branch counter record test [Failed record]" + err=1 + return + fi + if ! perf report -i "${perfdata}" -D -q | grep -q "$br_cntr_output" + then + echo "Branch counter report test [Failed missing output]" + err=1 + return + fi + if ! perf script -i "${perfdata}" -F +brstackinsn,+brcntr | grep -q "$br_cntr_script_output" + then + echo " Branch counter script test [Failed missing output]" + err=1 + return + fi + echo "Branch counter test [Success]" +} + +test_cgroup() { + echo "Cgroup sampling test" + if ! perf record -aB --synth=cgroup --all-cgroups -o "${perfdata}" ${testprog} 2> /dev/null + then + echo "Cgroup sampling [Skipped not supported]" + return + fi + if ! perf report -i "${perfdata}" -D | grep -q "CGROUP" + then + echo "Cgroup sampling [Failed missing output]" + err=1 + return + fi + if ! perf script -i "${perfdata}" -F cgroup | grep -q -v "unknown" + then + echo "Cgroup sampling [Failed cannot resolve cgroup names]" + err=1 + return + fi + echo "Cgroup sampling test [Success]" +} + +test_leader_sampling() { + echo "Basic leader sampling test" + if ! perf record -o "${perfdata}" -e "{cycles,cycles}:Su" -- \ + perf test -w brstack 2> /dev/null + then + echo "Leader sampling [Failed record]" + err=1 + return + fi + perf script -i "${perfdata}" | grep brstack > $script_output + # Check if the two instruction counts are equal in each record. + # However, the throttling code doesn't consider event grouping. During throttling, only the + # leader is stopped, causing the slave's counts significantly higher. To temporarily solve this, + # let's set the tolerance rate to 80%. + # TODO: Revert the code for tolerance once the throttling mechanism is fixed. + index=0 + valid_counts=0 + invalid_counts=0 + tolerance_rate=0.8 + while IFS= read -r line + do + cycles=$(echo $line | awk '{for(i=1;i<=NF;i++) if($i=="cycles:") print $(i-1)}') + if [ $(($index%2)) -ne 0 ] && [ ${cycles}x != ${prev_cycles}x ] + then + invalid_counts=$(($invalid_counts+1)) + else + valid_counts=$(($valid_counts+1)) + fi + index=$(($index+1)) + prev_cycles=$cycles + done < "${script_output}" + total_counts=$(bc <<< "$invalid_counts+$valid_counts") + if (( $(bc <<< "$total_counts <= 0") )) + then + echo "Leader sampling [No sample generated]" + err=1 + return + fi + isok=$(bc <<< "scale=2; if (($invalid_counts/$total_counts) < (1-$tolerance_rate)) { 0 } else { 1 };") + if [ $isok -eq 1 ] + then + echo "Leader sampling [Failed inconsistent cycles count]" + err=1 + else + echo "Basic leader sampling test [Success]" + fi +} + +test_topdown_leader_sampling() { + echo "Topdown leader sampling test" + if ! perf stat -e "{slots,topdown-retiring}" true 2> /dev/null + then + echo "Topdown leader sampling [Skipped event parsing failed]" + return + fi + if ! perf record -o "${perfdata}" -e "{instructions,slots,topdown-retiring}:S" true 2> /dev/null + then + echo "Topdown leader sampling [Failed topdown events not reordered correctly]" + err=1 + return + fi + echo "Topdown leader sampling test [Success]" +} + +test_precise_max() { + local -i skipped=0 + + echo "precise_max attribute test" + # Just to make sure event cycles is supported for sampling + if perf record -o "${perfdata}" -e "cycles" true 2> /dev/null + then + if ! perf record -o "${perfdata}" -e "cycles:P" true 2> /dev/null + then + echo "precise_max attribute [Failed cycles:P event]" + err=1 + return + fi + else + echo "precise_max attribute [Skipped no cycles:P event]" + ((skipped+=1)) + fi + # On s390 event instructions is not supported for perf record + if perf record -o "${perfdata}" -e "instructions" true 2> /dev/null + then + # On AMD, cycles and instructions events are treated differently + if ! perf record -o "${perfdata}" -e "instructions:P" true 2> /dev/null + then + echo "precise_max attribute [Failed instructions:P event]" + err=1 + return + fi + else + echo "precise_max attribute [Skipped no instructions:P event]" + ((skipped+=1)) + fi + if [ $skipped -eq 2 ] + then + echo "precise_max attribute [Skipped no hardware events]" + else + echo "precise_max attribute test [Success]" + fi +} + +# raise the limit of file descriptors to minimum +if [[ $default_fd_limit -lt $min_fd_limit ]]; then + ulimit -Sn $min_fd_limit +fi + +test_per_thread +test_register_capture +test_system_wide +test_workload +test_branch_counter +test_cgroup +test_leader_sampling +test_topdown_leader_sampling +test_precise_max + +# restore the default value +ulimit -Sn $default_fd_limit + +cleanup +exit $err diff --git a/tools/perf/tests/shell/record_bpf_filter.sh b/tools/perf/tests/shell/record_bpf_filter.sh new file mode 100755 index 000000000000..4d6c3c1b7fb9 --- /dev/null +++ b/tools/perf/tests/shell/record_bpf_filter.sh @@ -0,0 +1,202 @@ +#!/bin/sh +# perf record sample filtering (by BPF) tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +err=0 +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) + +cleanup() { + rm -f "${perfdata}" + rm -f "${perfdata}".old + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_bpf_filter_priv() { + echo "Checking BPF-filter privilege" + + if ! perf record -e task-clock --filter 'period > 1' \ + -o /dev/null --quiet true 2>&1 + then + if [ "$(id -u)" != 0 ] + then + echo "try 'sudo perf record --setup-filter pin' first." + echo "bpf-filter test [Skipped permission]" + err=2 + return + fi + echo "bpf-filter test [Skipped missing BPF support]" + err=2 + return + fi +} + +test_bpf_filter_basic() { + echo "Basic bpf-filter test" + + if ! perf record -e task-clock -c 10000 --filter 'ip < 0xffffffff00000000' \ + -o "${perfdata}" true 2> /dev/null + then + echo "Basic bpf-filter test [Failed record]" + err=1 + return + fi + if perf script -i "${perfdata}" -F ip | grep 'ffffffff[0-9a-f]*' + then + if uname -r | grep -q ^6.2 + then + echo "Basic bpf-filter test [Skipped unsupported kernel]" + err=2 + return + fi + echo "Basic bpf-filter test [Failed invalid output]" + err=1 + return + fi + echo "Basic bpf-filter test [Success]" +} + +test_bpf_filter_fail() { + echo "Failing bpf-filter test" + + # 'cpu' requires PERF_SAMPLE_CPU flag + if ! perf record -e task-clock --filter 'cpu > 0' \ + -o /dev/null true 2>&1 | grep -q PERF_SAMPLE_CPU + then + echo "Failing bpf-filter test [Failed forbidden CPU]" + err=1 + return + fi + + if ! perf record --sample-cpu -e task-clock --filter 'cpu > 0' \ + -o /dev/null true 2>/dev/null + then + echo "Failing bpf-filter test [Failed should succeed]" + err=1 + return + fi + + echo "Failing bpf-filter test [Success]" +} + +test_bpf_filter_group() { + echo "Group bpf-filter test" + + if ! perf record -e task-clock --filter 'period > 1000, ip > 0' \ + -o /dev/null true 2>/dev/null + then + echo "Group bpf-filter test [Failed should succeed]" + err=1 + return + fi + + if ! perf record -e task-clock --filter 'period > 1000 , cpu > 0 || ip > 0' \ + -o /dev/null true 2>&1 | grep -q PERF_SAMPLE_CPU + then + echo "Group bpf-filter test [Failed forbidden CPU]" + err=1 + return + fi + + if ! perf record -e task-clock --filter 'period > 0 || code_pgsz > 4096' \ + -o /dev/null true 2>&1 | grep -q PERF_SAMPLE_CODE_PAGE_SIZE + then + echo "Group bpf-filter test [Failed forbidden CODE_PAGE_SIZE]" + err=1 + return + fi + + echo "Group bpf-filter test [Success]" +} + +test_bpf_filter_multi() { + echo "Multiple bpf-filter test" + + if ! perf record -e task-clock --filter 'period > 100000' \ + -e page-faults --filter 'ip < 0xffffffff00000000' \ + -o "${perfdata}" true 2> /dev/null + then + echo "Multiple bpf-filter test [Failed record]" + err=1 + return + fi + + if ! perf script -i "${perfdata}" -F period,event | grep task-clock | \ + awk '{ if (int($1) <= 100000) { print $0; exit(1); } }' + then + echo "Multiple bpf-filter test [Failed task-clock period]" + err=1 + return + fi + + if perf script -i "${perfdata}" -F event,ip | grep page-fault | \ + grep 'ffffffff[0-9a-f]*' + then + echo "Multiple bpf-filter test [Failed page-faults ip]" + err=1 + return + fi + + echo "Multiple bpf-filter test [Success]" +} + +test_bpf_filter_cgroup() { + echo "Cgroup bpf-filter test" + + if ! perf record -e task-clock --filter 'cgroup == /' \ + -a --all-cgroups --synth=cgroup -o "${perfdata}" true 2> /dev/null + then + echo "Cgroup bpf-filter test [Skipped cgroup not supported]" + return + fi + + # 'cgroup' requires PERF_SAMPLE_CGROUP flag + if ! perf record -e task-clock --filter 'cgroup == /' \ + -o /dev/null true 2>&1 | grep -q PERF_SAMPLE_CGROUP + then + echo "Cgroup bpf-filter test [Failed CGROUP requires --all-cgroups]" + err=1 + return + fi + + if ! perf report -i "${perfdata}" -s cgroup -q | grep -q -F '100.00%' + then + echo "Cgroup bpf-filter test [Failed root cgroup does not have 100%]" + err=1 + return + fi + + echo "Cgroup bpf-filter test [Success]" +} + +test_bpf_filter_priv + +if [ $err = 0 ]; then + test_bpf_filter_basic +fi + +if [ $err = 0 ]; then + test_bpf_filter_fail +fi + +if [ $err = 0 ]; then + test_bpf_filter_group +fi + +if [ $err = 0 ]; then + test_bpf_filter_multi +fi + +if [ $err = 0 ]; then + test_bpf_filter_cgroup +fi + +cleanup +exit $err diff --git a/tools/perf/tests/shell/record_lbr.sh b/tools/perf/tests/shell/record_lbr.sh new file mode 100755 index 000000000000..6fcb5e52b9b4 --- /dev/null +++ b/tools/perf/tests/shell/record_lbr.sh @@ -0,0 +1,162 @@ +#!/bin/bash +# perf record LBR tests (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +set -e + +if [ ! -f /sys/bus/event_source/devices/cpu/caps/branches ] && + [ ! -f /sys/bus/event_source/devices/cpu_core/caps/branches ] +then + echo "Skip: only x86 CPUs support LBR" + exit 2 +fi + +err=0 +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) + +cleanup() { + rm -rf "${perfdata}" + rm -rf "${perfdata}".old + rm -rf "${perfdata}".txt + + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + + +lbr_callgraph_test() { + test="LBR callgraph" + + echo "$test" + if ! perf record -e cycles --call-graph lbr -o "${perfdata}" perf test -w thloop + then + echo "$test [Failed support missing]" + if [ $err -eq 0 ] + then + err=2 + fi + return + fi + + if ! perf report --stitch-lbr -i "${perfdata}" > "${perfdata}".txt + then + cat "${perfdata}".txt + echo "$test [Failed in perf report]" + err=1 + return + fi + + echo "$test [Success]" +} + +lbr_test() { + local branch_flags=$1 + local test="LBR $2 test" + local threshold=$3 + local out + local sam_nr + local bs_nr + local zero_nr + local r + + echo "$test" + if ! perf record -e cycles $branch_flags -o "${perfdata}" perf test -w thloop + then + echo "$test [Failed support missing]" + perf record -e cycles $branch_flags -o "${perfdata}" perf test -w thloop || true + if [ $err -eq 0 ] + then + err=2 + fi + return + fi + + out=$(perf report -D -i "${perfdata}" 2> /dev/null | grep -A1 'PERF_RECORD_SAMPLE') + sam_nr=$(echo "$out" | grep -c 'PERF_RECORD_SAMPLE' || true) + if [ $sam_nr -eq 0 ] + then + echo "$test [Failed no samples captured]" + err=1 + return + fi + echo "$test: $sam_nr samples" + + bs_nr=$(echo "$out" | grep -c 'branch stack: nr:' || true) + if [ $sam_nr -ne $bs_nr ] + then + echo "$test [Failed samples missing branch stacks]" + err=1 + return + fi + + zero_nr=$(echo "$out" | grep -A3 'branch stack: nr:0' | grep thread | grep -cv swapper || true) + r=$(($zero_nr * 100 / $bs_nr)) + if [ $r -gt $threshold ]; then + echo "$test [Failed empty br stack ratio exceed $threshold%: $r%]" + err=1 + return + fi + + echo "$test [Success]" +} + +parallel_lbr_test() { + err=0 + perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) + lbr_test "$1" "$2" "$3" + cleanup + exit $err +} + +lbr_callgraph_test + +# Sequential +lbr_test "-b" "any branch" 2 +lbr_test "-j any_call" "any call" 2 +lbr_test "-j any_ret" "any ret" 2 +lbr_test "-j ind_call" "any indirect call" 2 +lbr_test "-j ind_jmp" "any indirect jump" 100 +lbr_test "-j call" "direct calls" 2 +lbr_test "-j ind_call,u" "any indirect user call" 100 +lbr_test "-a -b" "system wide any branch" 2 +lbr_test "-a -j any_call" "system wide any call" 2 + +# Parallel +parallel_lbr_test "-b" "parallel any branch" 100 & +pid1=$! +parallel_lbr_test "-j any_call" "parallel any call" 100 & +pid2=$! +parallel_lbr_test "-j any_ret" "parallel any ret" 100 & +pid3=$! +parallel_lbr_test "-j ind_call" "parallel any indirect call" 100 & +pid4=$! +parallel_lbr_test "-j ind_jmp" "parallel any indirect jump" 100 & +pid5=$! +parallel_lbr_test "-j call" "parallel direct calls" 100 & +pid6=$! +parallel_lbr_test "-j ind_call,u" "parallel any indirect user call" 100 & +pid7=$! +parallel_lbr_test "-a -b" "parallel system wide any branch" 100 & +pid8=$! +parallel_lbr_test "-a -j any_call" "parallel system wide any call" 100 & +pid9=$! + +for pid in $pid1 $pid2 $pid3 $pid4 $pid5 $pid6 $pid7 $pid8 $pid9 +do + set +e + wait $pid + child_err=$? + set -e + if ([ $err -eq 2 ] && [ $child_err -eq 1 ]) || [ $err -eq 0 ] + then + err=$child_err + fi +done + +cleanup +exit $err diff --git a/tools/perf/tests/shell/record_offcpu.sh b/tools/perf/tests/shell/record_offcpu.sh new file mode 100755 index 000000000000..21a22efe08f5 --- /dev/null +++ b/tools/perf/tests/shell/record_offcpu.sh @@ -0,0 +1,174 @@ +#!/bin/sh +# perf record offcpu profiling tests (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +set -e + +err=0 +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) + +ts=$(printf "%u" $((~0 << 32))) # OFF_CPU_TIMESTAMP +dummy_timestamp=${ts%???} # remove the last 3 digits to match perf script + +cleanup() { + rm -f ${perfdata} + rm -f ${perfdata}.old + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_above_thresh="Threshold test (above threshold)" +test_below_thresh="Threshold test (below threshold)" + +test_offcpu_priv() { + echo "Checking off-cpu privilege" + + if [ "$(id -u)" != 0 ] + then + echo "off-cpu test [Skipped permission]" + err=2 + return + fi + if perf version --build-options 2>&1 | grep HAVE_BPF_SKEL | grep -q OFF + then + echo "off-cpu test [Skipped missing BPF support]" + err=2 + return + fi +} + +test_offcpu_basic() { + echo "Basic off-cpu test" + + if ! perf record --off-cpu -e dummy -o ${perfdata} sleep 1 2> /dev/null + then + echo "Basic off-cpu test [Failed record]" + err=1 + return + fi + if ! perf evlist -i ${perfdata} | grep -q "offcpu-time" + then + echo "Basic off-cpu test [Failed no event]" + err=1 + return + fi + if ! perf report -i ${perfdata} -q --percent-limit=90 | grep -E -q sleep + then + echo "Basic off-cpu test [Failed missing output]" + err=1 + return + fi + echo "Basic off-cpu test [Success]" +} + +test_offcpu_child() { + echo "Child task off-cpu test" + + # perf bench sched messaging creates 400 processes + if ! perf record --off-cpu -e dummy -o ${perfdata} -- \ + perf bench sched messaging -g 10 > /dev/null 2>&1 + then + echo "Child task off-cpu test [Failed record]" + err=1 + return + fi + if ! perf evlist -i ${perfdata} | grep -q "offcpu-time" + then + echo "Child task off-cpu test [Failed no event]" + err=1 + return + fi + # each process waits at least for poll, so it should be more than 400 events + if ! perf report -i ${perfdata} -s comm -q -n -t ';' --percent-limit=90 | \ + awk -F ";" '{ if (NF > 3 && int($3) < 400) exit 1; }' + then + echo "Child task off-cpu test [Failed invalid output]" + err=1 + return + fi + echo "Child task off-cpu test [Success]" +} + +# task blocks longer than the --off-cpu-thresh, perf should collect a direct sample +test_offcpu_above_thresh() { + echo "${test_above_thresh}" + + # collect direct off-cpu samples for tasks blocked for more than 999ms + if ! perf record -e dummy --off-cpu --off-cpu-thresh 999 -o ${perfdata} -- sleep 1 2> /dev/null + then + echo "${test_above_thresh} [Failed record]" + err=1 + return + fi + # direct sample's timestamp should be lower than the dummy_timestamp of the at-the-end sample + # check if a direct sample exists + if ! perf script --time "0, ${dummy_timestamp}" -i ${perfdata} -F event | grep -q "offcpu-time" + then + echo "${test_above_thresh} [Failed missing direct samples]" + err=1 + return + fi + # there should only be one direct sample, and its period should be higher than off-cpu-thresh + if ! perf script --time "0, ${dummy_timestamp}" -i ${perfdata} -F period | \ + awk '{ if (int($1) > 999000000) exit 0; else exit 1; }' + then + echo "${test_above_thresh} [Failed off-cpu time too short]" + err=1 + return + fi + echo "${test_above_thresh} [Success]" +} + +# task blocks shorter than the --off-cpu-thresh, perf should collect an at-the-end sample +test_offcpu_below_thresh() { + echo "${test_below_thresh}" + + # collect direct off-cpu samples for tasks blocked for more than 1.2s + if ! perf record -e dummy --off-cpu --off-cpu-thresh 1200 -o ${perfdata} -- sleep 1 2> /dev/null + then + echo "${test_below_thresh} [Failed record]" + err=1 + return + fi + # see if there's an at-the-end sample + if ! perf script --time "${dummy_timestamp}," -i ${perfdata} -F event | grep -q 'offcpu-time' + then + echo "${test_below_thresh} [Failed at-the-end samples cannot be found]" + err=1 + return + fi + # plus there shouldn't be any direct samples + if perf script --time "0, ${dummy_timestamp}" -i ${perfdata} -F event | grep -q 'offcpu-time' + then + echo "${test_below_thresh} [Failed direct samples are found when they shouldn't be]" + err=1 + return + fi + echo "${test_below_thresh} [Success]" +} + +test_offcpu_priv + +if [ $err = 0 ]; then + test_offcpu_basic +fi + +if [ $err = 0 ]; then + test_offcpu_child +fi + +if [ $err = 0 ]; then + test_offcpu_above_thresh +fi + +if [ $err = 0 ]; then + test_offcpu_below_thresh +fi + +cleanup +exit $err diff --git a/tools/perf/tests/shell/record_sideband.sh b/tools/perf/tests/shell/record_sideband.sh new file mode 100755 index 000000000000..ac70ac27d590 --- /dev/null +++ b/tools/perf/tests/shell/record_sideband.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# perf record sideband tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +err=0 +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) + +cleanup() +{ + rm -rf ${perfdata} + trap - EXIT TERM INT +} + +trap_cleanup() +{ + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +can_cpu_wide() +{ + if ! perf record -o ${perfdata} -BN --no-bpf-event -C $1 true > /dev/null 2>&1 + then + echo "record sideband test [Skipped cannot record cpu$1]" + err=2 + fi + + rm -f ${perfdata} + return $err +} + +test_system_wide_tracking() +{ + # Need CPU 0 and CPU 1 + can_cpu_wide 0 || return 0 + can_cpu_wide 1 || return 0 + + # Record on CPU 0 a task running on CPU 1 + perf record -BN --no-bpf-event -o ${perfdata} -C 0 -- taskset --cpu-list 1 true + + # Should get MMAP events from CPU 1 + mmap_cnt=`perf script -i ${perfdata} --show-mmap-events -C 1 2>/dev/null | grep MMAP | wc -l` + + if [ ${mmap_cnt} -gt 0 ] ; then + return 0 + fi + + echo "Failed to record MMAP events on CPU 1 when tracing CPU 0" + return 1 +} + +test_system_wide_tracking + +cleanup +exit $err diff --git a/tools/perf/tests/shell/script.sh b/tools/perf/tests/shell/script.sh new file mode 100755 index 000000000000..d3e2958d2242 --- /dev/null +++ b/tools/perf/tests/shell/script.sh @@ -0,0 +1,99 @@ +#!/bin/sh +# perf script tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +temp_dir=$(mktemp -d /tmp/perf-test-script.XXXXXXXXXX) + +perfdatafile="${temp_dir}/perf.data" +db_test="${temp_dir}/db_test.py" + +err=0 + +cleanup() +{ + trap - EXIT TERM INT + sane=$(echo "${temp_dir}" | cut -b 1-21) + if [ "${sane}" = "/tmp/perf-test-script" ] ; then + echo "--- Cleaning up ---" + rm -rf "${temp_dir:?}/"* + rmdir "${temp_dir}" + fi +} + +trap_cleanup() +{ + cleanup + exit 1 +} + +trap trap_cleanup EXIT TERM INT + + +test_db() +{ + echo "DB test" + + # Check if python script is supported + if perf version --build-options | grep python | grep -q OFF ; then + echo "SKIP: python scripting is not supported" + err=2 + return + fi + + cat << "_end_of_file_" > "${db_test}" +perf_db_export_mode = True +perf_db_export_calls = False +perf_db_export_callchains = True + +def sample_table(*args): + print(f'sample_table({args})') + +def call_path_table(*args): + print(f'call_path_table({args}') +_end_of_file_ + case $(uname -m) + in s390x) + cmd_flags="--call-graph dwarf -e cpu-clock";; + *) + cmd_flags="-g";; + esac + + perf record $cmd_flags -o "${perfdatafile}" true + # Disable lsan to avoid warnings about python memory leaks. + export ASAN_OPTIONS=detect_leaks=0 + perf script -i "${perfdatafile}" -s "${db_test}" + export ASAN_OPTIONS= + echo "DB test [Success]" +} + +test_parallel_perf() +{ + echo "parallel-perf test" + if ! python3 --version >/dev/null 2>&1 ; then + echo "SKIP: no python3" + err=2 + return + fi + pp=$(dirname "$0")/../../scripts/python/parallel-perf.py + if [ ! -f "${pp}" ] ; then + echo "SKIP: parallel-perf.py script not found " + err=2 + return + fi + perf_data="${temp_dir}/pp-perf.data" + output1_dir="${temp_dir}/output1" + output2_dir="${temp_dir}/output2" + perf record -o "${perf_data}" --sample-cpu uname + python3 "${pp}" -o "${output1_dir}" --jobs 4 --verbose -- perf script -i "${perf_data}" + python3 "${pp}" -o "${output2_dir}" --jobs 4 --verbose --per-cpu -- perf script -i "${perf_data}" + echo "parallel-perf test [Success]" +} + +test_db +test_parallel_perf + +cleanup + +exit $err diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/shell/stat+csv_output.sh new file mode 100755 index 000000000000..7a6f6e177402 --- /dev/null +++ b/tools/perf/tests/shell/stat+csv_output.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# perf stat CSV output linter +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +# Tests various perf stat CSV output commands for the +# correct number of fields and the CSV separator set to ','. + +set -e + +# shellcheck source=lib/stat_output.sh +. "$(dirname $0)"/lib/stat_output.sh + +csv_sep=@ + +stat_output=$(mktemp /tmp/__perf_test.stat_output.csv.XXXXX) + +cleanup() { + rm -f "${stat_output}" + + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +function commachecker() +{ + local -i cnt=0 + local exp=0 + + case "$1" + in "--no-args") exp=6 + ;; "--system-wide") exp=6 + ;; "--event") exp=6 + ;; "--interval") exp=7 + ;; "--per-thread") exp=7 + ;; "--system-wide-no-aggr") exp=7 + [ "$(uname -m)" = "s390x" ] && exp='^[6-7]$' + ;; "--per-core") exp=8 + ;; "--per-socket") exp=8 + ;; "--per-node") exp=8 + ;; "--per-die") exp=8 + ;; "--per-cluster") exp=8 + ;; "--per-cache") exp=8 + ;; "--metric-only") exp=2 + esac + + while read line + do + # Ignore initial "started on" comment. + x=${line:0:1} + [ "$x" = "#" ] && continue + # Ignore initial blank line. + [ "$line" = "" ] && continue + + # Count the number of commas + x=$(echo $line | tr -d -c $csv_sep) + cnt="${#x}" + # echo $line $cnt + [[ ! "$cnt" =~ $exp ]] && { + echo "wrong number of fields. expected $exp in $line" 1>&2 + exit 1; + } + done < "${stat_output}" + return 0 +} + +perf_cmd="-x$csv_sep -o ${stat_output}" + +skip_test=$(check_for_topology) +check_no_args "CSV" "$perf_cmd" +check_system_wide "CSV" "$perf_cmd" +check_interval "CSV" "$perf_cmd" +check_event "CSV" "$perf_cmd" +check_per_thread "CSV" "$perf_cmd" +check_per_node "CSV" "$perf_cmd" +check_metric_only "CSV" "$perf_cmd" +if [ $skip_test -ne 1 ] +then + check_system_wide_no_aggr "CSV" "$perf_cmd" + check_per_core "CSV" "$perf_cmd" + check_per_cache_instance "CSV" "$perf_cmd" + check_per_cluster "CSV" "$perf_cmd" + check_per_die "CSV" "$perf_cmd" + check_per_socket "CSV" "$perf_cmd" +else + echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die and per_socket since socket id exposed via topology is invalid" +fi +cleanup +exit 0 diff --git a/tools/perf/tests/shell/stat+csv_summary.sh b/tools/perf/tests/shell/stat+csv_summary.sh new file mode 100755 index 000000000000..323123ff4d19 --- /dev/null +++ b/tools/perf/tests/shell/stat+csv_summary.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# perf stat csv summary test +# SPDX-License-Identifier: GPL-2.0 + +set -e + +# +# 1.001364330 9224197 cycles 8012885033 100.00 +# summary 9224197 cycles 8012885033 100.00 +# +perf stat -e cycles -x' ' -I1000 --interval-count 1 --summary 2>&1 | \ +grep -e summary | \ +while read summary _ _ _ _ +do + if [ $summary != "summary" ]; then + exit 1 + fi +done + +# +# 1.001360298 9148534 cycles 8012853854 100.00 +#9148534 cycles 8012853854 100.00 +# +perf stat -e cycles -x' ' -I1000 --interval-count 1 --summary --no-csv-summary 2>&1 | \ +grep -e summary | \ +while read _ _ _ _ +do + exit 1 +done + +exit 0 diff --git a/tools/perf/tests/shell/stat+event_uniquifying.sh b/tools/perf/tests/shell/stat+event_uniquifying.sh new file mode 100755 index 000000000000..bf54bd6c3e2e --- /dev/null +++ b/tools/perf/tests/shell/stat+event_uniquifying.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# perf stat events uniquifying +# SPDX-License-Identifier: GPL-2.0 + +set -e + +stat_output=$(mktemp /tmp/__perf_test.stat_output.XXXXX) +perf_tool=perf +err=0 + +test_event_uniquifying() { + # We use `clockticks` in `uncore_imc` to verify the uniquify behavior. + pmu="uncore_imc" + event="clockticks" + + # If the `-A` option is added, the event should be uniquified. + # + # $perf list -v clockticks + # + # List of pre-defined events (to be used in -e or -M): + # + # uncore_imc_0/clockticks/ [Kernel PMU event] + # uncore_imc_1/clockticks/ [Kernel PMU event] + # uncore_imc_2/clockticks/ [Kernel PMU event] + # uncore_imc_3/clockticks/ [Kernel PMU event] + # uncore_imc_4/clockticks/ [Kernel PMU event] + # uncore_imc_5/clockticks/ [Kernel PMU event] + # + # ... + # + # $perf stat -e clockticks -A -- true + # + # Performance counter stats for 'system wide': + # + # CPU0 3,773,018 uncore_imc_0/clockticks/ + # CPU0 3,609,025 uncore_imc_1/clockticks/ + # CPU0 0 uncore_imc_2/clockticks/ + # CPU0 3,230,009 uncore_imc_3/clockticks/ + # CPU0 3,049,897 uncore_imc_4/clockticks/ + # CPU0 0 uncore_imc_5/clockticks/ + # + # 0.002029828 seconds time elapsed + + echo "stat event uniquifying test" + uniquified_event_array=() + + # Skip if the machine does not have `uncore_imc` device. + if ! ${perf_tool} list pmu | grep -q ${pmu}; then + echo "Target does not support PMU ${pmu} [Skipped]" + err=2 + return + fi + + # Check how many uniquified events. + while IFS= read -r line; do + uniquified_event=$(echo "$line" | awk '{print $1}') + uniquified_event_array+=("${uniquified_event}") + done < <(${perf_tool} list -v ${event} | grep ${pmu}) + + perf_command="${perf_tool} stat -e $event -A -o ${stat_output} -- true" + $perf_command + + # Check the output contains all uniquified events. + for uniquified_event in "${uniquified_event_array[@]}"; do + if ! cat "${stat_output}" | grep -q "${uniquified_event}"; then + echo "Event is not uniquified [Failed]" + echo "${perf_command}" + cat "${stat_output}" + err=1 + break + fi + done +} + +test_event_uniquifying +rm -f "${stat_output}" +exit $err diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/shell/stat+json_output.sh new file mode 100755 index 000000000000..98fb65274ac4 --- /dev/null +++ b/tools/perf/tests/shell/stat+json_output.sh @@ -0,0 +1,236 @@ +#!/bin/bash +# perf stat JSON output linter +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +# Checks various perf stat JSON output commands for the +# correct number of fields. + +set -e + +skip_test=0 + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh +pythonchecker=$(dirname $0)/lib/perf_json_output_lint.py + +stat_output=$(mktemp /tmp/__perf_test.stat_output.json.XXXXX) + +cleanup() { + rm -f "${stat_output}" + + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +# Return true if perf_event_paranoid is > $1 and not running as root. +function ParanoidAndNotRoot() +{ + [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ] +} + +check_no_args() +{ + echo -n "Checking json output: no args " + perf stat -j -o "${stat_output}" true + $PYTHON $pythonchecker --no-args --file "${stat_output}" + echo "[Success]" +} + +check_system_wide() +{ + echo -n "Checking json output: system wide " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j -a -o "${stat_output}" true + $PYTHON $pythonchecker --system-wide --file "${stat_output}" + echo "[Success]" +} + +check_system_wide_no_aggr() +{ + echo -n "Checking json output: system wide no aggregation " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j -A -a --no-merge -o "${stat_output}" true + $PYTHON $pythonchecker --system-wide-no-aggr --file "${stat_output}" + echo "[Success]" +} + +check_interval() +{ + echo -n "Checking json output: interval " + perf stat -j -I 1000 -o "${stat_output}" true + $PYTHON $pythonchecker --interval --file "${stat_output}" + echo "[Success]" +} + + +check_event() +{ + echo -n "Checking json output: event " + perf stat -j -e cpu-clock -o "${stat_output}" true + $PYTHON $pythonchecker --event --file "${stat_output}" + echo "[Success]" +} + +check_per_core() +{ + echo -n "Checking json output: per core " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j --per-core -a -o "${stat_output}" true + $PYTHON $pythonchecker --per-core --file "${stat_output}" + echo "[Success]" +} + +check_per_thread() +{ + echo -n "Checking json output: per thread " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j --per-thread -p $$ -o "${stat_output}" true + $PYTHON $pythonchecker --per-thread --file "${stat_output}" + echo "[Success]" +} + +check_per_cache_instance() +{ + echo -n "Checking json output: per cache_instance " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j --per-cache -a true 2>&1 | $PYTHON $pythonchecker --per-cache + echo "[Success]" +} + +check_per_cluster() +{ + echo -n "Checking json output: per cluster " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j --per-cluster -a true 2>&1 | $PYTHON $pythonchecker --per-cluster + echo "[Success]" +} + +check_per_die() +{ + echo -n "Checking json output: per die " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j --per-die -a -o "${stat_output}" true + $PYTHON $pythonchecker --per-die --file "${stat_output}" + echo "[Success]" +} + +check_per_node() +{ + echo -n "Checking json output: per node " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j --per-node -a -o "${stat_output}" true + $PYTHON $pythonchecker --per-node --file "${stat_output}" + echo "[Success]" +} + +check_per_socket() +{ + echo -n "Checking json output: per socket " + if ParanoidAndNotRoot 0 + then + echo "[Skip] paranoia and not root" + return + fi + perf stat -j --per-socket -a -o "${stat_output}" true + $PYTHON $pythonchecker --per-socket --file "${stat_output}" + echo "[Success]" +} + +check_metric_only() +{ + echo -n "Checking json output: metric only " + if [ "$(uname -m)" = "s390x" ] && ! grep '^facilities' /proc/cpuinfo | grep -qw 67 + then + echo "[Skip] CPU-measurement counter facility not installed" + return + fi + perf stat -j --metric-only -e instructions,cycles -o "${stat_output}" true + $PYTHON $pythonchecker --metric-only --file "${stat_output}" + echo "[Success]" +} + +# The perf stat options for per-socket, per-core, per-die +# and -A ( no_aggr mode ) uses the info fetched from this +# directory: "/sys/devices/system/cpu/cpu*/topology". For +# example, socket value is fetched from "physical_package_id" +# file in topology directory. +# Reference: cpu__get_topology_int in util/cpumap.c +# If the platform doesn't expose topology information, values +# will be set to -1. For example, incase of pSeries platform +# of powerpc, value for "physical_package_id" is restricted +# and set to -1. Check here validates the socket-id read from +# topology file before proceeding further + +FILE_LOC="/sys/devices/system/cpu/cpu*/topology/" +FILE_NAME="physical_package_id" + +check_for_topology() +{ + if ! ParanoidAndNotRoot 0 + then + socket_file=`ls $FILE_LOC/$FILE_NAME | head -n 1` + [ -z $socket_file ] && return 0 + socket_id=`cat $socket_file` + [ $socket_id == -1 ] && skip_test=1 + return 0 + fi +} + +check_for_topology +check_no_args +check_system_wide +check_interval +check_event +check_per_thread +check_per_node +check_metric_only +if [ $skip_test -ne 1 ] +then + check_system_wide_no_aggr + check_per_core + check_per_cache_instance + check_per_cluster + check_per_die + check_per_socket +else + echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die and per_socket since socket id exposed via topology is invalid" +fi +cleanup +exit 0 diff --git a/tools/perf/tests/shell/stat+shadow_stat.sh b/tools/perf/tests/shell/stat+shadow_stat.sh new file mode 100755 index 000000000000..0c7d79a230ea --- /dev/null +++ b/tools/perf/tests/shell/stat+shadow_stat.sh @@ -0,0 +1,99 @@ +#!/bin/sh +# perf stat metrics (shadow stat) test +# SPDX-License-Identifier: GPL-2.0 + +set -e + +THRESHOLD=0.015 + +# skip if system-wide mode is forbidden +perf stat -a true > /dev/null 2>&1 || exit 2 + +# skip if on hybrid platform +perf stat -a -e cycles sleep 1 2>&1 | grep -e cpu_core && exit 2 + +test_global_aggr() +{ + perf stat -a --no-big-num -e cycles,instructions sleep 1 2>&1 | \ + grep -e cycles -e instructions | \ + while read num evt _ ipc rest + do + # skip not counted events + if [ "$num" = "<not" ]; then + continue + fi + + # save cycles count + if [ "$evt" = "cycles" ]; then + cyc=$num + continue + fi + + # skip if no cycles + if [ -z "$cyc" ]; then + continue + fi + + # use printf for rounding and a leading zero + res=`echo $num $cyc | awk '{printf "%.2f", $1 / $2}'` + if [ "$ipc" != "$res" ]; then + # check the difference from the real result for FP imperfections + diff=`echo $ipc $res $THRESHOLD | \ + awk '{x = ($1 - $2) < 0 ? ($2 - $1) : ($1 - $2); print (x > $3)}'` + + if [ $diff -eq 1 ]; then + echo "IPC is different: $res != $ipc ($num / $cyc)" + exit 1 + fi + + echo "Warning: Difference of IPC is under the threshold" + fi + done +} + +test_no_aggr() +{ + perf stat -a -A --no-big-num -e cycles,instructions sleep 1 2>&1 | \ + grep ^CPU | \ + while read cpu num evt _ ipc rest + do + # skip not counted events + if [ "$num" = "<not" ]; then + continue + fi + + # save cycles count + if [ "$evt" = "cycles" ]; then + results="$results $cpu:$num" + continue + fi + + cyc=${results##* $cpu:} + cyc=${cyc%% *} + + # skip if no cycles + if [ -z "$cyc" ]; then + continue + fi + + # use printf for rounding and a leading zero + res=`echo $num $cyc | awk '{printf "%.2f", $1 / $2}'` + if [ "$ipc" != "$res" ]; then + # check difference from the real result for FP imperfections + diff=`echo $ipc $res $THRESHOLD | \ + awk '{x = ($1 - $2) < 0 ? ($2 - $1) : ($1 - $2); print (x > $3)}'` + + if [ $diff -eq 1 ]; then + echo "IPC is different: $res != $ipc ($num / $cyc)" + exit 1 + fi + + echo "Warning: Difference of IPC is under the threshold" + fi + done +} + +test_global_aggr +test_no_aggr + +exit 0 diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/shell/stat+std_output.sh new file mode 100755 index 000000000000..6fee67693ba7 --- /dev/null +++ b/tools/perf/tests/shell/stat+std_output.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# perf stat STD output linter +# SPDX-License-Identifier: GPL-2.0 +# Tests various perf stat STD output commands for +# default event and metricgroup + +set -e + +# shellcheck source=lib/stat_output.sh +. "$(dirname $0)"/lib/stat_output.sh + +stat_output=$(mktemp /tmp/__perf_test.stat_output.std.XXXXX) + +event_name=(cpu-clock task-clock context-switches cpu-migrations page-faults stalled-cycles-frontend stalled-cycles-backend cycles instructions branches branch-misses) +event_metric=("CPUs utilized" "CPUs utilized" "/sec" "/sec" "/sec" "frontend cycles idle" "backend cycles idle" "GHz" "insn per cycle" "/sec" "of all branches") +skip_metric=("stalled cycles per insn" "tma_" "retiring" "frontend_bound" "bad_speculation" "backend_bound" "TopdownL1" "percent of slots") + +cleanup() { + rm -f "${stat_output}" + + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +function commachecker() +{ + local prefix=1 + local -i metric_only=0 + + case "$1" + in "--interval") prefix=2 + ;; "--per-thread") prefix=2 + ;; "--system-wide-no-aggr") prefix=2 + ;; "--per-core") prefix=3 + ;; "--per-socket") prefix=3 + ;; "--per-node") prefix=3 + ;; "--per-die") prefix=3 + ;; "--per-cache") prefix=3 + ;; "--per-cluster") prefix=3 + ;; "--metric-only") metric_only=1 + esac + + while read line + do + # Ignore initial "started on" comment. + x=${line:0:1} + [ "$x" = "#" ] && continue + # Ignore initial blank line. + [ "$line" = "" ] && continue + # Ignore "Performance counter stats" + x=${line:0:25} + [ "$x" = "Performance counter stats" ] && continue + # Ignore "seconds time elapsed" and break + [[ "$line" == *"time elapsed"* ]] && break + + main_body=$(echo $line | cut -d' ' -f$prefix-) + x=${main_body%#*} + [ "$x" = "" ] && continue + + # Check metric only - if it has a non-empty result + [ $metric_only -eq 1 ] && return 0 + + # Skip metrics without event name + y=${main_body#*#} + for i in "${!skip_metric[@]}"; do + [[ "$y" == *"${skip_metric[$i]}"* ]] && break + done + [[ "$y" == *"${skip_metric[$i]}"* ]] && continue + + # Check default event + for i in "${!event_name[@]}"; do + [[ "$x" == *"${event_name[$i]}"* ]] && break + done + + [[ ! "$x" == *"${event_name[$i]}"* ]] && { + echo "Unknown event name in $line" 1>&2 + exit 1; + } + + # Check event metric if it exists + [[ ! "$main_body" == *"#"* ]] && continue + [[ ! "$main_body" == *"${event_metric[$i]}"* ]] && { + echo "wrong event metric. expected ${event_metric[$i]} in $line" 1>&2 + exit 1; + } + done < "${stat_output}" + + [ $metric_only -eq 1 ] && exit 1 + return 0 +} + +perf_cmd="-o ${stat_output}" + +skip_test=$(check_for_topology) +check_no_args "STD" "$perf_cmd" +check_system_wide "STD" "$perf_cmd" +check_interval "STD" "$perf_cmd" +check_per_thread "STD" "$perf_cmd" +check_per_node "STD" "$perf_cmd" +check_metric_only "STD" "$perf_cmd" +if [ $skip_test -ne 1 ] +then + check_system_wide_no_aggr "STD" "$perf_cmd" + check_per_core "STD" "$perf_cmd" + check_per_cache_instance "STD" "$perf_cmd" + check_per_cluster "STD" "$perf_cmd" + check_per_die "STD" "$perf_cmd" + check_per_socket "STD" "$perf_cmd" +else + echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die and per_socket since socket id exposed via topology is invalid" +fi +cleanup +exit 0 diff --git a/tools/perf/tests/shell/stat.sh b/tools/perf/tests/shell/stat.sh new file mode 100755 index 000000000000..8a100a7f2dc1 --- /dev/null +++ b/tools/perf/tests/shell/stat.sh @@ -0,0 +1,222 @@ +#!/bin/bash +# perf stat tests +# SPDX-License-Identifier: GPL-2.0 + +set -e + +err=0 +test_default_stat() { + echo "Basic stat command test" + if ! perf stat true 2>&1 | grep -E -q "Performance counter stats for 'true':" + then + echo "Basic stat command test [Failed]" + err=1 + return + fi + echo "Basic stat command test [Success]" +} + +test_stat_record_report() { + echo "stat record and report test" + if ! perf stat record -o - true | perf stat report -i - 2>&1 | \ + grep -E -q "Performance counter stats for 'pipe':" + then + echo "stat record and report test [Failed]" + err=1 + return + fi + echo "stat record and report test [Success]" +} + +test_stat_record_script() { + echo "stat record and script test" + if ! perf stat record -o - true | perf script -i - 2>&1 | \ + grep -E -q "CPU[[:space:]]+THREAD[[:space:]]+VAL[[:space:]]+ENA[[:space:]]+RUN[[:space:]]+TIME[[:space:]]+EVENT" + then + echo "stat record and script test [Failed]" + err=1 + return + fi + echo "stat record and script test [Success]" +} + +test_stat_repeat_weak_groups() { + echo "stat repeat weak groups test" + if ! perf stat -e '{cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles}' \ + true 2>&1 | grep -q 'seconds time elapsed' + then + echo "stat repeat weak groups test [Skipped event parsing failed]" + return + fi + if ! perf stat -r2 -e '{cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles}:W' \ + true > /dev/null 2>&1 + then + echo "stat repeat weak groups test [Failed]" + err=1 + return + fi + echo "stat repeat weak groups test [Success]" +} + +test_topdown_groups() { + # Topdown events must be grouped with the slots event first. Test that + # parse-events reorders this. + echo "Topdown event group test" + if ! perf stat -e '{slots,topdown-retiring}' true > /dev/null 2>&1 + then + echo "Topdown event group test [Skipped event parsing failed]" + return + fi + td_err=0 + do_topdown_group_test() { + events=$1 + failure=$2 + if perf stat -e "$events" true 2>&1 | grep -E -q "<not supported>" + then + echo "Topdown event group test [Failed $failure for '$events']" + td_err=1 + return + fi + } + do_topdown_group_test "{slots,topdown-retiring}" "events not supported" + do_topdown_group_test "{instructions,r400,r8000}" "raw format slots not reordered first" + filler_events=("instructions" "cycles" + "context-switches" "faults") + for ((i = 0; i < ${#filler_events[@]}; i+=2)) + do + filler1=${filler_events[i]} + filler2=${filler_events[i+1]} + do_topdown_group_test "$filler1,topdown-retiring,slots" \ + "slots not reordered first in no-group case" + do_topdown_group_test "slots,$filler1,topdown-retiring" \ + "topdown metrics event not reordered in no-group case" + do_topdown_group_test "{$filler1,topdown-retiring,slots}" \ + "slots not reordered first in single group case" + do_topdown_group_test "{$filler1,slots},topdown-retiring" \ + "topdown metrics event not move into slots group" + do_topdown_group_test "topdown-retiring,{$filler1,slots}" \ + "topdown metrics event not move into slots group last" + do_topdown_group_test "{$filler1,slots},{topdown-retiring}" \ + "topdown metrics group not merge into slots group" + do_topdown_group_test "{topdown-retiring},{$filler1,slots}" \ + "topdown metrics group not merge into slots group last" + do_topdown_group_test "{$filler1,slots},$filler2,topdown-retiring" \ + "non-adjacent topdown metrics group not move into slots group" + do_topdown_group_test "$filler2,topdown-retiring,{$filler1,slots}" \ + "non-adjacent topdown metrics group not move into slots group last" + do_topdown_group_test "{$filler1,slots},{$filler2,topdown-retiring}" \ + "metrics group not merge into slots group" + do_topdown_group_test "{$filler1,topdown-retiring},{$filler2,slots}" \ + "metrics group not merge into slots group last" + done + if test "$td_err" -eq 0 + then + echo "Topdown event group test [Success]" + else + err="$td_err" + fi +} + +test_topdown_weak_groups() { + # Weak groups break if the perf_event_open of multiple grouped events + # fails. Breaking a topdown group causes the events to fail. Test a very large + # grouping to see that the topdown events aren't broken out. + echo "Topdown weak groups test" + ok_grouping="{slots,topdown-bad-spec,topdown-be-bound,topdown-fe-bound,topdown-retiring},branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,mem-loads,mem-stores,ref-cycles,cache-misses,cache-references" + if ! perf stat --no-merge -e "$ok_grouping" true > /dev/null 2>&1 + then + echo "Topdown weak groups test [Skipped event parsing failed]" + return + fi + group_needs_break="{slots,topdown-bad-spec,topdown-be-bound,topdown-fe-bound,topdown-retiring,branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,mem-loads,mem-stores,ref-cycles,cache-misses,cache-references}:W" + if perf stat --no-merge -e "$group_needs_break" true 2>&1 | grep -E -q "<not supported>" + then + echo "Topdown weak groups test [Failed events not supported]" + err=1 + return + fi + echo "Topdown weak groups test [Success]" +} + +test_cputype() { + # Test --cputype argument. + echo "cputype test" + + # Bogus PMU should fail. + if perf stat --cputype="123" -e instructions true > /dev/null 2>&1 + then + echo "cputype test [Bogus PMU didn't fail]" + err=1 + return + fi + + # Find a known PMU for cputype. + pmu="" + devs="/sys/bus/event_source/devices" + for i in $devs/cpu $devs/cpu_atom $devs/armv8_pmuv3_0 $devs/armv8_cortex_* + do + i_base=$(basename "$i") + if test -d "$i" + then + pmu="$i_base" + break + fi + if perf stat -e "$i_base/instructions/" true > /dev/null 2>&1 + then + pmu="$i_base" + break + fi + done + if test "x$pmu" = "x" + then + echo "cputype test [Skipped known PMU not found]" + return + fi + + # Test running with cputype produces output. + if ! perf stat --cputype="$pmu" -e instructions true 2>&1 | grep -E -q "instructions" + then + echo "cputype test [Failed count missed with given filter]" + err=1 + return + fi + echo "cputype test [Success]" +} + +test_hybrid() { + # Test the default stat command on hybrid devices opens one cycles event for + # each CPU type. + echo "hybrid test" + + # Count the number of core PMUs, assume minimum of 1 + pmus=$(ls /sys/bus/event_source/devices/*/cpus 2>/dev/null | wc -l) + if [ "$pmus" -lt 1 ] + then + pmus=1 + fi + + # Run default Perf stat + cycles_events=$(perf stat -- true 2>&1 | grep -E "/cycles/[uH]*| cycles[:uH]* " -c) + + # The expectation is that default output will have a cycles events on each + # hybrid PMU. In situations with no cycles PMU events, like virtualized, this + # can fall back to task-clock and so the end count may be 0. Fail if neither + # condition holds. + if [ "$pmus" -ne "$cycles_events" ] && [ "0" -ne "$cycles_events" ] + then + echo "hybrid test [Found $pmus PMUs but $cycles_events cycles events. Failed]" + err=1 + return + fi + echo "hybrid test [Success]" +} + +test_default_stat +test_stat_record_report +test_stat_record_script +test_stat_repeat_weak_groups +test_topdown_groups +test_topdown_weak_groups +test_cputype +test_hybrid +exit $err diff --git a/tools/perf/tests/shell/stat_all_metricgroups.sh b/tools/perf/tests/shell/stat_all_metricgroups.sh new file mode 100755 index 000000000000..c6d61a4ac3e7 --- /dev/null +++ b/tools/perf/tests/shell/stat_all_metricgroups.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# perf all metricgroups test +# SPDX-License-Identifier: GPL-2.0 + +ParanoidAndNotRoot() +{ + [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ] +} + +system_wide_flag="-a" +if ParanoidAndNotRoot 0 +then + system_wide_flag="" +fi +err=0 +for m in $(perf list --raw-dump metricgroups) +do + echo "Testing $m" + result=$(perf stat -M "$m" $system_wide_flag sleep 0.01 2>&1) + result_err=$? + if [[ $result_err -gt 0 ]] + then + if [[ "$result" =~ \ + "Access to performance monitoring and observability operations is limited" ]] + then + echo "Permission failure" + echo $result + if [[ $err -eq 0 ]] + then + err=2 # Skip + fi + elif [[ "$result" =~ "in per-thread mode, enable system wide" ]] + then + echo "Permissions - need system wide mode" + echo $result + if [[ $err -eq 0 ]] + then + err=2 # Skip + fi + else + echo "Metric group $m failed" + echo $result + err=1 # Fail + fi + fi +done + +exit $err diff --git a/tools/perf/tests/shell/stat_all_metrics.sh b/tools/perf/tests/shell/stat_all_metrics.sh new file mode 100755 index 000000000000..6fa585a1e34c --- /dev/null +++ b/tools/perf/tests/shell/stat_all_metrics.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# perf all metrics test +# SPDX-License-Identifier: GPL-2.0 + +ParanoidAndNotRoot() +{ + [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ] +} + +test_prog="sleep 0.01" +system_wide_flag="-a" +if ParanoidAndNotRoot 0 +then + system_wide_flag="" + test_prog="perf test -w noploop" +fi + +err=0 +for m in $(perf list --raw-dump metrics); do + echo "Testing $m" + result=$(perf stat -M "$m" $system_wide_flag -- $test_prog 2>&1) + result_err=$? + if [[ $result_err -eq 0 && "$result" =~ ${m:0:50} ]] + then + # No error result and metric shown. + continue + fi + if [[ "$result" =~ "Cannot resolve IDs for" ]] + then + echo "Metric contains missing events" + echo $result + err=1 # Fail + continue + elif [[ "$result" =~ \ + "Access to performance monitoring and observability operations is limited" ]] + then + echo "Permission failure" + echo $result + if [[ $err -eq 0 ]] + then + err=2 # Skip + fi + continue + elif [[ "$result" =~ "in per-thread mode, enable system wide" ]] + then + echo "Permissions - need system wide mode" + echo $result + if [[ $err -eq 0 ]] + then + err=2 # Skip + fi + continue + elif [[ "$result" =~ "<not supported>" ]] + then + echo "Not supported events" + echo $result + if [[ $err -eq 0 ]] + then + err=2 # Skip + fi + continue + elif [[ "$result" =~ "<not counted>" ]] + then + echo "Not counted events" + echo $result + if [[ $err -eq 0 ]] + then + err=2 # Skip + fi + continue + elif [[ "$result" =~ "FP_ARITH" || "$result" =~ "AMX" ]] + then + echo "FP issues" + echo $result + if [[ $err -eq 0 ]] + then + err=2 # Skip + fi + continue + elif [[ "$result" =~ "PMM" ]] + then + echo "Optane memory issues" + echo $result + if [[ $err -eq 0 ]] + then + err=2 # Skip + fi + continue + fi + + # Failed, possibly the workload was too small so retry with something longer. + result=$(perf stat -M "$m" $system_wide_flag -- perf bench internals synthesize 2>&1) + result_err=$? + if [[ $result_err -eq 0 && "$result" =~ ${m:0:50} ]] + then + # No error result and metric shown. + continue + fi + echo "Metric '$m' has non-zero error '$result_err' or not printed in:" + echo "$result" + err=1 +done + +exit "$err" diff --git a/tools/perf/tests/shell/stat_all_pfm.sh b/tools/perf/tests/shell/stat_all_pfm.sh new file mode 100755 index 000000000000..4d004f777a6e --- /dev/null +++ b/tools/perf/tests/shell/stat_all_pfm.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# perf all libpfm4 events test +# SPDX-License-Identifier: GPL-2.0 + +if perf version --build-options | grep HAVE_LIBPFM | grep -q OFF +then + echo "Skipping, no libpfm4 support" + exit 2 +fi + +err=0 +for p in $(perf list --raw-dump pfm) +do + if echo "$p" | grep -q unc_ + then + echo "Skipping uncore event '$p' that may require additional options." + continue + fi + echo "Testing $p" + result=$(perf stat --pfm-events "$p" true 2>&1) + x=$? + if echo "$result" | grep -q "failed to parse event $p : invalid or missing unit mask" + then + continue + fi + if test "$x" -ne "0" + then + echo "Unexpected exit code '$x'" + err=1 + fi + if ! echo "$result" | grep -q "$p" && ! echo "$result" | grep -q "<not supported>" + then + # We failed to see the event and it is supported. Possibly the workload was + # too small so retry with something longer. + result=$(perf stat --pfm-events "$p" perf bench internals synthesize 2>&1) + x=$? + if test "$x" -ne "0" + then + echo "Unexpected exit code '$x'" + err=1 + fi + if ! echo "$result" | grep -q "$p" + then + echo "Event '$p' not printed in:" + echo "$result" + err=1 + fi + fi +done + +exit "$err" diff --git a/tools/perf/tests/shell/stat_all_pmu.sh b/tools/perf/tests/shell/stat_all_pmu.sh new file mode 100755 index 000000000000..9c466c0efa85 --- /dev/null +++ b/tools/perf/tests/shell/stat_all_pmu.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# perf all PMU test (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +err=0 +result="" + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + echo "$result" + exit 1 +} +trap trap_cleanup EXIT TERM INT + +# Test all PMU events; however exclude parameterized ones (name contains '?') +for p in $(perf list --raw-dump pmu | sed 's/[[:graph:]]\+?[[:graph:]]\+[[:space:]]//g') +do + echo -n "Testing $p -- " + output=$(perf stat -e "$p" true 2>&1) + stat_result=$? + if echo "$output" | grep -q "$p" + then + # Event seen in output. + if [ $stat_result -eq 0 ] && ! echo "$output" | grep -q "<not supported>" + then + # Event supported. + echo "supported" + continue + elif echo "$output" | grep -q "<not supported>" + then + # Event not supported, so ignore. + echo "not supported" + continue + elif echo "$output" | grep -q "No permission to enable" + then + # No permissions, so ignore. + echo "no permission to enable" + continue + elif echo "$output" | grep -q "Bad event name" + then + # Non-existent event. + echo "Error: Bad event name" + echo "$output" + err=1 + continue + fi + fi + + if echo "$output" | grep -q "Access to performance monitoring and observability operations is limited." + then + # Access is limited, so ignore. + echo "access limited" + continue + fi + + # We failed to see the event and it is supported. Possibly the workload was + # too small so retry with something longer. + output=$(perf stat -e "$p" perf bench internals synthesize 2>&1) + if echo "$output" | grep -q "$p" + then + # Event seen in output. + echo "supported" + continue + fi + echo "Error: event '$p' not printed in:" + echo "$output" + err=1 +done + +trap - EXIT TERM INT +exit $err diff --git a/tools/perf/tests/shell/stat_bpf_counters.sh b/tools/perf/tests/shell/stat_bpf_counters.sh new file mode 100755 index 000000000000..95d2ad5d17c6 --- /dev/null +++ b/tools/perf/tests/shell/stat_bpf_counters.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# perf stat --bpf-counters test (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +set -e + +workload="perf test -w sqrtloop" + +# check whether $2 is within +/- 20% of $1 +compare_number() +{ + first_num=$1 + second_num=$2 + + # upper bound is first_num * 120% + upper=$(expr $first_num + $first_num / 5 ) + # lower bound is first_num * 80% + lower=$(expr $first_num - $first_num / 5 ) + + if [ $second_num -gt $upper ] || [ $second_num -lt $lower ]; then + echo "The difference between $first_num and $second_num are greater than 20%." + exit 1 + fi +} + +check_counts() +{ + base_instructions=$1 + bpf_instructions=$2 + + if [ "$base_instructions" = "<not" ]; then + echo "Skipping: instructions event not counted" + exit 2 + fi + if [ "$bpf_instructions" = "<not" ]; then + echo "Failed: instructions not counted with --bpf-counters" + exit 1 + fi +} + +test_bpf_counters() +{ + printf "Testing --bpf-counters " + base_instructions=$(perf stat --no-big-num -e instructions -- $workload 2>&1 | awk '/instructions/ {print $1}') + bpf_instructions=$(perf stat --no-big-num --bpf-counters -e instructions -- $workload 2>&1 | awk '/instructions/ {print $1}') + check_counts $base_instructions $bpf_instructions + compare_number $base_instructions $bpf_instructions + echo "[Success]" +} + +test_bpf_modifier() +{ + printf "Testing bpf event modifier " + stat_output=$(perf stat --no-big-num -e instructions/name=base_instructions/,instructions/name=bpf_instructions/b -- $workload 2>&1) + base_instructions=$(echo "$stat_output"| awk '/base_instructions/ {print $1}') + bpf_instructions=$(echo "$stat_output"| awk '/bpf_instructions/ {print $1}') + check_counts $base_instructions $bpf_instructions + compare_number $base_instructions $bpf_instructions + echo "[Success]" +} + +# skip if --bpf-counters is not supported +if ! perf stat -e instructions --bpf-counters true > /dev/null 2>&1; then + if [ "$1" = "-v" ]; then + echo "Skipping: --bpf-counters not supported" + perf --no-pager stat -e instructions --bpf-counters true || true + fi + exit 2 +fi + +test_bpf_counters +test_bpf_modifier + +exit 0 diff --git a/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh new file mode 100755 index 000000000000..2ec69060c42f --- /dev/null +++ b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# perf stat --bpf-counters --for-each-cgroup test +# SPDX-License-Identifier: GPL-2.0 + +set -e + +test_cgroups= +if [ "$1" = "-v" ]; then + verbose="1" +fi + +# skip if --bpf-counters --for-each-cgroup is not supported +check_bpf_counter() +{ + if ! perf stat -a --bpf-counters --for-each-cgroup / true > /dev/null 2>&1; then + if [ "${verbose}" = "1" ]; then + echo "Skipping: --bpf-counters --for-each-cgroup not supported" + perf --no-pager stat -a --bpf-counters --for-each-cgroup / true || true + fi + exit 2 + fi +} + +# find two cgroups to measure +find_cgroups() +{ + # try usual systemd slices first + if [ -d /sys/fs/cgroup/system.slice ] && [ -d /sys/fs/cgroup/user.slice ]; then + test_cgroups="system.slice,user.slice" + return + fi + + # try root and self cgroups + find_cgroups_self_cgrp=$(grep perf_event /proc/self/cgroup | cut -d: -f3) + if [ -z ${find_cgroups_self_cgrp} ]; then + # cgroup v2 doesn't specify perf_event + find_cgroups_self_cgrp=$(grep ^0: /proc/self/cgroup | cut -d: -f3) + fi + + if [ -z ${find_cgroups_self_cgrp} ]; then + test_cgroups="/" + else + test_cgroups="/,${find_cgroups_self_cgrp}" + fi +} + +# As cgroup events are cpu-wide, we cannot simply compare the result. +# Just check if it runs without failure and has non-zero results. +check_system_wide_counted() +{ + check_system_wide_counted_output=$(perf stat -a --bpf-counters --for-each-cgroup ${test_cgroups} -e cpu-clock -x, sleep 1 2>&1) + if echo ${check_system_wide_counted_output} | grep -q -F "<not "; then + echo "Some system-wide events are not counted" + if [ "${verbose}" = "1" ]; then + echo ${check_system_wide_counted_output} + fi + exit 1 + fi +} + +check_bpf_counter +find_cgroups + +check_system_wide_counted + +exit 0 diff --git a/tools/perf/tests/shell/stat_metrics_values.sh b/tools/perf/tests/shell/stat_metrics_values.sh new file mode 100755 index 000000000000..30566f0b5427 --- /dev/null +++ b/tools/perf/tests/shell/stat_metrics_values.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# perf metrics value validation +# SPDX-License-Identifier: GPL-2.0 + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +grep -q GenuineIntel /proc/cpuinfo || { echo Skipping non-Intel; exit 2; } + +pythonvalidator=$(dirname $0)/lib/perf_metric_validation.py +rulefile=$(dirname $0)/lib/perf_metric_validation_rules.json +tmpdir=$(mktemp -d /tmp/__perf_test.program.XXXXX) +workload="perf bench futex hash -r 2 -s" + +# Add -debug, save data file and full rule file +echo "Launch python validation script $pythonvalidator" +echo "Output will be stored in: $tmpdir" +for cputype in /sys/bus/event_source/devices/cpu_*; do + cputype=$(basename "$cputype") + echo "Testing metrics for: $cputype" + $PYTHON $pythonvalidator -rule $rulefile -output_dir $tmpdir -wl "${workload}" \ + -cputype "${cputype}" + ret=$? + rm -rf $tmpdir + if [ $ret -ne 0 ]; then + echo "Metric validation return with errors. Please check metrics reported with errors." + fi +done +exit $ret + diff --git a/tools/perf/tests/shell/test_arm_callgraph_fp.sh b/tools/perf/tests/shell/test_arm_callgraph_fp.sh new file mode 100755 index 000000000000..9caa36130175 --- /dev/null +++ b/tools/perf/tests/shell/test_arm_callgraph_fp.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# Check Arm64 callgraphs are complete in fp mode +# SPDX-License-Identifier: GPL-2.0 + +shelldir=$(dirname "$0") +# shellcheck source=lib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +if [ "$(uname -m)" != "aarch64" ]; then + exit 2 +fi + +if perf version --build-options | grep HAVE_DWARF_UNWIND_SUPPORT | grep -q OFF +then + echo "Skipping, no dwarf unwind support" + exit 2 +fi + +skip_test_missing_symbol leafloop + +PERF_DATA=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +TEST_PROGRAM="perf test -w leafloop" + +cleanup_files() +{ + rm -f "$PERF_DATA" +} + +trap cleanup_files EXIT TERM INT + +# shellcheck disable=SC2086 +perf record -o "$PERF_DATA" --call-graph fp -e cycles//u --user-callchains -- $TEST_PROGRAM + +# Try opening the file so any immediate errors are visible in the log +perf script -i "$PERF_DATA" -F comm,ip,sym | head -n4 + +# expected perf-script output if 'leaf' has been inserted correctly: +# +# perf +# 728 leaf +# 753 parent +# 76c leafloop +# ... remaining stack to main() ... + +# Each frame is separated by a tab, some spaces and an address +SEP="[[:space:]]+ [[:xdigit:]]+" +perf script -i "$PERF_DATA" -F comm,ip,sym | tr '\n' ' ' | \ + grep -E -q "perf $SEP leaf $SEP parent $SEP leafloop" diff --git a/tools/perf/tests/shell/test_arm_coresight.sh b/tools/perf/tests/shell/test_arm_coresight.sh new file mode 100755 index 000000000000..573af9235b72 --- /dev/null +++ b/tools/perf/tests/shell/test_arm_coresight.sh @@ -0,0 +1,214 @@ +#!/bin/sh +# Check Arm CoreSight trace data recording and synthesized samples (exclusive) + +# Uses the 'perf record' to record trace data with Arm CoreSight sinks; +# then verify if there have any branch samples and instruction samples +# are generated by CoreSight with 'perf script' and 'perf report' +# commands. + +# SPDX-License-Identifier: GPL-2.0 +# Leo Yan <leo.yan@linaro.org>, 2020 + +glb_err=0 + +skip_if_no_cs_etm_event() { + perf list pmu | grep -q 'cs_etm//' && return 0 + + # cs_etm event doesn't exist + return 2 +} + +skip_if_no_cs_etm_event || exit 2 + +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +file=$(mktemp /tmp/temporary_file.XXXXX) + +cleanup_files() +{ + rm -f ${perfdata} + rm -f ${file} + rm -f "${perfdata}.old" + trap - EXIT TERM INT + exit $glb_err +} + +trap cleanup_files EXIT TERM INT + +record_touch_file() { + echo "Recording trace (only user mode) with path: CPU$2 => $1" + rm -f $file + perf record -o ${perfdata} -e cs_etm/@$1/u --per-thread \ + -- taskset -c $2 touch $file > /dev/null 2>&1 +} + +perf_script_branch_samples() { + echo "Looking at perf.data file for dumping branch samples:" + + # Below is an example of the branch samples dumping: + # touch 6512 1 branches:u: ffffb220824c strcmp+0xc (/lib/aarch64-linux-gnu/ld-2.27.so) + # touch 6512 1 branches:u: ffffb22082e0 strcmp+0xa0 (/lib/aarch64-linux-gnu/ld-2.27.so) + # touch 6512 1 branches:u: ffffb2208320 strcmp+0xe0 (/lib/aarch64-linux-gnu/ld-2.27.so) + perf script -F,-time -i ${perfdata} 2>&1 | \ + grep -E " +$1 +[0-9]+ .* +branches:(.*:)? +" > /dev/null 2>&1 +} + +perf_report_branch_samples() { + echo "Looking at perf.data file for reporting branch samples:" + + # Below is an example of the branch samples reporting: + # 73.04% 73.04% touch libc-2.27.so [.] _dl_addr + # 7.71% 7.71% touch libc-2.27.so [.] getenv + # 2.59% 2.59% touch ld-2.27.so [.] strcmp + perf report --stdio -i ${perfdata} 2>&1 | \ + grep -E " +[0-9]+\.[0-9]+% +[0-9]+\.[0-9]+% +$1 " > /dev/null 2>&1 +} + +perf_report_instruction_samples() { + echo "Looking at perf.data file for instruction samples:" + + # Below is an example of the instruction samples reporting: + # 68.12% touch libc-2.27.so [.] _dl_addr + # 5.80% touch libc-2.27.so [.] getenv + # 4.35% touch ld-2.27.so [.] _dl_fixup + perf report --itrace=i20i --stdio -i ${perfdata} 2>&1 | \ + grep -E " +[0-9]+\.[0-9]+% +$1" > /dev/null 2>&1 +} + +arm_cs_report() { + if [ $2 != 0 ]; then + echo "$1: FAIL" + glb_err=$2 + else + echo "$1: PASS" + fi +} + +is_device_sink() { + # If the node of "enable_sink" is existed under the device path, this + # means the device is a sink device. Need to exclude 'tpiu' since it + # cannot support perf PMU. + echo "$1" | grep -E -q -v "tpiu" + + if [ $? -eq 0 ] && [ -e "$1/enable_sink" ]; then + + pmu_dev="/sys/bus/event_source/devices/cs_etm/sinks/$2" + + # Warn if the device is not supported by PMU + if ! [ -f $pmu_dev ]; then + echo "PMU doesn't support $pmu_dev" + fi + + return 0 + fi + + # Otherwise, it's not a sink device + return 1 +} + +arm_cs_iterate_devices() { + for dev in $1/connections/out\:*; do + + # Skip testing if it's not a directory + ! [ -d $dev ] && continue; + + # Read out its symbol link file name + path=`readlink -f $dev` + + # Extract device name from path, e.g. + # path = '/sys/devices/platform/20010000.etf/tmc_etf0' + # `> device_name = 'tmc_etf0' + device_name=$(basename $path) + + if is_device_sink $path $device_name; then + + record_touch_file $device_name $2 && + perf_script_branch_samples touch && + perf_report_branch_samples touch && + perf_report_instruction_samples touch + + err=$? + arm_cs_report "CoreSight path testing (CPU$2 -> $device_name)" $err + fi + + arm_cs_iterate_devices $dev $2 + done +} + +arm_cs_etm_traverse_path_test() { + # Iterate for every ETM device + for dev in /sys/bus/event_source/devices/cs_etm/cpu*; do + # Canonicalize the path + dev=`readlink -f $dev` + + # Find the ETM device belonging to which CPU + cpu=`cat $dev/cpu` + + # Use depth-first search (DFS) to iterate outputs + arm_cs_iterate_devices $dev $cpu + done +} + +arm_cs_etm_system_wide_test() { + echo "Recording trace with system wide mode" + perf record -o ${perfdata} -e cs_etm// -a -- ls > /dev/null 2>&1 + + # System-wide mode should include perf samples so test for that + # instead of ls + perf_script_branch_samples perf && + perf_report_branch_samples perf && + perf_report_instruction_samples perf + + err=$? + arm_cs_report "CoreSight system wide testing" $err +} + +arm_cs_etm_snapshot_test() { + echo "Recording trace with snapshot mode" + perf record -o ${perfdata} -e cs_etm// -S \ + -- dd if=/dev/zero of=/dev/null > /dev/null 2>&1 & + PERFPID=$! + + # Wait for perf program + sleep 1 + + # Send signal to snapshot trace data + kill -USR2 $PERFPID + + # Stop perf program + kill $PERFPID + wait $PERFPID + + perf_script_branch_samples dd && + perf_report_branch_samples dd && + perf_report_instruction_samples dd + + err=$? + arm_cs_report "CoreSight snapshot testing" $err +} + +arm_cs_etm_basic_test() { + echo "Recording trace with '$*'" + perf record -o ${perfdata} "$@" -m,8M -- ls > /dev/null 2>&1 + + perf_script_branch_samples ls && + perf_report_branch_samples ls && + perf_report_instruction_samples ls + + err=$? + arm_cs_report "CoreSight basic testing with '$*'" $err +} + +arm_cs_etm_traverse_path_test +arm_cs_etm_system_wide_test +arm_cs_etm_snapshot_test + +# Test all combinations of per-thread, system-wide and normal mode with +# and without timestamps +arm_cs_etm_basic_test -e cs_etm/timestamp=0/ --per-thread +arm_cs_etm_basic_test -e cs_etm/timestamp=1/ --per-thread +arm_cs_etm_basic_test -e cs_etm/timestamp=0/ -a +arm_cs_etm_basic_test -e cs_etm/timestamp=1/ -a +arm_cs_etm_basic_test -e cs_etm/timestamp=0/ +arm_cs_etm_basic_test -e cs_etm/timestamp=1/ + +exit $glb_err diff --git a/tools/perf/tests/shell/test_arm_coresight_disasm.sh b/tools/perf/tests/shell/test_arm_coresight_disasm.sh new file mode 100755 index 000000000000..be2d26303f94 --- /dev/null +++ b/tools/perf/tests/shell/test_arm_coresight_disasm.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# Check Arm CoreSight disassembly script completes without errors (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +# The disassembly script reconstructs ranges of instructions and gives these to objdump to +# decode. objdump doesn't like ranges that go backwards, but these are a good indication +# that decoding has gone wrong either in OpenCSD, Perf or in the range reconstruction in +# the script. Test all 3 parts are working correctly by running the script. + +skip_if_no_cs_etm_event() { + perf list pmu | grep -q 'cs_etm//' && return 0 + + # cs_etm event doesn't exist + return 2 +} + +skip_if_no_cs_etm_event || exit 2 + +# Assume an error unless we reach the very end +set -e +glb_err=1 + +perfdata_dir=$(mktemp -d /tmp/__perf_test.perf.data.XXXXX) +perfdata=${perfdata_dir}/perf.data +file=$(mktemp /tmp/temporary_file.XXXXX) +# Relative path works whether it's installed or running from repo +script_path=$(dirname "$0")/../../scripts/python/arm-cs-trace-disasm.py + +cleanup_files() +{ + set +e + rm -rf ${perfdata_dir} + rm -f ${file} + trap - EXIT TERM INT + exit $glb_err +} + +trap cleanup_files EXIT TERM INT + +# Ranges start and end on branches, so check for some likely branch instructions +sep="\s\|\s" +branch_search="\sbl${sep}b${sep}b.ne${sep}b.eq${sep}cbz\s" + +## Test kernel ## +if [ -e /proc/kcore ]; then + echo "Testing kernel disassembly" + perf record -o ${perfdata} -e cs_etm//k --kcore -- touch $file > /dev/null 2>&1 + perf script -i ${perfdata} -s python:${script_path} -- \ + -d --stop-sample=30 2> /dev/null > ${file} + grep -q -e ${branch_search} ${file} + echo "Found kernel branches" +else + # kcore is required for correct kernel decode due to runtime code patching + echo "No kcore, skipping kernel test" +fi + +## Test user ## +echo "Testing userspace disassembly" +perf record -o ${perfdata} -e cs_etm//u -- touch $file > /dev/null 2>&1 +perf script -i ${perfdata} -s python:${script_path} -- \ + -d --stop-sample=30 2> /dev/null > ${file} +grep -q -e ${branch_search} ${file} +echo "Found userspace branches" + +glb_err=0 diff --git a/tools/perf/tests/shell/test_arm_spe.sh b/tools/perf/tests/shell/test_arm_spe.sh new file mode 100755 index 000000000000..a69aab70dd8a --- /dev/null +++ b/tools/perf/tests/shell/test_arm_spe.sh @@ -0,0 +1,143 @@ +#!/bin/sh +# Check Arm SPE trace data recording and synthesized samples (exclusive) + +# Uses the 'perf record' to record trace data of Arm SPE events; +# then verify if any SPE event samples are generated by SPE with +# 'perf script' and 'perf report' commands. + +# SPDX-License-Identifier: GPL-2.0 +# German Gomez <german.gomez@arm.com>, 2021 + +skip_if_no_arm_spe_event() { + perf list pmu | grep -E -q 'arm_spe_[0-9]+//' && return 0 + + # arm_spe event doesn't exist + return 2 +} + +skip_if_no_arm_spe_event || exit 2 + +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +glb_err=0 + +cleanup_files() +{ + rm -f ${perfdata} + rm -f ${perfdata}.old + exit $glb_err +} + +trap cleanup_files EXIT TERM INT + +arm_spe_report() { + if [ $2 = 0 ]; then + echo "$1: PASS" + elif [ $2 = 2 ]; then + echo "$1: SKIPPED" + else + echo "$1: FAIL" + glb_err=$2 + fi +} + +perf_script_samples() { + echo "Looking at perf.data file for dumping samples:" + + # from arm-spe.c/arm_spe_synth_events() + events="(ld1-miss|ld1-access|llc-miss|lld-access|tlb-miss|tlb-access|branch-miss|remote-access|memory)" + + # Below is an example of the samples dumping: + # dd 3048 [002] 1 l1d-access: ffffaa64999c __GI___libc_write+0x3c (/lib/aarch64-linux-gnu/libc-2.27.so) + # dd 3048 [002] 1 tlb-access: ffffaa64999c __GI___libc_write+0x3c (/lib/aarch64-linux-gnu/libc-2.27.so) + # dd 3048 [002] 1 memory: ffffaa64999c __GI___libc_write+0x3c (/lib/aarch64-linux-gnu/libc-2.27.so) + perf script -F,-time -i ${perfdata} 2>&1 | \ + grep -E " +$1 +[0-9]+ .* +${events}:(.*:)? +" > /dev/null 2>&1 +} + +perf_report_samples() { + echo "Looking at perf.data file for reporting samples:" + + # Below is an example of the samples reporting: + # 73.04% 73.04% dd libc-2.27.so [.] _dl_addr + # 7.71% 7.71% dd libc-2.27.so [.] getenv + # 2.59% 2.59% dd ld-2.27.so [.] strcmp + perf report --stdio -i ${perfdata} 2>&1 | \ + grep -E " +[0-9]+\.[0-9]+% +[0-9]+\.[0-9]+% +$1 " > /dev/null 2>&1 +} + +arm_spe_snapshot_test() { + echo "Recording trace with snapshot mode $perfdata" + perf record -o ${perfdata} -e arm_spe// -S \ + -- dd if=/dev/zero of=/dev/null > /dev/null 2>&1 & + PERFPID=$! + + # Wait for perf program + sleep 1 + + # Send signal to snapshot trace data + kill -USR2 $PERFPID + + # Stop perf program + kill $PERFPID + wait $PERFPID + + perf_script_samples dd && + perf_report_samples dd + + err=$? + arm_spe_report "SPE snapshot testing" $err +} + +arm_spe_system_wide_test() { + echo "Recording trace with system-wide mode $perfdata" + + perf record -o - -e dummy -a -B true > /dev/null 2>&1 + if [ $? != 0 ]; then + arm_spe_report "SPE system-wide testing" 2 + return + fi + + perf record -o ${perfdata} -e arm_spe// -a --no-bpf-event \ + -- dd if=/dev/zero of=/dev/null count=100000 > /dev/null 2>&1 + + perf_script_samples dd && + perf_report_samples dd + + err=$? + arm_spe_report "SPE system-wide testing" $err +} + +arm_spe_discard_test() { + echo "SPE discard mode" + + for f in /sys/bus/event_source/devices/arm_spe_*; do + if [ -e "$f/format/discard" ]; then + cpu=$(cut -c -1 "$f/cpumask") + break + fi + done + + if [ -z $cpu ]; then + arm_spe_report "SPE discard mode not present" 2 + return + fi + + # Test can use wildcard SPE instance and Perf will only open the event + # on instances that have that format flag. But make sure the target + # runs on an instance with discard mode otherwise we're not testing + # anything. + perf record -o ${perfdata} -e arm_spe/discard/ -N -B --no-bpf-event \ + -- taskset --cpu-list $cpu true + + if perf report -i ${perfdata} --stats | grep 'AUX events\|AUXTRACE events'; then + arm_spe_report "SPE discard mode found unexpected data" 1 + else + arm_spe_report "SPE discard mode" 0 + fi +} + +arm_spe_snapshot_test +arm_spe_system_wide_test +arm_spe_discard_test + +exit $glb_err diff --git a/tools/perf/tests/shell/test_arm_spe_fork.sh b/tools/perf/tests/shell/test_arm_spe_fork.sh new file mode 100755 index 000000000000..8efeef9fb956 --- /dev/null +++ b/tools/perf/tests/shell/test_arm_spe_fork.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# Check Arm SPE doesn't hang when there are forks + +# SPDX-License-Identifier: GPL-2.0 +# German Gomez <german.gomez@arm.com>, 2022 + +skip_if_no_arm_spe_event() { + perf list pmu | grep -E -q 'arm_spe_[0-9]+//' && return 0 + return 2 +} + +skip_if_no_arm_spe_event || exit 2 + +TEST_PROGRAM="perf test -w sqrtloop 10" +PERF_DATA=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +PERF_RECORD_LOG=$(mktemp /tmp/__perf_test.log.XXXXX) + +cleanup_files() +{ + echo "Cleaning up files..." + rm -f ${PERF_RECORD_LOG} + rm -f ${PERF_DATA} +} + +trap cleanup_files EXIT TERM INT + +echo "Recording workload..." +perf record -o ${PERF_DATA} -e arm_spe/period=65536/ -vvv -- $TEST_PROGRAM > ${PERF_RECORD_LOG} 2>&1 & +PERFPID=$! + +# Check if perf hangs by checking the perf-record logs. +sleep 1 +log0=$(wc -l $PERF_RECORD_LOG) +echo Log lines = $log0 +sleep 1 +log1=$(wc -l $PERF_RECORD_LOG) +echo Log lines after 1 second = $log1 + +kill $PERFPID +wait $PERFPID + +if [ "$log0" = "$log1" ]; +then + echo "SPE hang test: FAIL" + exit 1 +else + echo "SPE hang test: PASS" +fi + +exit 0 diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh new file mode 100755 index 000000000000..9138fa83bf36 --- /dev/null +++ b/tools/perf/tests/shell/test_brstack.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# Check branch stack sampling + +# SPDX-License-Identifier: GPL-2.0 +# German Gomez <german.gomez@arm.com>, 2022 + +shelldir=$(dirname "$0") +# shellcheck source=lib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +# skip the test if the hardware doesn't support branch stack sampling +# and if the architecture doesn't support filter types: any,save_type,u +if ! perf record -o- --no-buildid --branch-filter any,save_type,u -- true > /dev/null 2>&1 ; then + echo "skip: system doesn't support filter types: any,save_type,u" + exit 2 +fi + +skip_test_missing_symbol brstack_bench + +err=0 +TMPDIR=$(mktemp -d /tmp/__perf_test.program.XXXXX) +TESTPROG="perf test -w brstack" + +cleanup() { + rm -rf $TMPDIR + trap - EXIT TERM INT +} + +trap_cleanup() { + set +e + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_user_branches() { + echo "Testing user branch stack sampling" + + perf record -o "$TMPDIR/perf.data" --branch-filter any,save_type,u -- ${TESTPROG} > "$TMPDIR/record.txt" 2>&1 + perf script -i "$TMPDIR/perf.data" --fields brstacksym > "$TMPDIR/perf.script" + + # example of branch entries: + # brstack_foo+0x14/brstack_bar+0x40/P/-/-/0/CALL + + expected=( + "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/IND_CALL/.*$" + "^brstack_foo\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$" + "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/CALL/.*$" + "^brstack_bench\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$" + "^brstack_bar\+[^ ]*/brstack_foo\+[^ ]*/RET/.*$" + "^brstack_foo\+[^ ]*/brstack_bench\+[^ ]*/RET/.*$" + "^brstack_bench\+[^ ]*/brstack_bench\+[^ ]*/COND/.*$" + "^brstack\+[^ ]*/brstack\+[^ ]*/UNCOND/.*$" + ) + for x in "${expected[@]}" + do + if ! tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep -E -m1 -q "$x" + then + echo "Branches missing $x" + err=1 + fi + done + # some branch types are still not being tested: + # IND COND_CALL COND_RET SYSCALL SYSRET IRQ SERROR NO_TX +} + +# first argument <arg0> is the argument passed to "--branch-stack <arg0>,save_type,u" +# second argument are the expected branch types for the given filter +test_filter() { + test_filter_filter=$1 + test_filter_expect=$2 + + echo "Testing branch stack filtering permutation ($test_filter_filter,$test_filter_expect)" + perf record -o "$TMPDIR/perf.data" --branch-filter "$test_filter_filter,save_type,u" -- ${TESTPROG} > "$TMPDIR/record.txt" 2>&1 + perf script -i "$TMPDIR/perf.data" --fields brstack > "$TMPDIR/perf.script" + + # fail if we find any branch type that doesn't match any of the expected ones + # also consider UNKNOWN branch types (-) + if [ ! -s "$TMPDIR/perf.script" ] + then + echo "Empty script output" + err=1 + return + fi + # Look for lines not matching test_filter_expect ignoring issues caused + # by empty output + tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep '.' | \ + grep -E -vm1 "^[^ ]*/($test_filter_expect|-|( *))/.*$" \ + > "$TMPDIR/perf.script-filtered" || true + if [ -s "$TMPDIR/perf.script-filtered" ] + then + echo "Unexpected branch filter in script output" + cat "$TMPDIR/perf.script" + err=1 + return + fi +} + +set -e + +test_user_branches + +test_filter "any_call" "CALL|IND_CALL|COND_CALL|SYSCALL|IRQ" +test_filter "call" "CALL|SYSCALL" +test_filter "cond" "COND" +test_filter "any_ret" "RET|COND_RET|SYSRET|ERET" + +test_filter "call,cond" "CALL|SYSCALL|COND" +test_filter "any_call,cond" "CALL|IND_CALL|COND_CALL|IRQ|SYSCALL|COND" +test_filter "cond,any_call,any_ret" "COND|CALL|IND_CALL|COND_CALL|SYSCALL|IRQ|RET|COND_RET|SYSRET|ERET" + +cleanup +exit $err diff --git a/tools/perf/tests/shell/test_data_symbol.sh b/tools/perf/tests/shell/test_data_symbol.sh new file mode 100755 index 000000000000..d61b5659a46d --- /dev/null +++ b/tools/perf/tests/shell/test_data_symbol.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# Test data symbol (exclusive) + +# SPDX-License-Identifier: GPL-2.0 +# Leo Yan <leo.yan@linaro.org>, 2022 + +shelldir=$(dirname "$0") + +# shellcheck source=lib/perf_has_symbol.sh +. "${shelldir}"/lib/perf_has_symbol.sh + +skip_if_no_mem_event() { + perf mem record -e list 2>&1 | grep -E -q 'available' && return 0 + return 2 +} + +skip_if_no_mem_event || exit 2 + +skip_test_missing_symbol workload_datasym_buf1 + +TEST_PROGRAM="perf test -w datasym" +PERF_DATA=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +ERR_FILE=$(mktemp /tmp/__perf_test.stderr.XXXXX) + +check_result() { + # The memory report format is as below: + # 99.92% ... [.] workload_datasym_buf1+0x38 + result=$(perf mem report -i ${PERF_DATA} -s symbol_daddr -q 2>&1 | + awk '/workload_datasym_buf1/ { print $4 }') + + # Testing is failed if has no any sample for "workload_datasym_buf1" + [ -z "$result" ] && return 1 + + while IFS= read -r line; do + # The "data1" and "data2" fields in structure + # "workload_datasym_buf1" have offset "0x0" and "0x38", returns + # failure if detect any other offset value. + if [ "$line" != "workload_datasym_buf1+0x0" ] && \ + [ "$line" != "workload_datasym_buf1+0x38" ]; then + return 1 + fi + done <<< "$result" + + return 0 +} + +cleanup_files() +{ + echo "Cleaning up files..." + rm -f ${PERF_DATA} +} + +trap cleanup_files exit term int + +echo "Recording workload..." + +is_amd=$(grep -E -c 'vendor_id.*AuthenticAMD' /proc/cpuinfo) +if (($is_amd >= 1)); then + mem_events="$(perf mem record -v -e list 2>&1)" + if ! [[ "$mem_events" =~ ^mem\-ldst.*ibs_op/(.*)/.*available ]]; then + echo "ERROR: mem-ldst event is not matching" + exit 1 + fi + + # --ldlat on AMD: + # o Zen4 and earlier uarch does not support ldlat + # o Even on supported platforms, it's disabled (--ldlat=0) by default. + ldlat=${BASH_REMATCH[1]} + if [[ -n $ldlat ]]; then + if ! [[ "$ldlat" =~ ldlat=0 ]]; then + echo "ERROR: ldlat not initialized to 0?" + exit 1 + fi + + mem_events="$(perf mem record -v --ldlat=150 -e list 2>&1)" + if ! [[ "$mem_events" =~ ^mem-ldst.*ibs_op/ldlat=150/.*available ]]; then + echo "ERROR: --ldlat not honored?" + exit 1 + fi + fi + + # perf mem/c2c internally uses IBS PMU on AMD CPU which doesn't + # support user/kernel filtering and per-process monitoring on older + # kernels, spin program on specific CPU and test in per-CPU mode. + perf mem record -vvv -o ${PERF_DATA} -C 0 -- taskset -c 0 $TEST_PROGRAM 2>"${ERR_FILE}" +else + perf mem record -vvv --all-user -o ${PERF_DATA} -- $TEST_PROGRAM 2>"${ERR_FILE}" +fi + +check_result +exit $? diff --git a/tools/perf/tests/shell/test_intel_pt.sh b/tools/perf/tests/shell/test_intel_pt.sh new file mode 100755 index 000000000000..32a9b8dcb200 --- /dev/null +++ b/tools/perf/tests/shell/test_intel_pt.sh @@ -0,0 +1,721 @@ +#!/bin/sh +# Miscellaneous Intel PT testing (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +set -e + +# Skip if no Intel PT +perf list pmu | grep -q 'intel_pt//' || exit 2 + +shelldir=$(dirname "$0") +# shellcheck source=lib/waiting.sh +. "${shelldir}"/lib/waiting.sh + +skip_cnt=0 +ok_cnt=0 +err_cnt=0 + +temp_dir=$(mktemp -d /tmp/perf-test-intel-pt-sh.XXXXXXXXXX) + +tmpfile="${temp_dir}/tmp-perf.data" +perfdatafile="${temp_dir}/test-perf.data" +outfile="${temp_dir}/test-out.txt" +errfile="${temp_dir}/test-err.txt" +workload="${temp_dir}/workload" +awkscript="${temp_dir}/awkscript" +jitdump_workload="${temp_dir}/jitdump_workload" +maxbrstack="${temp_dir}/maxbrstack.py" + +cleanup() +{ + trap - EXIT TERM INT + sane=$(echo "${temp_dir}" | cut -b 1-26) + if [ "${sane}" = "/tmp/perf-test-intel-pt-sh" ] ; then + echo "--- Cleaning up ---" + rm -f "${temp_dir}/"* + rmdir "${temp_dir}" + fi +} + +trap_cleanup() +{ + cleanup + exit 1 +} + +trap trap_cleanup EXIT TERM INT + +# perf record for testing without decoding +perf_record_no_decode() +{ + # Options to speed up recording: no post-processing, no build-id cache update, + # and no BPF events. + perf record -B -N --no-bpf-event "$@" +} + +# perf record for testing should not need BPF events +perf_record_no_bpf() +{ + # Options for no BPF events + perf record --no-bpf-event "$@" +} + +have_workload=false +cat << _end_of_file_ | /usr/bin/cc -o "${workload}" -xc - -pthread && have_workload=true +#include <time.h> +#include <pthread.h> + +void work(void) { + struct timespec tm = { + .tv_nsec = 1000000, + }; + int i; + + /* Run for about 30 seconds */ + for (i = 0; i < 30000; i++) + nanosleep(&tm, NULL); +} + +void *threadfunc(void *arg) { + work(); + return NULL; +} + +int main(void) { + pthread_t th; + + pthread_create(&th, NULL, threadfunc, NULL); + work(); + pthread_join(th, NULL); + return 0; +} +_end_of_file_ + +can_cpu_wide() +{ + echo "Checking for CPU-wide recording on CPU $1" + if ! perf_record_no_decode -o "${tmpfile}" -e dummy:u -C "$1" true >/dev/null 2>&1 ; then + echo "No so skipping" + return 2 + fi + echo OK + return 0 +} + +test_system_wide_side_band() +{ + echo "--- Test system-wide sideband ---" + + # Need CPU 0 and CPU 1 + can_cpu_wide 0 || return $? + can_cpu_wide 1 || return $? + + # Record on CPU 0 a task running on CPU 1 + perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u -C 0 -- taskset --cpu-list 1 uname + + # Should get MMAP events from CPU 1 because they can be needed to decode + mmap_cnt=$(perf script -i "${perfdatafile}" --no-itrace --show-mmap-events -C 1 2>/dev/null | grep -c MMAP) + + if [ "${mmap_cnt}" -gt 0 ] ; then + echo OK + return 0 + fi + + echo "Failed to record MMAP events on CPU 1 when tracing CPU 0" + return 1 +} + +can_kernel() +{ + if [ -z "${can_kernel_trace}" ] ; then + can_kernel_trace=0 + perf_record_no_decode -o "${tmpfile}" -e dummy:k true >/dev/null 2>&1 && can_kernel_trace=1 + fi + if [ ${can_kernel_trace} -eq 0 ] ; then + echo "SKIP: no kernel tracing" + return 2 + fi + return 0 +} + +test_per_thread() +{ + k="$1" + desc="$2" + + echo "--- Test per-thread ${desc}recording ---" + + if ! $have_workload ; then + echo "No workload, so skipping" + return 2 + fi + + if [ "${k}" = "k" ] ; then + can_kernel || return 2 + fi + + cat <<- "_end_of_file_" > "${awkscript}" + BEGIN { + s = "[ ]*" + u = s"[0-9]+"s + d = s"[0-9-]+"s + x = s"[0-9a-fA-FxX]+"s + mmapping = "idx"u": mmapping fd"u + set_output = "idx"u": set output fd"u"->"u + perf_event_open = "sys_perf_event_open: pid"d"cpu"d"group_fd"d"flags"x"="u + } + + /perf record opening and mmapping events/ { + if (!done) + active = 1 + } + + /perf record done opening and mmapping events/ { + active = 0 + done = 1 + } + + $0 ~ perf_event_open && active { + match($0, perf_event_open) + $0 = substr($0, RSTART, RLENGTH) + pid = $3 + cpu = $5 + fd = $11 + print "pid " pid " cpu " cpu " fd " fd " : " $0 + fd_array[fd] = fd + pid_array[fd] = pid + cpu_array[fd] = cpu + } + + $0 ~ mmapping && active { + match($0, mmapping) + $0 = substr($0, RSTART, RLENGTH) + fd = $5 + print "fd " fd " : " $0 + if (fd in fd_array) { + mmap_array[fd] = 1 + } else { + print "Unknown fd " fd + exit 1 + } + } + + $0 ~ set_output && active { + match($0, set_output) + $0 = substr($0, RSTART, RLENGTH) + fd = $6 + fd_to = $8 + print "fd " fd " fd_to " fd_to " : " $0 + if (fd in fd_array) { + if (fd_to in fd_array) { + set_output_array[fd] = fd_to + } else { + print "Unknown fd " fd_to + exit 1 + } + } else { + print "Unknown fd " fd + exit 1 + } + } + + END { + print "Checking " length(fd_array) " fds" + for (fd in fd_array) { + if (fd in mmap_array) { + pid = pid_array[fd] + if (pid != -1) { + if (pid in pids) { + print "More than 1 mmap for PID " pid + exit 1 + } + pids[pid] = 1 + } + cpu = cpu_array[fd] + if (cpu != -1) { + if (cpu in cpus) { + print "More than 1 mmap for CPU " cpu + exit 1 + } + cpus[cpu] = 1 + } + } else if (!(fd in set_output_array)) { + print "No mmap for fd " fd + exit 1 + } + } + n = length(pids) + if (n != thread_cnt) { + print "Expected " thread_cnt " per-thread mmaps - found " n + exit 1 + } + } + _end_of_file_ + + $workload & + w1=$! + $workload & + w2=$! + echo "Workload PIDs are $w1 and $w2" + wait_for_threads ${w1} 2 + wait_for_threads ${w2} 2 + + perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u"${k}" -vvv --per-thread -p "${w1},${w2}" 2>"${errfile}" >"${outfile}" & + ppid=$! + echo "perf PID is $ppid" + wait_for_perf_to_start ${ppid} "${errfile}" || return 1 + + kill ${w1} + wait_for_process_to_exit ${w1} || return 1 + is_running ${ppid} || return 1 + + kill ${w2} + wait_for_process_to_exit ${w2} || return 1 + wait_for_process_to_exit ${ppid} || return 1 + + awk -v thread_cnt=4 -f "${awkscript}" "${errfile}" || return 1 + + echo OK + return 0 +} + +test_jitdump() +{ + echo "--- Test tracing self-modifying code that uses jitdump ---" + + script_path=$(realpath "$0") + script_dir=$(dirname "$script_path") + jitdump_incl_dir="${script_dir}/../../util" + jitdump_h="${jitdump_incl_dir}/jitdump.h" + + if ! perf check feature -q libelf ; then + echo "SKIP: libelf is needed for jitdump" + return 2 + fi + + if [ ! -e "${jitdump_h}" ] ; then + echo "SKIP: Include file jitdump.h not found" + return 2 + fi + + if [ -z "${have_jitdump_workload}" ] ; then + have_jitdump_workload=false + # Create a workload that uses self-modifying code and generates its own jitdump file + cat <<- "_end_of_file_" | /usr/bin/cc -o "${jitdump_workload}" -I "${jitdump_incl_dir}" -xc - -pthread && have_jitdump_workload=true + #define _GNU_SOURCE + #include <sys/mman.h> + #include <sys/types.h> + #include <stddef.h> + #include <stdio.h> + #include <stdint.h> + #include <unistd.h> + #include <string.h> + + #include "jitdump.h" + + #define CHK_BYTE 0x5a + + static inline uint64_t rdtsc(void) + { + unsigned int low, high; + + asm volatile("rdtsc" : "=a" (low), "=d" (high)); + + return low | ((uint64_t)high) << 32; + } + + static FILE *open_jitdump(void) + { + struct jitheader header = { + .magic = JITHEADER_MAGIC, + .version = JITHEADER_VERSION, + .total_size = sizeof(header), + .pid = getpid(), + .timestamp = rdtsc(), + .flags = JITDUMP_FLAGS_ARCH_TIMESTAMP, + }; + char filename[256]; + FILE *f; + void *m; + + snprintf(filename, sizeof(filename), "jit-%d.dump", getpid()); + f = fopen(filename, "w+"); + if (!f) + goto err; + /* Create an MMAP event for the jitdump file. That is how perf tool finds it. */ + m = mmap(0, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fileno(f), 0); + if (m == MAP_FAILED) + goto err_close; + munmap(m, 4096); + if (fwrite(&header,sizeof(header),1,f) != 1) + goto err_close; + return f; + + err_close: + fclose(f); + err: + return NULL; + } + + static int write_jitdump(FILE *f, void *addr, const uint8_t *dat, size_t sz, uint64_t *idx) + { + struct jr_code_load rec = { + .p.id = JIT_CODE_LOAD, + .p.total_size = sizeof(rec) + sz, + .p.timestamp = rdtsc(), + .pid = getpid(), + .tid = gettid(), + .vma = (unsigned long)addr, + .code_addr = (unsigned long)addr, + .code_size = sz, + .code_index = ++*idx, + }; + + if (fwrite(&rec,sizeof(rec),1,f) != 1 || + fwrite(dat, sz, 1, f) != 1) + return -1; + return 0; + } + + static void close_jitdump(FILE *f) + { + fclose(f); + } + + int main() + { + /* Get a memory page to store executable code */ + void *addr = mmap(0, 4096, PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + /* Code to execute: mov CHK_BYTE, %eax ; ret */ + uint8_t dat[] = {0xb8, CHK_BYTE, 0x00, 0x00, 0x00, 0xc3}; + FILE *f = open_jitdump(); + uint64_t idx = 0; + int ret = 1; + + if (!f) + return 1; + /* Copy executable code to executable memory page */ + memcpy(addr, dat, sizeof(dat)); + /* Record it in the jitdump file */ + if (write_jitdump(f, addr, dat, sizeof(dat), &idx)) + goto out_close; + /* Call it */ + ret = ((int (*)(void))addr)() - CHK_BYTE; + out_close: + close_jitdump(f); + return ret; + } + _end_of_file_ + fi + + if ! $have_jitdump_workload ; then + echo "SKIP: No jitdump workload" + return 2 + fi + + # Change to temp_dir so jitdump collateral files go there + cd "${temp_dir}" + perf_record_no_bpf -o "${tmpfile}" -e intel_pt//u "${jitdump_workload}" + perf inject -i "${tmpfile}" -o "${perfdatafile}" --jit + decode_br_cnt=$(perf script -i "${perfdatafile}" --itrace=b | wc -l) + # Note that overflow and lost errors are suppressed for the error count + decode_err_cnt=$(perf script -i "${perfdatafile}" --itrace=e-o-l | grep -ci error) + cd - + # Should be thousands of branches + if [ "${decode_br_cnt}" -lt 1000 ] ; then + echo "Decode failed, only ${decode_br_cnt} branches" + return 1 + fi + # Should be no errors + if [ "${decode_err_cnt}" -ne 0 ] ; then + echo "Decode failed, ${decode_err_cnt} errors" + perf script -i "${perfdatafile}" --itrace=e-o-l --show-mmap-events | cat + return 1 + fi + + echo OK + return 0 +} + +test_packet_filter() +{ + echo "--- Test with MTC and TSC disabled ---" + # Disable MTC and TSC + perf_record_no_decode -o "${perfdatafile}" -e intel_pt/mtc=0,tsc=0/u uname + # Should not get MTC packet + mtc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "MTC 0x") + if [ "${mtc_cnt}" -ne 0 ] ; then + echo "Failed to filter with mtc=0" + return 1 + fi + # Should not get TSC package + tsc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TSC 0x") + if [ "${tsc_cnt}" -ne 0 ] ; then + echo "Failed to filter with tsc=0" + return 1 + fi + echo OK + return 0 +} + +test_disable_branch() +{ + echo "--- Test with branches disabled ---" + # Disable branch + perf_record_no_decode -o "${perfdatafile}" -e intel_pt/branch=0/u uname + # Should not get branch related packets + tnt_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TNT 0x") + tip_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TIP 0x") + fup_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "FUP 0x") + if [ "${tnt_cnt}" -ne 0 ] || [ "${tip_cnt}" -ne 0 ] || [ "${fup_cnt}" -ne 0 ] ; then + echo "Failed to disable branches" + return 1 + fi + echo OK + return 0 +} + +test_time_cyc() +{ + echo "--- Test with/without CYC ---" + # Check if CYC is supported + cyc=$(cat /sys/bus/event_source/devices/intel_pt/caps/psb_cyc) + if [ "${cyc}" != "1" ] ; then + echo "SKIP: CYC is not supported" + return 2 + fi + # Enable CYC + perf_record_no_decode -o "${perfdatafile}" -e intel_pt/cyc/u uname + # should get CYC packets + cyc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "CYC 0x") + if [ "${cyc_cnt}" = "0" ] ; then + echo "Failed to get CYC packet" + return 1 + fi + # Without CYC + perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u uname + # Should not get CYC packets + cyc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "CYC 0x") + if [ "${cyc_cnt}" -gt 0 ] ; then + echo "Still get CYC packet without cyc" + return 1 + fi + echo OK + return 0 +} + +test_sample() +{ + echo "--- Test recording with sample mode ---" + # Check if recording with sample mode is working + if ! perf_record_no_decode -o "${perfdatafile}" --aux-sample=8192 -e '{intel_pt//u,branch-misses:u}' uname ; then + echo "perf record failed with --aux-sample" + return 1 + fi + # Check with event with PMU name + if perf_record_no_decode -o "${perfdatafile}" -e br_misp_retired.all_branches:u uname ; then + if ! perf_record_no_decode -o "${perfdatafile}" -e '{intel_pt//,br_misp_retired.all_branches/aux-sample-size=8192/}:u' uname ; then + echo "perf record failed with --aux-sample-size" + return 1 + fi + fi + echo OK + return 0 +} + +test_kernel_trace() +{ + echo "--- Test with kernel trace ---" + # Check if recording with kernel trace is working + can_kernel || return 2 + if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt//k -m1,128 uname ; then + echo "perf record failed with intel_pt//k" + return 1 + fi + echo OK + return 0 +} + +test_virtual_lbr() +{ + echo "--- Test virtual LBR ---" + # Check if python script is supported + libpython=$(perf version --build-options | grep python | grep -cv OFF) + if [ "${libpython}" != "1" ] ; then + echo "SKIP: python scripting is not supported" + return 2 + fi + + # Python script to determine the maximum size of branch stacks + cat << "_end_of_file_" > "${maxbrstack}" +from __future__ import print_function + +bmax = 0 + +def process_event(param_dict): + if "brstack" in param_dict: + brstack = param_dict["brstack"] + n = len(brstack) + global bmax + if n > bmax: + bmax = n + +def trace_end(): + print("max brstack", bmax) +_end_of_file_ + + # Check if virtual lbr is working + perf_record_no_bpf -o "${perfdatafile}" --aux-sample -e '{intel_pt//,cycles}:u' uname + times_val=$(perf script -i "${perfdatafile}" --itrace=L -s "${maxbrstack}" 2>/dev/null | grep "max brstack " | cut -d " " -f 3) + case "${times_val}" in + [0-9]*) ;; + *) times_val=0;; + esac + if [ "${times_val}" -lt 2 ] ; then + echo "Failed with virtual lbr" + return 1 + fi + echo OK + return 0 +} + +test_power_event() +{ + echo "--- Test power events ---" + # Check if power events are supported + power_event=$(cat /sys/bus/event_source/devices/intel_pt/caps/power_event_trace) + if [ "${power_event}" != "1" ] ; then + echo "SKIP: power_event_trace is not supported" + return 2 + fi + if ! perf_record_no_decode -o "${perfdatafile}" -a -e intel_pt/pwr_evt/u uname ; then + echo "perf record failed with pwr_evt" + return 1 + fi + echo OK + return 0 +} + +test_no_tnt() +{ + echo "--- Test with TNT packets disabled ---" + # Check if TNT disable is supported + notnt=$(cat /sys/bus/event_source/devices/intel_pt/caps/tnt_disable) + if [ "${notnt}" != "1" ] ; then + echo "SKIP: tnt_disable is not supported" + return 2 + fi + perf_record_no_decode -o "${perfdatafile}" -e intel_pt/notnt/u uname + # Should be no TNT packets + tnt_cnt=$(perf script -i "${perfdatafile}" -D | grep -c TNT) + if [ "${tnt_cnt}" -ne 0 ] ; then + echo "TNT packets still there after notnt" + return 1 + fi + echo OK + return 0 +} + +test_event_trace() +{ + echo "--- Test with event_trace ---" + # Check if event_trace is supported + event_trace=$(cat /sys/bus/event_source/devices/intel_pt/caps/event_trace) + if [ "${event_trace}" != 1 ] ; then + echo "SKIP: event_trace is not supported" + return 2 + fi + if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt/event/u uname ; then + echo "perf record failed with event trace" + return 1 + fi + echo OK + return 0 +} + +test_pipe() +{ + echo "--- Test with pipe mode ---" + # Check if it works with pipe + if ! perf_record_no_bpf -o- -e intel_pt//u uname | perf report -q -i- --itrace=i10000 ; then + echo "perf record + report failed with pipe mode" + return 1 + fi + if ! perf_record_no_bpf -o- -e intel_pt//u uname | perf inject -b > /dev/null ; then + echo "perf record + inject failed with pipe mode" + return 1 + fi + echo OK + return 0 +} + +test_pause_resume() +{ + echo "--- Test with pause / resume ---" + if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt/aux-action=start-paused/u uname ; then + echo "SKIP: pause / resume is not supported" + return 2 + fi + if ! perf_record_no_bpf -o "${perfdatafile}" \ + -e intel_pt/aux-action=start-paused/u \ + -e instructions/period=50000,aux-action=resume,name=Resume/u \ + -e instructions/period=100000,aux-action=pause,name=Pause/u uname ; then + echo "perf record with pause / resume failed" + return 1 + fi + if ! perf script -i "${perfdatafile}" --itrace=b -Fperiod,event | \ + awk 'BEGIN {paused=1;branches=0} + /Resume/ {paused=0} + /branches/ {if (paused) exit 1;branches=1} + /Pause/ {paused=1} + END {if (!branches) exit 1}' ; then + echo "perf record with pause / resume failed" + return 1 + fi + echo OK + return 0 +} + +count_result() +{ + if [ "$1" -eq 2 ] ; then + skip_cnt=$((skip_cnt + 1)) + return + fi + if [ "$1" -eq 0 ] ; then + ok_cnt=$((ok_cnt + 1)) + return + fi + err_cnt=$((err_cnt + 1)) +} + +ret=0 +test_system_wide_side_band || ret=$? ; count_result $ret ; ret=0 +test_per_thread "" "" || ret=$? ; count_result $ret ; ret=0 +test_per_thread "k" "(incl. kernel) " || ret=$? ; count_result $ret ; ret=0 +test_jitdump || ret=$? ; count_result $ret ; ret=0 +test_packet_filter || ret=$? ; count_result $ret ; ret=0 +test_disable_branch || ret=$? ; count_result $ret ; ret=0 +test_time_cyc || ret=$? ; count_result $ret ; ret=0 +test_sample || ret=$? ; count_result $ret ; ret=0 +test_kernel_trace || ret=$? ; count_result $ret ; ret=0 +test_virtual_lbr || ret=$? ; count_result $ret ; ret=0 +test_power_event || ret=$? ; count_result $ret ; ret=0 +test_no_tnt || ret=$? ; count_result $ret ; ret=0 +test_event_trace || ret=$? ; count_result $ret ; ret=0 +test_pipe || ret=$? ; count_result $ret ; ret=0 +test_pause_resume || ret=$? ; count_result $ret ; ret=0 + +cleanup + +echo "--- Done ---" + +if [ ${err_cnt} -gt 0 ] ; then + exit 1 +fi + +if [ ${ok_cnt} -gt 0 ] ; then + exit 0 +fi + +exit 2 diff --git a/tools/perf/tests/shell/test_java_symbol.sh b/tools/perf/tests/shell/test_java_symbol.sh new file mode 100755 index 000000000000..499539d1c479 --- /dev/null +++ b/tools/perf/tests/shell/test_java_symbol.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Test java symbol + +# SPDX-License-Identifier: GPL-2.0 +# Leo Yan <leo.yan@linaro.org>, 2022 + +# skip if there's no jshell +if ! [ -x "$(command -v jshell)" ]; then + echo "skip: no jshell, install JDK" + exit 2 +fi + +PERF_DATA=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +PERF_INJ_DATA=$(mktemp /tmp/__perf_test.perf.data.inj.XXXXX) + +cleanup_files() +{ + echo "Cleaning up files..." + rm -f ${PERF_DATA} + rm -f ${PERF_INJ_DATA} +} + +trap cleanup_files exit term int + +if [ -e "$PWD/tools/perf/libperf-jvmti.so" ]; then + LIBJVMTI=$PWD/tools/perf/libperf-jvmti.so +elif [ -e "$PWD/libperf-jvmti.so" ]; then + LIBJVMTI=$PWD/libperf-jvmti.so +elif [ -e "$PREFIX/lib64/libperf-jvmti.so" ]; then + LIBJVMTI=$PREFIX/lib64/libperf-jvmti.so +elif [ -e "$PREFIX/lib/libperf-jvmti.so" ]; then + LIBJVMTI=$PREFIX/lib/libperf-jvmti.so +elif [ -e "/usr/lib/linux-tools-$(uname -a | awk '{ print $3 }' | sed -r 's/-generic//')/libperf-jvmti.so" ]; then + LIBJVMTI=/usr/lib/linux-tools-$(uname -a | awk '{ print $3 }' | sed -r 's/-generic//')/libperf-jvmti.so +else + echo "Fail to find libperf-jvmti.so" + # JVMTI is a build option, skip the test if fail to find lib + exit 2 +fi + +cat <<EOF | perf record -k 1 -o $PERF_DATA jshell -s -J-agentpath:$LIBJVMTI +int fib(int x) { + return x > 1 ? fib(x - 2) + fib(x - 1) : 1; +} + +int q = 0; + +for (int i = 0; i < 10; i++) + q += fib(i); + +System.out.println(q); +EOF + +if [ $? -ne 0 ]; then + echo "Fail to record for java program" + exit 1 +fi + +if ! DEBUGINFOD_URLS='' perf inject -i $PERF_DATA -o $PERF_INJ_DATA -j; then + echo "Fail to inject samples" + exit 1 +fi + +# Below is an example of the instruction samples reporting: +# 8.18% jshell jitted-50116-29.so [.] Interpreter +# 0.75% Thread-1 jitted-83602-1670.so [.] jdk.internal.jimage.BasicImageReader.getString(int) +perf report --stdio -i ${PERF_INJ_DATA} 2>&1 | \ + grep -E " +[0-9]+\.[0-9]+% .* (Interpreter|jdk\.internal).*" > /dev/null 2>&1 + +if [ $? -ne 0 ]; then + echo "Fail to find java symbols" + exit 1 +fi + +exit 0 diff --git a/tools/perf/tests/shell/test_perf_data_converter_json.sh b/tools/perf/tests/shell/test_perf_data_converter_json.sh new file mode 100755 index 000000000000..c4f1b59d116f --- /dev/null +++ b/tools/perf/tests/shell/test_perf_data_converter_json.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# 'perf data convert --to-json' command test +# SPDX-License-Identifier: GPL-2.0 + +set -e + +err=0 + +shelldir=$(dirname "$0") +# shellcheck source=lib/setup_python.sh +. "${shelldir}"/lib/setup_python.sh + +perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) +result=$(mktemp /tmp/__perf_test.output.json.XXXXX) + +cleanup() +{ + rm -f "${perfdata}" + rm -f "${result}" + trap - exit term int +} + +trap_cleanup() +{ + cleanup + exit ${err} +} +trap trap_cleanup exit term int + +test_json_converter_command() +{ + echo "Testing Perf Data Convertion Command to JSON" + perf record -o "$perfdata" -F 99 -g -- perf test -w noploop > /dev/null 2>&1 + perf data convert --to-json "$result" --force -i "$perfdata" >/dev/null 2>&1 + if [ "$(cat ${result} | wc -l)" -gt "0" ] ; then + echo "Perf Data Converter Command to JSON [SUCCESS]" + else + echo "Perf Data Converter Command to JSON [FAILED]" + err=1 + exit + fi +} + +validate_json_format() +{ + echo "Validating Perf Data Converted JSON file" + if [ -f "$result" ] ; then + if $PYTHON -c "import json; json.load(open('$result'))" >/dev/null 2>&1 ; then + echo "The file contains valid JSON format [SUCCESS]" + else + echo "The file does not contain valid JSON format [FAILED]" + err=1 + exit + fi + else + echo "File not found [FAILED]" + err=2 + exit + fi +} + +test_json_converter_command +validate_json_format + +exit ${err} diff --git a/tools/perf/tests/shell/test_stat_intel_tpebs.sh b/tools/perf/tests/shell/test_stat_intel_tpebs.sh new file mode 100755 index 000000000000..a330ecdb7ba5 --- /dev/null +++ b/tools/perf/tests/shell/test_stat_intel_tpebs.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# test Intel TPEBS counting mode (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +set -e + +ParanoidAndNotRoot() { + [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ] +} + +if ! grep -q GenuineIntel /proc/cpuinfo +then + echo "Skipping non-Intel" + exit 2 +fi + +if ParanoidAndNotRoot 0 +then + echo "Skipping paranoid >0 and not root" + exit 2 +fi + +stat_output=$(mktemp /tmp/__perf_stat_tpebs_output.XXXXX) + +cleanup() { + rm -rf "${stat_output}" + trap - EXIT TERM INT +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cat "${stat_output}" + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +# Event to be used in tests +event=cache-misses + +if ! perf record -e "${event}:p" -a -o /dev/null sleep 0.01 > "${stat_output}" 2>&1 +then + echo "Missing ${event} support" + cleanup + exit 2 +fi + +test_with_record_tpebs() { + echo "Testing with --record-tpebs" + if ! perf stat -e "${event}:R" --record-tpebs -a sleep 0.01 > "${stat_output}" 2>&1 + then + echo "Testing with --record-tpebs [Failed perf stat]" + cat "${stat_output}" + exit 1 + fi + + # Expected output: + # $ perf stat --record-tpebs -e cache-misses:R -a sleep 0.01 + # Events enabled + # [ perf record: Woken up 2 times to write data ] + # [ perf record: Captured and wrote 0.056 MB - ] + # + # Performance counter stats for 'system wide': + # + # 0 cache-misses:R + # + # 0.013963299 seconds time elapsed + if ! grep "perf record" "${stat_output}" + then + echo "Testing with --record-tpebs [Failed missing perf record]" + cat "${stat_output}" + exit 1 + fi + if ! grep "${event}:R" "${stat_output}" && ! grep "/${event}/R" "${stat_output}" + then + echo "Testing with --record-tpebs [Failed missing event name]" + cat "${stat_output}" + exit 1 + fi + echo "Testing with --record-tpebs [Success]" +} + +test_with_record_tpebs +cleanup +exit 0 diff --git a/tools/perf/tests/shell/test_task_analyzer.sh b/tools/perf/tests/shell/test_task_analyzer.sh new file mode 100755 index 000000000000..e194fcf61df3 --- /dev/null +++ b/tools/perf/tests/shell/test_task_analyzer.sh @@ -0,0 +1,178 @@ +#!/bin/bash +# perf script task-analyzer tests (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +tmpdir=$(mktemp -d /tmp/perf-script-task-analyzer-XXXXX) +err=0 + +# set PERF_EXEC_PATH to find scripts in the source directory +perfdir=$(dirname "$0")/../.. +if [ -e "$perfdir/scripts/python/Perf-Trace-Util" ]; then + export PERF_EXEC_PATH=$perfdir +fi + +# Disable lsan to avoid warnings about python memory leaks. +export ASAN_OPTIONS=detect_leaks=0 + +cleanup() { + rm -f perf.data + rm -f perf.data.old + rm -f csv + rm -f csvsummary + rm -rf "$tmpdir" + trap - exit term int +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup exit term int + +report() { + if [ "$1" = 0 ]; then + echo "PASS: \"$2\"" + else + echo "FAIL: \"$2\" Error message: \"$3\"" + err=1 + fi +} + +check_exec_0() { + if [ $? != 0 ]; then + report 1 "invocation of $1 command failed" + fi +} + +find_str_or_fail() { + grep -q "$1" "$2" + if [ "$?" != 0 ]; then + report 1 "$3" "Failed to find required string:'${1}'." + else + report 0 "$3" + fi +} + +# check if perf is compiled with libtraceevent support +skip_no_probe_record_support() { + perf check feature -q libtraceevent && return 0 + return 2 +} + +prepare_perf_data() { + # 1s should be sufficient to catch at least some switches + perf record -e sched:sched_switch -a -- sleep 1 > /dev/null 2>&1 + # check if perf data file got created in above step. + if [ ! -e "perf.data" ]; then + printf "FAIL: perf record failed to create \"perf.data\" \n" + return 1 + fi +} + +# check standard inkvokation with no arguments +test_basic() { + out="$tmpdir/perf.out" + perf script report task-analyzer > "$out" + check_exec_0 "perf script report task-analyzer" + find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}" +} + +test_ns_rename(){ + out="$tmpdir/perf.out" + perf script report task-analyzer --ns --rename-comms-by-tids 0:random > "$out" + check_exec_0 "perf script report task-analyzer --ns --rename-comms-by-tids 0:random" + find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}" +} + +test_ms_filtertasks_highlight(){ + out="$tmpdir/perf.out" + perf script report task-analyzer --ms --filter-tasks perf --highlight-tasks perf \ + > "$out" + check_exec_0 "perf script report task-analyzer --ms --filter-tasks perf --highlight-tasks perf" + find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}" +} + +test_extended_times_timelimit_limittasks() { + out="$tmpdir/perf.out" + perf script report task-analyzer --extended-times --time-limit :99999 \ + --limit-to-tasks perf > "$out" + check_exec_0 "perf script report task-analyzer --extended-times --time-limit :99999 --limit-to-tasks perf" + find_str_or_fail "Out-Out" "$out" "${FUNCNAME[0]}" +} + +test_summary() { + out="$tmpdir/perf.out" + perf script report task-analyzer --summary > "$out" + check_exec_0 "perf script report task-analyzer --summary" + find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}" +} + +test_summaryextended() { + out="$tmpdir/perf.out" + perf script report task-analyzer --summary-extended > "$out" + check_exec_0 "perf script report task-analyzer --summary-extended" + find_str_or_fail "Inter Task Times" "$out" "${FUNCNAME[0]}" +} + +test_summaryonly() { + out="$tmpdir/perf.out" + perf script report task-analyzer --summary-only > "$out" + check_exec_0 "perf script report task-analyzer --summary-only" + find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}" +} + +test_extended_times_summary_ns() { + out="$tmpdir/perf.out" + perf script report task-analyzer --extended-times --summary --ns > "$out" + check_exec_0 "perf script report task-analyzer --extended-times --summary --ns" + find_str_or_fail "Out-Out" "$out" "${FUNCNAME[0]}" + find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}" +} + +test_csv() { + perf script report task-analyzer --csv csv > /dev/null + check_exec_0 "perf script report task-analyzer --csv csv" + find_str_or_fail "Comm;" csv "${FUNCNAME[0]}" +} + +test_csv_extended_times() { + perf script report task-analyzer --csv csv --extended-times > /dev/null + check_exec_0 "perf script report task-analyzer --csv csv --extended-times" + find_str_or_fail "Out-Out;" csv "${FUNCNAME[0]}" +} + +test_csvsummary() { + perf script report task-analyzer --csv-summary csvsummary > /dev/null + check_exec_0 "perf script report task-analyzer --csv-summary csvsummary" + find_str_or_fail "Comm;" csvsummary "${FUNCNAME[0]}" +} + +test_csvsummary_extended() { + perf script report task-analyzer --csv-summary csvsummary --summary-extended \ + >/dev/null + check_exec_0 "perf script report task-analyzer --csv-summary csvsummary --summary-extended" + find_str_or_fail "Out-Out;" csvsummary "${FUNCNAME[0]}" +} + +skip_no_probe_record_support +err=$? +if [ $err -ne 0 ]; then + echo "WARN: Skipping tests. No libtraceevent support" + cleanup + exit $err +fi +prepare_perf_data +test_basic +test_ns_rename +test_ms_filtertasks_highlight +test_extended_times_timelimit_limittasks +test_summary +test_summaryextended +test_summaryonly +test_extended_times_summary_ns +test_csv +test_csvsummary +test_csv_extended_times +test_csvsummary_extended +cleanup +exit $err diff --git a/tools/perf/tests/shell/test_uprobe_from_different_cu.sh b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh new file mode 100755 index 000000000000..7adf9755d6de --- /dev/null +++ b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# test perf probe of function from different CU +# SPDX-License-Identifier: GPL-2.0 + +set -e + +# shellcheck source=lib/probe.sh +. "$(dirname $0)"/lib/probe.sh + +skip_if_no_perf_probe || exit 2 +[ "$(id -u)" == 0 ] || exit 2 + +# skip if there's no gcc +if ! [ -x "$(command -v gcc)" ]; then + echo "failed: no gcc compiler" + exit 2 +fi + +temp_dir=$(mktemp -d /tmp/perf-uprobe-different-cu-sh.XXXXXXXXXX) + +cleanup() +{ + trap - EXIT TERM INT + if [[ "${temp_dir}" =~ ^/tmp/perf-uprobe-different-cu-sh.*$ ]]; then + echo "--- Cleaning up ---" + perf probe -x ${temp_dir}/testfile -d foo || true + rm -f "${temp_dir}/"* + rmdir "${temp_dir}" + fi +} + +trap_cleanup() +{ + cleanup + exit 1 +} + +trap trap_cleanup EXIT TERM INT + +cat > ${temp_dir}/testfile-foo.h << EOF +struct t +{ + int *p; + int c; +}; + +extern int foo (int i, struct t *t); +EOF + +cat > ${temp_dir}/testfile-foo.c << EOF +#include "testfile-foo.h" + +int +foo (int i, struct t *t) +{ + int j, res = 0; + for (j = 0; j < i && j < t->c; j++) + res += t->p[j]; + + return res; +} +EOF + +cat > ${temp_dir}/testfile-main.c << EOF +#include "testfile-foo.h" + +static struct t g; + +int +main (int argc, char **argv) +{ + int i; + int j[argc]; + g.c = argc; + g.p = j; + for (i = 0; i < argc; i++) + j[i] = (int) argv[i][0]; + return foo (3, &g); +} +EOF + +gcc -g -Og -flto -c ${temp_dir}/testfile-foo.c -o ${temp_dir}/testfile-foo.o +gcc -g -Og -c ${temp_dir}/testfile-main.c -o ${temp_dir}/testfile-main.o +gcc -g -Og -o ${temp_dir}/testfile ${temp_dir}/testfile-foo.o ${temp_dir}/testfile-main.o + +perf probe -x ${temp_dir}/testfile --funcs foo | grep "foo" +perf probe -x ${temp_dir}/testfile foo + +cleanup diff --git a/tools/perf/tests/shell/trace+probe_vfs_getname.sh b/tools/perf/tests/shell/trace+probe_vfs_getname.sh index 11cc2af13f2b..5d5019988d61 100755 --- a/tools/perf/tests/shell/trace+probe_vfs_getname.sh +++ b/tools/perf/tests/shell/trace+probe_vfs_getname.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Check open filename arg using perf trace + vfs_getname +# Check open filename arg using perf trace + vfs_getname (exclusive) # Uses the 'perf test shell' library to add probe:vfs_getname to the system # then use it with 'perf trace' using 'touch' to write to a temp file, then @@ -10,28 +10,35 @@ # SPDX-License-Identifier: GPL-2.0 # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017 -. $(dirname $0)/lib/probe.sh +# shellcheck source=lib/probe.sh +. "$(dirname $0)"/lib/probe.sh skip_if_no_perf_probe || exit 2 skip_if_no_perf_trace || exit 2 +[ "$(id -u)" = 0 ] || exit 2 -. $(dirname $0)/lib/probe_vfs_getname.sh - -file=$(mktemp /tmp/temporary_file.XXXXX) +. "$(dirname $0)"/lib/probe_vfs_getname.sh trace_open_vfs_getname() { - evts=$(echo $(perf list syscalls:sys_enter_open* 2>&1 | egrep 'open(at)? ' | sed -r 's/.*sys_enter_([a-z]+) +\[.*$/\1/') | sed 's/ /,/') + evts="$(echo "$(perf list tracepoint 2>/dev/null | grep -E 'syscalls:sys_enter_open(at)? ' | sed -r 's/.*sys_enter_([a-z]+) +\[.*$/\1/')" | sed ':a;N;s:\n:,:g')" perf trace -e $evts touch $file 2>&1 | \ - egrep " +[0-9]+\.[0-9]+ +\( +[0-9]+\.[0-9]+ ms\): +touch\/[0-9]+ open(at)?\((dfd: +CWD, +)?filename: +${file}, +flags: CREAT\|NOCTTY\|NONBLOCK\|WRONLY, +mode: +IRUGO\|IWUGO\) += +[0-9]+$" + grep -E " +[0-9]+\.[0-9]+ +\( +[0-9]+\.[0-9]+ ms\): +touch/[0-9]+ open(at)?\((dfd: +CWD, +)?filename: +\"?${file}\"?, +flags: CREAT\|NOCTTY\|NONBLOCK\|WRONLY, +mode: +IRUGO\|IWUGO\) += +[0-9]+$" } - -add_probe_vfs_getname || skip_if_no_debuginfo +add_probe_vfs_getname err=$? + +if [ $err -eq 1 ] ; then + skip_if_no_debuginfo + err=$? +fi + if [ $err -ne 0 ] ; then exit $err fi +file=$(mktemp /tmp/temporary_file.XXXXX) + # Do not use whatever ~/.perfconfig file, it may change the output # via trace.{show_timestamp,show_prefix,etc} export PERF_CONFIG=/dev/null diff --git a/tools/perf/tests/shell/trace_btf_enum.sh b/tools/perf/tests/shell/trace_btf_enum.sh new file mode 100755 index 000000000000..f0b49f7fb57d --- /dev/null +++ b/tools/perf/tests/shell/trace_btf_enum.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# perf trace enum augmentation tests +# SPDX-License-Identifier: GPL-2.0 + +err=0 +set -e + +syscall="landlock_add_rule" +non_syscall="timer:hrtimer_setup,timer:hrtimer_start" + +TESTPROG="perf test -w landlock" + +# shellcheck source=lib/probe.sh +. "$(dirname $0)"/lib/probe.sh +skip_if_no_perf_trace || exit 2 +[ "$(id -u)" = 0 ] || exit 2 + +check_vmlinux() { + echo "Checking if vmlinux exists" + if ! ls /sys/kernel/btf/vmlinux 1>/dev/null 2>&1 + then + echo "trace+enum test [Skipped missing vmlinux BTF support]" + err=2 + fi +} + +trace_landlock() { + echo "Tracing syscall ${syscall}" + + # test flight just to see if landlock_add_rule is available + if ! perf trace $TESTPROG 2>&1 | grep -q landlock + then + echo "No landlock system call found, skipping to non-syscall tracing." + return + fi + + if perf trace -e $syscall $TESTPROG 2>&1 | \ + grep -q -E ".*landlock_add_rule\(ruleset_fd: 11, rule_type: (LANDLOCK_RULE_PATH_BENEATH|LANDLOCK_RULE_NET_PORT), rule_attr: 0x[a-f0-9]+, flags: 45\) = -1.*" + then + err=0 + else + err=1 + fi +} + +trace_non_syscall() { + echo "Tracing non-syscall tracepoint ${non-syscall}" + if perf trace -e $non_syscall --max-events=1 2>&1 | \ + grep -q -E '.*timer:hrtimer_.*\(.*mode: HRTIMER_MODE_.*\)$' + then + err=0 + else + err=1 + fi +} + +check_vmlinux + +if [ $err = 0 ]; then + trace_landlock +fi + +if [ $err = 0 ]; then + trace_non_syscall +fi + +exit $err diff --git a/tools/perf/tests/shell/trace_btf_general.sh b/tools/perf/tests/shell/trace_btf_general.sh new file mode 100755 index 000000000000..a25d8744695e --- /dev/null +++ b/tools/perf/tests/shell/trace_btf_general.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# perf trace BTF general tests +# SPDX-License-Identifier: GPL-2.0 + +err=0 +set -e + +# shellcheck source=lib/probe.sh +. "$(dirname $0)"/lib/probe.sh + +file1=$(mktemp /tmp/file1_XXXX) +file2=$(echo $file1 | sed 's/file1/file2/g') + +buffer="buffer content" +perf_config_tmp=$(mktemp /tmp/.perfconfig_XXXXX) + +trap cleanup EXIT TERM INT HUP + +check_vmlinux() { + echo "Checking if vmlinux BTF exists" + if [ ! -f /sys/kernel/btf/vmlinux ] + then + echo "Skipped due to missing vmlinux BTF" + return 2 + fi + return 0 +} + +trace_test_string() { + echo "Testing perf trace's string augmentation" + if ! perf trace -e renameat* --max-events=1 -- mv ${file1} ${file2} 2>&1 | \ + grep -q -E "^mv/[0-9]+ renameat(2)?\(.*, \"${file1}\", .*, \"${file2}\", .*\) += +[0-9]+$" + then + echo "String augmentation test failed" + err=1 + fi +} + +trace_test_buffer() { + echo "Testing perf trace's buffer augmentation" + # echo will insert a newline (\10) at the end of the buffer + if ! perf trace -e write --max-events=1 -- echo "${buffer}" 2>&1 | \ + grep -q -E "^echo/[0-9]+ write\([0-9]+, ${buffer}.*, [0-9]+\) += +[0-9]+$" + then + echo "Buffer augmentation test failed" + err=1 + fi +} + +trace_test_struct_btf() { + echo "Testing perf trace's struct augmentation" + if ! perf trace -e clock_nanosleep --force-btf --max-events=1 -- sleep 1 2>&1 | \ + grep -q -E "^sleep/[0-9]+ clock_nanosleep\(0, 0, \{1,\}, 0x[0-9a-f]+\) += +[0-9]+$" + then + echo "BTF struct augmentation test failed" + err=1 + fi +} + +cleanup() { + rm -rf ${file1} ${file2} ${perf_config_tmp} +} + +trap_cleanup() { + echo "Unexpected signal in ${FUNCNAME[1]}" + cleanup + exit 1 +} + +# don't overwrite user's perf config +trace_config() { + export PERF_CONFIG=${perf_config_tmp} + perf config trace.show_arg_names=false trace.show_duration=false \ + trace.show_timestamp=false trace.args_alignment=0 +} + +skip_if_no_perf_trace || exit 2 +check_vmlinux || exit 2 +[ "$(id -u)" = 0 ] || exit 2 + +trace_config + +trace_test_string + +if [ $err = 0 ]; then + trace_test_buffer +fi + +if [ $err = 0 ]; then + trace_test_struct_btf +fi + +cleanup + +exit $err diff --git a/tools/perf/tests/shell/trace_exit_race.sh b/tools/perf/tests/shell/trace_exit_race.sh new file mode 100755 index 000000000000..1e247693e756 --- /dev/null +++ b/tools/perf/tests/shell/trace_exit_race.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# perf trace exit race +# SPDX-License-Identifier: GPL-2.0 + +# Check that the last events of a perf trace'd subprocess are not +# lost. Specifically, trace the exiting syscall of "true" 10 times and ensure +# the output contains 10 correct lines. + +# shellcheck source=lib/probe.sh +. "$(dirname $0)"/lib/probe.sh + +skip_if_no_perf_trace || exit 2 +[ "$(id -u)" = 0 ] || exit 2 + +if [ "$1" = "-v" ]; then + verbose="1" +fi + +iter=10 +regexp=" +[0-9]+\.[0-9]+ [0-9]+ syscalls:sys_enter_exit_group\(\)$" + +trace_shutdown_race() { + for _ in $(seq $iter); do + perf trace --no-comm -e syscalls:sys_enter_exit_group true 2>>$file + done + result="$(grep -c -E "$regexp" $file)" + [ $result = $iter ] +} + + +file=$(mktemp /tmp/temporary_file.XXXXX) + +# Do not use whatever ~/.perfconfig file, it may change the output +# via trace.{show_timestamp,show_prefix,etc} +export PERF_CONFIG=/dev/null + +trace_shutdown_race +err=$? + +if [ $err != 0 ] && [ "${verbose}" = "1" ]; then + lines_not_matching=$(mktemp /tmp/temporary_file.XXXXX) + if grep -v -E "$regexp" $file > $lines_not_matching ; then + echo "Lines not matching the expected regexp: '$regexp':" + cat $lines_not_matching + else + echo "Missing output, expected $iter but only got $result" + fi + rm -f $lines_not_matching +fi + +rm -f ${file} +exit $err diff --git a/tools/perf/tests/shell/trace_record_replay.sh b/tools/perf/tests/shell/trace_record_replay.sh new file mode 100755 index 000000000000..6b4ed863c1ef --- /dev/null +++ b/tools/perf/tests/shell/trace_record_replay.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# perf trace record and replay +# SPDX-License-Identifier: GPL-2.0 + +# Check that perf trace works with record and replay + +# shellcheck source=lib/probe.sh +. "$(dirname $0)"/lib/probe.sh + +skip_if_no_perf_trace || exit 2 +[ "$(id -u)" = 0 ] || exit 2 + +file=$(mktemp /tmp/temporary_file.XXXXX) + +perf trace record -o ${file} sleep 1 || exit 1 +if ! perf trace -i ${file} 2>&1 | grep nanosleep; then + echo "Failed: cannot find *nanosleep syscall" + exit 1 +fi + +rm -f ${file} diff --git a/tools/perf/tests/shell/trace_summary.sh b/tools/perf/tests/shell/trace_summary.sh new file mode 100755 index 000000000000..f9bb7f9388be --- /dev/null +++ b/tools/perf/tests/shell/trace_summary.sh @@ -0,0 +1,77 @@ +#!/bin/sh +# perf trace summary (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +# Check that perf trace works with various summary mode + +# shellcheck source=lib/probe.sh +. "$(dirname $0)"/lib/probe.sh + +skip_if_no_perf_trace || exit 2 +[ "$(id -u)" = 0 ] || exit 2 + +OUTPUT=$(mktemp /tmp/perf_trace_test.XXXXX) + +test_perf_trace() { + args=$1 + workload="true" + search="^\s*(open|read|close).*[0-9]+%$" + + echo "testing: perf trace ${args} -- ${workload}" + perf trace ${args} -- ${workload} >${OUTPUT} 2>&1 + if [ $? -ne 0 ]; then + echo "Error: perf trace ${args} failed unexpectedly" + cat ${OUTPUT} + rm -f ${OUTPUT} + exit 1 + fi + + count=$(grep -E -c -m 3 "${search}" ${OUTPUT}) + if [ "${count}" != "3" ]; then + echo "Error: cannot find enough pattern ${search} in the output" + cat ${OUTPUT} + rm -f ${OUTPUT} + exit 1 + fi +} + +# summary only for a process +test_perf_trace "-s" + +# normal output with summary at the end +test_perf_trace "-S" + +# summary only with an explicit summary mode +test_perf_trace "-s --summary-mode=thread" + +# summary with normal output - total summary mode +test_perf_trace "-S --summary-mode=total" + +# summary only for system wide - per-thread summary +test_perf_trace "-as --summary-mode=thread --no-bpf-summary" + +# summary only for system wide - total summary mode +test_perf_trace "-as --summary-mode=total --no-bpf-summary" + +if ! perf check feature -q bpf; then + echo "Skip --bpf-summary tests as perf built without libbpf" + rm -f ${OUTPUT} + exit 2 +fi + +# summary only for system wide - per-thread summary with BPF +test_perf_trace "-as --summary-mode=thread --bpf-summary" + +# summary only for system wide - total summary mode with BPF +test_perf_trace "-as --summary-mode=total --bpf-summary" + +# summary with normal output for system wide - total summary mode with BPF +test_perf_trace "-aS --summary-mode=total --bpf-summary" + +# summary only for system wide - cgroup summary mode with BPF +test_perf_trace "-as --summary-mode=cgroup --bpf-summary" + +# summary with normal output for system wide - cgroup summary mode with BPF +test_perf_trace "-aS --summary-mode=cgroup --bpf-summary" + +rm -f ${OUTPUT} diff --git a/tools/perf/tests/sigtrap.c b/tools/perf/tests/sigtrap.c new file mode 100644 index 000000000000..a67c756f90b8 --- /dev/null +++ b/tools/perf/tests/sigtrap.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Basic test for sigtrap support. + * + * Copyright (C) 2021, Google LLC. + */ + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <linux/hw_breakpoint.h> +#include <linux/string.h> +#include <pthread.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "cloexec.h" +#include "debug.h" +#include "event.h" +#include "tests.h" +#include "../perf-sys.h" + +#define NUM_THREADS 5 + +static struct { + int tids_want_signal; /* Which threads still want a signal. */ + int signal_count; /* Sanity check number of signals received. */ + volatile int iterate_on; /* Variable to set breakpoint on. */ + siginfo_t first_siginfo; /* First observed siginfo_t. */ +} ctx; + +#define TEST_SIG_DATA (~(unsigned long)(&ctx.iterate_on)) + +static struct perf_event_attr make_event_attr(void) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_BREAKPOINT, + .size = sizeof(attr), + .sample_period = 1, + .disabled = 1, + .bp_addr = (unsigned long)&ctx.iterate_on, + .bp_type = HW_BREAKPOINT_RW, + .bp_len = HW_BREAKPOINT_LEN_1, + .inherit = 1, /* Children inherit events ... */ + .inherit_thread = 1, /* ... but only cloned with CLONE_THREAD. */ + .remove_on_exec = 1, /* Required by sigtrap. */ + .sigtrap = 1, /* Request synchronous SIGTRAP on event. */ + .sig_data = TEST_SIG_DATA, + .exclude_kernel = 1, /* To allow */ + .exclude_hv = 1, /* running as !root */ + }; + return attr; +} + +#ifdef HAVE_BPF_SKEL +#include <bpf/btf.h> +#include <util/btf.h> + +static struct btf *btf; + +static bool btf__available(void) +{ + if (btf == NULL) + btf = btf__load_vmlinux_btf(); + + return btf != NULL; +} + +static void btf__exit(void) +{ + btf__free(btf); + btf = NULL; +} + +static bool attr_has_sigtrap(void) +{ + int id; + + if (!btf__available()) { + /* should be an old kernel */ + return false; + } + + id = btf__find_by_name_kind(btf, "perf_event_attr", BTF_KIND_STRUCT); + if (id < 0) + return false; + + return __btf_type__find_member_by_name(btf, id, "sigtrap") != NULL; +} + +static bool kernel_with_sleepable_spinlocks(void) +{ + const struct btf_member *member; + const struct btf_type *type; + const char *type_name; + int id; + + if (!btf__available()) + return false; + + id = btf__find_by_name_kind(btf, "spinlock", BTF_KIND_STRUCT); + if (id < 0) + return false; + + // Only RT has a "lock" member for "struct spinlock" + member = __btf_type__find_member_by_name(btf, id, "lock"); + if (member == NULL) + return false; + + // But check its type as well + type = btf__type_by_id(btf, member->type); + if (!type || !btf_is_struct(type)) + return false; + + type_name = btf__name_by_offset(btf, type->name_off); + return type_name && !strcmp(type_name, "rt_mutex_base"); +} +#else /* !HAVE_BPF_SKEL */ +static bool attr_has_sigtrap(void) +{ + struct perf_event_attr attr = { + .type = PERF_TYPE_SOFTWARE, + .config = PERF_COUNT_SW_DUMMY, + .size = sizeof(attr), + .remove_on_exec = 1, /* Required by sigtrap. */ + .sigtrap = 1, /* Request synchronous SIGTRAP on event. */ + }; + int fd; + bool ret = false; + + fd = sys_perf_event_open(&attr, 0, -1, -1, perf_event_open_cloexec_flag()); + if (fd >= 0) { + ret = true; + close(fd); + } + + return ret; +} + +static bool kernel_with_sleepable_spinlocks(void) +{ + return false; +} + +static void btf__exit(void) +{ +} +#endif /* HAVE_BPF_SKEL */ + +static void +sigtrap_handler(int signum __maybe_unused, siginfo_t *info, void *ucontext __maybe_unused) +{ + if (!__atomic_fetch_add(&ctx.signal_count, 1, __ATOMIC_RELAXED)) + ctx.first_siginfo = *info; + __atomic_fetch_sub(&ctx.tids_want_signal, syscall(SYS_gettid), __ATOMIC_RELAXED); +} + +static void *test_thread(void *arg) +{ + pthread_barrier_t *barrier = (pthread_barrier_t *)arg; + pid_t tid = syscall(SYS_gettid); + int i; + + pthread_barrier_wait(barrier); + + __atomic_fetch_add(&ctx.tids_want_signal, tid, __ATOMIC_RELAXED); + for (i = 0; i < ctx.iterate_on - 1; i++) + __atomic_fetch_add(&ctx.tids_want_signal, tid, __ATOMIC_RELAXED); + + return NULL; +} + +static int run_test_threads(pthread_t *threads, pthread_barrier_t *barrier) +{ + int i; + + pthread_barrier_wait(barrier); + for (i = 0; i < NUM_THREADS; i++) + TEST_ASSERT_EQUAL("pthread_join() failed", pthread_join(threads[i], NULL), 0); + + return TEST_OK; +} + +static int run_stress_test(int fd, pthread_t *threads, pthread_barrier_t *barrier) +{ + int ret, expected_sigtraps; + + ctx.iterate_on = 3000; + + TEST_ASSERT_EQUAL("misfired signal?", ctx.signal_count, 0); + TEST_ASSERT_EQUAL("enable failed", ioctl(fd, PERF_EVENT_IOC_ENABLE, 0), 0); + ret = run_test_threads(threads, barrier); + TEST_ASSERT_EQUAL("disable failed", ioctl(fd, PERF_EVENT_IOC_DISABLE, 0), 0); + + expected_sigtraps = NUM_THREADS * ctx.iterate_on; + + if (ctx.signal_count < expected_sigtraps && kernel_with_sleepable_spinlocks()) { + pr_debug("Expected %d sigtraps, got %d, running on a kernel with sleepable spinlocks.\n", + expected_sigtraps, ctx.signal_count); + pr_debug("See https://lore.kernel.org/all/e368f2c848d77fbc8d259f44e2055fe469c219cf.camel@gmx.de/\n"); + return TEST_SKIP; + } else + TEST_ASSERT_EQUAL("unexpected sigtraps", ctx.signal_count, expected_sigtraps); + + TEST_ASSERT_EQUAL("missing signals or incorrectly delivered", ctx.tids_want_signal, 0); + TEST_ASSERT_VAL("unexpected si_addr", ctx.first_siginfo.si_addr == &ctx.iterate_on); +#if 0 /* FIXME: enable when libc's signal.h has si_perf_{type,data} */ + TEST_ASSERT_EQUAL("unexpected si_perf_type", ctx.first_siginfo.si_perf_type, + PERF_TYPE_BREAKPOINT); + TEST_ASSERT_EQUAL("unexpected si_perf_data", ctx.first_siginfo.si_perf_data, + TEST_SIG_DATA); +#endif + + return ret; +} + +static int test__sigtrap(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + struct perf_event_attr attr = make_event_attr(); + struct sigaction action = {}; + struct sigaction oldact; + pthread_t threads[NUM_THREADS]; + pthread_barrier_t barrier; + char sbuf[STRERR_BUFSIZE]; + int i, fd, ret = TEST_FAIL; + + if (!BP_SIGNAL_IS_SUPPORTED) { + pr_debug("Test not supported on this architecture"); + return TEST_SKIP; + } + + pthread_barrier_init(&barrier, NULL, NUM_THREADS + 1); + + action.sa_flags = SA_SIGINFO | SA_NODEFER; + action.sa_sigaction = sigtrap_handler; + sigemptyset(&action.sa_mask); + if (sigaction(SIGTRAP, &action, &oldact)) { + pr_debug("FAILED sigaction(): %s\n", str_error_r(errno, sbuf, sizeof(sbuf))); + goto out; + } + + fd = sys_perf_event_open(&attr, 0, -1, -1, perf_event_open_cloexec_flag()); + if (fd < 0) { + if (attr_has_sigtrap()) { + pr_debug("FAILED sys_perf_event_open(): %s\n", + str_error_r(errno, sbuf, sizeof(sbuf))); + } else { + pr_debug("perf_event_attr doesn't have sigtrap\n"); + ret = TEST_SKIP; + } + goto out_restore_sigaction; + } + + for (i = 0; i < NUM_THREADS; i++) { + if (pthread_create(&threads[i], NULL, test_thread, &barrier)) { + pr_debug("FAILED pthread_create(): %s\n", str_error_r(errno, sbuf, sizeof(sbuf))); + goto out_close_perf_event; + } + } + + ret = run_stress_test(fd, threads, &barrier); + +out_close_perf_event: + close(fd); +out_restore_sigaction: + sigaction(SIGTRAP, &oldact, NULL); +out: + pthread_barrier_destroy(&barrier); + btf__exit(); + return ret; +} + +DEFINE_SUITE("Sigtrap", sigtrap); diff --git a/tools/perf/tests/stat.c b/tools/perf/tests/stat.c index c1911501c39c..d60983657bad 100644 --- a/tools/perf/tests/stat.c +++ b/tools/perf/tests/stat.c @@ -21,13 +21,13 @@ static bool has_term(struct perf_record_stat_config *config, return false; } -static int process_stat_config_event(struct perf_tool *tool __maybe_unused, +static int process_stat_config_event(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) { struct perf_record_stat_config *config = &event->stat_config; - struct perf_stat_config stat_config; + struct perf_stat_config test_stat_config = {}; #define HAS(term, val) \ has_term(config, PERF_STAT_CONFIG_TERM__##term, val) @@ -39,29 +39,32 @@ static int process_stat_config_event(struct perf_tool *tool __maybe_unused, #undef HAS - perf_event__read_stat_config(&stat_config, config); + perf_event__read_stat_config(&test_stat_config, config); - TEST_ASSERT_VAL("wrong aggr_mode", stat_config.aggr_mode == AGGR_CORE); - TEST_ASSERT_VAL("wrong scale", stat_config.scale == 1); - TEST_ASSERT_VAL("wrong interval", stat_config.interval == 1); + TEST_ASSERT_VAL("wrong aggr_mode", test_stat_config.aggr_mode == AGGR_CORE); + TEST_ASSERT_VAL("wrong scale", test_stat_config.scale == 1); + TEST_ASSERT_VAL("wrong interval", test_stat_config.interval == 1); return 0; } -int test__synthesize_stat_config(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__synthesize_stat_config(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { - struct perf_stat_config stat_config = { + struct perf_stat_config test_stat_config = { .aggr_mode = AGGR_CORE, .scale = 1, .interval = 1, }; TEST_ASSERT_VAL("failed to synthesize stat_config", - !perf_event__synthesize_stat_config(NULL, &stat_config, process_stat_config_event, NULL)); + !perf_event__synthesize_stat_config(NULL, &test_stat_config, + process_stat_config_event, + NULL)); return 0; } -static int process_stat_event(struct perf_tool *tool __maybe_unused, +static int process_stat_event(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) @@ -77,7 +80,7 @@ static int process_stat_event(struct perf_tool *tool __maybe_unused, return 0; } -int test__synthesize_stat(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__synthesize_stat(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct perf_counts_values count; @@ -86,12 +89,13 @@ int test__synthesize_stat(struct test *test __maybe_unused, int subtest __maybe_ count.run = 300; TEST_ASSERT_VAL("failed to synthesize stat_config", - !perf_event__synthesize_stat(NULL, 1, 2, 3, &count, process_stat_event, NULL)); + !perf_event__synthesize_stat(NULL, (struct perf_cpu){.cpu = 1}, 2, 3, + &count, process_stat_event, NULL)); return 0; } -static int process_stat_round_event(struct perf_tool *tool __maybe_unused, +static int process_stat_round_event(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) @@ -103,7 +107,7 @@ static int process_stat_round_event(struct perf_tool *tool __maybe_unused, return 0; } -int test__synthesize_stat_round(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__synthesize_stat_round(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { TEST_ASSERT_VAL("failed to synthesize stat_config", !perf_event__synthesize_stat_round(NULL, 0xdeadbeef, PERF_STAT_ROUND_TYPE__INTERVAL, @@ -111,3 +115,7 @@ int test__synthesize_stat_round(struct test *test __maybe_unused, int subtest __ return 0; } + +DEFINE_SUITE("Synthesize stat config", synthesize_stat_config); +DEFINE_SUITE("Synthesize stat", synthesize_stat); +DEFINE_SUITE("Synthesize stat round", synthesize_stat_round); diff --git a/tools/perf/tests/sw-clock.c b/tools/perf/tests/sw-clock.c index 4b9b731977c8..4a2ad7176fa0 100644 --- a/tools/perf/tests/sw-clock.c +++ b/tools/perf/tests/sw-clock.c @@ -13,6 +13,7 @@ #include "util/evlist.h" #include "util/cpumap.h" #include "util/mmap.h" +#include "util/sample.h" #include "util/thread_map.h" #include <perf/evlist.h> #include <perf/mmap.h> @@ -42,8 +43,8 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id) .disabled = 1, .freq = 1, }; - struct perf_cpu_map *cpus; - struct perf_thread_map *threads; + struct perf_cpu_map *cpus = NULL; + struct perf_thread_map *threads = NULL; struct mmap *md; attr.sample_freq = 500; @@ -61,19 +62,16 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id) } evlist__add(evlist, evsel); - cpus = perf_cpu_map__dummy_new(); + cpus = perf_cpu_map__new_any_cpu(); threads = thread_map__new_by_tid(getpid()); if (!cpus || !threads) { err = -ENOMEM; pr_debug("Not enough memory to create thread/cpu maps\n"); - goto out_free_maps; + goto out_delete_evlist; } perf_evlist__set_maps(&evlist->core, cpus, threads); - cpus = NULL; - threads = NULL; - if (evlist__open(evlist)) { const char *knob = "/proc/sys/kernel/perf_event_max_sample_rate"; @@ -106,12 +104,14 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id) while ((event = perf_mmap__read_event(&md->core)) != NULL) { struct perf_sample sample; + perf_sample__init(&sample, /*all=*/false); if (event->header.type != PERF_RECORD_SAMPLE) goto next_event; - err = perf_evlist__parse_sample(evlist, event, &sample); + err = evlist__parse_sample(evlist, event, &sample); if (err < 0) { pr_debug("Error during parse sample\n"); + perf_sample__exit(&sample); goto out_delete_evlist; } @@ -119,6 +119,7 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id) nr_samples++; next_event: perf_mmap__consume(&md->core); + perf_sample__exit(&sample); } perf_mmap__read_done(&md->core); @@ -129,15 +130,14 @@ out_init: err = -1; } -out_free_maps: +out_delete_evlist: perf_cpu_map__put(cpus); perf_thread_map__put(threads); -out_delete_evlist: evlist__delete(evlist); return err; } -int test__sw_clock_freq(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__sw_clock_freq(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int ret; @@ -147,3 +147,5 @@ int test__sw_clock_freq(struct test *test __maybe_unused, int subtest __maybe_un return ret; } + +DEFINE_SUITE("Software clock events period values", sw_clock_freq); diff --git a/tools/perf/tests/switch-tracking.c b/tools/perf/tests/switch-tracking.c index db5e1f70053a..6b3aac283c37 100644 --- a/tools/perf/tests/switch-tracking.c +++ b/tools/perf/tests/switch-tracking.c @@ -6,6 +6,7 @@ #include <time.h> #include <stdlib.h> #include <linux/zalloc.h> +#include <linux/err.h> #include <perf/cpumap.h> #include <perf/evlist.h> #include <perf/mmap.h> @@ -18,6 +19,8 @@ #include "record.h" #include "tests.h" #include "util/mmap.h" +#include "util/sample.h" +#include "pmus.h" static int spin_sleep(void) { @@ -128,12 +131,14 @@ static int process_sample_event(struct evlist *evlist, pid_t next_tid, prev_tid; int cpu, err; - if (perf_evlist__parse_sample(evlist, event, &sample)) { - pr_debug("perf_evlist__parse_sample failed\n"); - return -1; + perf_sample__init(&sample, /*all=*/false); + if (evlist__parse_sample(evlist, event, &sample)) { + pr_debug("evlist__parse_sample failed\n"); + err = -1; + goto out; } - evsel = perf_evlist__id2evsel(evlist, sample.id); + evsel = evlist__id2evsel(evlist, sample.id); if (evsel == switch_tracking->switch_evsel) { next_tid = evsel__intval(evsel, &sample, "next_pid"); prev_tid = evsel__intval(evsel, &sample, "prev_pid"); @@ -142,7 +147,7 @@ static int process_sample_event(struct evlist *evlist, cpu, prev_tid, next_tid); err = check_cpu(switch_tracking, cpu); if (err) - return err; + goto out; /* * Check for no missing sched_switch events i.e. that the * evsel->core.system_wide flag has worked. @@ -150,7 +155,8 @@ static int process_sample_event(struct evlist *evlist, if (switch_tracking->tids[cpu] != -1 && switch_tracking->tids[cpu] != prev_tid) { pr_debug("Missing sched_switch events\n"); - return -1; + err = -1; + goto out; } switch_tracking->tids[cpu] = next_tid; } @@ -166,7 +172,10 @@ static int process_sample_event(struct evlist *evlist, switch_tracking->cycles_after_comm_4 = 1; } - return 0; + err = 0; +out: + perf_sample__exit(&sample); + return err; } static int process_event(struct evlist *evlist, union perf_event *event, @@ -223,8 +232,8 @@ static int add_event(struct evlist *evlist, struct list_head *events, node->event = event; list_add(&node->list, events); - if (perf_evlist__parse_sample(evlist, event, &sample)) { - pr_debug("perf_evlist__parse_sample failed\n"); + if (evlist__parse_sample(evlist, event, &sample)) { + pr_debug("evlist__parse_sample failed\n"); return -1; } @@ -255,7 +264,7 @@ static int compar(const void *a, const void *b) const struct event_node *nodeb = b; s64 cmp = nodea->event_time - nodeb->event_time; - return cmp; + return cmp < 0 ? -1 : (cmp > 0 ? 1 : 0); } static int process_events(struct evlist *evlist, @@ -320,9 +329,10 @@ out_free_nodes: * evsel->core.system_wide and evsel->tracking flags (respectively) with other events * sometimes enabled or disabled. */ -int test__switch_tracking(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__switch_tracking(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { const char *sched_switch = "sched:sched_switch"; + const char *cycles = "cycles:u"; struct switch_tracking switch_tracking = { .tids = NULL, }; struct record_opts opts = { .mmap_pages = UINT_MAX, @@ -347,7 +357,7 @@ int test__switch_tracking(struct test *test __maybe_unused, int subtest __maybe_ goto out_err; } - cpus = perf_cpu_map__new(NULL); + cpus = perf_cpu_map__new_online_cpus(); if (!cpus) { pr_debug("perf_cpu_map__new failed!\n"); goto out_err; @@ -362,7 +372,7 @@ int test__switch_tracking(struct test *test __maybe_unused, int subtest __maybe_ perf_evlist__set_maps(&evlist->core, cpus, threads); /* First event */ - err = parse_events(evlist, "cpu-clock:u", NULL); + err = parse_event(evlist, "cpu-clock:u"); if (err) { pr_debug("Failed to parse event dummy:u\n"); goto out_err; @@ -371,34 +381,28 @@ int test__switch_tracking(struct test *test __maybe_unused, int subtest __maybe_ cpu_clocks_evsel = evlist__last(evlist); /* Second event */ - err = parse_events(evlist, "cycles:u", NULL); + err = parse_event(evlist, cycles); if (err) { - pr_debug("Failed to parse event cycles:u\n"); + pr_debug("Failed to parse event %s\n", cycles); goto out_err; } cycles_evsel = evlist__last(evlist); /* Third event */ - if (!perf_evlist__can_select_event(evlist, sched_switch)) { + if (!evlist__can_select_event(evlist, sched_switch)) { pr_debug("No sched_switch\n"); err = 0; goto out; } - err = parse_events(evlist, sched_switch, NULL); - if (err) { - pr_debug("Failed to parse event %s\n", sched_switch); + switch_evsel = evlist__add_sched_switch(evlist, true); + if (IS_ERR(switch_evsel)) { + err = PTR_ERR(switch_evsel); + pr_debug("Failed to create event %s\n", sched_switch); goto out_err; } - switch_evsel = evlist__last(evlist); - - evsel__set_sample_bit(switch_evsel, CPU); - evsel__set_sample_bit(switch_evsel, TIME); - - switch_evsel->core.system_wide = true; - switch_evsel->no_aux_samples = true; switch_evsel->immediate = true; /* Test moving an event to the front */ @@ -406,7 +410,7 @@ int test__switch_tracking(struct test *test __maybe_unused, int subtest __maybe_ pr_debug("cycles event already at front"); goto out_err; } - perf_evlist__to_front(evlist, cycles_evsel); + evlist__to_front(evlist, cycles_evsel); if (cycles_evsel != evlist__first(evlist)) { pr_debug("Failed to move cycles event to front"); goto out_err; @@ -416,7 +420,7 @@ int test__switch_tracking(struct test *test __maybe_unused, int subtest __maybe_ evsel__set_sample_bit(cycles_evsel, TIME); /* Fourth event */ - err = parse_events(evlist, "dummy:u", NULL); + err = parse_event(evlist, "dummy:u"); if (err) { pr_debug("Failed to parse event dummy:u\n"); goto out_err; @@ -424,7 +428,7 @@ int test__switch_tracking(struct test *test __maybe_unused, int subtest __maybe_ tracking_evsel = evlist__last(evlist); - perf_evlist__set_tracking_event(evlist, tracking_evsel); + evlist__set_tracking_event(evlist, tracking_evsel); tracking_evsel->core.attr.freq = 0; tracking_evsel->core.attr.sample_period = 1; @@ -432,7 +436,7 @@ int test__switch_tracking(struct test *test __maybe_unused, int subtest __maybe_ evsel__set_sample_bit(tracking_evsel, TIME); /* Config events */ - perf_evlist__config(evlist, &opts, NULL); + evlist__config(evlist, &opts, NULL); /* Check moved event is still at the front */ if (cycles_evsel != evlist__first(evlist)) { @@ -574,10 +578,9 @@ out: if (evlist) { evlist__disable(evlist); evlist__delete(evlist); - } else { - perf_cpu_map__put(cpus); - perf_thread_map__put(threads); } + perf_cpu_map__put(cpus); + perf_thread_map__put(threads); return err; @@ -585,3 +588,5 @@ out_err: err = -1; goto out; } + +DEFINE_SUITE_EXCLUSIVE("Track with sched_switch", switch_tracking); diff --git a/tools/perf/tests/symbols.c b/tools/perf/tests/symbols.c new file mode 100644 index 000000000000..ee20a366f32f --- /dev/null +++ b/tools/perf/tests/symbols.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/compiler.h> +#include <linux/string.h> +#include <sys/mman.h> +#include <limits.h> +#include "debug.h" +#include "dso.h" +#include "machine.h" +#include "thread.h" +#include "symbol.h" +#include "map.h" +#include "util.h" +#include "tests.h" + +struct test_info { + struct machine *machine; + struct thread *thread; +}; + +static int init_test_info(struct test_info *ti) +{ + ti->machine = machine__new_host(); + if (!ti->machine) { + pr_debug("machine__new_host() failed!\n"); + return TEST_FAIL; + } + + /* Create a dummy thread */ + ti->thread = machine__findnew_thread(ti->machine, 100, 100); + if (!ti->thread) { + pr_debug("machine__findnew_thread() failed!\n"); + return TEST_FAIL; + } + + return TEST_OK; +} + +static void exit_test_info(struct test_info *ti) +{ + thread__put(ti->thread); + machine__delete(ti->machine); +} + +struct dso_map { + struct dso *dso; + struct map *map; +}; + +static int find_map_cb(struct map *map, void *d) +{ + struct dso_map *data = d; + + if (map__dso(map) != data->dso) + return 0; + data->map = map; + return 1; +} + +static struct map *find_module_map(struct machine *machine, struct dso *dso) +{ + struct dso_map data = { .dso = dso }; + + machine__for_each_kernel_map(machine, find_map_cb, &data); + + return data.map; +} + +static void get_test_dso_filename(char *filename, size_t max_sz) +{ + if (dso_to_test) + strlcpy(filename, dso_to_test, max_sz); + else + perf_exe(filename, max_sz); +} + +static int create_map(struct test_info *ti, char *filename, struct map **map_p) +{ + struct dso *dso = machine__findnew_dso(ti->machine, filename); + + /* + * If 'filename' matches a current kernel module, must use a kernel + * map. Find the one that already exists. + */ + if (dso && dso__kernel(dso) != DSO_SPACE__USER) { + *map_p = find_module_map(ti->machine, dso); + dso__put(dso); + if (!*map_p) { + pr_debug("Failed to find map for current kernel module %s", + filename); + return TEST_FAIL; + } + map__get(*map_p); + return TEST_OK; + } + + dso__put(dso); + + /* Create a dummy map at 0x100000 */ + *map_p = map__new(ti->machine, 0x100000, 0xffffffff, 0, NULL, + PROT_EXEC, 0, NULL, filename, ti->thread); + if (!*map_p) { + pr_debug("Failed to create map!"); + return TEST_FAIL; + } + + return TEST_OK; +} + +static int test_dso(struct dso *dso) +{ + struct symbol *last_sym = NULL; + struct rb_node *nd; + int ret = TEST_OK; + + /* dso__fprintf() prints all the symbols */ + if (verbose > 1) + dso__fprintf(dso, stderr); + + for (nd = rb_first_cached(dso__symbols(dso)); nd; nd = rb_next(nd)) { + struct symbol *sym = rb_entry(nd, struct symbol, rb_node); + + if (sym->type != STT_FUNC && sym->type != STT_GNU_IFUNC) + continue; + + /* Check for overlapping function symbols */ + if (last_sym && sym->start < last_sym->end) { + pr_debug("Overlapping symbols:\n"); + symbol__fprintf(last_sym, stderr); + symbol__fprintf(sym, stderr); + ret = TEST_FAIL; + } + /* Check for zero-length function symbol */ + if (sym->start == sym->end) { + pr_debug("Zero-length symbol:\n"); + symbol__fprintf(sym, stderr); + ret = TEST_FAIL; + } + last_sym = sym; + } + + return ret; +} + +static int subdivided_dso_cb(struct dso *dso, struct machine *machine __maybe_unused, void *d) +{ + struct dso *text_dso = d; + + if (dso != text_dso && strstarts(dso__short_name(dso), dso__short_name(text_dso))) + if (test_dso(dso) != TEST_OK) + return -1; + + return 0; +} + +static int process_subdivided_dso(struct machine *machine, struct dso *dso) +{ + int ret; + + ret = machine__for_each_dso(machine, subdivided_dso_cb, dso); + + return ret < 0 ? TEST_FAIL : TEST_OK; +} + +static int test_file(struct test_info *ti, char *filename) +{ + struct map *map = NULL; + int ret, nr; + struct dso *dso; + + pr_debug("Testing %s\n", filename); + + ret = create_map(ti, filename, &map); + if (ret != TEST_OK) + return ret; + + dso = map__dso(map); + nr = dso__load(dso, map); + if (nr < 0) { + pr_debug("dso__load() failed!\n"); + ret = TEST_FAIL; + goto out_put; + } + + if (nr == 0) { + pr_debug("DSO has no symbols!\n"); + ret = TEST_SKIP; + goto out_put; + } + + ret = test_dso(dso); + + /* Module dso is split into many dsos by section */ + if (ret == TEST_OK && dso__kernel(dso) != DSO_SPACE__USER) + ret = process_subdivided_dso(ti->machine, dso); +out_put: + map__put(map); + + return ret; +} + +static int test__symbols(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + char filename[PATH_MAX]; + struct test_info ti; + int ret; + + ret = init_test_info(&ti); + if (ret != TEST_OK) + return ret; + + get_test_dso_filename(filename, sizeof(filename)); + + ret = test_file(&ti, filename); + + exit_test_info(&ti); + + return ret; +} + +DEFINE_SUITE("Symbols", symbols); diff --git a/tools/perf/tests/task-exit.c b/tools/perf/tests/task-exit.c index adaff9044331..8e328bbd509d 100644 --- a/tools/perf/tests/task-exit.c +++ b/tools/perf/tests/task-exit.c @@ -23,7 +23,7 @@ static void sig_handler(int sig __maybe_unused) } /* - * perf_evlist__prepare_workload will send a SIGUSR1 if the fork fails, since + * evlist__prepare_workload will send a SIGUSR1 if the fork fails, since * we asked by setting its exec_error to this handler. */ static void workload_exec_failed_signal(int signo __maybe_unused, @@ -39,7 +39,7 @@ static void workload_exec_failed_signal(int signo __maybe_unused, * if the number of exit event reported by the kernel is 1 or not * in order to check the kernel returns correct number of event. */ -int test__task_exit(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__task_exit(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { int err = -1; union perf_event *event; @@ -58,33 +58,29 @@ int test__task_exit(struct test *test __maybe_unused, int subtest __maybe_unused signal(SIGCHLD, sig_handler); - evlist = perf_evlist__new_default(); + evlist = evlist__new_dummy(); if (evlist == NULL) { - pr_debug("perf_evlist__new_default\n"); + pr_debug("evlist__new_dummy\n"); return -1; } /* * 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 + * evlist__prepare_workload we'll fill in the only thread * we're monitoring, the one forked there. */ - cpus = perf_cpu_map__dummy_new(); + cpus = perf_cpu_map__new_any_cpu(); threads = thread_map__new_by_tid(-1); if (!cpus || !threads) { err = -ENOMEM; pr_debug("Not enough memory to create thread/cpu maps\n"); - goto out_free_maps; + goto out_delete_evlist; } perf_evlist__set_maps(&evlist->core, cpus, threads); - cpus = NULL; - threads = NULL; - - err = perf_evlist__prepare_workload(evlist, &target, argv, false, - workload_exec_failed_signal); + err = evlist__prepare_workload(evlist, &target, argv, false, workload_exec_failed_signal); if (err < 0) { pr_debug("Couldn't run the workload!\n"); goto out_delete_evlist; @@ -116,7 +112,7 @@ int test__task_exit(struct test *test __maybe_unused, int subtest __maybe_unused goto out_delete_evlist; } - perf_evlist__start_workload(evlist); + evlist__start_workload(evlist); retry: md = &evlist->mmap[0]; @@ -138,7 +134,7 @@ out_init: if (retry_count++ > 1000) { pr_debug("Failed after retrying 1000 times\n"); err = -1; - goto out_free_maps; + goto out_delete_evlist; } goto retry; @@ -149,10 +145,18 @@ out_init: err = -1; } -out_free_maps: +out_delete_evlist: perf_cpu_map__put(cpus); perf_thread_map__put(threads); -out_delete_evlist: evlist__delete(evlist); return err; } + +struct test_case tests__task_exit[] = { + TEST_CASE_EXCLUSIVE("Number of exit events of a simple workload", task_exit), + { .name = NULL, } +}; +struct test_suite suite__task_exit = { + .desc = "Number of exit events of a simple workload", + .test_cases = tests__task_exit, +}; diff --git a/tools/perf/tests/tests-scripts.c b/tools/perf/tests/tests-scripts.c new file mode 100644 index 000000000000..3a2a8438f9af --- /dev/null +++ b/tools/perf/tests/tests-scripts.c @@ -0,0 +1,294 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/ctype.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/zalloc.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include <subcmd/exec-cmd.h> +#include <subcmd/parse-options.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <api/io.h> +#include "builtin.h" +#include "tests-scripts.h" +#include "color.h" +#include "debug.h" +#include "hist.h" +#include "intlist.h" +#include "string2.h" +#include "symbol.h" +#include "tests.h" +#include "util/rlimit.h" +#include "util/util.h" + +static int shell_tests__dir_fd(void) +{ + struct stat st; + char path[PATH_MAX], path2[PATH_MAX], *exec_path; + static const char * const devel_dirs[] = { + "./tools/perf/tests/shell", + "./tests/shell", + "./source/tests/shell" + }; + int fd; + char *p; + + for (size_t i = 0; i < ARRAY_SIZE(devel_dirs); ++i) { + fd = open(devel_dirs[i], O_PATH); + + if (fd >= 0) + return fd; + } + + /* Use directory of executable */ + if (readlink("/proc/self/exe", path2, sizeof path2) < 0) + return -1; + /* Follow another level of symlink if there */ + if (lstat(path2, &st) == 0 && (st.st_mode & S_IFMT) == S_IFLNK) { + scnprintf(path, sizeof(path), path2); + if (readlink(path, path2, sizeof path2) < 0) + return -1; + } + /* Get directory */ + p = strrchr(path2, '/'); + if (p) + *p = 0; + scnprintf(path, sizeof(path), "%s/tests/shell", path2); + fd = open(path, O_PATH); + if (fd >= 0) + return fd; + scnprintf(path, sizeof(path), "%s/source/tests/shell", path2); + fd = open(path, O_PATH); + if (fd >= 0) + return fd; + + /* Then installed path. */ + exec_path = get_argv_exec_path(); + scnprintf(path, sizeof(path), "%s/tests/shell", exec_path); + free(exec_path); + return open(path, O_PATH); +} + +static char *shell_test__description(int dir_fd, const char *name) +{ + struct io io; + char buf[128], desc[256]; + int ch, pos = 0; + + io__init(&io, openat(dir_fd, name, O_RDONLY), buf, sizeof(buf)); + if (io.fd < 0) + return NULL; + + /* Skip first line - should be #!/bin/sh Shebang */ + if (io__get_char(&io) != '#') + goto err_out; + if (io__get_char(&io) != '!') + goto err_out; + do { + ch = io__get_char(&io); + if (ch < 0) + goto err_out; + } while (ch != '\n'); + + do { + ch = io__get_char(&io); + if (ch < 0) + goto err_out; + } while (ch == '#' || isspace(ch)); + while (ch > 0 && ch != '\n') { + desc[pos++] = ch; + if (pos >= (int)sizeof(desc) - 1) + break; + ch = io__get_char(&io); + } + while (pos > 0 && isspace(desc[--pos])) + ; + desc[++pos] = '\0'; + close(io.fd); + return strdup(desc); +err_out: + close(io.fd); + return NULL; +} + +/* Is this full file path a shell script */ +static bool is_shell_script(int dir_fd, const char *path) +{ + const char *ext; + + ext = strrchr(path, '.'); + if (!ext) + return false; + if (!strcmp(ext, ".sh")) { /* Has .sh extension */ + if (faccessat(dir_fd, path, R_OK | X_OK, 0) == 0) /* Is executable */ + return true; + } + return false; +} + +/* Is this file in this dir a shell script (for test purposes) */ +static bool is_test_script(int dir_fd, const char *name) +{ + return is_shell_script(dir_fd, name); +} + +/* Duplicate a string and fall over and die if we run out of memory */ +static char *strdup_check(const char *str) +{ + char *newstr; + + newstr = strdup(str); + if (!newstr) { + pr_err("Out of memory while duplicating test script string\n"); + abort(); + } + return newstr; +} + +static int shell_test__run(struct test_suite *test, int subtest __maybe_unused) +{ + const char *file = test->priv; + int err; + char *cmd = NULL; + + if (asprintf(&cmd, "%s%s", file, verbose ? " -v" : "") < 0) + return TEST_FAIL; + err = system(cmd); + free(cmd); + if (!err) + return TEST_OK; + + return WEXITSTATUS(err) == 2 ? TEST_SKIP : TEST_FAIL; +} + +static void append_script(int dir_fd, const char *name, char *desc, + struct test_suite ***result, + size_t *result_sz) +{ + char filename[PATH_MAX], link[128]; + struct test_suite *test_suite, **result_tmp; + struct test_case *tests; + ssize_t len; + char *exclusive; + + snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd); + len = readlink(link, filename, sizeof(filename)); + if (len < 0) { + pr_err("Failed to readlink %s", link); + return; + } + filename[len++] = '/'; + strcpy(&filename[len], name); + + tests = calloc(2, sizeof(*tests)); + if (!tests) { + pr_err("Out of memory while building script test suite list\n"); + return; + } + tests[0].name = strdup_check(name); + exclusive = strstr(desc, " (exclusive)"); + if (exclusive != NULL) { + tests[0].exclusive = true; + exclusive[0] = '\0'; + } + tests[0].desc = strdup_check(desc); + tests[0].run_case = shell_test__run; + test_suite = zalloc(sizeof(*test_suite)); + if (!test_suite) { + pr_err("Out of memory while building script test suite list\n"); + free(tests); + return; + } + test_suite->desc = desc; + test_suite->test_cases = tests; + test_suite->priv = strdup_check(filename); + /* Realloc is good enough, though we could realloc by chunks, not that + * anyone will ever measure performance here */ + result_tmp = realloc(*result, (*result_sz + 1) * sizeof(*result_tmp)); + if (result_tmp == NULL) { + pr_err("Out of memory while building script test suite list\n"); + free(tests); + free(test_suite); + return; + } + /* Add file to end and NULL terminate the struct array */ + *result = result_tmp; + (*result)[*result_sz] = test_suite; + (*result_sz)++; +} + +static void append_scripts_in_dir(int dir_fd, + struct test_suite ***result, + size_t *result_sz) +{ + struct dirent **entlist; + struct dirent *ent; + int n_dirs, i; + + /* List files, sorted by alpha */ + n_dirs = scandirat(dir_fd, ".", &entlist, NULL, alphasort); + if (n_dirs == -1) + return; + for (i = 0; i < n_dirs && (ent = entlist[i]); i++) { + int fd; + + if (ent->d_name[0] == '.') + continue; /* Skip hidden files */ + if (is_test_script(dir_fd, ent->d_name)) { /* It's a test */ + char *desc = shell_test__description(dir_fd, ent->d_name); + + if (desc) /* It has a desc line - valid script */ + append_script(dir_fd, ent->d_name, desc, result, result_sz); + continue; + } + if (ent->d_type != DT_DIR) { + struct stat st; + + if (ent->d_type != DT_UNKNOWN) + continue; + fstatat(dir_fd, ent->d_name, &st, 0); + if (!S_ISDIR(st.st_mode)) + continue; + } + if (strncmp(ent->d_name, "base_", 5) == 0) + continue; /* Skip scripts that have a separate driver. */ + fd = openat(dir_fd, ent->d_name, O_PATH); + append_scripts_in_dir(fd, result, result_sz); + close(fd); + } + for (i = 0; i < n_dirs; i++) /* Clean up */ + zfree(&entlist[i]); + free(entlist); +} + +struct test_suite **create_script_test_suites(void) +{ + struct test_suite **result = NULL, **result_tmp; + size_t result_sz = 0; + int dir_fd = shell_tests__dir_fd(); /* Walk dir */ + + /* + * Append scripts if fd is good, otherwise return a NULL terminated zero + * length array. + */ + if (dir_fd >= 0) + append_scripts_in_dir(dir_fd, &result, &result_sz); + + result_tmp = realloc(result, (result_sz + 1) * sizeof(*result_tmp)); + if (result_tmp == NULL) { + pr_err("Out of memory while building script test suite list\n"); + abort(); + } + /* NULL terminate the test suite array. */ + result = result_tmp; + result[result_sz] = NULL; + if (dir_fd >= 0) + close(dir_fd); + return result; +} diff --git a/tools/perf/tests/tests-scripts.h b/tools/perf/tests/tests-scripts.h new file mode 100644 index 000000000000..b553ad26ea17 --- /dev/null +++ b/tools/perf/tests/tests-scripts.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef TESTS_SCRIPTS_H +#define TESTS_SCRIPTS_H + +#include "tests.h" + +struct test_suite **create_script_test_suites(void); + +#endif /* TESTS_SCRIPTS_H */ diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index 76a4e352eaaf..bb7951c61971 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -4,11 +4,17 @@ #include <stdbool.h> +enum { + TEST_OK = 0, + TEST_FAIL = -1, + TEST_SKIP = -2, +}; + #define TEST_ASSERT_VAL(text, cond) \ do { \ if (!(cond)) { \ pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \ - return -1; \ + return TEST_FAIL; \ } \ } while (0) @@ -17,126 +23,213 @@ do { \ if (val != expected) { \ pr_debug("FAILED %s:%d %s (%d != %d)\n", \ __FILE__, __LINE__, text, val, expected); \ - return -1; \ + return TEST_FAIL; \ } \ } while (0) -enum { - TEST_OK = 0, - TEST_FAIL = -1, - TEST_SKIP = -2, +struct test_suite; + +typedef int (*test_fnptr)(struct test_suite *, int); + +struct test_case { + const char *name; + const char *desc; + const char *skip_reason; + test_fnptr run_case; + bool exclusive; }; -struct test { +struct test_suite { const char *desc; - int (*func)(struct test *test, int subtest); - struct { - bool skip_if_fail; - int (*get_nr)(void); - const char *(*get_desc)(int subtest); - const char *(*skip_reason)(int subtest); - } subtest; - bool (*is_supported)(void); + struct test_case *test_cases; void *priv; }; +#define DECLARE_SUITE(name) \ + extern struct test_suite suite__##name; + +#define TEST_CASE(description, _name) \ + { \ + .name = #_name, \ + .desc = description, \ + .run_case = test__##_name, \ + } + +#define TEST_CASE_REASON(description, _name, _reason) \ + { \ + .name = #_name, \ + .desc = description, \ + .run_case = test__##_name, \ + .skip_reason = _reason, \ + } + +#define TEST_CASE_EXCLUSIVE(description, _name) \ + { \ + .name = #_name, \ + .desc = description, \ + .run_case = test__##_name, \ + .exclusive = true, \ + } + +#define DEFINE_SUITE(description, _name) \ + struct test_case tests__##_name[] = { \ + TEST_CASE(description, _name), \ + { .name = NULL, } \ + }; \ + struct test_suite suite__##_name = { \ + .desc = description, \ + .test_cases = tests__##_name, \ + } + +#define DEFINE_SUITE_EXCLUSIVE(description, _name) \ + struct test_case tests__##_name[] = { \ + TEST_CASE_EXCLUSIVE(description, _name),\ + { .name = NULL, } \ + }; \ + struct test_suite suite__##_name = { \ + .desc = description, \ + .test_cases = tests__##_name, \ + } + /* Tests */ -int test__vmlinux_matches_kallsyms(struct test *test, int subtest); -int test__openat_syscall_event(struct test *test, int subtest); -int test__openat_syscall_event_on_all_cpus(struct test *test, int subtest); -int test__basic_mmap(struct test *test, int subtest); -int test__PERF_RECORD(struct test *test, int subtest); -int test__perf_evsel__roundtrip_name_test(struct test *test, int subtest); -int test__perf_evsel__tp_sched_test(struct test *test, int subtest); -int test__syscall_openat_tp_fields(struct test *test, int subtest); -int test__pmu(struct test *test, int subtest); -int test__pmu_events(struct test *test, int subtest); -const char *test__pmu_events_subtest_get_desc(int subtest); -const char *test__pmu_events_subtest_skip_reason(int subtest); -int test__pmu_events_subtest_get_nr(void); -int test__attr(struct test *test, int subtest); -int test__dso_data(struct test *test, int subtest); -int test__dso_data_cache(struct test *test, int subtest); -int test__dso_data_reopen(struct test *test, int subtest); -int test__parse_events(struct test *test, int subtest); -int test__hists_link(struct test *test, int subtest); -int test__python_use(struct test *test, int subtest); -int test__bp_signal(struct test *test, int subtest); -int test__bp_signal_overflow(struct test *test, int subtest); -int test__bp_accounting(struct test *test, int subtest); -int test__wp(struct test *test, int subtest); -const char *test__wp_subtest_get_desc(int subtest); -int test__wp_subtest_get_nr(void); -int test__task_exit(struct test *test, int subtest); -int test__mem(struct test *test, int subtest); -int test__sw_clock_freq(struct test *test, int subtest); -int test__code_reading(struct test *test, int subtest); -int test__sample_parsing(struct test *test, int subtest); -int test__keep_tracking(struct test *test, int subtest); -int test__parse_no_sample_id_all(struct test *test, int subtest); -int test__dwarf_unwind(struct test *test, int subtest); -int test__expr(struct test *test, int subtest); -int test__hists_filter(struct test *test, int subtest); -int test__mmap_thread_lookup(struct test *test, int subtest); -int test__thread_maps_share(struct test *test, int subtest); -int test__hists_output(struct test *test, int subtest); -int test__hists_cumulate(struct test *test, int subtest); -int test__switch_tracking(struct test *test, int subtest); -int test__fdarray__filter(struct test *test, int subtest); -int test__fdarray__add(struct test *test, int subtest); -int test__kmod_path__parse(struct test *test, int subtest); -int test__thread_map(struct test *test, int subtest); -int test__llvm(struct test *test, int subtest); -const char *test__llvm_subtest_get_desc(int subtest); -int test__llvm_subtest_get_nr(void); -int test__bpf(struct test *test, int subtest); -const char *test__bpf_subtest_get_desc(int subtest); -int test__bpf_subtest_get_nr(void); -int test__session_topology(struct test *test, int subtest); -int test__thread_map_synthesize(struct test *test, int subtest); -int test__thread_map_remove(struct test *test, int subtest); -int test__cpu_map_synthesize(struct test *test, int subtest); -int test__synthesize_stat_config(struct test *test, int subtest); -int test__synthesize_stat(struct test *test, int subtest); -int test__synthesize_stat_round(struct test *test, int subtest); -int test__event_update(struct test *test, int subtest); -int test__event_times(struct test *test, int subtest); -int test__backward_ring_buffer(struct test *test, int subtest); -int test__cpu_map_print(struct test *test, int subtest); -int test__cpu_map_merge(struct test *test, int subtest); -int test__sdt_event(struct test *test, int subtest); -int test__is_printable_array(struct test *test, int subtest); -int test__bitmap_print(struct test *test, int subtest); -int test__perf_hooks(struct test *test, int subtest); -int test__clang(struct test *test, int subtest); -const char *test__clang_subtest_get_desc(int subtest); -int test__clang_subtest_get_nr(void); -int test__unit_number__scnprint(struct test *test, int subtest); -int test__mem2node(struct test *t, int subtest); -int test__maps__merge_in(struct test *t, int subtest); -int test__time_utils(struct test *t, int subtest); -int test__jit_write_elf(struct test *test, int subtest); -int test__api_io(struct test *test, int subtest); -int test__demangle_java(struct test *test, int subtest); -int test__pfm(struct test *test, int subtest); -const char *test__pfm_subtest_get_desc(int subtest); -int test__pfm_subtest_get_nr(void); - -bool test__bp_signal_is_supported(void); -bool test__bp_account_is_supported(void); -bool test__wp_is_supported(void); - -#if defined(__arm__) || defined(__aarch64__) +DECLARE_SUITE(vmlinux_matches_kallsyms); +DECLARE_SUITE(openat_syscall_event); +DECLARE_SUITE(openat_syscall_event_on_all_cpus); +DECLARE_SUITE(basic_mmap); +DECLARE_SUITE(PERF_RECORD); +DECLARE_SUITE(perf_evsel__roundtrip_name_test); +DECLARE_SUITE(perf_evsel__tp_sched_test); +DECLARE_SUITE(syscall_openat_tp_fields); +DECLARE_SUITE(pmu); +DECLARE_SUITE(pmu_events); +DECLARE_SUITE(hwmon_pmu); +DECLARE_SUITE(tool_pmu); +DECLARE_SUITE(attr); +DECLARE_SUITE(dso_data); +DECLARE_SUITE(dso_data_cache); +DECLARE_SUITE(dso_data_reopen); +DECLARE_SUITE(parse_events); +DECLARE_SUITE(hists_link); +DECLARE_SUITE(python_use); +DECLARE_SUITE(bp_signal); +DECLARE_SUITE(bp_signal_overflow); +DECLARE_SUITE(bp_accounting); +DECLARE_SUITE(wp); +DECLARE_SUITE(task_exit); +DECLARE_SUITE(mem); +DECLARE_SUITE(sw_clock_freq); +DECLARE_SUITE(code_reading); +DECLARE_SUITE(sample_parsing); +DECLARE_SUITE(keep_tracking); +DECLARE_SUITE(parse_no_sample_id_all); +DECLARE_SUITE(dwarf_unwind); +DECLARE_SUITE(expr); +DECLARE_SUITE(hists_filter); +DECLARE_SUITE(mmap_thread_lookup); +DECLARE_SUITE(thread_maps_share); +DECLARE_SUITE(hists_output); +DECLARE_SUITE(hists_cumulate); +DECLARE_SUITE(switch_tracking); +DECLARE_SUITE(fdarray__filter); +DECLARE_SUITE(fdarray__add); +DECLARE_SUITE(kmod_path__parse); +DECLARE_SUITE(thread_map); +DECLARE_SUITE(bpf); +DECLARE_SUITE(session_topology); +DECLARE_SUITE(thread_map_synthesize); +DECLARE_SUITE(thread_map_remove); +DECLARE_SUITE(cpu_map); +DECLARE_SUITE(synthesize_stat_config); +DECLARE_SUITE(synthesize_stat); +DECLARE_SUITE(synthesize_stat_round); +DECLARE_SUITE(event_update); +DECLARE_SUITE(event_times); +DECLARE_SUITE(backward_ring_buffer); +DECLARE_SUITE(sdt_event); +DECLARE_SUITE(is_printable_array); +DECLARE_SUITE(bitmap_print); +DECLARE_SUITE(perf_hooks); +DECLARE_SUITE(unit_number__scnprint); +DECLARE_SUITE(mem2node); +DECLARE_SUITE(maps__merge_in); +DECLARE_SUITE(time_utils); +DECLARE_SUITE(jit_write_elf); +DECLARE_SUITE(api_io); +DECLARE_SUITE(demangle_java); +DECLARE_SUITE(demangle_ocaml); +DECLARE_SUITE(demangle_rust); +DECLARE_SUITE(pfm); +DECLARE_SUITE(parse_metric); +DECLARE_SUITE(pe_file_parsing); +DECLARE_SUITE(expand_cgroup_events); +DECLARE_SUITE(perf_time_to_tsc); +DECLARE_SUITE(dlfilter); +DECLARE_SUITE(sigtrap); +DECLARE_SUITE(event_groups); +DECLARE_SUITE(symbols); +DECLARE_SUITE(util); + +/* + * PowerPC and S390 do not support creation of instruction breakpoints using the + * perf_event interface. + * + * ARM requires explicit rounding down of the instruction pointer in Thumb mode, + * and then requires the single-step to be handled explicitly in the overflow + * handler to avoid stepping into the SIGIO handler and getting stuck on the + * breakpointed instruction. + * + * Since arm64 has the same issue with arm for the single-step handling, this + * case also gets stuck on the breakpointed instruction. + * + * Just disable the test for these architectures until these issues are + * resolved. + */ +#if defined(__powerpc__) || defined(__s390x__) || defined(__arm__) || defined(__aarch64__) +#define BP_SIGNAL_IS_SUPPORTED 0 +#else +#define BP_SIGNAL_IS_SUPPORTED 1 +#endif + #ifdef HAVE_DWARF_UNWIND_SUPPORT struct thread; struct perf_sample; int test__arch_unwind_sample(struct perf_sample *sample, struct thread *thread); #endif -#endif #if defined(__arm__) -int test__vectors_page(struct test *test, int subtest); +DECLARE_SUITE(vectors_page); #endif +/* + * Define test workloads to be used in test suites. + */ +typedef int (*workload_fnptr)(int argc, const char **argv); + +struct test_workload { + const char *name; + workload_fnptr func; +}; + +#define DECLARE_WORKLOAD(work) \ + extern struct test_workload workload__##work + +#define DEFINE_WORKLOAD(work) \ +struct test_workload workload__##work = { \ + .name = #work, \ + .func = work, \ +} + +/* The list of test workloads */ +DECLARE_WORKLOAD(noploop); +DECLARE_WORKLOAD(thloop); +DECLARE_WORKLOAD(leafloop); +DECLARE_WORKLOAD(sqrtloop); +DECLARE_WORKLOAD(brstack); +DECLARE_WORKLOAD(datasym); +DECLARE_WORKLOAD(landlock); + +extern const char *dso_to_test; +extern const char *test_objdump_path; + #endif /* TESTS_H */ diff --git a/tools/perf/tests/thread-map.c b/tools/perf/tests/thread-map.c index 28f51c4bd373..1fe521466bf4 100644 --- a/tools/perf/tests/thread-map.c +++ b/tools/perf/tests/thread-map.c @@ -11,6 +11,7 @@ #include "util/synthetic-events.h" #include <linux/zalloc.h> #include <perf/event.h> +#include <internal/threadmap.h> struct perf_sample; struct perf_tool; @@ -19,7 +20,7 @@ struct machine; #define NAME (const char *) "perf" #define NAMEUL (unsigned long) NAME -int test__thread_map(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__thread_map(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct perf_thread_map *map; @@ -59,7 +60,7 @@ int test__thread_map(struct test *test __maybe_unused, int subtest __maybe_unuse return 0; } -static int process_event(struct perf_tool *tool __maybe_unused, +static int process_event(const struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine __maybe_unused) @@ -86,7 +87,7 @@ static int process_event(struct perf_tool *tool __maybe_unused, return 0; } -int test__thread_map_synthesize(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__thread_map_synthesize(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct perf_thread_map *threads; @@ -102,19 +103,20 @@ int test__thread_map_synthesize(struct test *test __maybe_unused, int subtest __ TEST_ASSERT_VAL("failed to synthesize map", !perf_event__synthesize_thread_map2(NULL, threads, process_event, NULL)); + perf_thread_map__put(threads); return 0; } -int test__thread_map_remove(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__thread_map_remove(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct perf_thread_map *threads; char *str; - int i; TEST_ASSERT_VAL("failed to allocate map string", asprintf(&str, "%d,%d", getpid(), getppid()) >= 0); threads = thread_map__new_str(str, NULL, 0, false); + free(str); TEST_ASSERT_VAL("failed to allocate thread_map", threads); @@ -141,9 +143,10 @@ int test__thread_map_remove(struct test *test __maybe_unused, int subtest __mayb TEST_ASSERT_VAL("failed to not remove thread", thread_map__remove(threads, 0)); - for (i = 0; i < threads->nr; i++) - zfree(&threads->map[i].comm); - - free(threads); + perf_thread_map__put(threads); return 0; } + +DEFINE_SUITE("Thread map", thread_map); +DEFINE_SUITE("Synthesize thread map", thread_map_synthesize); +DEFINE_SUITE("Remove thread map", thread_map_remove); diff --git a/tools/perf/tests/thread-maps-share.c b/tools/perf/tests/thread-maps-share.c index 9371484973f2..e9ecd30a5c05 100644 --- a/tools/perf/tests/thread-maps-share.c +++ b/tools/perf/tests/thread-maps-share.c @@ -4,7 +4,7 @@ #include "thread.h" #include "debug.h" -int test__thread_maps_share(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__thread_maps_share(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct machines machines; struct machine *machine; @@ -42,13 +42,13 @@ int test__thread_maps_share(struct test *test __maybe_unused, int subtest __mayb TEST_ASSERT_VAL("failed to create threads", leader && t1 && t2 && t3 && other); - maps = leader->maps; - TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&maps->refcnt), 4); + maps = thread__maps(leader); + TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(maps__refcnt(maps)), 4); /* test the maps pointer is shared */ - TEST_ASSERT_VAL("maps don't match", maps == t1->maps); - TEST_ASSERT_VAL("maps don't match", maps == t2->maps); - TEST_ASSERT_VAL("maps don't match", maps == t3->maps); + TEST_ASSERT_VAL("maps don't match", maps__equal(maps, thread__maps(t1))); + TEST_ASSERT_VAL("maps don't match", maps__equal(maps, thread__maps(t2))); + TEST_ASSERT_VAL("maps don't match", maps__equal(maps, thread__maps(t3))); /* * Verify the other leader was created by previous call. @@ -70,29 +70,31 @@ int test__thread_maps_share(struct test *test __maybe_unused, int subtest __mayb machine__remove_thread(machine, other); machine__remove_thread(machine, other_leader); - other_maps = other->maps; - TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&other_maps->refcnt), 2); + other_maps = thread__maps(other); + TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(maps__refcnt(other_maps)), 2); - TEST_ASSERT_VAL("maps don't match", other_maps == other_leader->maps); + TEST_ASSERT_VAL("maps don't match", maps__equal(other_maps, thread__maps(other_leader))); /* release thread group */ - thread__put(leader); - TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&maps->refcnt), 3); - - thread__put(t1); - TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&maps->refcnt), 2); + thread__put(t3); + TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(maps__refcnt(maps)), 3); thread__put(t2); - TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&maps->refcnt), 1); + TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(maps__refcnt(maps)), 2); - thread__put(t3); + thread__put(t1); + TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(maps__refcnt(maps)), 1); + + thread__put(leader); /* release other group */ thread__put(other_leader); - TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(&other_maps->refcnt), 1); + TEST_ASSERT_EQUAL("wrong refcnt", refcount_read(maps__refcnt(other_maps)), 1); thread__put(other); machines__exit(&machines); return 0; } + +DEFINE_SUITE("Share thread maps", thread_maps_share); diff --git a/tools/perf/tests/time-utils-test.c b/tools/perf/tests/time-utils-test.c index fe57ca3b6e54..38df10373c1e 100644 --- a/tools/perf/tests/time-utils-test.c +++ b/tools/perf/tests/time-utils-test.c @@ -131,7 +131,7 @@ out: return pass; } -int test__time_utils(struct test *t __maybe_unused, int subtest __maybe_unused) +static int test__time_utils(struct test_suite *t __maybe_unused, int subtest __maybe_unused) { bool pass = true; @@ -249,3 +249,5 @@ int test__time_utils(struct test *t __maybe_unused, int subtest __maybe_unused) return pass ? 0 : TEST_FAIL; } + +DEFINE_SUITE("time utils", time_utils); diff --git a/tools/perf/tests/tool_pmu.c b/tools/perf/tests/tool_pmu.c new file mode 100644 index 000000000000..1e900ef92e37 --- /dev/null +++ b/tools/perf/tests/tool_pmu.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +#include "debug.h" +#include "evlist.h" +#include "parse-events.h" +#include "tests.h" +#include "tool_pmu.h" + +static int do_test(enum tool_pmu_event ev, bool with_pmu) +{ + struct evlist *evlist = evlist__new(); + struct evsel *evsel; + struct parse_events_error err; + int ret; + char str[128]; + bool found = false; + + if (!evlist) { + pr_err("evlist allocation failed\n"); + return TEST_FAIL; + } + + if (with_pmu) + snprintf(str, sizeof(str), "tool/%s/", tool_pmu__event_to_str(ev)); + else + snprintf(str, sizeof(str), "%s", tool_pmu__event_to_str(ev)); + + parse_events_error__init(&err); + ret = parse_events(evlist, str, &err); + if (ret) { + if (!tool_pmu__event_to_str(ev)) { + ret = TEST_OK; + goto out; + } + + pr_debug("FAILED %s:%d failed to parse event '%s', err %d\n", + __FILE__, __LINE__, str, ret); + parse_events_error__print(&err, str); + ret = TEST_FAIL; + goto out; + } + + ret = TEST_OK; + if (with_pmu ? (evlist->core.nr_entries != 1) : (evlist->core.nr_entries < 1)) { + pr_debug("FAILED %s:%d Unexpected number of events for '%s' of %d\n", + __FILE__, __LINE__, str, evlist->core.nr_entries); + ret = TEST_FAIL; + goto out; + } + + evlist__for_each_entry(evlist, evsel) { + if (perf_pmu__is_tool(evsel->pmu)) { + if (evsel->core.attr.config != ev) { + pr_debug("FAILED %s:%d Unexpected config for '%s', %lld != %d\n", + __FILE__, __LINE__, str, evsel->core.attr.config, ev); + ret = TEST_FAIL; + goto out; + } + found = true; + } + } + + if (!found && tool_pmu__event_to_str(ev)) { + pr_debug("FAILED %s:%d Didn't find tool event '%s' in parsed evsels\n", + __FILE__, __LINE__, str); + ret = TEST_FAIL; + } + +out: + parse_events_error__exit(&err); + evlist__delete(evlist); + return ret; +} + +static int test__tool_pmu_without_pmu(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int i; + + tool_pmu__for_each_event(i) { + int ret = do_test(i, /*with_pmu=*/false); + + if (ret != TEST_OK) + return ret; + } + return TEST_OK; +} + +static int test__tool_pmu_with_pmu(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int i; + + tool_pmu__for_each_event(i) { + int ret = do_test(i, /*with_pmu=*/true); + + if (ret != TEST_OK) + return ret; + } + return TEST_OK; +} + +static struct test_case tests__tool_pmu[] = { + TEST_CASE("Parsing without PMU name", tool_pmu_without_pmu), + TEST_CASE("Parsing with PMU name", tool_pmu_with_pmu), + { .name = NULL, } +}; + +struct test_suite suite__tool_pmu = { + .desc = "Tool PMU", + .test_cases = tests__tool_pmu, +}; diff --git a/tools/perf/tests/topology.c b/tools/perf/tests/topology.c index 22daf2bdf5fa..a8cb5ba898ab 100644 --- a/tools/perf/tests/topology.c +++ b/tools/perf/tests/topology.c @@ -8,6 +8,7 @@ #include "session.h" #include "evlist.h" #include "debug.h" +#include "pmus.h" #include <linux/err.h> #define TEMPL "/tmp/perf-test-XXXXXX" @@ -37,10 +38,10 @@ static int session_write_header(char *path) .mode = PERF_DATA_MODE_WRITE, }; - session = perf_session__new(&data, false, NULL); + session = perf_session__new(&data, NULL); TEST_ASSERT_VAL("can't get session", !IS_ERR(session)); - session->evlist = perf_evlist__new_default(); + session->evlist = evlist__new_default(); TEST_ASSERT_VAL("can't get evlist", session->evlist); perf_header__set_feat(&session->header, HEADER_CPU_TOPOLOGY); @@ -52,6 +53,7 @@ static int session_write_header(char *path) TEST_ASSERT_VAL("failed to write header", !perf_session__write_header(session, session->evlist, data.file.fd, true)); + evlist__delete(session->evlist); perf_session__delete(session); return 0; @@ -65,9 +67,12 @@ static int check_cpu_topology(char *path, struct perf_cpu_map *map) .mode = PERF_DATA_MODE_READ, }; int i; + struct aggr_cpu_id id; + struct perf_cpu cpu; - session = perf_session__new(&data, false, NULL); + session = perf_session__new(&data, NULL); TEST_ASSERT_VAL("can't get session", !IS_ERR(session)); + cpu__setup_cpunode_map(); /* On platforms with large numbers of CPUs process_cpu_topology() * might issue an error while reading the perf.data file section @@ -78,40 +83,126 @@ static int check_cpu_topology(char *path, struct perf_cpu_map *map) * CPU 1 is on core_id 1 and physical_package_id 3 * * Core_id and physical_package_id are platform and architecture - * dependend and might have higher numbers than the CPU id. + * dependent and might have higher numbers than the CPU id. * This actually depends on the configuration. * * In this case process_cpu_topology() prints error message: * "socket_id number is too big. You may need to upgrade the * perf tool." * - * This is the reason why this test might be skipped. + * This is the reason why this test might be skipped. aarch64 and + * s390 always write this part of the header, even when the above + * condition is true (see do_core_id_test in header.c). So always + * run this test on those platforms. */ - if (!session->header.env.cpu) + if (!session->header.env.cpu + && strncmp(session->header.env.arch, "s390", 4) + && strncmp(session->header.env.arch, "aarch64", 7)) return TEST_SKIP; + /* + * In powerpc pSeries platform, not all the topology information + * are exposed via sysfs. Due to restriction, detail like + * physical_package_id will be set to -1. Hence skip this + * test if physical_package_id returns -1 for cpu from perf_cpu_map. + */ + if (!strncmp(session->header.env.arch, "ppc64le", 7)) { + if (cpu__get_socket_id(perf_cpu_map__cpu(map, 0)) == -1) + return TEST_SKIP; + } + + TEST_ASSERT_VAL("Session header CPU map not set", session->header.env.cpu); + for (i = 0; i < session->header.env.nr_cpus_avail; i++) { - if (!cpu_map__has(map, i)) + cpu.cpu = i; + if (!perf_cpu_map__has(map, cpu)) continue; pr_debug("CPU %d, core %d, socket %d\n", i, session->header.env.cpu[i].core_id, session->header.env.cpu[i].socket_id); } - for (i = 0; i < map->nr; i++) { - TEST_ASSERT_VAL("Core ID doesn't match", - (session->header.env.cpu[map->map[i]].core_id == (cpu_map__get_core(map, i, NULL) & 0xffff))); + // Test that CPU ID contains socket, die, core and CPU + perf_cpu_map__for_each_cpu(cpu, i, map) { + id = aggr_cpu_id__cpu(cpu, NULL); + TEST_ASSERT_VAL("Cpu map - CPU ID doesn't match", + cpu.cpu == id.cpu.cpu); + + TEST_ASSERT_VAL("Cpu map - Core ID doesn't match", + session->header.env.cpu[cpu.cpu].core_id == id.core); + TEST_ASSERT_VAL("Cpu map - Socket ID doesn't match", + session->header.env.cpu[cpu.cpu].socket_id == + id.socket); + + TEST_ASSERT_VAL("Cpu map - Die ID doesn't match", + session->header.env.cpu[cpu.cpu].die_id == id.die); + TEST_ASSERT_VAL("Cpu map - Node ID is set", id.node == -1); + TEST_ASSERT_VAL("Cpu map - Thread IDX is set", id.thread_idx == -1); + } + + // Test that core ID contains socket, die and core + perf_cpu_map__for_each_cpu(cpu, i, map) { + id = aggr_cpu_id__core(cpu, NULL); + TEST_ASSERT_VAL("Core map - Core ID doesn't match", + session->header.env.cpu[cpu.cpu].core_id == id.core); - TEST_ASSERT_VAL("Socket ID doesn't match", - (session->header.env.cpu[map->map[i]].socket_id == cpu_map__get_socket(map, i, NULL))); + TEST_ASSERT_VAL("Core map - Socket ID doesn't match", + session->header.env.cpu[cpu.cpu].socket_id == + id.socket); + + TEST_ASSERT_VAL("Core map - Die ID doesn't match", + session->header.env.cpu[cpu.cpu].die_id == id.die); + TEST_ASSERT_VAL("Core map - Node ID is set", id.node == -1); + TEST_ASSERT_VAL("Core map - Thread IDX is set", id.thread_idx == -1); } + // Test that die ID contains socket and die + perf_cpu_map__for_each_cpu(cpu, i, map) { + id = aggr_cpu_id__die(cpu, NULL); + TEST_ASSERT_VAL("Die map - Socket ID doesn't match", + session->header.env.cpu[cpu.cpu].socket_id == + id.socket); + + TEST_ASSERT_VAL("Die map - Die ID doesn't match", + session->header.env.cpu[cpu.cpu].die_id == id.die); + + TEST_ASSERT_VAL("Die map - Node ID is set", id.node == -1); + TEST_ASSERT_VAL("Die map - Core is set", id.core == -1); + TEST_ASSERT_VAL("Die map - CPU is set", id.cpu.cpu == -1); + TEST_ASSERT_VAL("Die map - Thread IDX is set", id.thread_idx == -1); + } + + // Test that socket ID contains only socket + perf_cpu_map__for_each_cpu(cpu, i, map) { + id = aggr_cpu_id__socket(cpu, NULL); + TEST_ASSERT_VAL("Socket map - Socket ID doesn't match", + session->header.env.cpu[cpu.cpu].socket_id == + id.socket); + + TEST_ASSERT_VAL("Socket map - Node ID is set", id.node == -1); + TEST_ASSERT_VAL("Socket map - Die ID is set", id.die == -1); + TEST_ASSERT_VAL("Socket map - Core is set", id.core == -1); + TEST_ASSERT_VAL("Socket map - CPU is set", id.cpu.cpu == -1); + TEST_ASSERT_VAL("Socket map - Thread IDX is set", id.thread_idx == -1); + } + + // Test that node ID contains only node + perf_cpu_map__for_each_cpu(cpu, i, map) { + id = aggr_cpu_id__node(cpu, NULL); + TEST_ASSERT_VAL("Node map - Node ID doesn't match", + cpu__get_node(cpu) == id.node); + TEST_ASSERT_VAL("Node map - Socket is set", id.socket == -1); + TEST_ASSERT_VAL("Node map - Die ID is set", id.die == -1); + TEST_ASSERT_VAL("Node map - Core is set", id.core == -1); + TEST_ASSERT_VAL("Node map - CPU is set", id.cpu.cpu == -1); + TEST_ASSERT_VAL("Node map - Thread IDX is set", id.thread_idx == -1); + } perf_session__delete(session); return 0; } -int test__session_topology(struct test *test __maybe_unused, int subtest __maybe_unused) +static int test__session_topology(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { char path[PATH_MAX]; struct perf_cpu_map *map; @@ -124,7 +215,7 @@ int test__session_topology(struct test *test __maybe_unused, int subtest __maybe if (session_write_header(path)) goto free_path; - map = perf_cpu_map__new(NULL); + map = perf_cpu_map__new_online_cpus(); if (map == NULL) { pr_debug("failed to get system cpumap\n"); goto free_path; @@ -137,3 +228,5 @@ free_path: unlink(path); return ret; } + +DEFINE_SUITE("Session topology", session_topology); diff --git a/tools/perf/tests/unit_number__scnprintf.c b/tools/perf/tests/unit_number__scnprintf.c index 3721757435da..88bcada1c78f 100644 --- a/tools/perf/tests/unit_number__scnprintf.c +++ b/tools/perf/tests/unit_number__scnprintf.c @@ -7,7 +7,7 @@ #include "units.h" #include "debug.h" -int test__unit_number__scnprint(struct test *t __maybe_unused, int subtest __maybe_unused) +static int test__unit_number__scnprint(struct test_suite *t __maybe_unused, int subtest __maybe_unused) { struct { u64 n; @@ -38,3 +38,5 @@ int test__unit_number__scnprint(struct test *t __maybe_unused, int subtest __may return TEST_OK; } + +DEFINE_SUITE("unit_number__scnprintf", unit_number__scnprint); diff --git a/tools/perf/tests/util.c b/tools/perf/tests/util.c new file mode 100644 index 000000000000..6366db5cbf8c --- /dev/null +++ b/tools/perf/tests/util.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "tests.h" +#include "util/debug.h" + +#include <linux/compiler.h> +#include <stdlib.h> +#include <string2.h> + +static int test_strreplace(char needle, const char *haystack, + const char *replace, const char *expected) +{ + char *new = strreplace_chars(needle, haystack, replace); + int ret = strcmp(new, expected); + + free(new); + return ret == 0; +} + +static int test__util(struct test_suite *t __maybe_unused, int subtest __maybe_unused) +{ + TEST_ASSERT_VAL("empty string", test_strreplace(' ', "", "123", "")); + TEST_ASSERT_VAL("no match", test_strreplace('5', "123", "4", "123")); + TEST_ASSERT_VAL("replace 1", test_strreplace('3', "123", "4", "124")); + TEST_ASSERT_VAL("replace 2", test_strreplace('a', "abcabc", "ef", "efbcefbc")); + TEST_ASSERT_VAL("replace long", test_strreplace('a', "abcabc", "longlong", + "longlongbclonglongbc")); + + return 0; +} + +DEFINE_SUITE("util", util); diff --git a/tools/perf/tests/vmlinux-kallsyms.c b/tools/perf/tests/vmlinux-kallsyms.c index 193b7c91b4e2..74cdbd2ce9d0 100644 --- a/tools/perf/tests/vmlinux-kallsyms.c +++ b/tools/perf/tests/vmlinux-kallsyms.c @@ -3,6 +3,7 @@ #include <linux/rbtree.h> #include <inttypes.h> #include <string.h> +#include <ctype.h> #include <stdlib.h> #include "dso.h" #include "map.h" @@ -12,18 +13,190 @@ #include "debug.h" #include "machine.h" -#define UM(x) kallsyms_map->unmap_ip(kallsyms_map, (x)) +#define UM(x) map__unmap_ip(kallsyms_map, (x)) -int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest __maybe_unused) +static bool is_ignored_symbol(const char *name, char type) { - int err = -1; + /* Symbol names that exactly match to the following are ignored.*/ + static const char * const ignored_symbols[] = { + /* + * Symbols which vary between passes. Passes 1 and 2 must have + * identical symbol lists. The kallsyms_* symbols below are + * only added after pass 1, they would be included in pass 2 + * when --all-symbols is specified so exclude them to get a + * stable symbol list. + */ + "kallsyms_offsets", + "kallsyms_relative_base", + "kallsyms_num_syms", + "kallsyms_names", + "kallsyms_markers", + "kallsyms_token_table", + "kallsyms_token_index", + /* Exclude linker generated symbols which vary between passes */ + "_SDA_BASE_", /* ppc */ + "_SDA2_BASE_", /* ppc */ + NULL + }; + + /* Symbol names that begin with the following are ignored.*/ + static const char * const ignored_prefixes[] = { + "$", /* local symbols for ARM, MIPS, etc. */ + ".L", /* local labels, .LBB,.Ltmpxxx,.L__unnamed_xx,.LASANPC, etc. */ + "__crc_", /* modversions */ + "__efistub_", /* arm64 EFI stub namespace */ + "__kvm_nvhe_$", /* arm64 local symbols in non-VHE KVM namespace */ + "__kvm_nvhe_.L", /* arm64 local symbols in non-VHE KVM namespace */ + "__AArch64ADRPThunk_", /* arm64 lld */ + "__ARMV5PILongThunk_", /* arm lld */ + "__ARMV7PILongThunk_", + "__ThumbV7PILongThunk_", + "__LA25Thunk_", /* mips lld */ + "__microLA25Thunk_", + NULL + }; + + /* Symbol names that end with the following are ignored.*/ + static const char * const ignored_suffixes[] = { + "_from_arm", /* arm */ + "_from_thumb", /* arm */ + "_veneer", /* arm */ + NULL + }; + + /* Symbol names that contain the following are ignored.*/ + static const char * const ignored_matches[] = { + ".long_branch.", /* ppc stub */ + ".plt_branch.", /* ppc stub */ + NULL + }; + + const char * const *p; + + for (p = ignored_symbols; *p; p++) + if (!strcmp(name, *p)) + return true; + + for (p = ignored_prefixes; *p; p++) + if (!strncmp(name, *p, strlen(*p))) + return true; + + for (p = ignored_suffixes; *p; p++) { + int l = strlen(name) - strlen(*p); + + if (l >= 0 && !strcmp(name + l, *p)) + return true; + } + + for (p = ignored_matches; *p; p++) { + if (strstr(name, *p)) + return true; + } + + if (type == 'U' || type == 'u') + return true; + /* exclude debugging symbols */ + if (type == 'N' || type == 'n') + return true; + + if (toupper(type) == 'A') { + /* Keep these useful absolute symbols */ + if (strcmp(name, "__kernel_syscall_via_break") && + strcmp(name, "__kernel_syscall_via_epc") && + strcmp(name, "__kernel_sigtramp") && + strcmp(name, "__gp")) + return true; + } + + return false; +} + +struct test__vmlinux_matches_kallsyms_cb_args { + struct machine kallsyms; + struct map *vmlinux_map; + bool header_printed; +}; + +static int test__vmlinux_matches_kallsyms_cb1(struct map *map, void *data) +{ + struct test__vmlinux_matches_kallsyms_cb_args *args = data; + struct dso *dso = map__dso(map); + /* + * 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. + */ + struct map *pair = maps__find_by_name(args->kallsyms.kmaps, + (dso__kernel(dso) ? dso__short_name(dso) : dso__name(dso))); + + if (pair) { + map__set_priv(pair); + map__put(pair); + } else { + if (!args->header_printed) { + pr_info("WARN: Maps only in vmlinux:\n"); + args->header_printed = true; + } + map__fprintf(map, stderr); + } + return 0; +} + +static int test__vmlinux_matches_kallsyms_cb2(struct map *map, void *data) +{ + struct test__vmlinux_matches_kallsyms_cb_args *args = data; + struct map *pair; + u64 mem_start = map__unmap_ip(args->vmlinux_map, map__start(map)); + u64 mem_end = map__unmap_ip(args->vmlinux_map, map__end(map)); + + pair = maps__find(args->kallsyms.kmaps, mem_start); + + if (pair != NULL && !map__priv(pair) && map__start(pair) == mem_start) { + struct dso *dso = map__dso(map); + + if (!args->header_printed) { + pr_info("WARN: Maps in vmlinux with a different name in kallsyms:\n"); + args->header_printed = true; + } + + pr_info("WARN: %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as", + map__start(map), map__end(map), map__pgoff(map), dso__name(dso)); + if (mem_end != map__end(pair)) + pr_info(":\nWARN: *%" PRIx64 "-%" PRIx64 " %" PRIx64, + map__start(pair), map__end(pair), map__pgoff(pair)); + pr_info(" %s\n", dso__name(dso)); + map__set_priv(pair); + } + map__put(pair); + return 0; +} + +static int test__vmlinux_matches_kallsyms_cb3(struct map *map, void *data) +{ + struct test__vmlinux_matches_kallsyms_cb_args *args = data; + + if (!map__priv(map)) { + if (!args->header_printed) { + pr_info("WARN: Maps only in kallsyms:\n"); + args->header_printed = true; + } + map__fprintf(map, stderr); + } + return 0; +} + +static int test__vmlinux_matches_kallsyms(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int err = TEST_FAIL; struct rb_node *nd; struct symbol *sym; - struct map *kallsyms_map, *vmlinux_map, *map; - struct machine kallsyms, vmlinux; - struct maps *maps = machine__kernel_maps(&vmlinux); + struct map *kallsyms_map; + struct machine vmlinux; + struct maps *maps; u64 mem_start, mem_end; - bool header_printed; + struct test__vmlinux_matches_kallsyms_cb_args args; /* * Step 1: @@ -31,9 +204,11 @@ int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest * 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(&args.kallsyms, "", HOST_KERNEL_ID); machine__init(&vmlinux, "", HOST_KERNEL_ID); + maps = machine__kernel_maps(&vmlinux); + /* * Step 2: * @@ -41,8 +216,9 @@ int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest * 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 "); + if (machine__create_kernel_maps(&args.kallsyms) < 0) { + pr_debug("machine__create_kernel_maps failed"); + err = TEST_SKIP; goto out; } @@ -57,8 +233,9 @@ int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest * be compacted against the list of modules found in the "vmlinux" * code and with the one got from /proc/modules from the "kallsyms" code. */ - if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms") <= 0) { - pr_debug("dso__load_kallsyms "); + if (machine__load_kallsyms(&args.kallsyms, "/proc/kallsyms") <= 0) { + pr_debug("machine__load_kallsyms failed"); + err = TEST_SKIP; goto out; } @@ -70,7 +247,7 @@ int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest * 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); + kallsyms_map = machine__kernel_map(&args.kallsyms); /* * Step 5: @@ -78,11 +255,11 @@ int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest * 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 "); + pr_info("machine__create_kernel_maps failed"); goto out; } - vmlinux_map = machine__kernel_map(&vmlinux); + args.vmlinux_map = machine__kernel_map(&vmlinux); /* * Step 6: @@ -96,7 +273,7 @@ int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest * to fixup the symbols. */ if (machine__load_vmlinux_path(&vmlinux) <= 0) { - pr_debug("Couldn't find a vmlinux that matches the kernel running on this machine, skipping test\n"); + pr_info("Couldn't find a vmlinux that matches the kernel running on this machine, skipping test\n"); err = TEST_SKIP; goto out; } @@ -109,7 +286,7 @@ int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest * in the kallsyms dso. For the ones that are in both, check its names and * end addresses too. */ - map__for_each_symbol(vmlinux_map, sym, nd) { + map__for_each_symbol(args.vmlinux_map, sym, nd) { struct symbol *pair, *first_pair; sym = rb_entry(nd, struct symbol, rb_node); @@ -117,10 +294,10 @@ int test__vmlinux_matches_kallsyms(struct test *test __maybe_unused, int subtest if (sym->start == sym->end) continue; - mem_start = vmlinux_map->unmap_ip(vmlinux_map, sym->start); - mem_end = vmlinux_map->unmap_ip(vmlinux_map, sym->end); + mem_start = map__unmap_ip(args.vmlinux_map, sym->start); + mem_end = map__unmap_ip(args.vmlinux_map, sym->end); - first_pair = machine__find_kernel_symbol(&kallsyms, mem_start, NULL); + first_pair = machine__find_kernel_symbol(&args.kallsyms, mem_start, NULL); pair = first_pair; if (pair && UM(pair->start) == mem_start) { @@ -149,7 +326,8 @@ next_pair: */ continue; } else { - pair = machine__find_kernel_symbol_by_name(&kallsyms, sym->name, NULL); + pair = machine__find_kernel_symbol_by_name(&args.kallsyms, + sym->name, NULL); if (pair) { if (UM(pair->start) == mem_start) goto next_pair; @@ -163,12 +341,17 @@ next_pair: continue; } - } else if (mem_start == kallsyms.vmlinux_map->end) { + } else if (mem_start == map__end(args.kallsyms.vmlinux_map)) { /* * Ignore aliases to _etext, i.e. to the end of the kernel text area, * such as __indirect_thunk_end. */ continue; + } else if (is_ignored_symbol(sym->name, sym->type)) { + /* + * Ignore hidden symbols, see scripts/kallsyms.c for the details + */ + continue; } else { pr_debug("ERR : %#" PRIx64 ": %s not on kallsyms\n", mem_start, sym->name); @@ -180,73 +363,20 @@ next_pair: if (verbose <= 0) goto out; - header_printed = false; - - maps__for_each_entry(maps, map) { - struct map * - /* - * 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 = maps__find_by_name(&kallsyms.kmaps, (map->dso->kernel ? - map->dso->short_name : - map->dso->name)); - if (pair) { - pair->priv = 1; - } else { - if (!header_printed) { - pr_info("WARN: Maps only in vmlinux:\n"); - header_printed = true; - } - map__fprintf(map, stderr); - } - } - - header_printed = false; - - maps__for_each_entry(maps, map) { - struct map *pair; - - mem_start = vmlinux_map->unmap_ip(vmlinux_map, map->start); - mem_end = vmlinux_map->unmap_ip(vmlinux_map, map->end); - - pair = maps__find(&kallsyms.kmaps, mem_start); - if (pair == NULL || pair->priv) - continue; - - if (pair->start == mem_start) { - if (!header_printed) { - pr_info("WARN: Maps in vmlinux with a different name in kallsyms:\n"); - header_printed = true; - } - - pr_info("WARN: %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as", - map->start, map->end, map->pgoff, map->dso->name); - if (mem_end != pair->end) - pr_info(":\nWARN: *%" PRIx64 "-%" PRIx64 " %" PRIx64, - pair->start, pair->end, pair->pgoff); - pr_info(" %s\n", pair->dso->name); - pair->priv = 1; - } - } + args.header_printed = false; + maps__for_each_map(maps, test__vmlinux_matches_kallsyms_cb1, &args); - header_printed = false; + args.header_printed = false; + maps__for_each_map(maps, test__vmlinux_matches_kallsyms_cb2, &args); - maps = machine__kernel_maps(&kallsyms); + args.header_printed = false; + maps = machine__kernel_maps(&args.kallsyms); + maps__for_each_map(maps, test__vmlinux_matches_kallsyms_cb3, &args); - maps__for_each_entry(maps, map) { - if (!map->priv) { - if (!header_printed) { - pr_info("WARN: Maps only in kallsyms:\n"); - header_printed = true; - } - map__fprintf(map, stderr); - } - } out: - machine__exit(&kallsyms); + machine__exit(&args.kallsyms); machine__exit(&vmlinux); return err; } + +DEFINE_SUITE("vmlinux symtab matches kallsyms", vmlinux_matches_kallsyms); diff --git a/tools/perf/tests/workloads/Build b/tools/perf/tests/workloads/Build new file mode 100644 index 000000000000..5af17206f04d --- /dev/null +++ b/tools/perf/tests/workloads/Build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +perf-test-y += noploop.o +perf-test-y += thloop.o +perf-test-y += leafloop.o +perf-test-y += sqrtloop.o +perf-test-y += brstack.o +perf-test-y += datasym.o +perf-test-y += landlock.o + +CFLAGS_sqrtloop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE +CFLAGS_leafloop.o = -g -O0 -fno-inline -fno-omit-frame-pointer -U_FORTIFY_SOURCE +CFLAGS_brstack.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE +CFLAGS_datasym.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE diff --git a/tools/perf/tests/workloads/brstack.c b/tools/perf/tests/workloads/brstack.c new file mode 100644 index 000000000000..0b60bd37b9d1 --- /dev/null +++ b/tools/perf/tests/workloads/brstack.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <stdlib.h> +#include "../tests.h" + +#define BENCH_RUNS 999999 + +static volatile int cnt; + +static void brstack_bar(void) { +} /* return */ + +static void brstack_foo(void) { + brstack_bar(); /* call */ +} /* return */ + +static void brstack_bench(void) { + void (*brstack_foo_ind)(void) = brstack_foo; + + if ((cnt++) % 3) /* branch (cond) */ + brstack_foo(); /* call */ + brstack_bar(); /* call */ + brstack_foo_ind(); /* call (ind) */ +} + +static int brstack(int argc, const char **argv) +{ + int num_loops = BENCH_RUNS; + + if (argc > 0) + num_loops = atoi(argv[0]); + + while (1) { + if ((cnt++) > num_loops) + break; + brstack_bench();/* call */ + } /* branch (uncond) */ + return 0; +} + +DEFINE_WORKLOAD(brstack); diff --git a/tools/perf/tests/workloads/datasym.c b/tools/perf/tests/workloads/datasym.c new file mode 100644 index 000000000000..1d0b7d64e1ba --- /dev/null +++ b/tools/perf/tests/workloads/datasym.c @@ -0,0 +1,60 @@ +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <linux/compiler.h> +#include "../tests.h" + +typedef struct _buf { + char data1; + char reserved[55]; + char data2; +} buf __attribute__((aligned(64))); + +/* volatile to try to avoid the compiler seeing reserved as unused. */ +static volatile buf workload_datasym_buf1 = { + /* to have this in the data section */ + .reserved[0] = 1, +}; + +static volatile sig_atomic_t done; + +static void sighandler(int sig __maybe_unused) +{ + done = 1; +} + +static int datasym(int argc, const char **argv) +{ + int sec = 1; + + if (argc > 0) + sec = atoi(argv[0]); + + signal(SIGINT, sighandler); + signal(SIGALRM, sighandler); + alarm(sec); + + while (!done) { + workload_datasym_buf1.data1++; + if (workload_datasym_buf1.data1 == 123) { + /* + * Add some 'noise' in the loop to work around errata + * 1694299 on Arm N1. + * + * Bias exists in SPE sampling which can cause the load + * and store instructions to be skipped entirely. This + * comes and goes randomly depending on the offset the + * linker places the datasym loop at in the Perf binary. + * With an extra branch in the middle of the loop that + * isn't always taken, the instruction stream is no + * longer a continuous repeating pattern that interacts + * badly with the bias. + */ + workload_datasym_buf1.data1++; + } + workload_datasym_buf1.data2 += workload_datasym_buf1.data1; + } + return 0; +} + +DEFINE_WORKLOAD(datasym); diff --git a/tools/perf/tests/workloads/landlock.c b/tools/perf/tests/workloads/landlock.c new file mode 100644 index 000000000000..1f285b7b6236 --- /dev/null +++ b/tools/perf/tests/workloads/landlock.c @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/compiler.h> +#include <linux/types.h> +#include <unistd.h> +#include "../tests.h" + +/* This workload was initially added to test enum augmentation with BTF in perf + * trace because its the only syscall that has an enum argument. Since it is + * a recent addition to the Linux kernel (at the time of the introduction of this + * 'perf test' workload) we just add the required types and defines here instead + * of including linux/landlock, that isn't available in older systems. + * + * We are not interested in the result of the syscall, just in intercepting + * its arguments. + */ + +#ifndef __NR_landlock_add_rule +#define __NR_landlock_add_rule 445 +#endif + +#ifndef LANDLOCK_ACCESS_FS_READ_FILE +#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2) + +#define LANDLOCK_RULE_PATH_BENEATH 1 + +struct landlock_path_beneath_attr { + __u64 allowed_access; + __s32 parent_fd; +}; +#endif + +#ifndef LANDLOCK_ACCESS_NET_CONNECT_TCP +#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1) + +#define LANDLOCK_RULE_NET_PORT 2 + +struct landlock_net_port_attr { + __u64 allowed_access; + __u64 port; +}; +#endif + +static int landlock(int argc __maybe_unused, const char **argv __maybe_unused) +{ + int fd = 11, flags = 45; + + struct landlock_path_beneath_attr path_beneath_attr = { + .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE, + .parent_fd = 14, + }; + + struct landlock_net_port_attr net_port_attr = { + .port = 19, + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + + syscall(__NR_landlock_add_rule, fd, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath_attr, flags); + + syscall(__NR_landlock_add_rule, fd, LANDLOCK_RULE_NET_PORT, + &net_port_attr, flags); + + return 0; +} + +DEFINE_WORKLOAD(landlock); diff --git a/tools/perf/tests/workloads/leafloop.c b/tools/perf/tests/workloads/leafloop.c new file mode 100644 index 000000000000..f7561767e32c --- /dev/null +++ b/tools/perf/tests/workloads/leafloop.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <signal.h> +#include <stdlib.h> +#include <linux/compiler.h> +#include <unistd.h> +#include "../tests.h" + +/* We want to check these symbols in perf script */ +noinline void leaf(volatile int b); +noinline void parent(volatile int b); + +static volatile int a; +static volatile sig_atomic_t done; + +static void sighandler(int sig __maybe_unused) +{ + done = 1; +} + +noinline void leaf(volatile int b) +{ + while (!done) + a += b; +} + +noinline void parent(volatile int b) +{ + leaf(b); +} + +static int leafloop(int argc, const char **argv) +{ + int sec = 1; + + if (argc > 0) + sec = atoi(argv[0]); + + signal(SIGINT, sighandler); + signal(SIGALRM, sighandler); + alarm(sec); + + parent(sec); + return 0; +} + +DEFINE_WORKLOAD(leafloop); diff --git a/tools/perf/tests/workloads/noploop.c b/tools/perf/tests/workloads/noploop.c new file mode 100644 index 000000000000..940ea5910a84 --- /dev/null +++ b/tools/perf/tests/workloads/noploop.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <linux/compiler.h> +#include "../tests.h" + +static volatile sig_atomic_t done; + +static void sighandler(int sig __maybe_unused) +{ + done = 1; +} + +static int noploop(int argc, const char **argv) +{ + int sec = 1; + + if (argc > 0) + sec = atoi(argv[0]); + + signal(SIGINT, sighandler); + signal(SIGALRM, sighandler); + alarm(sec); + + while (!done) + continue; + + return 0; +} + +DEFINE_WORKLOAD(noploop); diff --git a/tools/perf/tests/workloads/sqrtloop.c b/tools/perf/tests/workloads/sqrtloop.c new file mode 100644 index 000000000000..ccc94c6a6676 --- /dev/null +++ b/tools/perf/tests/workloads/sqrtloop.c @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <math.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/compiler.h> +#include <sys/wait.h> +#include "../tests.h" + +static volatile sig_atomic_t done; + +static void sighandler(int sig __maybe_unused) +{ + done = 1; +} + +static int __sqrtloop(int sec) +{ + signal(SIGALRM, sighandler); + alarm(sec); + + while (!done) + (void)sqrt(rand()); + return 0; +} + +static int sqrtloop(int argc, const char **argv) +{ + int sec = 1; + + if (argc > 0) + sec = atoi(argv[0]); + + switch (fork()) { + case 0: + return __sqrtloop(sec); + case -1: + return -1; + default: + wait(NULL); + } + return 0; +} + +DEFINE_WORKLOAD(sqrtloop); diff --git a/tools/perf/tests/workloads/thloop.c b/tools/perf/tests/workloads/thloop.c new file mode 100644 index 000000000000..457b29f91c3e --- /dev/null +++ b/tools/perf/tests/workloads/thloop.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <pthread.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <linux/compiler.h> +#include "../tests.h" + +static volatile sig_atomic_t done; + +/* We want to check this symbol in perf report */ +noinline void test_loop(void); + +static void sighandler(int sig __maybe_unused) +{ + done = 1; +} + +noinline void test_loop(void) +{ + while (!done); +} + +static void *thfunc(void *arg) +{ + void (*loop_fn)(void) = arg; + + loop_fn(); + return NULL; +} + +static int thloop(int argc, const char **argv) +{ + int sec = 1; + pthread_t th; + + if (argc > 0) + sec = atoi(argv[0]); + + signal(SIGINT, sighandler); + signal(SIGALRM, sighandler); + alarm(sec); + + pthread_create(&th, NULL, thfunc, test_loop); + test_loop(); + pthread_join(th, NULL); + + return 0; +} + +DEFINE_WORKLOAD(thloop); diff --git a/tools/perf/tests/wp.c b/tools/perf/tests/wp.c index d262d6639829..6c178985e37f 100644 --- a/tools/perf/tests/wp.c +++ b/tools/perf/tests/wp.c @@ -2,7 +2,9 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <errno.h> #include <sys/ioctl.h> +#include <linux/compiler.h> #include <linux/hw_breakpoint.h> #include <linux/kernel.h> #include "tests.h" @@ -18,9 +20,15 @@ do { \ TEST_ASSERT_VAL(text, count == val); \ } while (0) +#ifdef __i386__ +/* Only breakpoint length less-than 8 has hardware support on i386. */ +volatile u32 data1; +#else volatile u64 data1; +#endif volatile u8 data2[3]; +#ifndef __s390x__ static int wp_read(int fd, long long *count, int size) { int ret = read(fd, count, size); @@ -56,20 +64,27 @@ static int __event(int wp_type, void *wp_addr, unsigned long wp_len) get__perf_event_attr(&attr, wp_type, wp_addr, wp_len); fd = sys_perf_event_open(&attr, 0, -1, -1, perf_event_open_cloexec_flag()); - if (fd < 0) + if (fd < 0) { + fd = -errno; pr_debug("failed opening event %x\n", attr.bp_type); + } return fd; } +#endif -static int wp_ro_test(void) +static int test__wp_ro(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { +#if defined(__s390x__) || defined(__x86_64__) || defined(__i386__) + return TEST_SKIP; +#else int fd; unsigned long tmp, tmp1 = rand(); fd = __event(HW_BREAKPOINT_R, (void *)&data1, sizeof(data1)); if (fd < 0) - return -1; + return fd == -ENODEV ? TEST_SKIP : -1; tmp = data1; WP_TEST_ASSERT_VAL(fd, "RO watchpoint", 1); @@ -79,16 +94,21 @@ static int wp_ro_test(void) close(fd); return 0; +#endif } -static int wp_wo_test(void) +static int test__wp_wo(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { +#if defined(__s390x__) + return TEST_SKIP; +#else int fd; unsigned long tmp, tmp1 = rand(); fd = __event(HW_BREAKPOINT_W, (void *)&data1, sizeof(data1)); if (fd < 0) - return -1; + return fd == -ENODEV ? TEST_SKIP : -1; tmp = data1; WP_TEST_ASSERT_VAL(fd, "WO watchpoint", 0); @@ -98,17 +118,22 @@ static int wp_wo_test(void) close(fd); return 0; +#endif } -static int wp_rw_test(void) +static int test__wp_rw(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { +#if defined(__s390x__) + return TEST_SKIP; +#else int fd; unsigned long tmp, tmp1 = rand(); fd = __event(HW_BREAKPOINT_R | HW_BREAKPOINT_W, (void *)&data1, sizeof(data1)); if (fd < 0) - return -1; + return fd == -ENODEV ? TEST_SKIP : -1; tmp = data1; WP_TEST_ASSERT_VAL(fd, "RW watchpoint", 1); @@ -118,17 +143,21 @@ static int wp_rw_test(void) close(fd); return 0; +#endif } -static int wp_modify_test(void) +static int test__wp_modify(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { +#if defined(__s390x__) + return TEST_SKIP; +#else int fd, ret; unsigned long tmp = rand(); struct perf_event_attr new_attr; fd = __event(HW_BREAKPOINT_W, (void *)&data1, sizeof(data1)); if (fd < 0) - return -1; + return fd == -ENODEV ? TEST_SKIP : -1; data1 = tmp; WP_TEST_ASSERT_VAL(fd, "Modify watchpoint", 1); @@ -139,6 +168,11 @@ static int wp_modify_test(void) new_attr.disabled = 1; ret = ioctl(fd, PERF_EVENT_IOC_MODIFY_ATTRIBUTES, &new_attr); if (ret < 0) { + if (errno == ENOTTY) { + test->test_cases[subtest].skip_reason = "missing kernel support"; + ret = TEST_SKIP; + } + pr_debug("ioctl(PERF_EVENT_IOC_MODIFY_ATTRIBUTES) failed\n"); close(fd); return ret; @@ -163,84 +197,18 @@ static int wp_modify_test(void) close(fd); return 0; -} - -static bool wp_ro_supported(void) -{ -#if defined (__x86_64__) || defined (__i386__) - return false; -#else - return true; -#endif -} - -static void wp_ro_skip_msg(void) -{ -#if defined (__x86_64__) || defined (__i386__) - pr_debug("Hardware does not support read only watchpoints.\n"); #endif } -static struct { - const char *desc; - int (*target_func)(void); - bool (*is_supported)(void); - void (*skip_msg)(void); -} wp_testcase_table[] = { - { - .desc = "Read Only Watchpoint", - .target_func = &wp_ro_test, - .is_supported = &wp_ro_supported, - .skip_msg = &wp_ro_skip_msg, - }, - { - .desc = "Write Only Watchpoint", - .target_func = &wp_wo_test, - }, - { - .desc = "Read / Write Watchpoint", - .target_func = &wp_rw_test, - }, - { - .desc = "Modify Watchpoint", - .target_func = &wp_modify_test, - }, +static struct test_case wp_tests[] = { + TEST_CASE_REASON("Read Only Watchpoint", wp_ro, "missing hardware support"), + TEST_CASE_REASON("Write Only Watchpoint", wp_wo, "missing hardware support"), + TEST_CASE_REASON("Read / Write Watchpoint", wp_rw, "missing hardware support"), + TEST_CASE_REASON("Modify Watchpoint", wp_modify, "missing hardware support"), + { .name = NULL, } }; -int test__wp_subtest_get_nr(void) -{ - return (int)ARRAY_SIZE(wp_testcase_table); -} - -const char *test__wp_subtest_get_desc(int i) -{ - if (i < 0 || i >= (int)ARRAY_SIZE(wp_testcase_table)) - return NULL; - return wp_testcase_table[i].desc; -} - -int test__wp(struct test *test __maybe_unused, int i) -{ - if (i < 0 || i >= (int)ARRAY_SIZE(wp_testcase_table)) - return TEST_FAIL; - - if (wp_testcase_table[i].is_supported && - !wp_testcase_table[i].is_supported()) { - wp_testcase_table[i].skip_msg(); - return TEST_SKIP; - } - - return !wp_testcase_table[i].target_func() ? TEST_OK : TEST_FAIL; -} - -/* The s390 so far does not have support for - * instruction breakpoint using the perf_event_open() system call. - */ -bool test__wp_is_supported(void) -{ -#if defined(__s390x__) - return false; -#else - return true; -#endif -} +struct test_suite suite__wp = { + .desc = "Watchpoint", + .test_cases = wp_tests, +}; |