From 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 Mon Sep 17 00:00:00 2001 From: Yunhong Jiang Date: Tue, 4 Aug 2015 12:17:53 -0700 Subject: Add the rt linux 4.1.3-rt3 as base Import the rt linux 4.1.3-rt3 as OPNFV kvm base. It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and the base is: commit 0917f823c59692d751951bf5ea699a2d1e2f26a2 Author: Sebastian Andrzej Siewior Date: Sat Jul 25 12:13:34 2015 +0200 Prepare v4.1.3-rt3 Signed-off-by: Sebastian Andrzej Siewior We lose all the git history this way and it's not good. We should apply another opnfv project repo in future. Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423 Signed-off-by: Yunhong Jiang --- kernel/tools/perf/tests/Build | 43 + kernel/tools/perf/tests/attr.c | 176 ++ kernel/tools/perf/tests/attr.py | 331 ++++ kernel/tools/perf/tests/attr/README | 64 + kernel/tools/perf/tests/attr/base-record | 41 + kernel/tools/perf/tests/attr/base-stat | 41 + kernel/tools/perf/tests/attr/test-record-C0 | 13 + kernel/tools/perf/tests/attr/test-record-basic | 5 + .../tools/perf/tests/attr/test-record-branch-any | 8 + .../perf/tests/attr/test-record-branch-filter-any | 8 + .../tests/attr/test-record-branch-filter-any_call | 8 + .../tests/attr/test-record-branch-filter-any_ret | 8 + .../perf/tests/attr/test-record-branch-filter-hv | 8 + .../tests/attr/test-record-branch-filter-ind_call | 8 + .../perf/tests/attr/test-record-branch-filter-k | 8 + .../perf/tests/attr/test-record-branch-filter-u | 8 + kernel/tools/perf/tests/attr/test-record-count | 8 + kernel/tools/perf/tests/attr/test-record-data | 11 + kernel/tools/perf/tests/attr/test-record-freq | 6 + .../perf/tests/attr/test-record-graph-default | 6 + .../tools/perf/tests/attr/test-record-graph-dwarf | 10 + kernel/tools/perf/tests/attr/test-record-graph-fp | 6 + kernel/tools/perf/tests/attr/test-record-group | 20 + .../perf/tests/attr/test-record-group-sampling | 36 + kernel/tools/perf/tests/attr/test-record-group1 | 21 + kernel/tools/perf/tests/attr/test-record-no-delay | 9 + .../tools/perf/tests/attr/test-record-no-inherit | 7 + .../tools/perf/tests/attr/test-record-no-samples | 6 + kernel/tools/perf/tests/attr/test-record-period | 7 + kernel/tools/perf/tests/attr/test-record-raw | 7 + kernel/tools/perf/tests/attr/test-stat-C0 | 9 + kernel/tools/perf/tests/attr/test-stat-basic | 6 + kernel/tools/perf/tests/attr/test-stat-default | 64 + kernel/tools/perf/tests/attr/test-stat-detailed-1 | 101 ++ kernel/tools/perf/tests/attr/test-stat-detailed-2 | 155 ++ kernel/tools/perf/tests/attr/test-stat-detailed-3 | 173 ++ kernel/tools/perf/tests/attr/test-stat-group | 15 + kernel/tools/perf/tests/attr/test-stat-group1 | 15 + kernel/tools/perf/tests/attr/test-stat-no-inherit | 7 + kernel/tools/perf/tests/bp_signal.c | 194 +++ kernel/tools/perf/tests/bp_signal_overflow.c | 134 ++ kernel/tools/perf/tests/builtin-test.c | 331 ++++ kernel/tools/perf/tests/code-reading.c | 578 +++++++ kernel/tools/perf/tests/dso-data.c | 365 ++++ kernel/tools/perf/tests/dwarf-unwind.c | 179 ++ kernel/tools/perf/tests/evsel-roundtrip-name.c | 115 ++ kernel/tools/perf/tests/evsel-tp-sched.c | 82 + kernel/tools/perf/tests/fdarray.c | 174 ++ kernel/tools/perf/tests/hists_common.c | 209 +++ kernel/tools/perf/tests/hists_common.h | 75 + kernel/tools/perf/tests/hists_cumulate.c | 726 ++++++++ kernel/tools/perf/tests/hists_filter.c | 290 ++++ kernel/tools/perf/tests/hists_link.c | 346 ++++ kernel/tools/perf/tests/hists_output.c | 621 +++++++ kernel/tools/perf/tests/keep-tracking.c | 152 ++ kernel/tools/perf/tests/kmod-path.c | 73 + kernel/tools/perf/tests/make | 232 +++ kernel/tools/perf/tests/mmap-basic.c | 149 ++ kernel/tools/perf/tests/mmap-thread-lookup.c | 233 +++ kernel/tools/perf/tests/open-syscall-all-cpus.c | 115 ++ kernel/tools/perf/tests/open-syscall-tp-fields.c | 121 ++ kernel/tools/perf/tests/open-syscall.c | 61 + kernel/tools/perf/tests/parse-events.c | 1734 ++++++++++++++++++++ kernel/tools/perf/tests/parse-no-sample-id-all.c | 109 ++ kernel/tools/perf/tests/perf-record.c | 314 ++++ kernel/tools/perf/tests/perf-targz-src-pkg | 21 + kernel/tools/perf/tests/perf-time-to-tsc.c | 162 ++ kernel/tools/perf/tests/pmu.c | 173 ++ kernel/tools/perf/tests/python-use.c | 23 + kernel/tools/perf/tests/rdpmc.c | 177 ++ kernel/tools/perf/tests/sample-parsing.c | 346 ++++ kernel/tools/perf/tests/sw-clock.c | 124 ++ kernel/tools/perf/tests/switch-tracking.c | 572 +++++++ kernel/tools/perf/tests/task-exit.c | 120 ++ kernel/tools/perf/tests/tests.h | 64 + kernel/tools/perf/tests/thread-mg-share.c | 91 + kernel/tools/perf/tests/vmlinux-kallsyms.c | 242 +++ 77 files changed, 11310 insertions(+) create mode 100644 kernel/tools/perf/tests/Build create mode 100644 kernel/tools/perf/tests/attr.c create mode 100644 kernel/tools/perf/tests/attr.py create mode 100644 kernel/tools/perf/tests/attr/README create mode 100644 kernel/tools/perf/tests/attr/base-record create mode 100644 kernel/tools/perf/tests/attr/base-stat create mode 100644 kernel/tools/perf/tests/attr/test-record-C0 create mode 100644 kernel/tools/perf/tests/attr/test-record-basic create mode 100644 kernel/tools/perf/tests/attr/test-record-branch-any create mode 100644 kernel/tools/perf/tests/attr/test-record-branch-filter-any create mode 100644 kernel/tools/perf/tests/attr/test-record-branch-filter-any_call create mode 100644 kernel/tools/perf/tests/attr/test-record-branch-filter-any_ret create mode 100644 kernel/tools/perf/tests/attr/test-record-branch-filter-hv create mode 100644 kernel/tools/perf/tests/attr/test-record-branch-filter-ind_call create mode 100644 kernel/tools/perf/tests/attr/test-record-branch-filter-k create mode 100644 kernel/tools/perf/tests/attr/test-record-branch-filter-u create mode 100644 kernel/tools/perf/tests/attr/test-record-count create mode 100644 kernel/tools/perf/tests/attr/test-record-data create mode 100644 kernel/tools/perf/tests/attr/test-record-freq create mode 100644 kernel/tools/perf/tests/attr/test-record-graph-default create mode 100644 kernel/tools/perf/tests/attr/test-record-graph-dwarf create mode 100644 kernel/tools/perf/tests/attr/test-record-graph-fp create mode 100644 kernel/tools/perf/tests/attr/test-record-group create mode 100644 kernel/tools/perf/tests/attr/test-record-group-sampling create mode 100644 kernel/tools/perf/tests/attr/test-record-group1 create mode 100644 kernel/tools/perf/tests/attr/test-record-no-delay create mode 100644 kernel/tools/perf/tests/attr/test-record-no-inherit create mode 100644 kernel/tools/perf/tests/attr/test-record-no-samples create mode 100644 kernel/tools/perf/tests/attr/test-record-period create mode 100644 kernel/tools/perf/tests/attr/test-record-raw create mode 100644 kernel/tools/perf/tests/attr/test-stat-C0 create mode 100644 kernel/tools/perf/tests/attr/test-stat-basic create mode 100644 kernel/tools/perf/tests/attr/test-stat-default create mode 100644 kernel/tools/perf/tests/attr/test-stat-detailed-1 create mode 100644 kernel/tools/perf/tests/attr/test-stat-detailed-2 create mode 100644 kernel/tools/perf/tests/attr/test-stat-detailed-3 create mode 100644 kernel/tools/perf/tests/attr/test-stat-group create mode 100644 kernel/tools/perf/tests/attr/test-stat-group1 create mode 100644 kernel/tools/perf/tests/attr/test-stat-no-inherit create mode 100644 kernel/tools/perf/tests/bp_signal.c create mode 100644 kernel/tools/perf/tests/bp_signal_overflow.c create mode 100644 kernel/tools/perf/tests/builtin-test.c create mode 100644 kernel/tools/perf/tests/code-reading.c create mode 100644 kernel/tools/perf/tests/dso-data.c create mode 100644 kernel/tools/perf/tests/dwarf-unwind.c create mode 100644 kernel/tools/perf/tests/evsel-roundtrip-name.c create mode 100644 kernel/tools/perf/tests/evsel-tp-sched.c create mode 100644 kernel/tools/perf/tests/fdarray.c create mode 100644 kernel/tools/perf/tests/hists_common.c create mode 100644 kernel/tools/perf/tests/hists_common.h create mode 100644 kernel/tools/perf/tests/hists_cumulate.c create mode 100644 kernel/tools/perf/tests/hists_filter.c create mode 100644 kernel/tools/perf/tests/hists_link.c create mode 100644 kernel/tools/perf/tests/hists_output.c create mode 100644 kernel/tools/perf/tests/keep-tracking.c create mode 100644 kernel/tools/perf/tests/kmod-path.c create mode 100644 kernel/tools/perf/tests/make create mode 100644 kernel/tools/perf/tests/mmap-basic.c create mode 100644 kernel/tools/perf/tests/mmap-thread-lookup.c create mode 100644 kernel/tools/perf/tests/open-syscall-all-cpus.c create mode 100644 kernel/tools/perf/tests/open-syscall-tp-fields.c create mode 100644 kernel/tools/perf/tests/open-syscall.c create mode 100644 kernel/tools/perf/tests/parse-events.c create mode 100644 kernel/tools/perf/tests/parse-no-sample-id-all.c create mode 100644 kernel/tools/perf/tests/perf-record.c create mode 100755 kernel/tools/perf/tests/perf-targz-src-pkg create mode 100644 kernel/tools/perf/tests/perf-time-to-tsc.c create mode 100644 kernel/tools/perf/tests/pmu.c create mode 100644 kernel/tools/perf/tests/python-use.c create mode 100644 kernel/tools/perf/tests/rdpmc.c create mode 100644 kernel/tools/perf/tests/sample-parsing.c create mode 100644 kernel/tools/perf/tests/sw-clock.c create mode 100644 kernel/tools/perf/tests/switch-tracking.c create mode 100644 kernel/tools/perf/tests/task-exit.c create mode 100644 kernel/tools/perf/tests/tests.h create mode 100644 kernel/tools/perf/tests/thread-mg-share.c create mode 100644 kernel/tools/perf/tests/vmlinux-kallsyms.c (limited to 'kernel/tools/perf/tests') diff --git a/kernel/tools/perf/tests/Build b/kernel/tools/perf/tests/Build new file mode 100644 index 000000000..6a8801b32 --- /dev/null +++ b/kernel/tools/perf/tests/Build @@ -0,0 +1,43 @@ +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 += open-syscall.o +perf-y += open-syscall-all-cpus.o +perf-y += open-syscall-tp-fields.o +perf-y += mmap-basic.o +perf-y += perf-record.o +perf-y += rdpmc.o +perf-y += evsel-roundtrip-name.o +perf-y += evsel-tp-sched.o +perf-y += fdarray.o +perf-y += pmu.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 += task-exit.o +perf-y += sw-clock.o +perf-y += mmap-thread-lookup.o +perf-y += thread-mg-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-$(CONFIG_X86) += perf-time-to-tsc.o + +ifeq ($(ARCH),$(filter $(ARCH),x86 arm)) +perf-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o +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 diff --git a/kernel/tools/perf/tests/attr.c b/kernel/tools/perf/tests/attr.c new file mode 100644 index 000000000..2dfc9ad0e --- /dev/null +++ b/kernel/tools/perf/tests/attr.c @@ -0,0 +1,176 @@ +/* + * 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 +#include +#include +#include +#include "../perf.h" +#include "util.h" +#include "exec_cmd.h" +#include "tests.h" + +#define ENV "PERF_TEST_ATTR" + +extern int verbose; + +static char *dir; + +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]; + + 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(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 (store_event(attr, pid, cpu, fd, group_fd, flags)) + die("test attr FAILED"); + + errno = errno_saved; +} + +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) + vcnt++; + + snprintf(cmd, 3*PATH_MAX, PYTHON " %s/attr.py -d %s/attr/ -p %s %.*s", + d, d, perf, vcnt, v); + + return system(cmd); +} + +int test__attr(void) +{ + struct stat st; + char path_perf[PATH_MAX]; + char path_dir[PATH_MAX]; + + /* First try developement tree tests. */ + if (!lstat("./tests", &st)) + return run_dir("./tests", "./perf"); + + /* Then installed path. */ + snprintf(path_dir, PATH_MAX, "%s/tests", perf_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); + + fprintf(stderr, " (omitted)"); + return 0; +} diff --git a/kernel/tools/perf/tests/attr.py b/kernel/tools/perf/tests/attr.py new file mode 100644 index 000000000..1091bd47a --- /dev/null +++ b/kernel/tools/perf/tests/attr.py @@ -0,0 +1,331 @@ +#! /usr/bin/python + +import os +import sys +import glob +import optparse +import tempfile +import logging +import shutil +import ConfigParser + +class Fail(Exception): + def __init__(self, test, msg): + self.msg = msg + self.test = test + def getMsg(self): + return '\'%s\' - %s' % (self.test.path, self.msg) + +class Unsup(Exception): + def __init__(self, test): + self.test = test + def getMsg(self): + return '\'%s\'' % self.test.path + +class Event(dict): + terms = [ + 'cpu', + 'flags', + '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', + ] + + def add(self, data): + for key, val in data: + log.debug(" %s = %s" % (key, val)) + self[key] = val + + def __init__(self, name, data, base): + log.debug(" Event %s" % name); + self.name = name; + self.group = '' + self.add(base) + self.add(data) + + def compare_data(self, a, b): + # Allow multiple values in assignment separated by '|' + a_list = a.split('|') + b_list = b.split('|') + + for a_item in a_list: + for b_item in b_list: + if (a_item == b_item): + return True + elif (a_item == '*') or (b_item == '*'): + return True + + return False + + def equal(self, other): + for t in Event.terms: + log.debug(" [%s] %s %s" % (t, self[t], other[t])); + if not self.has_key(t) or not other.has_key(t): + return False + if not self.compare_data(self[t], other[t]): + return False + return True + + def diff(self, other): + for t in Event.terms: + if not self.has_key(t) or not other.has_key(t): + continue + if not self.compare_data(self[t], other[t]): + log.warning("expected %s=%s, got %s" % (t, self[t], other[t])) + +# 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) +# +# [eventX:base] +# - one or multiple instances in file +# - expected values assignments +class Test(object): + def __init__(self, path, options): + parser = ConfigParser.SafeConfigParser() + parser.read(path) + + log.warning("running '%s'" % path) + + self.path = path + self.test_dir = options.test_dir + self.perf = options.perf + self.command = parser.get('config', 'command') + self.args = parser.get('config', 'args') + + try: + self.ret = parser.get('config', 'ret') + except: + self.ret = 0 + + self.expect = {} + self.result = {} + log.debug(" loading expected events"); + self.load_events(path, self.expect) + + def is_event(self, name): + if name.find("event") == -1: + return False + else: + return True + + def load_events(self, path, events): + parser_event = ConfigParser.SafeConfigParser() + parser_event.read(path) + + # The event record section header contains 'event' word, + # optionaly followed by ':' allowing to load 'parent + # event' first as a base + for section in filter(self.is_event, parser_event.sections()): + + parser_items = parser_event.items(section); + base_items = {} + + # Read parent event if there's any + if (':' in section): + base = section[section.index(':') + 1:] + parser_base = ConfigParser.SafeConfigParser() + parser_base.read(self.test_dir + '/' + base) + base_items = parser_base.items('event') + + e = Event(section, parser_items, base_items) + events[section] = e + + def run_cmd(self, tempdir): + 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)) + + log.info(" '%s' ret %d " % (cmd, ret)) + + if ret != int(self.ret): + raise Unsup(self) + + def compare(self, expect, result): + match = {} + + log.debug(" compare"); + + # For each expected event find all matching + # events in result. Fail if there's not any. + for exp_name, exp_event in expect.items(): + exp_list = [] + log.debug(" matching [%s]" % exp_name) + for res_name, res_event in result.items(): + log.debug(" to [%s]" % res_name) + if (exp_event.equal(res_event)): + exp_list.append(res_name) + log.debug(" ->OK") + else: + log.debug(" ->FAIL"); + + log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list))) + + # we did not any matching event - fail + if (not exp_list): + exp_event.diff(res_event) + raise Fail(self, 'match failure'); + + match[exp_name] = exp_list + + # For each defined group in the expected events + # check we match the same group in the result. + for exp_name, exp_event in expect.items(): + group = exp_event.group + + if (group == ''): + continue + + for res_name in match[exp_name]: + res_group = result[res_name].group + if res_group not in match[group]: + raise Fail(self, 'group failure') + + log.debug(" group: [%s] matches group leader %s" % + (exp_name, str(match[group]))) + + log.debug(" matched") + + def resolve_groups(self, events): + for name, event in events.items(): + group_fd = event['group_fd']; + if group_fd == '-1': + continue; + + for iname, ievent in events.items(): + if (ievent['fd'] == group_fd): + event.group = iname + log.debug('[%s] has group leader [%s]' % (name, iname)) + break; + + def run(self): + tempdir = tempfile.mkdtemp(); + + try: + # run the test script + self.run_cmd(tempdir); + + # load events expectation for the test + log.debug(" loading result events"); + for f in glob.glob(tempdir + '/event*'): + self.load_events(f, self.result); + + # resolve group_fd to event names + self.resolve_groups(self.expect); + self.resolve_groups(self.result); + + # do the expectation - results matching - both ways + self.compare(self.expect, self.result) + self.compare(self.result, self.expect) + + finally: + # cleanup + shutil.rmtree(tempdir) + + +def run_tests(options): + for f in glob.glob(options.test_dir + '/' + options.test): + try: + Test(f, options).run() + except Unsup, obj: + log.warning("unsupp %s" % obj.getMsg()) + +def setup_log(verbose): + global log + level = logging.CRITICAL + + if verbose == 1: + level = logging.WARNING + if verbose == 2: + level = logging.INFO + if verbose >= 3: + level = logging.DEBUG + + log = logging.getLogger('test') + log.setLevel(level) + ch = logging.StreamHandler() + ch.setLevel(level) + formatter = logging.Formatter('%(message)s') + ch.setFormatter(formatter) + log.addHandler(ch) + +USAGE = '''%s [OPTIONS] + -d dir # tests dir + -p path # perf binary + -t test # single test + -v # verbose level +''' % sys.argv[0] + +def main(): + parser = optparse.OptionParser(usage=USAGE) + + parser.add_option("-t", "--test", + action="store", type="string", dest="test") + parser.add_option("-d", "--test-dir", + action="store", type="string", dest="test_dir") + parser.add_option("-p", "--perf", + action="store", type="string", dest="perf") + parser.add_option("-v", "--verbose", + action="count", dest="verbose") + + options, args = parser.parse_args() + if args: + parser.error('FAILED wrong arguments %s' % ' '.join(args)) + return -1 + + setup_log(options.verbose) + + if not options.test_dir: + print 'FAILED no -d option specified' + sys.exit(-1) + + if not options.test: + options.test = 'test*' + + try: + run_tests(options) + + except Fail, obj: + print "FAILED %s" % obj.getMsg(); + sys.exit(-1) + + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/kernel/tools/perf/tests/attr/README b/kernel/tools/perf/tests/attr/README new file mode 100644 index 000000000..430024f61 --- /dev/null +++ b/kernel/tools/perf/tests/attr/README @@ -0,0 +1,64 @@ +The struct perf_event_attr test (attr tests) support +==================================================== +This testing support is embedded into perf directly and is governed +by the PERF_TEST_ATTR environment variable and hook inside the +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 are checked for values we expect for command. + +The attr tests consist of following parts: + +tests/attr.c +------------ +This is the sys_perf_event_open hook implementation. The hook +is triggered when the PERF_TEST_ATTR environment variable is +defined. It must contain name of existing directory with access +and write permissions. + +For each sys_perf_event_open call event details are stored in +separate file. Besides 'struct perf_event_attr' values we also +store 'fd' and 'group_fd' values to allow checking for groups. + +tests/attr.py +------------- +This is the python script that does all the hard work. It reads +the test definition, executes it and checks results. + +tests/attr/ +----------- +Directory containing all attr test definitions. +Following tests are defined (with perf commands): + + perf record kill (test-record-basic) + perf record -b kill (test-record-branch-any) + perf record -j any kill (test-record-branch-filter-any) + perf record -j any_call kill (test-record-branch-filter-any_call) + perf record -j any_ret kill (test-record-branch-filter-any_ret) + perf record -j hv kill (test-record-branch-filter-hv) + perf record -j ind_call kill (test-record-branch-filter-ind_call) + perf record -j k kill (test-record-branch-filter-k) + perf record -j u kill (test-record-branch-filter-u) + perf record -c 123 kill (test-record-count) + 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 --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 -e '{cycles,instructions}' kill (test-record-group1) + 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 -R kill (test-record-raw) + 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/kernel/tools/perf/tests/attr/base-record b/kernel/tools/perf/tests/attr/base-record new file mode 100644 index 000000000..7e6d74946 --- /dev/null +++ b/kernel/tools/perf/tests/attr/base-record @@ -0,0 +1,41 @@ +[event] +fd=1 +group_fd=-1 +# 0 or PERF_FLAG_FD_CLOEXEC flag +flags=0|8 +cpu=* +type=0|1 +size=112 +config=0 +sample_period=4000 +sample_type=263 +read_format=0 +disabled=1 +inherit=1 +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=1 +task=0 +watermark=0 +precise_ip=0 +mmap_data=0 +sample_id_all=1 +exclude_host=0|1 +exclude_guest=0|1 +exclude_callchain_kernel=0 +exclude_callchain_user=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/kernel/tools/perf/tests/attr/base-stat b/kernel/tools/perf/tests/attr/base-stat new file mode 100644 index 000000000..f4cf148f1 --- /dev/null +++ b/kernel/tools/perf/tests/attr/base-stat @@ -0,0 +1,41 @@ +[event] +fd=1 +group_fd=-1 +# 0 or PERF_FLAG_FD_CLOEXEC flag +flags=0|8 +cpu=* +type=0 +size=112 +config=0 +sample_period=0 +sample_type=0 +read_format=3 +disabled=1 +inherit=1 +pinned=0 +exclusive=0 +exclude_user=0 +exclude_kernel=0 +exclude_hv=0 +exclude_idle=0 +mmap=0 +comm=0 +freq=0 +inherit_stat=0 +enable_on_exec=1 +task=0 +watermark=0 +precise_ip=0 +mmap_data=0 +sample_id_all=0 +exclude_host=0|1 +exclude_guest=0|1 +exclude_callchain_kernel=0 +exclude_callchain_user=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/kernel/tools/perf/tests/attr/test-record-C0 b/kernel/tools/perf/tests/attr/test-record-C0 new file mode 100644 index 000000000..d6a7e43f6 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-C0 @@ -0,0 +1,13 @@ +[config] +command = record +args = -C 0 kill >/dev/null 2>&1 + +[event:base-record] +cpu=0 + +# no enable on exec for CPU attached +enable_on_exec=0 + +# PERF_SAMPLE_IP | PERF_SAMPLE_TID PERF_SAMPLE_TIME | # PERF_SAMPLE_PERIOD +# + PERF_SAMPLE_CPU added by -C 0 +sample_type=391 diff --git a/kernel/tools/perf/tests/attr/test-record-basic b/kernel/tools/perf/tests/attr/test-record-basic new file mode 100644 index 000000000..55c042837 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-basic @@ -0,0 +1,5 @@ +[config] +command = record +args = kill >/dev/null 2>&1 + +[event:base-record] diff --git a/kernel/tools/perf/tests/attr/test-record-branch-any b/kernel/tools/perf/tests/attr/test-record-branch-any new file mode 100644 index 000000000..1421960ed --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-branch-any @@ -0,0 +1,8 @@ +[config] +command = record +args = -b kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=2311 +branch_sample_type=8 diff --git a/kernel/tools/perf/tests/attr/test-record-branch-filter-any b/kernel/tools/perf/tests/attr/test-record-branch-filter-any new file mode 100644 index 000000000..915c4df0e --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-branch-filter-any @@ -0,0 +1,8 @@ +[config] +command = record +args = -j any kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=2311 +branch_sample_type=8 diff --git a/kernel/tools/perf/tests/attr/test-record-branch-filter-any_call b/kernel/tools/perf/tests/attr/test-record-branch-filter-any_call new file mode 100644 index 000000000..8708dbd4f --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-branch-filter-any_call @@ -0,0 +1,8 @@ +[config] +command = record +args = -j any_call kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=2311 +branch_sample_type=16 diff --git a/kernel/tools/perf/tests/attr/test-record-branch-filter-any_ret b/kernel/tools/perf/tests/attr/test-record-branch-filter-any_ret new file mode 100644 index 000000000..0d3607a6d --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-branch-filter-any_ret @@ -0,0 +1,8 @@ +[config] +command = record +args = -j any_ret kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=2311 +branch_sample_type=32 diff --git a/kernel/tools/perf/tests/attr/test-record-branch-filter-hv b/kernel/tools/perf/tests/attr/test-record-branch-filter-hv new file mode 100644 index 000000000..f25526740 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-branch-filter-hv @@ -0,0 +1,8 @@ +[config] +command = record +args = -j hv kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=2311 +branch_sample_type=8 diff --git a/kernel/tools/perf/tests/attr/test-record-branch-filter-ind_call b/kernel/tools/perf/tests/attr/test-record-branch-filter-ind_call new file mode 100644 index 000000000..e862dd179 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-branch-filter-ind_call @@ -0,0 +1,8 @@ +[config] +command = record +args = -j ind_call kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=2311 +branch_sample_type=64 diff --git a/kernel/tools/perf/tests/attr/test-record-branch-filter-k b/kernel/tools/perf/tests/attr/test-record-branch-filter-k new file mode 100644 index 000000000..182971e89 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-branch-filter-k @@ -0,0 +1,8 @@ +[config] +command = record +args = -j k kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=2311 +branch_sample_type=8 diff --git a/kernel/tools/perf/tests/attr/test-record-branch-filter-u b/kernel/tools/perf/tests/attr/test-record-branch-filter-u new file mode 100644 index 000000000..83449ef9e --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-branch-filter-u @@ -0,0 +1,8 @@ +[config] +command = record +args = -j u kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=2311 +branch_sample_type=8 diff --git a/kernel/tools/perf/tests/attr/test-record-count b/kernel/tools/perf/tests/attr/test-record-count new file mode 100644 index 000000000..2f841de56 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-count @@ -0,0 +1,8 @@ +[config] +command = record +args = -c 123 kill >/dev/null 2>&1 + +[event:base-record] +sample_period=123 +sample_type=7 +freq=0 diff --git a/kernel/tools/perf/tests/attr/test-record-data b/kernel/tools/perf/tests/attr/test-record-data new file mode 100644 index 000000000..716e143b5 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-data @@ -0,0 +1,11 @@ +[config] +command = record +args = -d kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 + +# sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | +# PERF_SAMPLE_ADDR | PERF_SAMPLE_PERIOD | PERF_SAMPLE_DATA_SRC +sample_type=33039 +mmap_data=1 diff --git a/kernel/tools/perf/tests/attr/test-record-freq b/kernel/tools/perf/tests/attr/test-record-freq new file mode 100644 index 000000000..600d0f8f2 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-freq @@ -0,0 +1,6 @@ +[config] +command = record +args = -F 100 kill >/dev/null 2>&1 + +[event:base-record] +sample_period=100 diff --git a/kernel/tools/perf/tests/attr/test-record-graph-default b/kernel/tools/perf/tests/attr/test-record-graph-default new file mode 100644 index 000000000..853597a9a --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-graph-default @@ -0,0 +1,6 @@ +[config] +command = record +args = -g kill >/dev/null 2>&1 + +[event:base-record] +sample_type=295 diff --git a/kernel/tools/perf/tests/attr/test-record-graph-dwarf b/kernel/tools/perf/tests/attr/test-record-graph-dwarf new file mode 100644 index 000000000..d6f324ea5 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-graph-dwarf @@ -0,0 +1,10 @@ +[config] +command = record +args = --call-graph dwarf -- kill >/dev/null 2>&1 + +[event:base-record] +sample_type=12583 +exclude_callchain_user=1 +sample_stack_user=8192 +# TODO different for each arch, no support for that now +sample_regs_user=* diff --git a/kernel/tools/perf/tests/attr/test-record-graph-fp b/kernel/tools/perf/tests/attr/test-record-graph-fp new file mode 100644 index 000000000..055e3bee7 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-graph-fp @@ -0,0 +1,6 @@ +[config] +command = record +args = --call-graph fp kill >/dev/null 2>&1 + +[event:base-record] +sample_type=295 diff --git a/kernel/tools/perf/tests/attr/test-record-group b/kernel/tools/perf/tests/attr/test-record-group new file mode 100644 index 000000000..57739cacd --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-group @@ -0,0 +1,20 @@ +[config] +command = record +args = --group -e cycles,instructions kill >/dev/null 2>&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 +enable_on_exec=0 +disabled=0 diff --git a/kernel/tools/perf/tests/attr/test-record-group-sampling b/kernel/tools/perf/tests/attr/test-record-group-sampling new file mode 100644 index 000000000..658f5d60c --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-group-sampling @@ -0,0 +1,36 @@ +[config] +command = record +args = -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 + +[event-1:base-record] +fd=1 +group_fd=-1 +sample_type=343 +read_format=12 +inherit=0 + +[event-2:base-record] +fd=2 +group_fd=1 + +# cache-misses +type=0 +config=3 + +# default | PERF_SAMPLE_READ +sample_type=343 + +# PERF_FORMAT_ID | PERF_FORMAT_GROUP +read_format=12 + +mmap=0 +comm=0 +enable_on_exec=0 +disabled=0 + +# inherit is disabled for group sampling +inherit=0 + +# sampling disabled +sample_freq=0 +sample_period=0 diff --git a/kernel/tools/perf/tests/attr/test-record-group1 b/kernel/tools/perf/tests/attr/test-record-group1 new file mode 100644 index 000000000..c5548d054 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-group1 @@ -0,0 +1,21 @@ +[config] +command = record +args = -e '{cycles,instructions}' kill >/dev/null 2>&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 +type=0 +config=1 +sample_type=327 +read_format=4 +mmap=0 +comm=0 +enable_on_exec=0 +disabled=0 diff --git a/kernel/tools/perf/tests/attr/test-record-no-delay b/kernel/tools/perf/tests/attr/test-record-no-delay new file mode 100644 index 000000000..f253b78cd --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-no-delay @@ -0,0 +1,9 @@ +[config] +command = record +args = -D kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=263 +watermark=0 +wakeup_events=1 diff --git a/kernel/tools/perf/tests/attr/test-record-no-inherit b/kernel/tools/perf/tests/attr/test-record-no-inherit new file mode 100644 index 000000000..44edcb2ed --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-no-inherit @@ -0,0 +1,7 @@ +[config] +command = record +args = -i kill >/dev/null 2>&1 + +[event:base-record] +sample_type=263 +inherit=0 diff --git a/kernel/tools/perf/tests/attr/test-record-no-samples b/kernel/tools/perf/tests/attr/test-record-no-samples new file mode 100644 index 000000000..d0141b241 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-no-samples @@ -0,0 +1,6 @@ +[config] +command = record +args = -n kill >/dev/null 2>&1 + +[event:base-record] +sample_period=0 diff --git a/kernel/tools/perf/tests/attr/test-record-period b/kernel/tools/perf/tests/attr/test-record-period new file mode 100644 index 000000000..8abc5314f --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-period @@ -0,0 +1,7 @@ +[config] +command = record +args = -c 100 -P kill >/dev/null 2>&1 + +[event:base-record] +sample_period=100 +freq=0 diff --git a/kernel/tools/perf/tests/attr/test-record-raw b/kernel/tools/perf/tests/attr/test-record-raw new file mode 100644 index 000000000..4a8ef25b5 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-record-raw @@ -0,0 +1,7 @@ +[config] +command = record +args = -R kill >/dev/null 2>&1 + +[event:base-record] +sample_period=4000 +sample_type=1415 diff --git a/kernel/tools/perf/tests/attr/test-stat-C0 b/kernel/tools/perf/tests/attr/test-stat-C0 new file mode 100644 index 000000000..aa8359507 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-C0 @@ -0,0 +1,9 @@ +[config] +command = stat +args = -e cycles -C 0 kill >/dev/null 2>&1 +ret = 1 + +[event:base-stat] +# events are enabled by default when attached to cpu +disabled=0 +enable_on_exec=0 diff --git a/kernel/tools/perf/tests/attr/test-stat-basic b/kernel/tools/perf/tests/attr/test-stat-basic new file mode 100644 index 000000000..74e17881f --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-basic @@ -0,0 +1,6 @@ +[config] +command = stat +args = -e cycles kill >/dev/null 2>&1 +ret = 1 + +[event:base-stat] diff --git a/kernel/tools/perf/tests/attr/test-stat-default b/kernel/tools/perf/tests/attr/test-stat-default new file mode 100644 index 000000000..19270f54c --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-default @@ -0,0 +1,64 @@ +[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 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND +[event6:base-stat] +fd=6 +type=0 +config=7 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND +[event7:base-stat] +fd=7 +type=0 +config=8 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS +[event8:base-stat] +fd=8 +type=0 +config=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS +[event9:base-stat] +fd=9 +type=0 +config=4 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES +[event10:base-stat] +fd=10 +type=0 +config=5 diff --git a/kernel/tools/perf/tests/attr/test-stat-detailed-1 b/kernel/tools/perf/tests/attr/test-stat-detailed-1 new file mode 100644 index 000000000..51426b871 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-detailed-1 @@ -0,0 +1,101 @@ +[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 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND +[event6:base-stat] +fd=6 +type=0 +config=7 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND +[event7:base-stat] +fd=7 +type=0 +config=8 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS +[event8:base-stat] +fd=8 +type=0 +config=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS +[event9:base-stat] +fd=9 +type=0 +config=4 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES +[event10:base-stat] +fd=10 +type=0 +config=5 + +# 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 + +# 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 + +# 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 + +# 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 diff --git a/kernel/tools/perf/tests/attr/test-stat-detailed-2 b/kernel/tools/perf/tests/attr/test-stat-detailed-2 new file mode 100644 index 000000000..8de5acc31 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-detailed-2 @@ -0,0 +1,155 @@ +[config] +command = stat +args = -dd 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 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND +[event6:base-stat] +fd=6 +type=0 +config=7 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND +[event7:base-stat] +fd=7 +type=0 +config=8 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS +[event8:base-stat] +fd=8 +type=0 +config=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS +[event9:base-stat] +fd=9 +type=0 +config=4 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES +[event10:base-stat] +fd=10 +type=0 +config=5 + +# 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 + +# 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 + +# 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 + +# 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 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=1 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=65537 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=3 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=65539 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=4 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=65540 diff --git a/kernel/tools/perf/tests/attr/test-stat-detailed-3 b/kernel/tools/perf/tests/attr/test-stat-detailed-3 new file mode 100644 index 000000000..0a1f45bf7 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-detailed-3 @@ -0,0 +1,173 @@ +[config] +command = stat +args = -ddd 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 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND +[event6:base-stat] +fd=6 +type=0 +config=7 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND +[event7:base-stat] +fd=7 +type=0 +config=8 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS +[event8:base-stat] +fd=8 +type=0 +config=1 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS +[event9:base-stat] +fd=9 +type=0 +config=4 + +# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES +[event10:base-stat] +fd=10 +type=0 +config=5 + +# 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 + +# 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 + +# 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 + +# 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 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=1 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=65537 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=3 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=65539 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=4 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=65540 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=512 + +# PERF_TYPE_HW_CACHE, +# 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 +type=3 +config=66048 diff --git a/kernel/tools/perf/tests/attr/test-stat-group b/kernel/tools/perf/tests/attr/test-stat-group new file mode 100644 index 000000000..fdc1596a8 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-group @@ -0,0 +1,15 @@ +[config] +command = stat +args = --group -e cycles,instructions kill >/dev/null 2>&1 +ret = 1 + +[event-1:base-stat] +fd=1 +group_fd=-1 + +[event-2:base-stat] +fd=2 +group_fd=1 +config=1 +disabled=0 +enable_on_exec=0 diff --git a/kernel/tools/perf/tests/attr/test-stat-group1 b/kernel/tools/perf/tests/attr/test-stat-group1 new file mode 100644 index 000000000..2a1f86e4a --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-group1 @@ -0,0 +1,15 @@ +[config] +command = stat +args = -e '{cycles,instructions}' kill >/dev/null 2>&1 +ret = 1 + +[event-1:base-stat] +fd=1 +group_fd=-1 + +[event-2:base-stat] +fd=2 +group_fd=1 +config=1 +disabled=0 +enable_on_exec=0 diff --git a/kernel/tools/perf/tests/attr/test-stat-no-inherit b/kernel/tools/perf/tests/attr/test-stat-no-inherit new file mode 100644 index 000000000..d54b2a1e3 --- /dev/null +++ b/kernel/tools/perf/tests/attr/test-stat-no-inherit @@ -0,0 +1,7 @@ +[config] +command = stat +args = -i -e cycles kill >/dev/null 2>&1 +ret = 1 + +[event:base-stat] +inherit=0 diff --git a/kernel/tools/perf/tests/bp_signal.c b/kernel/tools/perf/tests/bp_signal.c new file mode 100644 index 000000000..a02b035fd --- /dev/null +++ b/kernel/tools/perf/tests/bp_signal.c @@ -0,0 +1,194 @@ +/* + * Inspired by breakpoint overflow test done by + * Vince Weaver for perf_event_tests + * (git://github.com/deater/perf_event_tests) + */ + +/* + * Powerpc needs __SANE_USERSPACE_TYPES__ before to select + * 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu. + */ +#define __SANE_USERSPACE_TYPES__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tests.h" +#include "debug.h" +#include "perf.h" +#include "cloexec.h" + +static int fd1; +static int fd2; +static int overflows; + +__attribute__ ((noinline)) +static int test_function(void) +{ + return time(NULL); +} + +static void sig_handler(int signum __maybe_unused, + siginfo_t *oh __maybe_unused, + void *uc __maybe_unused) +{ + overflows++; + + if (overflows > 10) { + /* + * This should be executed only once during + * this test, if we are here for the 10th + * time, consider this the recursive issue. + * + * We can get out of here by disable events, + * so no new SIGIO is delivered. + */ + ioctl(fd1, PERF_EVENT_IOC_DISABLE, 0); + ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0); + } +} + +static int bp_event(void *fn, int setup_signal) +{ + struct perf_event_attr pe; + int fd; + + memset(&pe, 0, sizeof(struct perf_event_attr)); + pe.type = PERF_TYPE_BREAKPOINT; + pe.size = sizeof(struct perf_event_attr); + + pe.config = 0; + pe.bp_type = HW_BREAKPOINT_X; + pe.bp_addr = (unsigned long) fn; + pe.bp_len = sizeof(long); + + pe.sample_period = 1; + pe.sample_type = PERF_SAMPLE_IP; + pe.wakeup_events = 1; + + pe.disabled = 1; + pe.exclude_kernel = 1; + pe.exclude_hv = 1; + + fd = sys_perf_event_open(&pe, 0, -1, -1, + perf_event_open_cloexec_flag()); + if (fd < 0) { + pr_debug("failed opening event %llx\n", pe.config); + return TEST_FAIL; + } + + if (setup_signal) { + fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC); + fcntl(fd, F_SETSIG, SIGIO); + fcntl(fd, F_SETOWN, getpid()); + } + + ioctl(fd, PERF_EVENT_IOC_RESET, 0); + + return fd; +} + +static long long bp_count(int fd) +{ + long long count; + int ret; + + ret = read(fd, &count, sizeof(long long)); + if (ret != sizeof(long long)) { + pr_debug("failed to read: %d\n", ret); + return TEST_FAIL; + } + + return count; +} + +int test__bp_signal(void) +{ + struct sigaction sa; + long long count1, count2; + + /* setup SIGIO signal handler */ + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_sigaction = (void *) sig_handler; + sa.sa_flags = SA_SIGINFO; + + if (sigaction(SIGIO, &sa, NULL) < 0) { + pr_debug("failed setting up signal handler\n"); + return TEST_FAIL; + } + + /* + * We create following events: + * + * fd1 - breakpoint event on test_function with SIGIO + * signal configured. We should get signal + * notification each time the breakpoint is hit + * + * fd2 - breakpoint event on sig_handler without SIGIO + * configured. + * + * Following processing should happen: + * - execute test_function + * - fd1 event breakpoint hit -> count1 == 1 + * - SIGIO is delivered -> overflows == 1 + * - fd2 event breakpoint hit -> count2 == 1 + * + * The test case check following error conditions: + * - we get stuck in signal handler because of debug + * exception being triggered receursively due to + * the wrong RF EFLAG management + * + * - we never trigger the sig_handler breakpoint due + * to the rong RF EFLAG management + * + */ + + fd1 = bp_event(test_function, 1); + fd2 = bp_event(sig_handler, 0); + + ioctl(fd1, PERF_EVENT_IOC_ENABLE, 0); + ioctl(fd2, PERF_EVENT_IOC_ENABLE, 0); + + /* + * Kick off the test by trigering 'fd1' + * breakpoint. + */ + test_function(); + + ioctl(fd1, PERF_EVENT_IOC_DISABLE, 0); + ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0); + + count1 = bp_count(fd1); + count2 = bp_count(fd2); + + close(fd1); + close(fd2); + + pr_debug("count1 %lld, count2 %lld, overflow %d\n", + count1, count2, overflows); + + if (count1 != 1) { + if (count1 == 11) + pr_debug("failed: RF EFLAG recursion issue detected\n"); + else + pr_debug("failed: wrong count for bp1%lld\n", count1); + } + + if (overflows != 1) + pr_debug("failed: wrong overflow hit\n"); + + if (count2 != 1) + pr_debug("failed: wrong count for bp2\n"); + + return count1 == 1 && overflows == 1 && count2 == 1 ? + TEST_OK : TEST_FAIL; +} diff --git a/kernel/tools/perf/tests/bp_signal_overflow.c b/kernel/tools/perf/tests/bp_signal_overflow.c new file mode 100644 index 000000000..e76537724 --- /dev/null +++ b/kernel/tools/perf/tests/bp_signal_overflow.c @@ -0,0 +1,134 @@ +/* + * Originally done by Vince Weaver for + * perf_event_tests (git://github.com/deater/perf_event_tests) + */ + +/* + * Powerpc needs __SANE_USERSPACE_TYPES__ before to select + * 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu. + */ +#define __SANE_USERSPACE_TYPES__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tests.h" +#include "debug.h" +#include "perf.h" +#include "cloexec.h" + +static int overflows; + +__attribute__ ((noinline)) +static int test_function(void) +{ + return time(NULL); +} + +static void sig_handler(int signum __maybe_unused, + siginfo_t *oh __maybe_unused, + void *uc __maybe_unused) +{ + overflows++; +} + +static long long bp_count(int fd) +{ + long long count; + int ret; + + ret = read(fd, &count, sizeof(long long)); + if (ret != sizeof(long long)) { + pr_debug("failed to read: %d\n", ret); + return TEST_FAIL; + } + + return count; +} + +#define EXECUTIONS 10000 +#define THRESHOLD 100 + +int test__bp_signal_overflow(void) +{ + struct perf_event_attr pe; + struct sigaction sa; + long long count; + int fd, i, fails = 0; + + /* setup SIGIO signal handler */ + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_sigaction = (void *) sig_handler; + sa.sa_flags = SA_SIGINFO; + + if (sigaction(SIGIO, &sa, NULL) < 0) { + pr_debug("failed setting up signal handler\n"); + return TEST_FAIL; + } + + memset(&pe, 0, sizeof(struct perf_event_attr)); + pe.type = PERF_TYPE_BREAKPOINT; + pe.size = sizeof(struct perf_event_attr); + + pe.config = 0; + pe.bp_type = HW_BREAKPOINT_X; + pe.bp_addr = (unsigned long) test_function; + pe.bp_len = sizeof(long); + + pe.sample_period = THRESHOLD; + pe.sample_type = PERF_SAMPLE_IP; + pe.wakeup_events = 1; + + pe.disabled = 1; + pe.exclude_kernel = 1; + pe.exclude_hv = 1; + + fd = sys_perf_event_open(&pe, 0, -1, -1, + perf_event_open_cloexec_flag()); + if (fd < 0) { + pr_debug("failed opening event %llx\n", pe.config); + return TEST_FAIL; + } + + fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC); + fcntl(fd, F_SETSIG, SIGIO); + fcntl(fd, F_SETOWN, getpid()); + + ioctl(fd, PERF_EVENT_IOC_RESET, 0); + ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + + for (i = 0; i < EXECUTIONS; i++) + test_function(); + + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + count = bp_count(fd); + + close(fd); + + pr_debug("count %lld, overflow %d\n", + count, overflows); + + if (count != EXECUTIONS) { + pr_debug("\tWrong number of executions %lld != %d\n", + count, EXECUTIONS); + fails++; + } + + if (overflows != EXECUTIONS / THRESHOLD) { + pr_debug("\tWrong number of overflows %d != %d\n", + overflows, EXECUTIONS / THRESHOLD); + fails++; + } + + return fails ? TEST_FAIL : TEST_OK; +} diff --git a/kernel/tools/perf/tests/builtin-test.c b/kernel/tools/perf/tests/builtin-test.c new file mode 100644 index 000000000..4f4098167 --- /dev/null +++ b/kernel/tools/perf/tests/builtin-test.c @@ -0,0 +1,331 @@ +/* + * builtin-test.c + * + * Builtin regression testing command: ever growing number of sanity tests + */ +#include +#include +#include "builtin.h" +#include "hist.h" +#include "intlist.h" +#include "tests.h" +#include "debug.h" +#include "color.h" +#include "parse-options.h" +#include "symbol.h" + +static struct test { + const char *desc; + int (*func)(void); +} tests[] = { + { + .desc = "vmlinux symtab matches kallsyms", + .func = test__vmlinux_matches_kallsyms, + }, + { + .desc = "detect open syscall event", + .func = test__open_syscall_event, + }, + { + .desc = "detect open syscall event on all cpus", + .func = test__open_syscall_event_on_all_cpus, + }, + { + .desc = "read samples using the mmap interface", + .func = test__basic_mmap, + }, + { + .desc = "parse events tests", + .func = test__parse_events, + }, +#if defined(__x86_64__) || defined(__i386__) + { + .desc = "x86 rdpmc test", + .func = test__rdpmc, + }, +#endif + { + .desc = "Validate PERF_RECORD_* events & perf_sample fields", + .func = test__PERF_RECORD, + }, + { + .desc = "Test perf pmu format parsing", + .func = test__pmu, + }, + { + .desc = "Test dso data read", + .func = test__dso_data, + }, + { + .desc = "Test dso data cache", + .func = test__dso_data_cache, + }, + { + .desc = "Test dso data reopen", + .func = test__dso_data_reopen, + }, + { + .desc = "roundtrip evsel->name check", + .func = test__perf_evsel__roundtrip_name_test, + }, + { + .desc = "Check parsing of sched tracepoints fields", + .func = test__perf_evsel__tp_sched_test, + }, + { + .desc = "Generate and check syscalls:sys_enter_open event fields", + .func = test__syscall_open_tp_fields, + }, + { + .desc = "struct perf_event_attr setup", + .func = test__attr, + }, + { + .desc = "Test matching and linking multiple hists", + .func = test__hists_link, + }, + { + .desc = "Try 'import perf' in python, checking link problems", + .func = test__python_use, + }, + { + .desc = "Test breakpoint overflow signal handler", + .func = test__bp_signal, + }, + { + .desc = "Test breakpoint overflow sampling", + .func = test__bp_signal_overflow, + }, + { + .desc = "Test number of exit event of a simple workload", + .func = test__task_exit, + }, + { + .desc = "Test software clock events have valid period values", + .func = test__sw_clock_freq, + }, +#if defined(__x86_64__) || defined(__i386__) + { + .desc = "Test converting perf time to TSC", + .func = test__perf_time_to_tsc, + }, +#endif + { + .desc = "Test object code reading", + .func = test__code_reading, + }, + { + .desc = "Test sample parsing", + .func = test__sample_parsing, + }, + { + .desc = "Test using a dummy software event to keep tracking", + .func = test__keep_tracking, + }, + { + .desc = "Test parsing with no sample_id_all bit set", + .func = test__parse_no_sample_id_all, + }, +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) +#ifdef HAVE_DWARF_UNWIND_SUPPORT + { + .desc = "Test dwarf unwind", + .func = test__dwarf_unwind, + }, +#endif +#endif + { + .desc = "Test filtering hist entries", + .func = test__hists_filter, + }, + { + .desc = "Test mmap thread lookup", + .func = test__mmap_thread_lookup, + }, + { + .desc = "Test thread mg sharing", + .func = test__thread_mg_share, + }, + { + .desc = "Test output sorting of hist entries", + .func = test__hists_output, + }, + { + .desc = "Test cumulation of child hist entries", + .func = test__hists_cumulate, + }, + { + .desc = "Test tracking 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 = "Test kmod_path__parse function", + .func = test__kmod_path__parse, + }, + { + .func = NULL, + }, +}; + +static bool perf_test__matches(int curr, int argc, const char *argv[]) +{ + int i; + + if (argc == 0) + return true; + + for (i = 0; i < argc; ++i) { + char *end; + long nr = strtoul(argv[i], &end, 10); + + if (*end == '\0') { + if (nr == curr + 1) + return true; + continue; + } + + if (strstr(tests[curr].desc, argv[i])) + return true; + } + + return false; +} + +static int run_test(struct test *test) +{ + int status, err = -1, child = fork(); + char sbuf[STRERR_BUFSIZE]; + + if (child < 0) { + pr_err("failed to fork test: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + return -1; + } + + if (!child) { + pr_debug("test child forked, pid %d\n", getpid()); + err = test->func(); + exit(err); + } + + wait(&status); + + if (WIFEXITED(status)) { + err = WEXITSTATUS(status); + pr_debug("test child finished with %d\n", err); + } else if (WIFSIGNALED(status)) { + err = -1; + pr_debug("test child interrupted\n"); + } + + return err; +} + +static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) +{ + int i = 0; + int width = 0; + + while (tests[i].func) { + int len = strlen(tests[i].desc); + + if (width < len) + width = len; + ++i; + } + + i = 0; + while (tests[i].func) { + int curr = i++, err; + + if (!perf_test__matches(curr, argc, argv)) + continue; + + pr_info("%2d: %-*s:", i, width, tests[curr].desc); + + if (intlist__find(skiplist, i)) { + color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip (user override)\n"); + continue; + } + + pr_debug("\n--- start ---\n"); + err = run_test(&tests[curr]); + pr_debug("---- end ----\n%s:", tests[curr].desc); + + switch (err) { + case TEST_OK: + pr_info(" Ok\n"); + break; + case TEST_SKIP: + color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip\n"); + break; + case TEST_FAIL: + default: + color_fprintf(stderr, PERF_COLOR_RED, " FAILED!\n"); + break; + } + } + + return 0; +} + +static int perf_test__list(int argc, const char **argv) +{ + int i = 0; + + while (tests[i].func) { + int curr = i++; + + if (argc > 1 && !strstr(tests[curr].desc, argv[1])) + continue; + + pr_info("%2d: %s\n", i, tests[curr].desc); + } + + return 0; +} + +int cmd_test(int argc, const char **argv, const char *prefix __maybe_unused) +{ + const char *test_usage[] = { + "perf test [] [{list |[|]}]", + NULL, + }; + const char *skip = NULL; + 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_END() + }; + const char * const test_subcommands[] = { "list", NULL }; + struct intlist *skiplist = NULL; + int ret = hists__init(); + + if (ret < 0) + return ret; + + 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, argv); + + 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; + + if (skip != NULL) + skiplist = intlist__new(skip); + + return __cmd_test(argc, argv, skiplist); +} diff --git a/kernel/tools/perf/tests/code-reading.c b/kernel/tools/perf/tests/code-reading.c new file mode 100644 index 000000000..f671ec37a --- /dev/null +++ b/kernel/tools/perf/tests/code-reading.c @@ -0,0 +1,578 @@ +#include +#include +#include +#include +#include +#include + +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "machine.h" +#include "event.h" +#include "thread.h" + +#include "tests.h" + +#define BUFSZ 1024 +#define READLEN 128 + +struct state { + u64 done[1024]; + 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 void read_objdump_line(const char *line, size_t line_len, void **buf, + size_t *len) +{ + const char *p; + size_t i; + + /* Skip to a colon */ + p = strchr(line, ':'); + if (!p) + return; + i = p + 1 - line; + + /* Read bytes */ + while (*len) { + char c1, c2; + + /* Skip spaces */ + for (; i < line_len; i++) { + if (!isspace(line[i])) + break; + } + /* Get 2 hex digits */ + if (i >= line_len || !isxdigit(line[i])) + break; + c1 = line[i++]; + if (i >= line_len || !isxdigit(line[i])) + break; + c2 = line[i++]; + /* Followed by a space */ + if (i < line_len && line[i] && !isspace(line[i])) + break; + /* Store byte */ + *(unsigned char *)*buf = (hex(c1) << 4) | hex(c2); + *buf += 1; + *len -= 1; + } +} + +static int read_objdump_output(FILE *f, void **buf, size_t *len) +{ + char *line = NULL; + size_t line_len; + ssize_t ret; + int err = 0; + + while (1) { + ret = getline(&line, &line_len, f); + if (feof(f)) + break; + if (ret < 0) { + pr_debug("getline failed\n"); + err = -1; + break; + } + read_objdump_line(line, ret, buf, len); + } + + free(line); + + return err; +} + +static int read_via_objdump(const char *filename, u64 addr, void *buf, + size_t len) +{ + char cmd[PATH_MAX * 2]; + const char *fmt; + FILE *f; + int ret; + + fmt = "%s -d --start-address=0x%"PRIx64" --stop-address=0x%"PRIx64" %s"; + ret = snprintf(cmd, sizeof(cmd), fmt, "objdump", addr, addr + len, + filename); + if (ret <= 0 || (size_t)ret >= sizeof(cmd)) + return -1; + + pr_debug("Objdump command is: %s\n", cmd); + + /* Ignore objdump errors */ + strcat(cmd, " 2>/dev/null"); + + f = popen(cmd, "r"); + if (!f) { + pr_debug("popen failed\n"); + return -1; + } + + ret = read_objdump_output(f, &buf, &len); + if (len) { + pr_debug("objdump read too few bytes\n"); + if (!ret) + ret = len; + } + + pclose(f); + + return ret; +} + +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]; + size_t ret_len; + u64 objdump_addr; + int ret; + + pr_debug("Reading object code for memory address: %#"PRIx64"\n", addr); + + thread__find_addr_map(thread, cpumode, MAP__FUNCTION, addr, &al); + if (!al.map || !al.map->dso) { + pr_debug("thread__find_addr_map failed\n"); + return -1; + } + + 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)) { + pr_debug("Unexpected kernel address - skipping\n"); + return 0; + } + + pr_debug("On file address is: %#"PRIx64"\n", al.addr); + + if (len > BUFSZ) + len = BUFSZ; + + /* Do not go off the map */ + if (addr + len > al.map->end) + len = al.map->end - addr; + + /* Read the object code using perf */ + ret_len = dso__data_read_offset(al.map->dso, thread->mg->machine, + al.addr, buf1, len); + if (ret_len != len) { + pr_debug("dso__data_read_offset failed\n"); + return -1; + } + + /* + * Converting addresses for use by objdump requires more information. + * map__load() does that. See map__rip_2objdump() for details. + */ + if (map__load(al.map, NULL)) + return -1; + + /* objdump struggles with kcore - try each map only once */ + if (dso__is_kcore(al.map->dso)) { + size_t d; + + for (d = 0; d < state->done_cnt; d++) { + if (state->done[d] == al.map->start) { + pr_debug("kcore map tested already"); + pr_debug(" - skipping\n"); + return 0; + } + } + if (state->done_cnt >= ARRAY_SIZE(state->done)) { + pr_debug("Too many kcore maps - skipping\n"); + return 0; + } + state->done[state->done_cnt++] = al.map->start; + } + + /* Read the object code using objdump */ + objdump_addr = map__rip_2objdump(al.map, al.addr); + ret = read_via_objdump(al.map->dso->long_name, objdump_addr, buf2, len); + if (ret > 0) { + /* + * The kernel maps are inaccurate - assume objdump is right in + * that case. + */ + if (cpumode == PERF_RECORD_MISC_KERNEL || + cpumode == PERF_RECORD_MISC_GUEST_KERNEL) { + len -= ret; + if (len) { + pr_debug("Reducing len to %zu\n", len); + } else if (dso__is_kcore(al.map->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; + } + } + } + if (ret < 0) { + pr_debug("read_via_objdump failed\n"); + return -1; + } + + /* The results should be identical */ + if (memcmp(buf1, buf2, len)) { + pr_debug("Bytes read differ from those read by objdump\n"); + return -1; + } + pr_debug("Bytes read match those read by objdump\n"); + + return 0; +} + +static int process_sample_event(struct machine *machine, + struct perf_evlist *evlist, + union perf_event *event, struct state *state) +{ + struct perf_sample sample; + struct thread *thread; + u8 cpumode; + + if (perf_evlist__parse_sample(evlist, event, &sample)) { + pr_debug("perf_evlist__parse_sample failed\n"); + return -1; + } + + thread = machine__findnew_thread(machine, sample.pid, sample.tid); + if (!thread) { + pr_debug("machine__findnew_thread failed\n"); + return -1; + } + + cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + + return read_object_code(sample.ip, READLEN, cpumode, thread, state); +} + +static int process_event(struct machine *machine, struct perf_evlist *evlist, + union perf_event *event, struct state *state) +{ + if (event->header.type == PERF_RECORD_SAMPLE) + return process_sample_event(machine, evlist, event, state); + + if (event->header.type == PERF_RECORD_THROTTLE || + event->header.type == PERF_RECORD_UNTHROTTLE) + return 0; + + if (event->header.type < PERF_RECORD_MAX) { + int ret; + + ret = machine__process_event(machine, event, NULL); + if (ret < 0) + pr_debug("machine__process_event failed, event type %u\n", + event->header.type); + return ret; + } + + return 0; +} + +static int process_events(struct machine *machine, struct perf_evlist *evlist, + struct state *state) +{ + union perf_event *event; + int i, ret; + + for (i = 0; i < evlist->nr_mmaps; i++) { + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + ret = process_event(machine, evlist, event, state); + perf_evlist__mmap_consume(evlist, i); + if (ret < 0) + return ret; + } + } + return 0; +} + +static int comp(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +static void do_sort_something(void) +{ + int buf[40960], i; + + for (i = 0; i < (int)ARRAY_SIZE(buf); i++) + buf[i] = ARRAY_SIZE(buf) - i - 1; + + qsort(buf, ARRAY_SIZE(buf), sizeof(int), comp); + + for (i = 0; i < (int)ARRAY_SIZE(buf); i++) { + if (buf[i] != i) { + pr_debug("qsort failed\n"); + break; + } + } +} + +static void sort_something(void) +{ + int i; + + for (i = 0; i < 10; i++) + do_sort_something(); +} + +static void syscall_something(void) +{ + int pipefd[2]; + int i; + + for (i = 0; i < 1000; i++) { + if (pipe(pipefd) < 0) { + pr_debug("pipe failed\n"); + break; + } + close(pipefd[1]); + close(pipefd[0]); + } +} + +static void fs_something(void) +{ + const char *test_file_name = "temp-perf-code-reading-test-file--"; + FILE *f; + int i; + + for (i = 0; i < 1000; i++) { + f = fopen(test_file_name, "w+"); + if (f) { + fclose(f); + unlink(test_file_name); + } + } +} + +static void do_something(void) +{ + fs_something(); + + sort_something(); + + syscall_something(); +} + +enum { + TEST_CODE_READING_OK, + TEST_CODE_READING_NO_VMLINUX, + TEST_CODE_READING_NO_KCORE, + TEST_CODE_READING_NO_ACCESS, + TEST_CODE_READING_NO_KERNEL_OBJ, +}; + +static int do_test_code_reading(bool try_kcore) +{ + struct machines machines; + struct machine *machine; + struct thread *thread; + struct record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .freq = 4000, + .target = { + .uses_mmap = true, + }, + }; + struct state state = { + .done_cnt = 0, + }; + struct thread_map *threads = NULL; + struct cpu_map *cpus = NULL; + struct perf_evlist *evlist = NULL; + struct perf_evsel *evsel = NULL; + int err = -1, ret; + pid_t pid; + struct map *map; + bool have_vmlinux, have_kcore, excl_kernel = false; + + pid = getpid(); + + machines__init(&machines); + machine = &machines.host; + + ret = machine__create_kernel_maps(machine); + if (ret < 0) { + pr_debug("machine__create_kernel_maps failed\n"); + goto out_err; + } + + /* Force the use of kallsyms instead of vmlinux to try kcore */ + if (try_kcore) + symbol_conf.kallsyms_name = "/proc/kallsyms"; + + /* Load kernel map */ + map = machine->vmlinux_maps[MAP__FUNCTION]; + ret = map__load(map, NULL); + if (ret < 0) { + pr_debug("map__load failed\n"); + goto out_err; + } + have_vmlinux = dso__is_vmlinux(map->dso); + have_kcore = dso__is_kcore(map->dso); + + /* 2nd time through we just try kcore */ + if (try_kcore && !have_kcore) + return TEST_CODE_READING_NO_KCORE; + + /* No point getting kernel events if there is no kernel object */ + if (!have_vmlinux && !have_kcore) + excl_kernel = true; + + threads = thread_map__new_by_tid(pid); + if (!threads) { + pr_debug("thread_map__new_by_tid failed\n"); + goto out_err; + } + + ret = perf_event__synthesize_thread_map(NULL, threads, + perf_event__process, machine, false); + if (ret < 0) { + pr_debug("perf_event__synthesize_thread_map failed\n"); + goto out_err; + } + + thread = machine__findnew_thread(machine, pid, pid); + if (!thread) { + pr_debug("machine__findnew_thread failed\n"); + goto out_err; + } + + cpus = cpu_map__new(NULL); + if (!cpus) { + pr_debug("cpu_map__new failed\n"); + goto out_err; + } + + while (1) { + const char *str; + + evlist = perf_evlist__new(); + if (!evlist) { + pr_debug("perf_evlist__new failed\n"); + goto out_err; + } + + perf_evlist__set_maps(evlist, cpus, threads); + + if (excl_kernel) + str = "cycles:u"; + else + str = "cycles"; + pr_debug("Parsing event '%s'\n", str); + ret = parse_events(evlist, str); + if (ret < 0) { + pr_debug("parse_events failed\n"); + goto out_err; + } + + perf_evlist__config(evlist, &opts); + + evsel = perf_evlist__first(evlist); + + evsel->attr.comm = 1; + evsel->attr.disabled = 1; + evsel->attr.enable_on_exec = 0; + + ret = perf_evlist__open(evlist); + if (ret < 0) { + if (!excl_kernel) { + excl_kernel = true; + perf_evlist__set_maps(evlist, NULL, NULL); + perf_evlist__delete(evlist); + evlist = NULL; + continue; + } + pr_debug("perf_evlist__open failed\n"); + goto out_err; + } + break; + } + + ret = perf_evlist__mmap(evlist, UINT_MAX, false); + if (ret < 0) { + pr_debug("perf_evlist__mmap failed\n"); + goto out_err; + } + + perf_evlist__enable(evlist); + + do_something(); + + perf_evlist__disable(evlist); + + ret = process_events(machine, evlist, &state); + if (ret < 0) + goto out_err; + + if (!have_vmlinux && !have_kcore && !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) + err = TEST_CODE_READING_NO_ACCESS; + else + err = TEST_CODE_READING_OK; +out_err: + if (evlist) { + perf_evlist__delete(evlist); + } else { + cpu_map__delete(cpus); + thread_map__delete(threads); + } + machines__destroy_kernel_maps(&machines); + machine__delete_threads(machine); + machines__exit(&machines); + + return err; +} + +int test__code_reading(void) +{ + int ret; + + ret = do_test_code_reading(false); + if (!ret) + ret = do_test_code_reading(true); + + switch (ret) { + case TEST_CODE_READING_OK: + return 0; + case TEST_CODE_READING_NO_VMLINUX: + fprintf(stderr, " (no vmlinux)"); + return 0; + case TEST_CODE_READING_NO_KCORE: + fprintf(stderr, " (no kcore)"); + return 0; + case TEST_CODE_READING_NO_ACCESS: + fprintf(stderr, " (no access)"); + return 0; + case TEST_CODE_READING_NO_KERNEL_OBJ: + fprintf(stderr, " (no kernel obj)"); + return 0; + default: + return -1; + }; +} diff --git a/kernel/tools/perf/tests/dso-data.c b/kernel/tools/perf/tests/dso-data.c new file mode 100644 index 000000000..513e5febb --- /dev/null +++ b/kernel/tools/perf/tests/dso-data.c @@ -0,0 +1,365 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "util.h" +#include "machine.h" +#include "symbol.h" +#include "tests.h" +#include "debug.h" + +static char *test_file(int size) +{ +#define TEMPL "/tmp/perf-test-XXXXXX" + static char buf_templ[sizeof(TEMPL)]; + char *templ = buf_templ; + int fd, i; + unsigned char *buf; + + strcpy(buf_templ, TEMPL); +#undef TEMPL + + fd = mkstemp(templ); + if (fd < 0) { + perror("mkstemp failed"); + return NULL; + } + + buf = malloc(size); + if (!buf) { + close(fd); + return NULL; + } + + for (i = 0; i < size; i++) + buf[i] = (unsigned char) ((int) i % 10); + + if (size != write(fd, buf, size)) + templ = NULL; + + free(buf); + close(fd); + return templ; +} + +#define TEST_FILE_SIZE (DSO__DATA_CACHE_SIZE * 20) + +struct test_data_offset { + off_t offset; + u8 data[10]; + int size; +}; + +struct test_data_offset offsets[] = { + /* Fill first cache page. */ + { + .offset = 10, + .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + .size = 10, + }, + /* Read first cache page. */ + { + .offset = 10, + .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + .size = 10, + }, + /* Fill cache boundary pages. */ + { + .offset = DSO__DATA_CACHE_SIZE - DSO__DATA_CACHE_SIZE % 10, + .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + .size = 10, + }, + /* Read cache boundary pages. */ + { + .offset = DSO__DATA_CACHE_SIZE - DSO__DATA_CACHE_SIZE % 10, + .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + .size = 10, + }, + /* Fill final cache page. */ + { + .offset = TEST_FILE_SIZE - 10, + .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + .size = 10, + }, + /* Read final cache page. */ + { + .offset = TEST_FILE_SIZE - 10, + .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, + .size = 10, + }, + /* Read final cache page. */ + { + .offset = TEST_FILE_SIZE - 3, + .data = { 7, 8, 9, 0, 0, 0, 0, 0, 0, 0 }, + .size = 3, + }, +}; + +int test__dso_data(void) +{ + struct machine machine; + struct dso *dso; + char *file = test_file(TEST_FILE_SIZE); + size_t i; + + TEST_ASSERT_VAL("No test file", file); + + memset(&machine, 0, sizeof(machine)); + + dso = dso__new((const char *)file); + + TEST_ASSERT_VAL("Failed to access to dso", + dso__data_fd(dso, &machine) >= 0); + + /* Basic 10 bytes tests. */ + for (i = 0; i < ARRAY_SIZE(offsets); i++) { + struct test_data_offset *data = &offsets[i]; + ssize_t size; + u8 buf[10]; + + memset(buf, 0, 10); + size = dso__data_read_offset(dso, &machine, data->offset, + buf, 10); + + TEST_ASSERT_VAL("Wrong size", size == data->size); + TEST_ASSERT_VAL("Wrong data", !memcmp(buf, data->data, 10)); + } + + /* Read cross multiple cache pages. */ + { + ssize_t size; + int c; + u8 *buf; + + buf = malloc(TEST_FILE_SIZE); + TEST_ASSERT_VAL("ENOMEM\n", buf); + + /* First iteration to fill caches, second one to read them. */ + for (c = 0; c < 2; c++) { + memset(buf, 0, TEST_FILE_SIZE); + size = dso__data_read_offset(dso, &machine, 10, + buf, TEST_FILE_SIZE); + + TEST_ASSERT_VAL("Wrong size", + size == (TEST_FILE_SIZE - 10)); + + for (i = 0; i < (size_t)size; i++) + TEST_ASSERT_VAL("Wrong data", + buf[i] == (i % 10)); + } + + free(buf); + } + + dso__delete(dso); + unlink(file); + return 0; +} + +static long open_files_cnt(void) +{ + char path[PATH_MAX]; + struct dirent *dent; + DIR *dir; + long nr = 0; + + scnprintf(path, PATH_MAX, "%s/self/fd", procfs__mountpoint()); + pr_debug("fd path: %s\n", path); + + dir = opendir(path); + TEST_ASSERT_VAL("failed to open fd directory", dir); + + while ((dent = readdir(dir)) != NULL) { + if (!strcmp(dent->d_name, ".") || + !strcmp(dent->d_name, "..")) + continue; + + nr++; + } + + closedir(dir); + return nr - 1; +} + +static struct dso **dsos; + +static int dsos__create(int cnt, int size) +{ + int i; + + dsos = malloc(sizeof(dsos) * cnt); + TEST_ASSERT_VAL("failed to alloc dsos array", dsos); + + for (i = 0; i < cnt; i++) { + char *file; + + 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__delete(dso); + } + + free(dsos); +} + +static int set_fd_limit(int n) +{ + struct rlimit rlim; + + if (getrlimit(RLIMIT_NOFILE, &rlim)) + return -1; + + pr_debug("file limit %ld, new %d\n", (long) rlim.rlim_cur, n); + + rlim.rlim_cur = n; + return setrlimit(RLIMIT_NOFILE, &rlim); +} + +int test__dso_data_cache(void) +{ + struct machine machine; + long nr_end, nr = open_files_cnt(); + int dso_cnt, limit, i, fd; + + memset(&machine, 0, sizeof(machine)); + + /* set as system limit */ + limit = nr * 4; + TEST_ASSERT_VAL("failed to set file limit", !set_fd_limit(limit)); + + /* 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)); + + for (i = 0; i < (dso_cnt - 1); i++) { + struct dso *dso = dsos[i]; + + /* + * Open dsos via dso__data_fd(), it opens the data + * file and keep it open (unless open file limit). + */ + fd = dso__data_fd(dso, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + if (i % 2) { + #define BUFSIZE 10 + u8 buf[BUFSIZE]; + ssize_t n; + + n = dso__data_read_offset(dso, &machine, 0, buf, BUFSIZE); + TEST_ASSERT_VAL("failed to read dso", n == BUFSIZE); + } + } + + /* verify the first one is already open */ + TEST_ASSERT_VAL("dsos[0] is not open", dsos[0]->data.fd != -1); + + /* open +1 dso to reach the allowed limit */ + fd = dso__data_fd(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); + + /* cleanup everything */ + dsos__delete(dso_cnt); + + /* Make sure we did not leak any file descriptor. */ + nr_end = open_files_cnt(); + pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); + TEST_ASSERT_VAL("failed leadking files", nr == nr_end); + return 0; +} + +int test__dso_data_reopen(void) +{ + struct machine machine; + long nr_end, nr = open_files_cnt(); + int fd, fd_extra; + +#define dso_0 (dsos[0]) +#define dso_1 (dsos[1]) +#define dso_2 (dsos[2]) + + memset(&machine, 0, sizeof(machine)); + + /* + * Test scenario: + * - create 3 dso objects + * - set process file descriptor limit to current + * files count + 3 + * - test that the first dso gets closed when we + * reach the files count limit + */ + + /* Make sure we are able to open 3 fds anyway */ + TEST_ASSERT_VAL("failed to set file limit", + !set_fd_limit((nr + 3))); + + TEST_ASSERT_VAL("failed to create dsos\n", !dsos__create(3, TEST_FILE_SIZE)); + + /* open dso_0 */ + fd = dso__data_fd(dso_0, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* open dso_1 */ + fd = dso__data_fd(dso_1, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * open extra file descriptor and we just + * reached the files count limit + */ + fd_extra = open("/dev/null", O_RDONLY); + TEST_ASSERT_VAL("failed to open extra fd", fd_extra > 0); + + /* open dso_2 */ + fd = dso__data_fd(dso_2, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * 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); + + /* open dso_0 */ + fd = dso__data_fd(dso_0, &machine); + TEST_ASSERT_VAL("failed to get fd", fd > 0); + + /* + * 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); + + /* cleanup everything */ + close(fd_extra); + dsos__delete(3); + + /* Make sure we did not leak any file descriptor. */ + nr_end = open_files_cnt(); + pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); + TEST_ASSERT_VAL("failed leadking files", nr == nr_end); + return 0; +} diff --git a/kernel/tools/perf/tests/dwarf-unwind.c b/kernel/tools/perf/tests/dwarf-unwind.c new file mode 100644 index 000000000..0bf06bec6 --- /dev/null +++ b/kernel/tools/perf/tests/dwarf-unwind.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include "tests.h" +#include "debug.h" +#include "machine.h" +#include "event.h" +#include "unwind.h" +#include "perf_regs.h" +#include "map.h" +#include "thread.h" +#include "callchain.h" + +/* For bsearch. We try to unwind functions in shared object. */ +#include + +static int mmap_handler(struct perf_tool *tool __maybe_unused, + union perf_event *event, + struct perf_sample *sample __maybe_unused, + struct machine *machine) +{ + return machine__process_mmap2_event(machine, event, NULL); +} + +static int init_live_machine(struct machine *machine) +{ + union perf_event event; + pid_t pid = getpid(); + + return perf_event__synthesize_mmap_events(NULL, &event, pid, pid, + mmap_handler, machine, true); +} + +#define MAX_STACK 8 + +static int unwind_entry(struct unwind_entry *entry, void *arg) +{ + unsigned long *cnt = (unsigned long *) arg; + char *symbol = entry->sym ? entry->sym->name : NULL; + static const char *funcs[MAX_STACK] = { + "test__arch_unwind_sample", + "unwind_thread", + "compare", + "bsearch", + "krava_3", + "krava_2", + "krava_1", + "test__dwarf_unwind" + }; + + if (*cnt >= MAX_STACK) { + pr_debug("failed: crossed the max stack value %d\n", MAX_STACK); + return -1; + } + + if (!symbol) { + pr_debug("failed: got unresolved address 0x%" PRIx64 "\n", + entry->ip); + return -1; + } + + pr_debug("got: %s 0x%" PRIx64 "\n", symbol, entry->ip); + return strcmp((const char *) symbol, funcs[(*cnt)++]); +} + +__attribute__ ((noinline)) +static int unwind_thread(struct thread *thread) +{ + struct perf_sample sample; + unsigned long cnt = 0; + int err = -1; + + memset(&sample, 0, sizeof(sample)); + + 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); + if (err) + pr_debug("unwind failed\n"); + else if (cnt != MAX_STACK) { + pr_debug("got wrong number of stack entries %lu != %d\n", + cnt, MAX_STACK); + err = -1; + } + + out: + free(sample.user_stack.data); + free(sample.user_regs.regs); + return err; +} + +static int global_unwind_retval = -INT_MAX; + +__attribute__ ((noinline)) +static int compare(void *p1, void *p2) +{ + /* Any possible value should be 'thread' */ + struct thread *thread = *(struct thread **)p1; + + if (global_unwind_retval == -INT_MAX) + global_unwind_retval = unwind_thread(thread); + + return p1 - p2; +} + +__attribute__ ((noinline)) +static int krava_3(struct thread *thread) +{ + struct thread *array[2] = {thread, thread}; + void *fp = &bsearch; + /* + * make _bsearch a volatile function pointer to + * prevent potential optimization, which may expand + * bsearch and call compare directly from this function, + * instead of libc shared object. + */ + void *(*volatile _bsearch)(void *, void *, size_t, + size_t, int (*)(void *, void *)); + + _bsearch = fp; + _bsearch(array, &thread, 2, sizeof(struct thread **), compare); + return global_unwind_retval; +} + +__attribute__ ((noinline)) +static int krava_2(struct thread *thread) +{ + return krava_3(thread); +} + +__attribute__ ((noinline)) +static int krava_1(struct thread *thread) +{ + return krava_2(thread); +} + +int test__dwarf_unwind(void) +{ + struct machines machines; + struct machine *machine; + struct thread *thread; + int err = -1; + + machines__init(&machines); + + machine = machines__find(&machines, HOST_KERNEL_ID); + if (!machine) { + pr_err("Could not get machine\n"); + return -1; + } + + callchain_param.record_mode = CALLCHAIN_DWARF; + + 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()); + if (!thread) { + pr_err("Could not get thread\n"); + goto out; + } + + err = krava_1(thread); + + out: + machine__delete_threads(machine); + machine__exit(machine); + machines__exit(&machines); + return err; +} diff --git a/kernel/tools/perf/tests/evsel-roundtrip-name.c b/kernel/tools/perf/tests/evsel-roundtrip-name.c new file mode 100644 index 000000000..b8d8341b3 --- /dev/null +++ b/kernel/tools/perf/tests/evsel-roundtrip-name.c @@ -0,0 +1,115 @@ +#include "evlist.h" +#include "evsel.h" +#include "parse-events.h" +#include "tests.h" +#include "debug.h" + +static int perf_evsel__roundtrip_cache_name_test(void) +{ + char name[128]; + int type, op, err = 0, ret = 0, i, idx; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + + if (evlist == NULL) + return -ENOMEM; + + for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) { + for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) { + /* skip invalid cache type */ + if (!perf_evsel__is_cache_op_valid(type, op)) + continue; + + for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { + __perf_evsel__hw_cache_type_op_res_name(type, op, i, + name, sizeof(name)); + err = parse_events(evlist, name); + if (err) + ret = err; + } + } + } + + idx = 0; + evsel = perf_evlist__first(evlist); + + for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) { + for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) { + /* skip invalid cache type */ + if (!perf_evsel__is_cache_op_valid(type, op)) + continue; + + for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) { + __perf_evsel__hw_cache_type_op_res_name(type, op, i, + name, sizeof(name)); + if (evsel->idx != idx) + continue; + + ++idx; + + if (strcmp(perf_evsel__name(evsel), name)) { + pr_debug("%s != %s\n", perf_evsel__name(evsel), name); + ret = -1; + } + + evsel = perf_evsel__next(evsel); + } + } + } + + perf_evlist__delete(evlist); + return ret; +} + +static int __perf_evsel__name_array_test(const char *names[], int nr_names) +{ + int i, err; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + + if (evlist == NULL) + return -ENOMEM; + + for (i = 0; i < nr_names; ++i) { + err = parse_events(evlist, names[i]); + if (err) { + pr_debug("failed to parse event '%s', err %d\n", + names[i], err); + goto out_delete_evlist; + } + } + + err = 0; + evlist__for_each(evlist, evsel) { + if (strcmp(perf_evsel__name(evsel), names[evsel->idx])) { + --err; + pr_debug("%s != %s\n", perf_evsel__name(evsel), names[evsel->idx]); + } + } + +out_delete_evlist: + perf_evlist__delete(evlist); + return err; +} + +#define perf_evsel__name_array_test(names) \ + __perf_evsel__name_array_test(names, ARRAY_SIZE(names)) + +int test__perf_evsel__roundtrip_name_test(void) +{ + int err = 0, ret = 0; + + err = perf_evsel__name_array_test(perf_evsel__hw_names); + if (err) + ret = err; + + err = perf_evsel__name_array_test(perf_evsel__sw_names); + if (err) + ret = err; + + err = perf_evsel__roundtrip_cache_name_test(); + if (err) + ret = err; + + return ret; +} diff --git a/kernel/tools/perf/tests/evsel-tp-sched.c b/kernel/tools/perf/tests/evsel-tp-sched.c new file mode 100644 index 000000000..52162425c --- /dev/null +++ b/kernel/tools/perf/tests/evsel-tp-sched.c @@ -0,0 +1,82 @@ +#include +#include "evsel.h" +#include "tests.h" +#include "debug.h" + +static int perf_evsel__test_field(struct perf_evsel *evsel, const char *name, + int size, bool should_be_signed) +{ + struct format_field *field = perf_evsel__field(evsel, name); + int is_signed; + int ret = 0; + + if (field == NULL) { + pr_debug("%s: \"%s\" field not found!\n", evsel->name, name); + return -1; + } + + is_signed = !!(field->flags | FIELD_IS_SIGNED); + if (should_be_signed && !is_signed) { + pr_debug("%s: \"%s\" signedness(%d) is wrong, should be %d\n", + evsel->name, name, is_signed, should_be_signed); + ret = -1; + } + + if (field->size != size) { + pr_debug("%s: \"%s\" size (%d) should be %d!\n", + evsel->name, name, field->size, size); + ret = -1; + } + + return ret; +} + +int test__perf_evsel__tp_sched_test(void) +{ + struct perf_evsel *evsel = perf_evsel__newtp("sched", "sched_switch"); + int ret = 0; + + if (evsel == NULL) { + pr_debug("perf_evsel__new\n"); + return -1; + } + + if (perf_evsel__test_field(evsel, "prev_comm", 16, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "prev_pid", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "prev_prio", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "prev_state", sizeof(long), true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "next_comm", 16, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "next_pid", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "next_prio", 4, true)) + ret = -1; + + perf_evsel__delete(evsel); + + evsel = perf_evsel__newtp("sched", "sched_wakeup"); + + if (perf_evsel__test_field(evsel, "comm", 16, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "pid", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "prio", 4, true)) + ret = -1; + + if (perf_evsel__test_field(evsel, "target_cpu", 4, true)) + ret = -1; + + return ret; +} diff --git a/kernel/tools/perf/tests/fdarray.c b/kernel/tools/perf/tests/fdarray.c new file mode 100644 index 000000000..d24b83795 --- /dev/null +++ b/kernel/tools/perf/tests/fdarray.c @@ -0,0 +1,174 @@ +#include +#include "util/debug.h" +#include "tests/tests.h" + +static void fdarray__init_revents(struct fdarray *fda, short revents) +{ + int fd; + + fda->nr = fda->nr_alloc; + + for (fd = 0; fd < fda->nr; ++fd) { + fda->entries[fd].fd = fda->nr - fd; + fda->entries[fd].revents = revents; + } +} + +static int fdarray__fprintf_prefix(struct fdarray *fda, const char *prefix, FILE *fp) +{ + int printed = 0; + + if (!verbose) + return 0; + + printed += fprintf(fp, "\n%s: ", prefix); + return printed + fdarray__fprintf(fda, fp); +} + +int test__fdarray__filter(void) +{ + int nr_fds, expected_fd[2], fd, err = TEST_FAIL; + struct fdarray *fda = fdarray__new(5, 5); + + if (fda == NULL) { + pr_debug("\nfdarray__new() failed!"); + goto out; + } + + fdarray__init_revents(fda, POLLIN); + nr_fds = fdarray__filter(fda, POLLHUP, NULL); + if (nr_fds != fda->nr_alloc) { + pr_debug("\nfdarray__filter()=%d != %d shouldn't have filtered anything", + nr_fds, fda->nr_alloc); + goto out_delete; + } + + fdarray__init_revents(fda, POLLHUP); + nr_fds = fdarray__filter(fda, POLLHUP, NULL); + if (nr_fds != 0) { + pr_debug("\nfdarray__filter()=%d != %d, should have filtered all fds", + nr_fds, fda->nr_alloc); + goto out_delete; + } + + 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); + nr_fds = fdarray__filter(fda, POLLHUP, NULL); + fdarray__fprintf_prefix(fda, " after", stderr); + if (nr_fds != 1) { + pr_debug("\nfdarray__filter()=%d != 1, should have left just one event", nr_fds); + 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); + nr_fds = fdarray__filter(fda, POLLHUP, NULL); + fdarray__fprintf_prefix(fda, " after", stderr); + if (nr_fds != 2) { + pr_debug("\nfdarray__filter()=%d != 2, should have left just two events", + nr_fds); + 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; +out_delete: + fdarray__delete(fda); +out: + return err; +} + +int test__fdarray__add(void) +{ + int err = TEST_FAIL; + struct fdarray *fda = fdarray__new(2, 2); + + if (fda == NULL) { + pr_debug("\nfdarray__new() failed!"); + goto out; + } + +#define FDA_CHECK(_idx, _fd, _revents) \ + if (fda->entries[_idx].fd != _fd) { \ + pr_debug("\n%d: fda->entries[%d](%d) != %d!", \ + __LINE__, _idx, fda->entries[1].fd, _fd); \ + goto out_delete; \ + } \ + if (fda->entries[_idx].events != (_revents)) { \ + pr_debug("\n%d: fda->entries[%d].revents(%d) != %d!", \ + __LINE__, _idx, fda->entries[_idx].fd, _revents); \ + goto out_delete; \ + } + +#define FDA_ADD(_idx, _fd, _revents, _nr) \ + if (fdarray__add(fda, _fd, _revents) < 0) { \ + pr_debug("\n%d: fdarray__add(fda, %d, %d) failed!", \ + __LINE__,_fd, _revents); \ + goto out_delete; \ + } \ + if (fda->nr != _nr) { \ + pr_debug("\n%d: fdarray__add(fda, %d, %d)=%d != %d", \ + __LINE__,_fd, _revents, fda->nr, _nr); \ + goto out_delete; \ + } \ + FDA_CHECK(_idx, _fd, _revents) + + FDA_ADD(0, 1, POLLIN, 1); + FDA_ADD(1, 2, POLLERR, 2); + + fdarray__fprintf_prefix(fda, "before growing array", stderr); + + FDA_ADD(2, 35, POLLHUP, 3); + + if (fda->entries == NULL) { + pr_debug("\nfdarray__add(fda, 35, POLLHUP) should have allocated fda->pollfd!"); + goto out_delete; + } + + fdarray__fprintf_prefix(fda, "after 3rd add", stderr); + + FDA_ADD(3, 88, POLLIN | POLLOUT, 4); + + fdarray__fprintf_prefix(fda, "after 4th add", stderr); + + FDA_CHECK(0, 1, POLLIN); + FDA_CHECK(1, 2, POLLERR); + FDA_CHECK(2, 35, POLLHUP); + FDA_CHECK(3, 88, POLLIN | POLLOUT); + +#undef FDA_ADD +#undef FDA_CHECK + + pr_debug("\n"); + + err = 0; +out_delete: + fdarray__delete(fda); +out: + return err; +} diff --git a/kernel/tools/perf/tests/hists_common.c b/kernel/tools/perf/tests/hists_common.c new file mode 100644 index 000000000..a62c09134 --- /dev/null +++ b/kernel/tools/perf/tests/hists_common.c @@ -0,0 +1,209 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "tests/hists_common.h" + +static struct { + u32 pid; + const char *comm; +} fake_threads[] = { + { FAKE_PID_PERF1, "perf" }, + { FAKE_PID_PERF2, "perf" }, + { FAKE_PID_BASH, "bash" }, +}; + +static struct { + u32 pid; + u64 start; + const char *filename; +} fake_mmap_info[] = { + { FAKE_PID_PERF1, FAKE_MAP_PERF, "perf" }, + { FAKE_PID_PERF1, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_PERF1, FAKE_MAP_KERNEL, "[kernel]" }, + { FAKE_PID_PERF2, FAKE_MAP_PERF, "perf" }, + { FAKE_PID_PERF2, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_PERF2, FAKE_MAP_KERNEL, "[kernel]" }, + { FAKE_PID_BASH, FAKE_MAP_BASH, "bash" }, + { FAKE_PID_BASH, FAKE_MAP_LIBC, "libc" }, + { FAKE_PID_BASH, FAKE_MAP_KERNEL, "[kernel]" }, +}; + +struct fake_sym { + u64 start; + u64 length; + const char *name; +}; + +static struct fake_sym perf_syms[] = { + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "main" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "run_command" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "cmd_record" }, +}; + +static struct fake_sym bash_syms[] = { + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "main" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "xmalloc" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "xfree" }, +}; + +static struct fake_sym libc_syms[] = { + { 700, 100, "malloc" }, + { 800, 100, "free" }, + { 900, 100, "realloc" }, + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "malloc" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "free" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "realloc" }, +}; + +static struct fake_sym kernel_syms[] = { + { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "schedule" }, + { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "page_fault" }, + { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "sys_perf_event_open" }, +}; + +static struct { + const char *dso_name; + struct fake_sym *syms; + size_t nr_syms; +} fake_symbols[] = { + { "perf", perf_syms, ARRAY_SIZE(perf_syms) }, + { "bash", bash_syms, ARRAY_SIZE(bash_syms) }, + { "libc", libc_syms, ARRAY_SIZE(libc_syms) }, + { "[kernel]", kernel_syms, ARRAY_SIZE(kernel_syms) }, +}; + +struct machine *setup_fake_machine(struct machines *machines) +{ + struct machine *machine = machines__find(machines, HOST_KERNEL_ID); + size_t i; + + if (machine == NULL) { + pr_debug("Not enough memory for machine setup\n"); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(fake_threads); i++) { + struct thread *thread; + + thread = machine__findnew_thread(machine, fake_threads[i].pid, + fake_threads[i].pid); + if (thread == NULL) + goto out; + + thread__set_comm(thread, fake_threads[i].comm, 0); + } + + for (i = 0; i < ARRAY_SIZE(fake_mmap_info); i++) { + union perf_event fake_mmap_event = { + .mmap = { + .header = { .misc = PERF_RECORD_MISC_USER, }, + .pid = fake_mmap_info[i].pid, + .tid = fake_mmap_info[i].pid, + .start = fake_mmap_info[i].start, + .len = FAKE_MAP_LENGTH, + .pgoff = 0ULL, + }, + }; + + strcpy(fake_mmap_event.mmap.filename, + fake_mmap_info[i].filename); + + machine__process_mmap_event(machine, &fake_mmap_event, NULL); + } + + for (i = 0; i < ARRAY_SIZE(fake_symbols); i++) { + size_t k; + struct dso *dso; + + dso = __dsos__findnew(&machine->user_dsos, + fake_symbols[i].dso_name); + if (dso == NULL) + goto out; + + /* emulate dso__load() */ + dso__set_loaded(dso, MAP__FUNCTION); + + for (k = 0; k < fake_symbols[i].nr_syms; k++) { + struct symbol *sym; + struct fake_sym *fsym = &fake_symbols[i].syms[k]; + + sym = symbol__new(fsym->start, fsym->length, + STB_GLOBAL, fsym->name); + if (sym == NULL) + goto out; + + symbols__insert(&dso->symbols[MAP__FUNCTION], sym); + } + } + + return machine; + +out: + pr_debug("Not enough memory for machine setup\n"); + machine__delete_threads(machine); + machine__delete(machine); + return NULL; +} + +void print_hists_in(struct hists *hists) +{ + int i = 0; + struct rb_root *root; + struct rb_node *node; + + if (sort__need_collapse) + root = &hists->entries_collapsed; + else + root = hists->entries_in; + + pr_info("----- %s --------\n", __func__); + node = rb_first(root); + while (node) { + struct hist_entry *he; + + he = rb_entry(node, struct hist_entry, rb_node_in); + + if (!he->filtered) { + pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n", + i, thread__comm_str(he->thread), + he->ms.map->dso->short_name, + he->ms.sym->name, he->stat.period); + } + + i++; + node = rb_next(node); + } +} + +void print_hists_out(struct hists *hists) +{ + int i = 0; + struct rb_root *root; + struct rb_node *node; + + root = &hists->entries; + + pr_info("----- %s --------\n", __func__); + node = rb_first(root); + while (node) { + struct hist_entry *he; + + he = rb_entry(node, struct hist_entry, rb_node); + + if (!he->filtered) { + 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, + he->ms.sym->name, he->stat.period, + he->stat_acc ? he->stat_acc->period : 0); + } + + i++; + node = rb_next(node); + } +} diff --git a/kernel/tools/perf/tests/hists_common.h b/kernel/tools/perf/tests/hists_common.h new file mode 100644 index 000000000..888254e86 --- /dev/null +++ b/kernel/tools/perf/tests/hists_common.h @@ -0,0 +1,75 @@ +#ifndef __PERF_TESTS__HISTS_COMMON_H__ +#define __PERF_TESTS__HISTS_COMMON_H__ + +struct machine; +struct machines; + +#define FAKE_PID_PERF1 100 +#define FAKE_PID_PERF2 200 +#define FAKE_PID_BASH 300 + +#define FAKE_MAP_PERF 0x400000 +#define FAKE_MAP_BASH 0x400000 +#define FAKE_MAP_LIBC 0x500000 +#define FAKE_MAP_KERNEL 0xf00000 +#define FAKE_MAP_LENGTH 0x100000 + +#define FAKE_SYM_OFFSET1 700 +#define FAKE_SYM_OFFSET2 800 +#define FAKE_SYM_OFFSET3 900 +#define FAKE_SYM_LENGTH 100 + +#define FAKE_IP_PERF_MAIN FAKE_MAP_PERF + FAKE_SYM_OFFSET1 +#define FAKE_IP_PERF_RUN_COMMAND FAKE_MAP_PERF + FAKE_SYM_OFFSET2 +#define FAKE_IP_PERF_CMD_RECORD FAKE_MAP_PERF + FAKE_SYM_OFFSET3 +#define FAKE_IP_BASH_MAIN FAKE_MAP_BASH + FAKE_SYM_OFFSET1 +#define FAKE_IP_BASH_XMALLOC FAKE_MAP_BASH + FAKE_SYM_OFFSET2 +#define FAKE_IP_BASH_XFREE FAKE_MAP_BASH + FAKE_SYM_OFFSET3 +#define FAKE_IP_LIBC_MALLOC FAKE_MAP_LIBC + FAKE_SYM_OFFSET1 +#define FAKE_IP_LIBC_FREE FAKE_MAP_LIBC + FAKE_SYM_OFFSET2 +#define FAKE_IP_LIBC_REALLOC FAKE_MAP_LIBC + FAKE_SYM_OFFSET3 +#define FAKE_IP_KERNEL_SCHEDULE FAKE_MAP_KERNEL + FAKE_SYM_OFFSET1 +#define FAKE_IP_KERNEL_PAGE_FAULT FAKE_MAP_KERNEL + FAKE_SYM_OFFSET2 +#define FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN FAKE_MAP_KERNEL + FAKE_SYM_OFFSET3 + +/* + * The setup_fake_machine() provides a test environment which consists + * of 3 processes that have 3 mappings and in turn, have 3 symbols + * respectively. See below table: + * + * Command: Pid Shared Object Symbol + * ............. ............. ................... + * perf: 100 perf main + * perf: 100 perf run_command + * perf: 100 perf cmd_record + * perf: 100 libc malloc + * perf: 100 libc free + * perf: 100 libc realloc + * perf: 100 [kernel] schedule + * perf: 100 [kernel] page_fault + * perf: 100 [kernel] sys_perf_event_open + * perf: 200 perf main + * perf: 200 perf run_command + * perf: 200 perf cmd_record + * perf: 200 libc malloc + * perf: 200 libc free + * perf: 200 libc realloc + * perf: 200 [kernel] schedule + * perf: 200 [kernel] page_fault + * perf: 200 [kernel] sys_perf_event_open + * bash: 300 bash main + * bash: 300 bash xmalloc + * bash: 300 bash xfree + * bash: 300 libc malloc + * bash: 300 libc free + * bash: 300 libc realloc + * bash: 300 [kernel] schedule + * bash: 300 [kernel] page_fault + * bash: 300 [kernel] sys_perf_event_open + */ +struct machine *setup_fake_machine(struct machines *machines); + +void print_hists_in(struct hists *hists); +void print_hists_out(struct hists *hists); + +#endif /* __PERF_TESTS__HISTS_COMMON_H__ */ diff --git a/kernel/tools/perf/tests/hists_cumulate.c b/kernel/tools/perf/tests/hists_cumulate.c new file mode 100644 index 000000000..186199664 --- /dev/null +++ b/kernel/tools/perf/tests/hists_cumulate.c @@ -0,0 +1,726 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "util/parse-events.h" +#include "tests/tests.h" +#include "tests/hists_common.h" + +struct sample { + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [perf] cmd_record() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, }, + /* perf [libc] malloc() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, + /* perf [libc] free() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [kernel] page_fault() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + /* bash [bash] main() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, + /* bash [bash] xmalloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, + /* bash [kernel] page_fault() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, +}; + +/* + * Will be casted to struct ip_callchain which has all 64 bit entries + * of nr and ips[]. + */ +static u64 fake_callchains[][10] = { + /* schedule => run_command => main */ + { 3, FAKE_IP_KERNEL_SCHEDULE, FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_PERF_MAIN, }, + /* cmd_record => run_command => main */ + { 3, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* malloc => cmd_record => run_command => main */ + { 4, FAKE_IP_LIBC_MALLOC, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, + FAKE_IP_PERF_MAIN, }, + /* free => cmd_record => run_command => main */ + { 4, FAKE_IP_LIBC_FREE, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, + FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_PERF_MAIN, }, + /* page_fault => sys_perf_event_open => run_command => main */ + { 4, FAKE_IP_KERNEL_PAGE_FAULT, FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN, + FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, }, + /* main */ + { 1, FAKE_IP_BASH_MAIN, }, + /* xmalloc => malloc => xmalloc => malloc => xmalloc => main */ + { 6, FAKE_IP_BASH_XMALLOC, FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_XMALLOC, + FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_XMALLOC, FAKE_IP_BASH_MAIN, }, + /* page_fault => malloc => main */ + { 3, FAKE_IP_KERNEL_PAGE_FAULT, FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_MAIN, }, +}; + +static int add_hist_entries(struct hists *hists, struct machine *machine) +{ + struct addr_location al; + struct perf_evsel *evsel = hists_to_evsel(hists); + struct perf_sample sample = { .period = 1000, }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + struct hist_entry_iter iter = { + .hide_unresolved = false, + }; + + if (symbol_conf.cumulate_callchain) + iter.ops = &hist_iter_cumulative; + else + iter.ops = &hist_iter_normal; + + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + sample.callchain = (struct ip_callchain *)fake_callchains[i]; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + } + + return TEST_OK; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +static void del_hist_entries(struct hists *hists) +{ + struct hist_entry *he; + struct rb_root *root_in; + struct rb_root *root_out; + struct rb_node *node; + + if (sort__need_collapse) + root_in = &hists->entries_collapsed; + else + root_in = hists->entries_in; + + root_out = &hists->entries; + + while (!RB_EMPTY_ROOT(root_out)) { + node = rb_first(root_out); + + he = rb_entry(node, struct hist_entry, rb_node); + rb_erase(node, root_out); + rb_erase(&he->rb_node_in, root_in); + hist_entry__delete(he); + } +} + +typedef int (*test_fn_t)(struct perf_evsel *, struct machine *); + +#define COMM(he) (thread__comm_str(he->thread)) +#define DSO(he) (he->ms.map->dso->short_name) +#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 CSYM(cl) (cl->ms.sym->name) + +struct result { + u64 children; + u64 self; + const char *comm; + const char *dso; + const char *sym; +}; + +struct callchain_result { + u64 nr; + struct { + const char *dso; + const char *sym; + } node[10]; +}; + +static int do_test(struct hists *hists, struct result *expected, size_t nr_expected, + struct callchain_result *expected_callchain, size_t nr_callchain) +{ + char buf[32]; + size_t i, c; + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + struct callchain_node *cnode; + struct callchain_list *clist; + + /* + * adding and deleting hist entries must be done outside of this + * function since TEST_ASSERT_VAL() returns in case of failure. + */ + hists__collapse_resort(hists, NULL); + hists__output_resort(hists, NULL); + + if (verbose > 2) { + pr_info("use callchain: %d, cumulate callchain: %d\n", + symbol_conf.use_callchain, + symbol_conf.cumulate_callchain); + print_hists_out(hists); + } + + root = &hists->entries; + for (node = rb_first(root), i = 0; + node && (he = rb_entry(node, struct hist_entry, rb_node)); + node = rb_next(node), i++) { + scnprintf(buf, sizeof(buf), "Invalid hist entry #%zd", i); + + TEST_ASSERT_VAL("Incorrect number of hist entry", + i < nr_expected); + TEST_ASSERT_VAL(buf, he->stat.period == expected[i].self && + !strcmp(COMM(he), expected[i].comm) && + !strcmp(DSO(he), expected[i].dso) && + !strcmp(SYM(he), expected[i].sym)); + + if (symbol_conf.cumulate_callchain) + TEST_ASSERT_VAL(buf, he->stat_acc->period == expected[i].children); + + if (!symbol_conf.use_callchain) + continue; + + /* check callchain entries */ + root = &he->callchain->node.rb_root; + cnode = rb_entry(rb_first(root), struct callchain_node, rb_node); + + c = 0; + list_for_each_entry(clist, &cnode->val, list) { + scnprintf(buf, sizeof(buf), "Invalid callchain entry #%zd/%zd", i, c); + + TEST_ASSERT_VAL("Incorrect number of callchain entry", + c < expected_callchain[i].nr); + TEST_ASSERT_VAL(buf, + !strcmp(CDSO(clist), expected_callchain[i].node[c].dso) && + !strcmp(CSYM(clist), expected_callchain[i].node[c].sym)); + c++; + } + /* TODO: handle multiple child nodes properly */ + TEST_ASSERT_VAL("Incorrect number of callchain entry", + c <= expected_callchain[i].nr); + } + TEST_ASSERT_VAL("Incorrect number of hist entry", + i == nr_expected); + TEST_ASSERT_VAL("Incorrect number of callchain entry", + !symbol_conf.use_callchain || nr_expected == nr_callchain); + return 0; +} + +/* NO callchain + NO children */ +static int test1(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * 10.00% bash [kernel] [k] page_fault + * 10.00% bash bash [.] main + * 10.00% bash bash [.] xmalloc + * 10.00% perf [kernel] [k] page_fault + * 10.00% perf [kernel] [k] schedule + * 10.00% perf libc [.] free + * 10.00% perf libc [.] malloc + * 10.00% perf perf [.] cmd_record + */ + struct result expected[] = { + { 0, 2000, "perf", "perf", "main" }, + { 0, 1000, "bash", "[kernel]", "page_fault" }, + { 0, 1000, "bash", "bash", "main" }, + { 0, 1000, "bash", "bash", "xmalloc" }, + { 0, 1000, "perf", "[kernel]", "page_fault" }, + { 0, 1000, "perf", "[kernel]", "schedule" }, + { 0, 1000, "perf", "libc", "free" }, + { 0, 1000, "perf", "libc", "malloc" }, + { 0, 1000, "perf", "perf", "cmd_record" }, + }; + + symbol_conf.use_callchain = false; + symbol_conf.cumulate_callchain = false; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), NULL, 0); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* callcain + NO children */ +static int test2(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * | + * --- main + * + * 10.00% bash [kernel] [k] page_fault + * | + * --- page_fault + * malloc + * main + * + * 10.00% bash bash [.] main + * | + * --- main + * + * 10.00% bash bash [.] xmalloc + * | + * --- xmalloc + * malloc + * xmalloc <--- NOTE: there's a cycle + * malloc + * xmalloc + * main + * + * 10.00% perf [kernel] [k] page_fault + * | + * --- page_fault + * sys_perf_event_open + * run_command + * main + * + * 10.00% perf [kernel] [k] schedule + * | + * --- schedule + * run_command + * main + * + * 10.00% perf libc [.] free + * | + * --- free + * cmd_record + * run_command + * main + * + * 10.00% perf libc [.] malloc + * | + * --- malloc + * cmd_record + * run_command + * main + * + * 10.00% perf perf [.] cmd_record + * | + * --- cmd_record + * run_command + * main + * + */ + struct result expected[] = { + { 0, 2000, "perf", "perf", "main" }, + { 0, 1000, "bash", "[kernel]", "page_fault" }, + { 0, 1000, "bash", "bash", "main" }, + { 0, 1000, "bash", "bash", "xmalloc" }, + { 0, 1000, "perf", "[kernel]", "page_fault" }, + { 0, 1000, "perf", "[kernel]", "schedule" }, + { 0, 1000, "perf", "libc", "free" }, + { 0, 1000, "perf", "libc", "malloc" }, + { 0, 1000, "perf", "perf", "cmd_record" }, + }; + struct callchain_result expected_callchain[] = { + { + 1, { { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "page_fault" }, + { "libc", "malloc" }, + { "bash", "main" }, }, + }, + { + 1, { { "bash", "main" }, }, + }, + { + 6, { { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, }, + }, + { + 4, { { "[kernel]", "page_fault" }, + { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "schedule" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "free" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + }; + + symbol_conf.use_callchain = true; + symbol_conf.cumulate_callchain = false; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), + expected_callchain, ARRAY_SIZE(expected_callchain)); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* NO callchain + children */ +static int test3(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + /* + * expected output: + * + * Children Self Command Shared Object Symbol + * ======== ======== ======= ============= ======================= + * 70.00% 20.00% perf perf [.] main + * 50.00% 0.00% perf perf [.] run_command + * 30.00% 10.00% bash bash [.] main + * 30.00% 10.00% perf perf [.] cmd_record + * 20.00% 0.00% bash libc [.] malloc + * 10.00% 10.00% bash [kernel] [k] page_fault + * 10.00% 10.00% bash bash [.] xmalloc + * 10.00% 10.00% perf [kernel] [k] page_fault + * 10.00% 10.00% perf libc [.] malloc + * 10.00% 10.00% perf [kernel] [k] schedule + * 10.00% 10.00% perf libc [.] free + * 10.00% 0.00% perf [kernel] [k] sys_perf_event_open + */ + struct result expected[] = { + { 7000, 2000, "perf", "perf", "main" }, + { 5000, 0, "perf", "perf", "run_command" }, + { 3000, 1000, "bash", "bash", "main" }, + { 3000, 1000, "perf", "perf", "cmd_record" }, + { 2000, 0, "bash", "libc", "malloc" }, + { 1000, 1000, "bash", "[kernel]", "page_fault" }, + { 1000, 1000, "bash", "bash", "xmalloc" }, + { 1000, 1000, "perf", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "[kernel]", "schedule" }, + { 1000, 1000, "perf", "libc", "free" }, + { 1000, 1000, "perf", "libc", "malloc" }, + { 1000, 0, "perf", "[kernel]", "sys_perf_event_open" }, + }; + + symbol_conf.use_callchain = false; + symbol_conf.cumulate_callchain = true; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), NULL, 0); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* callchain + children */ +static int test4(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + /* + * expected output: + * + * Children Self Command Shared Object Symbol + * ======== ======== ======= ============= ======================= + * 70.00% 20.00% perf perf [.] main + * | + * --- main + * + * 50.00% 0.00% perf perf [.] run_command + * | + * --- run_command + * main + * + * 30.00% 10.00% bash bash [.] main + * | + * --- main + * + * 30.00% 10.00% perf perf [.] cmd_record + * | + * --- cmd_record + * run_command + * main + * + * 20.00% 0.00% bash libc [.] malloc + * | + * --- malloc + * | + * |--50.00%-- xmalloc + * | main + * --50.00%-- main + * + * 10.00% 10.00% bash [kernel] [k] page_fault + * | + * --- page_fault + * malloc + * main + * + * 10.00% 10.00% bash bash [.] xmalloc + * | + * --- xmalloc + * malloc + * xmalloc <--- NOTE: there's a cycle + * malloc + * xmalloc + * main + * + * 10.00% 0.00% perf [kernel] [k] sys_perf_event_open + * | + * --- sys_perf_event_open + * run_command + * main + * + * 10.00% 10.00% perf [kernel] [k] page_fault + * | + * --- page_fault + * sys_perf_event_open + * run_command + * main + * + * 10.00% 10.00% perf [kernel] [k] schedule + * | + * --- schedule + * run_command + * main + * + * 10.00% 10.00% perf libc [.] free + * | + * --- free + * cmd_record + * run_command + * main + * + * 10.00% 10.00% perf libc [.] malloc + * | + * --- malloc + * cmd_record + * run_command + * main + * + */ + struct result expected[] = { + { 7000, 2000, "perf", "perf", "main" }, + { 5000, 0, "perf", "perf", "run_command" }, + { 3000, 1000, "bash", "bash", "main" }, + { 3000, 1000, "perf", "perf", "cmd_record" }, + { 2000, 0, "bash", "libc", "malloc" }, + { 1000, 1000, "bash", "[kernel]", "page_fault" }, + { 1000, 1000, "bash", "bash", "xmalloc" }, + { 1000, 0, "perf", "[kernel]", "sys_perf_event_open" }, + { 1000, 1000, "perf", "[kernel]", "page_fault" }, + { 1000, 1000, "perf", "[kernel]", "schedule" }, + { 1000, 1000, "perf", "libc", "free" }, + { 1000, 1000, "perf", "libc", "malloc" }, + }; + struct callchain_result expected_callchain[] = { + { + 1, { { "perf", "main" }, }, + }, + { + 2, { { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 1, { { "bash", "main" }, }, + }, + { + 3, { { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, + { "bash", "main" }, }, + }, + { + 3, { { "[kernel]", "page_fault" }, + { "libc", "malloc" }, + { "bash", "main" }, }, + }, + { + 6, { { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "libc", "malloc" }, + { "bash", "xmalloc" }, + { "bash", "main" }, }, + }, + { + 3, { { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "[kernel]", "page_fault" }, + { "[kernel]", "sys_perf_event_open" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 3, { { "[kernel]", "schedule" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "free" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + { + 4, { { "libc", "malloc" }, + { "perf", "cmd_record" }, + { "perf", "run_command" }, + { "perf", "main" }, }, + }, + }; + + symbol_conf.use_callchain = true; + symbol_conf.cumulate_callchain = true; + + setup_sorting(); + callchain_register_param(&callchain_param); + + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + err = do_test(hists, expected, ARRAY_SIZE(expected), + expected_callchain, ARRAY_SIZE(expected_callchain)); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +int test__hists_cumulate(void) +{ + int err = TEST_FAIL; + struct machines machines; + struct machine *machine; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + size_t i; + test_fn_t testcases[] = { + test1, + test2, + test3, + test4, + }; + + TEST_ASSERT_VAL("No memory", evlist); + + err = parse_events(evlist, "cpu-clock"); + if (err) + goto out; + + machines__init(&machines); + + /* setup threads/dso/map/symbols also */ + machine = setup_fake_machine(&machines); + if (!machine) + goto out; + + if (verbose > 1) + machine__fprintf(machine, stderr); + + evsel = perf_evlist__first(evlist); + + for (i = 0; i < ARRAY_SIZE(testcases); i++) { + err = testcases[i](evsel, machine); + if (err < 0) + break; + } + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + machines__exit(&machines); + + return err; +} diff --git a/kernel/tools/perf/tests/hists_filter.c b/kernel/tools/perf/tests/hists_filter.c new file mode 100644 index 000000000..59e53db79 --- /dev/null +++ b/kernel/tools/perf/tests/hists_filter.c @@ -0,0 +1,290 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "util/parse-events.h" +#include "tests/tests.h" +#include "tests/hists_common.h" + +struct sample { + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [libc] malloc() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, /* will be merged */ + /* perf [perf] cmd_record() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, }, + /* perf [kernel] page_fault() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + /* bash [bash] main() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, + /* bash [bash] xmalloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, + /* bash [libc] malloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, + /* bash [kernel] page_fault() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, +}; + +static int add_hist_entries(struct perf_evlist *evlist, + struct machine *machine) +{ + struct perf_evsel *evsel; + struct addr_location al; + struct perf_sample sample = { .period = 100, }; + size_t i; + + /* + * each evsel will have 10 samples but the 4th sample + * (perf [perf] main) will be collapsed to an existing entry + * so total 9 entries will be in the tree. + */ + evlist__for_each(evlist, evsel) { + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + struct hist_entry_iter iter = { + .ops = &hist_iter_normal, + .hide_unresolved = false, + }; + struct hists *hists = evsel__hists(evsel); + + /* make sure it has no filter at first */ + hists->thread_filter = NULL; + hists->dso_filter = NULL; + hists->symbol_filter_str = NULL; + + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + } + } + + return 0; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +int test__hists_filter(void) +{ + int err = TEST_FAIL; + struct machines machines; + struct machine *machine; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + + TEST_ASSERT_VAL("No memory", evlist); + + err = parse_events(evlist, "cpu-clock"); + if (err) + goto out; + err = parse_events(evlist, "task-clock"); + if (err) + goto out; + + /* default sort order (comm,dso,sym) will be used */ + if (setup_sorting() < 0) + goto out; + + machines__init(&machines); + + /* setup threads/dso/map/symbols also */ + machine = setup_fake_machine(&machines); + if (!machine) + goto out; + + if (verbose > 1) + machine__fprintf(machine, stderr); + + /* process sample events */ + err = add_hist_entries(evlist, machine); + if (err < 0) + goto out; + + evlist__for_each(evlist, evsel) { + struct hists *hists = evsel__hists(evsel); + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists, NULL); + + if (verbose > 2) { + pr_info("Normal histogram\n"); + print_hists_out(hists); + } + + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 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_non_filtered_samples); + TEST_ASSERT_VAL("Unmatched nr hist entries", + hists->nr_entries == hists->nr_non_filtered_entries); + TEST_ASSERT_VAL("Unmatched total period", + hists->stats.total_period == + hists->stats.total_non_filtered_period); + + /* now applying thread filter for 'bash' */ + hists->thread_filter = fake_samples[9].thread; + hists__filter_by_thread(hists); + + if (verbose > 2) { + pr_info("Histogram for thread filter\n"); + print_hists_out(hists); + } + + /* normal stats should be invariant */ + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + + /* but filter stats are changed */ + TEST_ASSERT_VAL("Unmatched nr samples for thread filter", + hists->stats.nr_non_filtered_samples == 4); + TEST_ASSERT_VAL("Unmatched nr hist entries for thread filter", + hists->nr_non_filtered_entries == 4); + TEST_ASSERT_VAL("Unmatched total period for thread filter", + hists->stats.total_non_filtered_period == 400); + + /* remove thread filter first */ + hists->thread_filter = NULL; + hists__filter_by_thread(hists); + + /* now applying dso filter for 'kernel' */ + hists->dso_filter = fake_samples[0].map->dso; + hists__filter_by_dso(hists); + + if (verbose > 2) { + pr_info("Histogram for dso filter\n"); + print_hists_out(hists); + } + + /* normal stats should be invariant */ + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + + /* but filter stats are changed */ + TEST_ASSERT_VAL("Unmatched nr samples for dso filter", + hists->stats.nr_non_filtered_samples == 3); + TEST_ASSERT_VAL("Unmatched nr hist entries for dso filter", + hists->nr_non_filtered_entries == 3); + TEST_ASSERT_VAL("Unmatched total period for dso filter", + hists->stats.total_non_filtered_period == 300); + + /* remove dso filter first */ + hists->dso_filter = NULL; + hists__filter_by_dso(hists); + + /* + * now applying symbol filter for 'main'. Also note that + * there's 3 samples that have 'main' symbol but the 4th + * entry of fake_samples was collapsed already so it won't + * be counted as a separate entry but the sample count and + * total period will be remained. + */ + hists->symbol_filter_str = "main"; + hists__filter_by_symbol(hists); + + if (verbose > 2) { + pr_info("Histogram for symbol filter\n"); + print_hists_out(hists); + } + + /* normal stats should be invariant */ + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + + /* but filter stats are changed */ + TEST_ASSERT_VAL("Unmatched nr samples for symbol filter", + hists->stats.nr_non_filtered_samples == 3); + TEST_ASSERT_VAL("Unmatched nr hist entries for symbol filter", + hists->nr_non_filtered_entries == 2); + TEST_ASSERT_VAL("Unmatched total period for symbol filter", + hists->stats.total_non_filtered_period == 300); + + /* now applying all filters at once. */ + hists->thread_filter = fake_samples[1].thread; + hists->dso_filter = fake_samples[1].map->dso; + hists__filter_by_thread(hists); + hists__filter_by_dso(hists); + + if (verbose > 2) { + pr_info("Histogram for all filters\n"); + print_hists_out(hists); + } + + /* normal stats should be invariant */ + TEST_ASSERT_VAL("Invalid nr samples", + hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10); + TEST_ASSERT_VAL("Invalid nr hist entries", + hists->nr_entries == 9); + TEST_ASSERT_VAL("Invalid total period", + hists->stats.total_period == 1000); + + /* but filter stats are changed */ + TEST_ASSERT_VAL("Unmatched nr samples for all filter", + hists->stats.nr_non_filtered_samples == 2); + TEST_ASSERT_VAL("Unmatched nr hist entries for all filter", + hists->nr_non_filtered_entries == 1); + TEST_ASSERT_VAL("Unmatched total period for all filter", + hists->stats.total_non_filtered_period == 200); + } + + + err = TEST_OK; + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + reset_output_field(); + machines__exit(&machines); + + return err; +} diff --git a/kernel/tools/perf/tests/hists_link.c b/kernel/tools/perf/tests/hists_link.c new file mode 100644 index 000000000..278ba8344 --- /dev/null +++ b/kernel/tools/perf/tests/hists_link.c @@ -0,0 +1,346 @@ +#include "perf.h" +#include "tests.h" +#include "debug.h" +#include "symbol.h" +#include "sort.h" +#include "evsel.h" +#include "evlist.h" +#include "machine.h" +#include "thread.h" +#include "parse-events.h" +#include "hists_common.h" + +struct sample { + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_common_samples[] = { + /* perf [kernel] schedule() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, + /* perf [perf] main() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [perf] cmd_record() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, }, + /* bash [bash] xmalloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, + /* bash [libc] malloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, +}; + +static struct sample fake_samples[][5] = { + { + /* perf [perf] run_command() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_RUN_COMMAND, }, + /* perf [libc] malloc() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, + /* perf [kernel] page_fault() */ + { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + /* perf [kernel] sys_perf_event_open() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN, }, + /* bash [libc] free() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_FREE, }, + }, + { + /* perf [libc] free() */ + { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_LIBC_FREE, }, + /* bash [libc] malloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, /* will be merged */ + /* bash [bash] xfee() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XFREE, }, + /* bash [libc] realloc() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_REALLOC, }, + /* bash [kernel] page_fault() */ + { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + }, +}; + +static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) +{ + struct perf_evsel *evsel; + struct addr_location al; + struct hist_entry *he; + struct perf_sample sample = { .period = 1, }; + size_t i = 0, k; + + /* + * each evsel will have 10 samples - 5 common and 5 distinct. + * However the second evsel also has a collapsed entry for + * "bash [libc] malloc" so total 9 entries will be in the tree. + */ + evlist__for_each(evlist, evsel) { + struct hists *hists = evsel__hists(evsel); + + for (k = 0; k < ARRAY_SIZE(fake_common_samples); k++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + + sample.pid = fake_common_samples[k].pid; + sample.tid = fake_common_samples[k].pid; + sample.ip = fake_common_samples[k].ip; + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + he = __hists__add_entry(hists, &al, NULL, + NULL, NULL, 1, 1, 0, true); + if (he == NULL) + goto out; + + fake_common_samples[k].thread = al.thread; + fake_common_samples[k].map = al.map; + fake_common_samples[k].sym = al.sym; + } + + for (k = 0; k < ARRAY_SIZE(fake_samples[i]); k++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + + sample.pid = fake_samples[i][k].pid; + sample.tid = fake_samples[i][k].pid; + sample.ip = fake_samples[i][k].ip; + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + he = __hists__add_entry(hists, &al, NULL, + NULL, NULL, 1, 1, 0, true); + if (he == NULL) + goto out; + + fake_samples[i][k].thread = al.thread; + fake_samples[i][k].map = al.map; + fake_samples[i][k].sym = al.sym; + } + i++; + } + + return 0; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return -1; +} + +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 && + samples->sym == s) + return 1; + samples++; + } + return 0; +} + +static int __validate_match(struct hists *hists) +{ + size_t count = 0; + struct rb_root *root; + struct rb_node *node; + + /* + * Only entries from fake_common_samples should have a pair. + */ + if (sort__need_collapse) + root = &hists->entries_collapsed; + else + root = hists->entries_in; + + node = rb_first(root); + while (node) { + struct hist_entry *he; + + he = rb_entry(node, struct hist_entry, rb_node_in); + + if (hist_entry__has_pairs(he)) { + if (find_sample(fake_common_samples, + ARRAY_SIZE(fake_common_samples), + he->thread, he->ms.map, he->ms.sym)) { + count++; + } else { + pr_debug("Can't find the matched entry\n"); + return -1; + } + } + + node = rb_next(node); + } + + if (count != ARRAY_SIZE(fake_common_samples)) { + pr_debug("Invalid count for matched entries: %zd of %zd\n", + count, ARRAY_SIZE(fake_common_samples)); + return -1; + } + + return 0; +} + +static int validate_match(struct hists *leader, struct hists *other) +{ + return __validate_match(leader) || __validate_match(other); +} + +static int __validate_link(struct hists *hists, int idx) +{ + size_t count = 0; + size_t count_pair = 0; + size_t count_dummy = 0; + struct rb_root *root; + struct rb_node *node; + + /* + * Leader hists (idx = 0) will have dummy entries from other, + * and some entries will have no pair. However every entry + * in other hists should have (dummy) pair. + */ + if (sort__need_collapse) + root = &hists->entries_collapsed; + else + root = hists->entries_in; + + node = rb_first(root); + while (node) { + struct hist_entry *he; + + he = rb_entry(node, struct hist_entry, rb_node_in); + + if (hist_entry__has_pairs(he)) { + if (!find_sample(fake_common_samples, + ARRAY_SIZE(fake_common_samples), + he->thread, he->ms.map, he->ms.sym) && + !find_sample(fake_samples[idx], + ARRAY_SIZE(fake_samples[idx]), + he->thread, he->ms.map, he->ms.sym)) { + count_dummy++; + } + count_pair++; + } else if (idx) { + pr_debug("A entry from the other hists should have pair\n"); + return -1; + } + + count++; + node = rb_next(node); + } + + /* + * Note that we have a entry collapsed in the other (idx = 1) hists. + */ + if (idx == 0) { + if (count_dummy != ARRAY_SIZE(fake_samples[1]) - 1) { + pr_debug("Invalid count of dummy entries: %zd of %zd\n", + count_dummy, ARRAY_SIZE(fake_samples[1]) - 1); + return -1; + } + if (count != count_pair + ARRAY_SIZE(fake_samples[0])) { + pr_debug("Invalid count of total leader entries: %zd of %zd\n", + count, count_pair + ARRAY_SIZE(fake_samples[0])); + return -1; + } + } else { + if (count != count_pair) { + pr_debug("Invalid count of total other entries: %zd of %zd\n", + count, count_pair); + return -1; + } + if (count_dummy > 0) { + pr_debug("Other hists should not have dummy entries: %zd\n", + count_dummy); + return -1; + } + } + + return 0; +} + +static int validate_link(struct hists *leader, struct hists *other) +{ + return __validate_link(leader, 0) || __validate_link(other, 1); +} + +int test__hists_link(void) +{ + int err = -1; + struct hists *hists, *first_hists; + struct machines machines; + struct machine *machine = NULL; + struct perf_evsel *evsel, *first; + struct perf_evlist *evlist = perf_evlist__new(); + + if (evlist == NULL) + return -ENOMEM; + + err = parse_events(evlist, "cpu-clock"); + if (err) + goto out; + err = parse_events(evlist, "task-clock"); + if (err) + goto out; + + /* default sort order (comm,dso,sym) will be used */ + if (setup_sorting() < 0) + goto out; + + machines__init(&machines); + + /* setup threads/dso/map/symbols also */ + machine = setup_fake_machine(&machines); + if (!machine) + goto out; + + if (verbose > 1) + machine__fprintf(machine, stderr); + + /* process sample events */ + err = add_hist_entries(evlist, machine); + if (err < 0) + goto out; + + evlist__for_each(evlist, evsel) { + hists = evsel__hists(evsel); + hists__collapse_resort(hists, NULL); + + if (verbose > 2) + print_hists_in(hists); + } + + first = perf_evlist__first(evlist); + evsel = perf_evlist__last(evlist); + + first_hists = evsel__hists(first); + hists = evsel__hists(evsel); + + /* match common entries */ + hists__match(first_hists, hists); + err = validate_match(first_hists, hists); + if (err) + goto out; + + /* link common and/or dummy entries */ + hists__link(first_hists, hists); + err = validate_link(first_hists, hists); + if (err) + goto out; + + err = 0; + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + reset_output_field(); + machines__exit(&machines); + + return err; +} diff --git a/kernel/tools/perf/tests/hists_output.c b/kernel/tools/perf/tests/hists_output.c new file mode 100644 index 000000000..b52c9faea --- /dev/null +++ b/kernel/tools/perf/tests/hists_output.c @@ -0,0 +1,621 @@ +#include "perf.h" +#include "util/debug.h" +#include "util/symbol.h" +#include "util/sort.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/machine.h" +#include "util/thread.h" +#include "util/parse-events.h" +#include "tests/tests.h" +#include "tests/hists_common.h" + +struct sample { + u32 cpu; + u32 pid; + u64 ip; + struct thread *thread; + struct map *map; + struct symbol *sym; +}; + +/* For the numbers, see hists_common.c */ +static struct sample fake_samples[] = { + /* perf [kernel] schedule() */ + { .cpu = 0, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, }, + /* perf [perf] main() */ + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [perf] cmd_record() */ + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, }, + /* perf [libc] malloc() */ + { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, }, + /* perf [libc] free() */ + { .cpu = 2, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, }, + /* perf [perf] main() */ + { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, + /* perf [kernel] page_fault() */ + { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, + /* bash [bash] main() */ + { .cpu = 3, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, }, + /* bash [bash] xmalloc() */ + { .cpu = 0, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, }, + /* bash [kernel] page_fault() */ + { .cpu = 1, .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, }, +}; + +static int add_hist_entries(struct hists *hists, struct machine *machine) +{ + struct addr_location al; + struct perf_evsel *evsel = hists_to_evsel(hists); + struct perf_sample sample = { .period = 100, }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(fake_samples); i++) { + const union perf_event event = { + .header = { + .misc = PERF_RECORD_MISC_USER, + }, + }; + struct hist_entry_iter iter = { + .ops = &hist_iter_normal, + .hide_unresolved = false, + }; + + sample.cpu = fake_samples[i].cpu; + sample.pid = fake_samples[i].pid; + sample.tid = fake_samples[i].pid; + sample.ip = fake_samples[i].ip; + + if (perf_event__preprocess_sample(&event, machine, &al, + &sample) < 0) + goto out; + + if (hist_entry_iter__add(&iter, &al, evsel, &sample, + PERF_MAX_STACK_DEPTH, NULL) < 0) + goto out; + + fake_samples[i].thread = al.thread; + fake_samples[i].map = al.map; + fake_samples[i].sym = al.sym; + } + + return TEST_OK; + +out: + pr_debug("Not enough memory for adding a hist entry\n"); + return TEST_FAIL; +} + +static void del_hist_entries(struct hists *hists) +{ + struct hist_entry *he; + struct rb_root *root_in; + struct rb_root *root_out; + struct rb_node *node; + + if (sort__need_collapse) + root_in = &hists->entries_collapsed; + else + root_in = hists->entries_in; + + root_out = &hists->entries; + + while (!RB_EMPTY_ROOT(root_out)) { + node = rb_first(root_out); + + he = rb_entry(node, struct hist_entry, rb_node); + rb_erase(node, root_out); + rb_erase(&he->rb_node_in, root_in); + hist_entry__delete(he); + } +} + +typedef int (*test_fn_t)(struct perf_evsel *, struct machine *); + +#define COMM(he) (thread__comm_str(he->thread)) +#define DSO(he) (he->ms.map->dso->short_name) +#define SYM(he) (he->ms.sym->name) +#define CPU(he) (he->cpu) +#define PID(he) (he->thread->tid) + +/* default sort keys (no field) */ +static int test1(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = NULL; + sort_order = NULL; /* equivalent to sort_order = "comm,dso,sym" */ + + setup_sorting(); + + /* + * expected output: + * + * Overhead Command Shared Object Symbol + * ======== ======= ============= ============== + * 20.00% perf perf [.] main + * 10.00% bash [kernel] [k] page_fault + * 10.00% bash bash [.] main + * 10.00% bash bash [.] xmalloc + * 10.00% perf [kernel] [k] page_fault + * 10.00% perf [kernel] [k] schedule + * 10.00% perf libc [.] free + * 10.00% perf libc [.] malloc + * 10.00% perf perf [.] cmd_record + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists, NULL); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &hists->entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "xmalloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "schedule") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "free") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "malloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "cmd_record") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* mixed fields and sort keys */ +static int test2(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "overhead,cpu"; + sort_order = "pid"; + + setup_sorting(); + + /* + * expected output: + * + * Overhead CPU Command: Pid + * ======== === ============= + * 30.00% 1 perf : 100 + * 10.00% 0 perf : 100 + * 10.00% 2 perf : 100 + * 20.00% 2 perf : 200 + * 10.00% 0 bash : 300 + * 10.00% 1 bash : 300 + * 10.00% 3 bash : 300 + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists, NULL); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &hists->entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && he->stat.period == 300); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 100 && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* fields only (no sort key) */ +static int test3(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "comm,overhead,dso"; + sort_order = NULL; + + setup_sorting(); + + /* + * expected output: + * + * Command Overhead Shared Object + * ======= ======== ============= + * bash 20.00% bash + * bash 10.00% [kernel] + * perf 30.00% perf + * perf 20.00% [kernel] + * perf 20.00% libc + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists, NULL); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &hists->entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + he->stat.period == 300); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + he->stat.period == 200); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* handle duplicate 'dso' field */ +static int test4(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "dso,sym,comm,overhead,dso"; + sort_order = "sym"; + + setup_sorting(); + + /* + * expected output: + * + * Shared Object Symbol Command Overhead + * ============= ============== ======= ======== + * perf [.] cmd_record perf 10.00% + * libc [.] free perf 10.00% + * bash [.] main bash 10.00% + * perf [.] main perf 20.00% + * libc [.] malloc perf 10.00% + * [kernel] [k] page_fault bash 10.00% + * [kernel] [k] page_fault perf 10.00% + * [kernel] [k] schedule perf 10.00% + * bash [.] xmalloc bash 10.00% + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists, NULL); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &hists->entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "cmd_record") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "free") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "main") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "main") && + !strcmp(COMM(he), "perf") && he->stat.period == 200); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "malloc") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "schedule") && + !strcmp(COMM(he), "perf") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "xmalloc") && + !strcmp(COMM(he), "bash") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +/* full sort keys w/o overhead field */ +static int test5(struct perf_evsel *evsel, struct machine *machine) +{ + int err; + struct hists *hists = evsel__hists(evsel); + struct hist_entry *he; + struct rb_root *root; + struct rb_node *node; + + field_order = "cpu,pid,comm,dso,sym"; + sort_order = "dso,pid"; + + setup_sorting(); + + /* + * expected output: + * + * CPU Command: Pid Command Shared Object Symbol + * === ============= ======= ============= ============== + * 0 perf: 100 perf [kernel] [k] schedule + * 2 perf: 200 perf [kernel] [k] page_fault + * 1 bash: 300 bash [kernel] [k] page_fault + * 0 bash: 300 bash bash [.] xmalloc + * 3 bash: 300 bash bash [.] main + * 1 perf: 100 perf libc [.] malloc + * 2 perf: 100 perf libc [.] free + * 1 perf: 100 perf perf [.] cmd_record + * 1 perf: 100 perf perf [.] main + * 2 perf: 200 perf perf [.] main + */ + err = add_hist_entries(hists, machine); + if (err < 0) + goto out; + + hists__collapse_resort(hists, NULL); + hists__output_resort(hists, NULL); + + if (verbose > 2) { + pr_info("[fields = %s, sort = %s]\n", field_order, sort_order); + print_hists_out(hists); + } + + root = &hists->entries; + node = rb_first(root); + he = rb_entry(node, struct hist_entry, rb_node); + + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "schedule") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 200 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") && + !strcmp(SYM(he), "page_fault") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 0 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "xmalloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 3 && PID(he) == 300 && + !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "malloc") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") && + !strcmp(SYM(he), "free") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "cmd_record") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 1 && PID(he) == 100 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + + node = rb_next(node); + he = rb_entry(node, struct hist_entry, rb_node); + TEST_ASSERT_VAL("Invalid hist entry", + CPU(he) == 2 && PID(he) == 200 && + !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") && + !strcmp(SYM(he), "main") && he->stat.period == 100); + +out: + del_hist_entries(hists); + reset_output_field(); + return err; +} + +int test__hists_output(void) +{ + int err = TEST_FAIL; + struct machines machines; + struct machine *machine; + struct perf_evsel *evsel; + struct perf_evlist *evlist = perf_evlist__new(); + size_t i; + test_fn_t testcases[] = { + test1, + test2, + test3, + test4, + test5, + }; + + TEST_ASSERT_VAL("No memory", evlist); + + err = parse_events(evlist, "cpu-clock"); + if (err) + goto out; + + machines__init(&machines); + + /* setup threads/dso/map/symbols also */ + machine = setup_fake_machine(&machines); + if (!machine) + goto out; + + if (verbose > 1) + machine__fprintf(machine, stderr); + + evsel = perf_evlist__first(evlist); + + for (i = 0; i < ARRAY_SIZE(testcases); i++) { + err = testcases[i](evsel, machine); + if (err < 0) + break; + } + +out: + /* tear down everything */ + perf_evlist__delete(evlist); + machines__exit(&machines); + + return err; +} diff --git a/kernel/tools/perf/tests/keep-tracking.c b/kernel/tools/perf/tests/keep-tracking.c new file mode 100644 index 000000000..7a5ab7b0b --- /dev/null +++ b/kernel/tools/perf/tests/keep-tracking.c @@ -0,0 +1,152 @@ +#include +#include +#include + +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tests.h" + +#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 find_comm(struct perf_evlist *evlist, const char *comm) +{ + union perf_event *event; + int i, found; + + found = 0; + for (i = 0; i < evlist->nr_mmaps; i++) { + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + if (event->header.type == PERF_RECORD_COMM && + (pid_t)event->comm.pid == getpid() && + (pid_t)event->comm.tid == getpid() && + strcmp(event->comm.comm, comm) == 0) + found += 1; + perf_evlist__mmap_consume(evlist, i); + } + } + return found; +} + +/** + * test__keep_tracking - test using a dummy software event to keep tracking. + * + * This function implements a test that checks that tracking events continue + * 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(void) +{ + struct record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .freq = 4000, + .target = { + .uses_mmap = true, + }, + }; + struct thread_map *threads = NULL; + struct cpu_map *cpus = NULL; + struct perf_evlist *evlist = NULL; + struct perf_evsel *evsel = NULL; + int found, err = -1; + const char *comm; + + threads = thread_map__new(-1, getpid(), UINT_MAX); + CHECK_NOT_NULL__(threads); + + cpus = cpu_map__new(NULL); + CHECK_NOT_NULL__(cpus); + + evlist = perf_evlist__new(); + CHECK_NOT_NULL__(evlist); + + perf_evlist__set_maps(evlist, cpus, threads); + + CHECK__(parse_events(evlist, "dummy:u")); + CHECK__(parse_events(evlist, "cycles:u")); + + perf_evlist__config(evlist, &opts); + + evsel = perf_evlist__first(evlist); + + evsel->attr.comm = 1; + evsel->attr.disabled = 1; + evsel->attr.enable_on_exec = 0; + + if (perf_evlist__open(evlist) < 0) { + fprintf(stderr, " (not supported)"); + err = 0; + goto out_err; + } + + CHECK__(perf_evlist__mmap(evlist, UINT_MAX, false)); + + /* + * First, test that a 'comm' event can be found when the event is + * enabled. + */ + + perf_evlist__enable(evlist); + + comm = "Test COMM 1"; + CHECK__(prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0)); + + perf_evlist__disable(evlist); + + found = find_comm(evlist, comm); + if (found != 1) { + pr_debug("First time, failed to find tracking event.\n"); + goto out_err; + } + + /* + * Secondly, test that a 'comm' event can be found when the event is + * disabled with the dummy event still enabled. + */ + + perf_evlist__enable(evlist); + + evsel = perf_evlist__last(evlist); + + CHECK__(perf_evlist__disable_event(evlist, evsel)); + + comm = "Test COMM 2"; + CHECK__(prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0)); + + perf_evlist__disable(evlist); + + found = find_comm(evlist, comm); + if (found != 1) { + pr_debug("Seconf time, failed to find tracking event.\n"); + goto out_err; + } + + err = 0; + +out_err: + if (evlist) { + perf_evlist__disable(evlist); + perf_evlist__delete(evlist); + } else { + cpu_map__delete(cpus); + thread_map__delete(threads); + } + + return err; +} diff --git a/kernel/tools/perf/tests/kmod-path.c b/kernel/tools/perf/tests/kmod-path.c new file mode 100644 index 000000000..e8d7cbb93 --- /dev/null +++ b/kernel/tools/perf/tests/kmod-path.c @@ -0,0 +1,73 @@ +#include +#include "tests.h" +#include "dso.h" +#include "debug.h" + +static int test(const char *path, bool alloc_name, bool alloc_ext, + bool kmod, bool comp, const char *name, const char *ext) +{ + struct kmod_path m; + + memset(&m, 0x0, sizeof(m)); + + TEST_ASSERT_VAL("kmod_path__parse", + !__kmod_path__parse(&m, path, alloc_name, alloc_ext)); + + pr_debug("%s - alloc name %d, alloc ext %d, kmod %d, comp %d, name '%s', ext '%s'\n", + path, alloc_name, alloc_ext, m.kmod, m.comp, m.name, m.ext); + + TEST_ASSERT_VAL("wrong kmod", m.kmod == kmod); + TEST_ASSERT_VAL("wrong comp", m.comp == comp); + + if (ext) + TEST_ASSERT_VAL("wrong ext", m.ext && !strcmp(ext, m.ext)); + else + TEST_ASSERT_VAL("wrong ext", !m.ext); + + if (name) + TEST_ASSERT_VAL("wrong name", m.name && !strcmp(name, m.name)); + else + TEST_ASSERT_VAL("wrong name", !m.name); + + free(m.name); + free(m.ext); + return 0; +} + +#define T(path, an, ae, k, c, n, e) \ + TEST_ASSERT_VAL("failed", !test(path, an, ae, k, c, n, e)) + +int test__kmod_path__parse(void) +{ + /* path alloc_name alloc_ext kmod comp name ext */ + T("/xxxx/xxxx/x-x.ko", true , true , true, false, "[x_x]", NULL); + T("/xxxx/xxxx/x-x.ko", false , true , true, false, NULL , NULL); + T("/xxxx/xxxx/x-x.ko", true , false , true, false, "[x_x]", NULL); + T("/xxxx/xxxx/x-x.ko", false , false , true, false, NULL , NULL); + + /* path alloc_name alloc_ext kmod comp name ext */ + T("/xxxx/xxxx/x.ko.gz", true , true , true, true, "[x]", "gz"); + T("/xxxx/xxxx/x.ko.gz", false , true , true, true, NULL , "gz"); + T("/xxxx/xxxx/x.ko.gz", true , false , true, true, "[x]", NULL); + T("/xxxx/xxxx/x.ko.gz", false , false , true, true, NULL , NULL); + + /* path alloc_name alloc_ext kmod comp name ext */ + T("/xxxx/xxxx/x.gz", true , true , false, true, "x.gz" ,"gz"); + T("/xxxx/xxxx/x.gz", false , true , false, true, NULL ,"gz"); + T("/xxxx/xxxx/x.gz", true , false , false, true, "x.gz" , NULL); + T("/xxxx/xxxx/x.gz", false , false , false, true, NULL , NULL); + + /* path alloc_name alloc_ext kmod comp name ext */ + T("x.gz", true , true , false, true, "x.gz", "gz"); + T("x.gz", false , true , false, true, NULL , "gz"); + T("x.gz", true , false , false, true, "x.gz", NULL); + T("x.gz", false , false , false, true, NULL , NULL); + + /* path alloc_name alloc_ext kmod comp name ext */ + T("x.ko.gz", true , true , true, true, "[x]", "gz"); + T("x.ko.gz", false , true , true, true, NULL , "gz"); + T("x.ko.gz", true , false , true, true, "[x]", NULL); + T("x.ko.gz", false , false , true, true, NULL , NULL); + + return 0; +} diff --git a/kernel/tools/perf/tests/make b/kernel/tools/perf/tests/make new file mode 100644 index 000000000..bff85324f --- /dev/null +++ b/kernel/tools/perf/tests/make @@ -0,0 +1,232 @@ +PERF := . +MK := Makefile + +include config/Makefile.arch + +# FIXME looks like x86 is the only arch running tests ;-) +# we need some IS_(32/64) flag to make this generic +ifeq ($(ARCH)$(IS_64_BIT), x861) +lib = lib64 +else +lib = lib +endif + +has = $(shell which $1 2>/dev/null) + +# standard single make variable specified +make_clean_all := clean all +make_python_perf_so := python/perf.so +make_debug := DEBUG=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_demangle := NO_DEMANGLE=1 +make_no_libelf := NO_LIBELF=1 +make_no_libunwind := NO_LIBUNWIND=1 +make_no_libdw_dwarf_unwind := NO_LIBDW_DWARF_UNWIND=1 +make_no_backtrace := NO_BACKTRACE=1 +make_no_libnuma := NO_LIBNUMA=1 +make_no_libaudit := NO_LIBAUDIT=1 +make_no_libbionic := NO_LIBBIONIC=1 +make_tags := tags +make_cscope := cscope +make_help := help +make_doc := doc +make_perf_o := perf.o +make_util_map_o := util/map.o +make_util_pmu_bison_o := util/pmu-bison.o +make_install := install +make_install_bin := install-bin +make_install_doc := install-doc +make_install_man := install-man +make_install_html := install-html +make_install_info := install-info +make_install_pdf := install-pdf +make_static := LDFLAGS=-static + +# 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_LIBDW_DWARF_UNWIND=1 + +# $(run) contains all available tests +run := make_pure +run += make_clean_all +run += make_python_perf_so +run += make_debug +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_no_libdw_dwarf_unwind +run += make_no_backtrace +run += make_no_libnuma +run += make_no_libaudit +run += make_no_libbionic +run += make_help +run += make_doc +run += make_perf_o +run += make_util_map_o +run += make_util_pmu_bison_o +run += make_install +run += make_install_bin +# FIXME 'install-*' commented out till they're fixed +# run += make_install_doc +# run += make_install_man +# run += make_install_html +# run += make_install_info +# run += make_install_pdf +run += make_minimal +run += make_static + +ifneq ($(call has,ctags),) +run += make_tags +endif +ifneq ($(call has,cscope),) +run += make_cscope +endif + +# $(run_O) contains same portion of $(run) tests with '_O' attached +# to distinguish O=... tests +run_O := $(addsuffix _O,$(run)) + +# disable some tests for O=... +run_O := $(filter-out make_python_perf_so_O,$(run_O)) + +# define test for each compile as 'test_NAME' variable +# with the test itself as a value +test_make_tags = test -f tags +test_make_cscope = test -f cscope.out + +test_make_tags_O := $(test_make_tags) +test_make_cscope_O := $(test_make_cscope) + +test_ok := true +test_make_help := $(test_ok) +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)/python/perf.so + +test_make_perf_o := test -f $(PERF)/perf.o +test_make_util_map_o := test -f $(PERF)/util/map.o +test_make_util_pmu_bison_o := test -f $(PERF)/util/pmu-bison.o + +define test_dest_files + for file in $(1); do \ + if [ ! -x $$TMP_DEST/$$file ]; then \ + echo " failed to find: $$file"; \ + fi \ + done +endef + +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)) +test_make_install_bin := $(call test_dest_files,$(installed_files_bin)) +test_make_install_bin_O := $(call test_dest_files,$(installed_files_bin)) + +# FIXME nothing gets installed +test_make_install_man := test -f $$TMP_DEST/share/man/man1/perf.1 +test_make_install_man_O := $(test_make_install_man) + +# FIXME nothing gets installed +test_make_install_doc := $(test_ok) +test_make_install_doc_O := $(test_ok) + +# FIXME nothing gets installed +test_make_install_html := $(test_ok) +test_make_install_html_O := $(test_ok) + +# FIXME nothing gets installed +test_make_install_info := $(test_ok) +test_make_install_info_O := $(test_ok) + +# FIXME nothing gets installed +test_make_install_pdf := $(test_ok) +test_make_install_pdf_O := $(test_ok) + +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 +test_make_util_pmu_bison_o_O := test -f $$TMP_O/util/pmu-bison.o + +test_default = test -x $(PERF)/perf +test = $(if $(test_$1),$(test_$1),$(test_default)) + +test_default_O = test -x $$TMP_O/perf +test_O = $(if $(test_$1),$(test_$1),$(test_default_O)) + +all: + +ifdef DEBUG +d := $(info run $(run)) +d := $(info run_O $(run_O)) +endif + +MAKEFLAGS := --no-print-directory + +clean := @(cd $(PERF); make -s -f $(MK) clean >/dev/null) + +$(run): + $(call clean) + @TMP_DEST=$$(mktemp -d); \ + cmd="cd $(PERF) && make -f $(MK) DESTDIR=$$TMP_DEST $($@)"; \ + echo "- $@: $$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); \ + TMP_DEST=$$(mktemp -d); \ + cmd="cd $(PERF) && make -f $(MK) O=$$TMP_O DESTDIR=$$TMP_DEST $($(patsubst %_O,%,$@))"; \ + echo "- $@: $$cmd" && echo $$cmd > $@ && \ + ( eval $$cmd ) >> $@ 2>&1 && \ + echo " test: $(call test_O,$@)" >> $@ 2>&1; \ + $(call test_O,$@) && \ + rm -rf $@ $$TMP_O $$TMP_DEST || (cat $@ ; false) + +tarpkg: + @cmd="$(PERF)/tests/perf-targz-src-pkg $(PERF)"; \ + echo "- $@: $$cmd" && echo $$cmd > $@ && \ + ( eval $$cmd ) >> $@ 2>&1 + +all: $(run) $(run_O) tarpkg + @echo OK + +out: $(run_O) + @echo OK + +.PHONY: all $(run) $(run_O) tarpkg clean diff --git a/kernel/tools/perf/tests/mmap-basic.c b/kernel/tools/perf/tests/mmap-basic.c new file mode 100644 index 000000000..9b9622a33 --- /dev/null +++ b/kernel/tools/perf/tests/mmap-basic.c @@ -0,0 +1,149 @@ +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tests.h" + +/* + * This test will generate random numbers of calls to some getpid syscalls, + * then establish an mmap for a group of events that are created to monitor + * the syscalls. + * + * It will receive the events, using mmap, use its PERF_SAMPLE_ID generated + * sample.id field to map back to its respective perf_evsel instance. + * + * Then it checks if the number of syscalls reported as perf events by + * the kernel corresponds to the number of syscalls made. + */ +int test__basic_mmap(void) +{ + int err = -1; + union perf_event *event; + struct thread_map *threads; + struct cpu_map *cpus; + struct perf_evlist *evlist; + cpu_set_t cpu_set; + const char *syscall_names[] = { "getsid", "getppid", "getpgrp", + "getpgid", }; + pid_t (*syscalls[])(void) = { (void *)getsid, getppid, getpgrp, + (void*)getpgid }; +#define nsyscalls ARRAY_SIZE(syscall_names) + unsigned int nr_events[nsyscalls], + expected_nr_events[nsyscalls], i, j; + struct perf_evsel *evsels[nsyscalls], *evsel; + char sbuf[STRERR_BUFSIZE]; + + threads = thread_map__new(-1, getpid(), UINT_MAX); + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + cpus = cpu_map__new(NULL); + if (cpus == NULL) { + pr_debug("cpu_map__new\n"); + goto out_free_threads; + } + + CPU_ZERO(&cpu_set); + CPU_SET(cpus->map[0], &cpu_set); + sched_setaffinity(0, sizeof(cpu_set), &cpu_set); + if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { + pr_debug("sched_setaffinity() failed on CPU %d: %s ", + cpus->map[0], strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_free_cpus; + } + + evlist = perf_evlist__new(); + if (evlist == NULL) { + pr_debug("perf_evlist__new\n"); + goto out_free_cpus; + } + + perf_evlist__set_maps(evlist, cpus, threads); + + for (i = 0; i < nsyscalls; ++i) { + char name[64]; + + snprintf(name, sizeof(name), "sys_enter_%s", syscall_names[i]); + evsels[i] = perf_evsel__newtp("syscalls", name); + if (evsels[i] == NULL) { + pr_debug("perf_evsel__new\n"); + goto out_delete_evlist; + } + + evsels[i]->attr.wakeup_events = 1; + perf_evsel__set_sample_id(evsels[i], false); + + perf_evlist__add(evlist, evsels[i]); + + if (perf_evsel__open(evsels[i], cpus, threads) < 0) { + pr_debug("failed to open counter: %s, " + "tweak /proc/sys/kernel/perf_event_paranoid?\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + nr_events[i] = 0; + expected_nr_events[i] = 1 + rand() % 127; + } + + if (perf_evlist__mmap(evlist, 128, true) < 0) { + pr_debug("failed to mmap events: %d (%s)\n", errno, + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + for (i = 0; i < nsyscalls; ++i) + for (j = 0; j < expected_nr_events[i]; ++j) { + int foo = syscalls[i](); + ++foo; + } + + while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) { + struct perf_sample sample; + + if (event->header.type != PERF_RECORD_SAMPLE) { + pr_debug("unexpected %s event\n", + perf_event__name(event->header.type)); + goto out_delete_evlist; + } + + err = perf_evlist__parse_sample(evlist, event, &sample); + if (err) { + pr_err("Can't parse sample, err = %d\n", err); + goto out_delete_evlist; + } + + err = -1; + evsel = perf_evlist__id2evsel(evlist, sample.id); + if (evsel == NULL) { + pr_debug("event with id %" PRIu64 + " doesn't map to an evsel\n", sample.id); + goto out_delete_evlist; + } + nr_events[evsel->idx]++; + perf_evlist__mmap_consume(evlist, 0); + } + + err = 0; + evlist__for_each(evlist, evsel) { + if (nr_events[evsel->idx] != expected_nr_events[evsel->idx]) { + pr_debug("expected %d %s events, got %d\n", + expected_nr_events[evsel->idx], + perf_evsel__name(evsel), nr_events[evsel->idx]); + err = -1; + goto out_delete_evlist; + } + } + +out_delete_evlist: + perf_evlist__delete(evlist); + cpus = NULL; + threads = NULL; +out_free_cpus: + cpu_map__delete(cpus); +out_free_threads: + thread_map__delete(threads); + return err; +} diff --git a/kernel/tools/perf/tests/mmap-thread-lookup.c b/kernel/tools/perf/tests/mmap-thread-lookup.c new file mode 100644 index 000000000..2113f1c86 --- /dev/null +++ b/kernel/tools/perf/tests/mmap-thread-lookup.c @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" +#include "tests.h" +#include "machine.h" +#include "thread_map.h" +#include "symbol.h" +#include "thread.h" + +#define THREADS 4 + +static int go_away; + +struct thread_data { + pthread_t pt; + pid_t tid; + void *map; + int ready[2]; +}; + +static struct thread_data threads[THREADS]; + +static int thread_init(struct thread_data *td) +{ + void *map; + + map = mmap(NULL, page_size, + PROT_READ|PROT_WRITE|PROT_EXEC, + MAP_SHARED|MAP_ANONYMOUS, -1, 0); + + if (map == MAP_FAILED) { + perror("mmap failed"); + return -1; + } + + td->map = map; + td->tid = syscall(SYS_gettid); + + pr_debug("tid = %d, map = %p\n", td->tid, map); + return 0; +} + +static void *thread_fn(void *arg) +{ + struct thread_data *td = arg; + ssize_t ret; + int go; + + if (thread_init(td)) + return NULL; + + /* Signal thread_create thread is initialized. */ + ret = write(td->ready[1], &go, sizeof(int)); + if (ret != sizeof(int)) { + pr_err("failed to notify\n"); + return NULL; + } + + while (!go_away) { + /* Waiting for main thread to kill us. */ + usleep(100); + } + + munmap(td->map, page_size); + return NULL; +} + +static int thread_create(int i) +{ + struct thread_data *td = &threads[i]; + int err, go; + + if (pipe(td->ready)) + return -1; + + err = pthread_create(&td->pt, NULL, thread_fn, td); + if (!err) { + /* Wait for thread initialization. */ + ssize_t ret = read(td->ready[0], &go, sizeof(int)); + err = ret != sizeof(int); + } + + close(td->ready[0]); + close(td->ready[1]); + return err; +} + +static int threads_create(void) +{ + struct thread_data *td0 = &threads[0]; + int i, err = 0; + + go_away = 0; + + /* 0 is main thread */ + if (thread_init(td0)) + return -1; + + for (i = 1; !err && i < THREADS; i++) + err = thread_create(i); + + return err; +} + +static int threads_destroy(void) +{ + struct thread_data *td0 = &threads[0]; + int i, err = 0; + + /* cleanup the main thread */ + munmap(td0->map, page_size); + + go_away = 1; + + for (i = 1; !err && i < THREADS; i++) + err = pthread_join(threads[i].pt, NULL); + + return err; +} + +typedef int (*synth_cb)(struct machine *machine); + +static int synth_all(struct machine *machine) +{ + return perf_event__synthesize_threads(NULL, + perf_event__process, + machine, 0); +} + +static int synth_process(struct machine *machine) +{ + struct thread_map *map; + int err; + + map = thread_map__new_by_pid(getpid()); + + err = perf_event__synthesize_thread_map(NULL, map, + perf_event__process, + machine, 0); + + thread_map__delete(map); + return err; +} + +static int mmap_events(synth_cb synth) +{ + struct machines machines; + struct machine *machine; + int err, i; + + /* + * The threads_create will not return before all threads + * are spawned and all created memory map. + * + * They will loop until threads_destroy is called, so we + * can safely run synthesizing function. + */ + TEST_ASSERT_VAL("failed to create threads", !threads_create()); + + machines__init(&machines); + machine = &machines.host; + + dump_trace = verbose > 1 ? 1 : 0; + + err = synth(machine); + + dump_trace = 0; + + TEST_ASSERT_VAL("failed to destroy threads", !threads_destroy()); + TEST_ASSERT_VAL("failed to synthesize maps", !err); + + /* + * All data is synthesized, try to find map for each + * thread object. + */ + for (i = 0; i < THREADS; i++) { + struct thread_data *td = &threads[i]; + struct addr_location al; + struct thread *thread; + + thread = machine__findnew_thread(machine, getpid(), td->tid); + + pr_debug("looking for map %p\n", td->map); + + thread__find_addr_map(thread, + PERF_RECORD_MISC_USER, MAP__FUNCTION, + (unsigned long) (td->map + 1), &al); + + if (!al.map) { + pr_debug("failed, couldn't find map\n"); + err = -1; + break; + } + + pr_debug("map %p, addr %" PRIx64 "\n", al.map, al.map->start); + } + + machine__delete_threads(machine); + machines__exit(&machines); + return err; +} + +/* + * This test creates 'THREADS' number of threads (including + * main thread) and each thread creates memory map. + * + * When threads are created, we synthesize them with both + * (separate tests): + * perf_event__synthesize_thread_map (process based) + * perf_event__synthesize_threads (global) + * + * We test we can find all memory maps via: + * thread__find_addr_map + * + * by using all thread objects. + */ +int test__mmap_thread_lookup(void) +{ + /* perf_event__synthesize_threads synthesize */ + TEST_ASSERT_VAL("failed with sythesizing all", + !mmap_events(synth_all)); + + /* perf_event__synthesize_thread_map synthesize */ + TEST_ASSERT_VAL("failed with sythesizing process", + !mmap_events(synth_process)); + + return 0; +} diff --git a/kernel/tools/perf/tests/open-syscall-all-cpus.c b/kernel/tools/perf/tests/open-syscall-all-cpus.c new file mode 100644 index 000000000..3ec885c48 --- /dev/null +++ b/kernel/tools/perf/tests/open-syscall-all-cpus.c @@ -0,0 +1,115 @@ +#include "evsel.h" +#include "tests.h" +#include "thread_map.h" +#include "cpumap.h" +#include "debug.h" + +int test__open_syscall_event_on_all_cpus(void) +{ + int err = -1, fd, cpu; + struct cpu_map *cpus; + struct perf_evsel *evsel; + unsigned int nr_open_calls = 111, i; + cpu_set_t cpu_set; + struct thread_map *threads = thread_map__new(-1, getpid(), UINT_MAX); + char sbuf[STRERR_BUFSIZE]; + + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + cpus = cpu_map__new(NULL); + if (cpus == NULL) { + pr_debug("cpu_map__new\n"); + goto out_thread_map_delete; + } + + CPU_ZERO(&cpu_set); + + evsel = perf_evsel__newtp("syscalls", "sys_enter_open"); + if (evsel == NULL) { + if (tracefs_configured()) + pr_debug("is tracefs mounted on /sys/kernel/tracing?\n"); + else if (debugfs_configured()) + pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); + else + pr_debug("Neither tracefs or debugfs is enabled in this kernel\n"); + goto out_thread_map_delete; + } + + if (perf_evsel__open(evsel, cpus, threads) < 0) { + pr_debug("failed to open counter: %s, " + "tweak /proc/sys/kernel/perf_event_paranoid?\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_evsel_delete; + } + + for (cpu = 0; cpu < cpus->nr; ++cpu) { + unsigned int ncalls = nr_open_calls + cpu; + /* + * XXX eventually lift this restriction in a way that + * keeps perf building on older glibc installations + * without CPU_ALLOC. 1024 cpus in 2010 still seems + * a reasonable upper limit tho :-) + */ + if (cpus->map[cpu] >= CPU_SETSIZE) { + pr_debug("Ignoring CPU %d\n", cpus->map[cpu]); + continue; + } + + CPU_SET(cpus->map[cpu], &cpu_set); + if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { + pr_debug("sched_setaffinity() failed on CPU %d: %s ", + cpus->map[cpu], + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_close_fd; + } + for (i = 0; i < ncalls; ++i) { + fd = open("/etc/passwd", O_RDONLY); + close(fd); + } + CPU_CLR(cpus->map[cpu], &cpu_set); + } + + /* + * Here we need to explicitely preallocate the counts, as if + * we use the auto allocation it will allocate just for 1 cpu, + * as we start by cpu 0. + */ + if (perf_evsel__alloc_counts(evsel, cpus->nr) < 0) { + pr_debug("perf_evsel__alloc_counts(ncpus=%d)\n", cpus->nr); + goto out_close_fd; + } + + err = 0; + + for (cpu = 0; cpu < cpus->nr; ++cpu) { + unsigned int expected; + + if (cpus->map[cpu] >= CPU_SETSIZE) + continue; + + if (perf_evsel__read_on_cpu(evsel, cpu, 0) < 0) { + pr_debug("perf_evsel__read_on_cpu\n"); + err = -1; + break; + } + + expected = nr_open_calls + cpu; + if (evsel->counts->cpu[cpu].val != expected) { + pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %" PRIu64 "\n", + expected, cpus->map[cpu], evsel->counts->cpu[cpu].val); + err = -1; + } + } + + perf_evsel__free_counts(evsel); +out_close_fd: + perf_evsel__close_fd(evsel, 1, threads->nr); +out_evsel_delete: + perf_evsel__delete(evsel); +out_thread_map_delete: + thread_map__delete(threads); + return err; +} diff --git a/kernel/tools/perf/tests/open-syscall-tp-fields.c b/kernel/tools/perf/tests/open-syscall-tp-fields.c new file mode 100644 index 000000000..127dcae0b --- /dev/null +++ b/kernel/tools/perf/tests/open-syscall-tp-fields.c @@ -0,0 +1,121 @@ +#include "perf.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "tests.h" +#include "debug.h" + +int test__syscall_open_tp_fields(void) +{ + struct record_opts opts = { + .target = { + .uid = UINT_MAX, + .uses_mmap = true, + }, + .no_buffering = true, + .freq = 1, + .mmap_pages = 256, + .raw_samples = true, + }; + const char *filename = "/etc/passwd"; + int flags = O_RDONLY | O_DIRECTORY; + struct perf_evlist *evlist = perf_evlist__new(); + struct perf_evsel *evsel; + int err = -1, i, nr_events = 0, nr_polls = 0; + char sbuf[STRERR_BUFSIZE]; + + if (evlist == NULL) { + pr_debug("%s: perf_evlist__new\n", __func__); + goto out; + } + + evsel = perf_evsel__newtp("syscalls", "sys_enter_open"); + if (evsel == NULL) { + pr_debug("%s: perf_evsel__newtp\n", __func__); + goto out_delete_evlist; + } + + perf_evlist__add(evlist, evsel); + + err = perf_evlist__create_maps(evlist, &opts.target); + if (err < 0) { + pr_debug("%s: perf_evlist__create_maps\n", __func__); + goto out_delete_evlist; + } + + perf_evsel__config(evsel, &opts); + + evlist->threads->map[0] = getpid(); + + err = perf_evlist__open(evlist); + if (err < 0) { + pr_debug("perf_evlist__open: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + err = perf_evlist__mmap(evlist, UINT_MAX, false); + if (err < 0) { + pr_debug("perf_evlist__mmap: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + perf_evlist__enable(evlist); + + /* + * Generate the event: + */ + open(filename, flags); + + while (1) { + int before = nr_events; + + for (i = 0; i < evlist->nr_mmaps; i++) { + union perf_event *event; + + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + const u32 type = event->header.type; + int tp_flags; + struct perf_sample sample; + + ++nr_events; + + if (type != PERF_RECORD_SAMPLE) { + perf_evlist__mmap_consume(evlist, i); + continue; + } + + err = perf_evsel__parse_sample(evsel, event, &sample); + if (err) { + pr_err("Can't parse sample, err = %d\n", err); + goto out_delete_evlist; + } + + tp_flags = perf_evsel__intval(evsel, &sample, "flags"); + + if (flags != tp_flags) { + pr_debug("%s: Expected flags=%#x, got %#x\n", + __func__, flags, tp_flags); + goto out_delete_evlist; + } + + goto out_ok; + } + } + + if (nr_events == before) + perf_evlist__poll(evlist, 10); + + if (++nr_polls > 5) { + pr_debug("%s: no events!\n", __func__); + goto out_delete_evlist; + } + } +out_ok: + err = 0; +out_delete_evlist: + perf_evlist__delete(evlist); +out: + return err; +} diff --git a/kernel/tools/perf/tests/open-syscall.c b/kernel/tools/perf/tests/open-syscall.c new file mode 100644 index 000000000..07aa319bf --- /dev/null +++ b/kernel/tools/perf/tests/open-syscall.c @@ -0,0 +1,61 @@ +#include "thread_map.h" +#include "evsel.h" +#include "debug.h" +#include "tests.h" + +int test__open_syscall_event(void) +{ + int err = -1, fd; + struct perf_evsel *evsel; + unsigned int nr_open_calls = 111, i; + struct thread_map *threads = thread_map__new(-1, getpid(), UINT_MAX); + char sbuf[STRERR_BUFSIZE]; + + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + evsel = perf_evsel__newtp("syscalls", "sys_enter_open"); + if (evsel == NULL) { + if (tracefs_configured()) + pr_debug("is tracefs mounted on /sys/kernel/tracing?\n"); + else if (debugfs_configured()) + pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); + else + pr_debug("Neither tracefs or debugfs is enabled in this kernel\n"); + goto out_thread_map_delete; + } + + if (perf_evsel__open_per_thread(evsel, threads) < 0) { + pr_debug("failed to open counter: %s, " + "tweak /proc/sys/kernel/perf_event_paranoid?\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_evsel_delete; + } + + for (i = 0; i < nr_open_calls; ++i) { + fd = open("/etc/passwd", O_RDONLY); + close(fd); + } + + if (perf_evsel__read_on_cpu(evsel, 0, 0) < 0) { + pr_debug("perf_evsel__read_on_cpu\n"); + goto out_close_fd; + } + + if (evsel->counts->cpu[0].val != nr_open_calls) { + pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls, got %" PRIu64 "\n", + nr_open_calls, evsel->counts->cpu[0].val); + goto out_close_fd; + } + + err = 0; +out_close_fd: + perf_evsel__close_fd(evsel, 1, threads->nr); +out_evsel_delete: + perf_evsel__delete(evsel); +out_thread_map_delete: + thread_map__delete(threads); + return err; +} diff --git a/kernel/tools/perf/tests/parse-events.c b/kernel/tools/perf/tests/parse-events.c new file mode 100644 index 000000000..3de744961 --- /dev/null +++ b/kernel/tools/perf/tests/parse-events.c @@ -0,0 +1,1734 @@ + +#include "parse-events.h" +#include "evsel.h" +#include "evlist.h" +#include +#include +#include +#include "tests.h" +#include "debug.h" +#include + +#define PERF_TP_SAMPLE_TYPE (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | \ + PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD) + +static int test__checkevent_tracepoint(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 0 == evlist->nr_groups); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong sample_type", + PERF_TP_SAMPLE_TYPE == evsel->attr.sample_type); + TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period); + return 0; +} + +static int test__checkevent_tracepoint_multi(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel; + + TEST_ASSERT_VAL("wrong number of entries", evlist->nr_entries > 1); + TEST_ASSERT_VAL("wrong number of groups", 0 == evlist->nr_groups); + + evlist__for_each(evlist, evsel) { + TEST_ASSERT_VAL("wrong type", + PERF_TYPE_TRACEPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong sample_type", + PERF_TP_SAMPLE_TYPE == evsel->attr.sample_type); + TEST_ASSERT_VAL("wrong sample_period", + 1 == evsel->attr.sample_period); + } + return 0; +} + +static int test__checkevent_raw(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0x1a == evsel->attr.config); + return 0; +} + +static int test__checkevent_numeric(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + return 0; +} + +static int test__checkevent_symbolic_name(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + return 0; +} + +static int test__checkevent_symbolic_name_config(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + TEST_ASSERT_VAL("wrong period", + 100000 == evsel->attr.sample_period); + TEST_ASSERT_VAL("wrong config1", + 0 == evsel->attr.config1); + TEST_ASSERT_VAL("wrong config2", + 1 == evsel->attr.config2); + return 0; +} + +static int test__checkevent_symbolic_alias(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_SW_PAGE_FAULTS == evsel->attr.config); + return 0; +} + +static int test__checkevent_genhw(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HW_CACHE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", (1 << 16) == evsel->attr.config); + return 0; +} + +static int test__checkevent_breakpoint(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", (HW_BREAKPOINT_R | HW_BREAKPOINT_W) == + evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_4 == + evsel->attr.bp_len); + return 0; +} + +static int test__checkevent_breakpoint_x(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", + HW_BREAKPOINT_X == evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", sizeof(long) == evsel->attr.bp_len); + return 0; +} + +static int test__checkevent_breakpoint_r(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", + PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", + HW_BREAKPOINT_R == evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", + HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len); + return 0; +} + +static int test__checkevent_breakpoint_w(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", + PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", + HW_BREAKPOINT_W == evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", + HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len); + return 0; +} + +static int test__checkevent_breakpoint_rw(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", + PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", + (HW_BREAKPOINT_R|HW_BREAKPOINT_W) == evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", + HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len); + return 0; +} + +static int test__checkevent_tracepoint_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + + return test__checkevent_tracepoint(evlist); +} + +static int +test__checkevent_tracepoint_multi_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel; + + TEST_ASSERT_VAL("wrong number of entries", evlist->nr_entries > 1); + + evlist__for_each(evlist, 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); +} + +static int test__checkevent_raw_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + + return test__checkevent_raw(evlist); +} + +static int test__checkevent_numeric_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + + return test__checkevent_numeric(evlist); +} + +static int test__checkevent_symbolic_name_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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 perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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 perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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_symbolic_alias_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + + return test__checkevent_symbolic_alias(evlist); +} + +static int test__checkevent_genhw_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + + return test__checkevent_genhw(evlist); +} + +static int test__checkevent_exclude_idle_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude idle", evsel->attr.exclude_idle); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + 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_idle_modifier_1(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude idle", evsel->attr.exclude_idle); + TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + 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_breakpoint_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + + 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); + TEST_ASSERT_VAL("wrong name", + !strcmp(perf_evsel__name(evsel), "mem:0:u")); + + return test__checkevent_breakpoint(evlist); +} + +static int test__checkevent_breakpoint_x_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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); + TEST_ASSERT_VAL("wrong name", + !strcmp(perf_evsel__name(evsel), "mem:0:x:k")); + + return test__checkevent_breakpoint_x(evlist); +} + +static int test__checkevent_breakpoint_r_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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); + TEST_ASSERT_VAL("wrong name", + !strcmp(perf_evsel__name(evsel), "mem:0:r:hp")); + + return test__checkevent_breakpoint_r(evlist); +} + +static int test__checkevent_breakpoint_w_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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); + TEST_ASSERT_VAL("wrong name", + !strcmp(perf_evsel__name(evsel), "mem:0:w:up")); + + return test__checkevent_breakpoint_w(evlist); +} + +static int test__checkevent_breakpoint_rw_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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); + TEST_ASSERT_VAL("wrong name", + !strcmp(perf_evsel__name(evsel), "mem:0:rw:kp")); + + return test__checkevent_breakpoint_rw(evlist); +} + +static int test__checkevent_pmu(struct perf_evlist *evlist) +{ + + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 10 == evsel->attr.config); + TEST_ASSERT_VAL("wrong config1", 1 == evsel->attr.config1); + TEST_ASSERT_VAL("wrong config2", 3 == evsel->attr.config2); + TEST_ASSERT_VAL("wrong period", 1000 == evsel->attr.sample_period); + + return 0; +} + +static int test__checkevent_list(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries); + + /* r1 */ + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + TEST_ASSERT_VAL("wrong config1", 0 == evsel->attr.config1); + TEST_ASSERT_VAL("wrong config2", 0 == evsel->attr.config2); + 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); + + /* syscalls:sys_enter_open:k */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong sample_type", + PERF_TP_SAMPLE_TYPE == evsel->attr.sample_type); + TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period); + 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); + + /* 1:1:hp */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + 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 0; +} + +static int test__checkevent_pmu_name(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + /* cpu/config=1,name=krava/u */ + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); + TEST_ASSERT_VAL("wrong name", !strcmp(perf_evsel__name(evsel), "krava")); + + /* cpu/config=2/u" */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 2 == evsel->attr.config); + TEST_ASSERT_VAL("wrong name", + !strcmp(perf_evsel__name(evsel), "cpu/config=2/u")); + + return 0; +} + +static int test__checkevent_pmu_events(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + 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); + TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned); + + return 0; +} + + +static int test__checkevent_pmu_events_mix(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + /* pmu-event:u */ + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + 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); + TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned); + + /* cpu/pmu-event/u*/ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); + 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); + TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned); + + return 0; +} + +static int test__checkterms_simple(struct list_head *terms) +{ + struct parse_events_term *term; + + /* config=10 */ + term = list_entry(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); + + /* config1 */ + term = list_entry(term->list.next, struct parse_events_term, list); + TEST_ASSERT_VAL("wrong type term", + term->type_term == PARSE_EVENTS__TERM_TYPE_CONFIG1); + 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); + + /* config2=3 */ + term = list_entry(term->list.next, struct parse_events_term, list); + TEST_ASSERT_VAL("wrong type term", + term->type_term == PARSE_EVENTS__TERM_TYPE_CONFIG2); + 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); + + /* umask=1*/ + term = list_entry(term->list.next, struct parse_events_term, list); + TEST_ASSERT_VAL("wrong type term", + term->type_term == PARSE_EVENTS__TERM_TYPE_USER); + 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", !strcmp(term->config, "umask")); + + return 0; +} + +static int test__group1(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + + /* instructions:k */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* cycles:upp */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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); + /* use of precise requires exclude_guest */ + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 2); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + return 0; +} + +static int test__group2(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + + /* faults + :ku modifier */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_SW_PAGE_FAULTS == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* cache-references + :u modifier */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_REFERENCES == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* cycles:k */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + return 0; +} + +static int test__group3(struct perf_evlist *evlist __maybe_unused) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 5 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 2 == evlist->nr_groups); + + /* group1 syscalls:sys_enter_open:H */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong sample_type", + PERF_TP_SAMPLE_TYPE == evsel->attr.sample_type); + TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong group name", + !strcmp(leader->group_name, "group1")); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* group1 cycles:kppp */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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); + /* use of precise requires exclude_guest */ + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 3); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* group2 cycles + G modifier */ + evsel = leader = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong group name", + !strcmp(leader->group_name, "group2")); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* group2 1:3 + G modifier */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 3 == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* instructions:u */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + return 0; +} + +static int test__group4(struct perf_evlist *evlist __maybe_unused) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + + /* cycles:u + p */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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); + /* use of precise requires exclude_guest */ + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 1); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* instructions:kp + p */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + 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); + /* use of precise requires exclude_guest */ + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 2); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + return 0; +} + +static int test__group5(struct perf_evlist *evlist __maybe_unused) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 5 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 2 == evlist->nr_groups); + + /* cycles + G */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* instructions + G */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* cycles:G */ + evsel = leader = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); + + /* instructions:G */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + + /* cycles */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + + return 0; +} + +static int test__group_gh1(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + + /* cycles + :H group modifier */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + + /* cache-misses:G + :H group modifier */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + + return 0; +} + +static int test__group_gh2(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + + /* cycles + :G group modifier */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + + /* cache-misses:H + :G group modifier */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + + return 0; +} + +static int test__group_gh3(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + + /* cycles:G + :u group modifier */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + + /* cache-misses:H + :u group modifier */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + + return 0; +} + +static int test__group_gh4(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups); + + /* cycles:G + :uG group modifier */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + + /* cache-misses:H + :uG group modifier */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config); + 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 exclude guest", !evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + + return 0; +} + +static int test__leader_sample1(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries); + + /* cycles - sampling group leader */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + /* cache-misses - not sampling */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + /* branch-misses - not sampling */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + return 0; +} + +static int test__leader_sample2(struct perf_evlist *evlist __maybe_unused) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + + /* instructions - sampling group leader */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + /* branch-misses - not sampling */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config); + 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 exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + return 0; +} + +static int test__checkevent_pinned_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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); + TEST_ASSERT_VAL("wrong pinned", evsel->attr.pinned); + + return test__checkevent_symbolic_name(evlist); +} + +static int test__pinned_group(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries); + + /* cycles - group leader */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->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->attr.pinned); + + /* cache-misses - can not be pinned, but will go on with the leader */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config); + TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned); + + /* branch-misses - ditto */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config); + TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned); + + return 0; +} + +static int test__checkevent_breakpoint_len(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", (HW_BREAKPOINT_R | HW_BREAKPOINT_W) == + evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_1 == + evsel->attr.bp_len); + + return 0; +} + +static int test__checkevent_breakpoint_len_w(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); + TEST_ASSERT_VAL("wrong bp_type", HW_BREAKPOINT_W == + evsel->attr.bp_type); + TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_2 == + evsel->attr.bp_len); + + return 0; +} + +static int +test__checkevent_breakpoint_len_rw_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + 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_breakpoint_rw(evlist); +} + +static int count_tracepoints(void) +{ + char events_path[PATH_MAX]; + struct dirent *events_ent; + const char *mountpoint; + DIR *events_dir; + int cnt = 0; + + mountpoint = tracefs_find_mountpoint(); + if (mountpoint) { + scnprintf(events_path, PATH_MAX, "%s/events", + mountpoint); + } else { + mountpoint = debugfs_find_mountpoint(); + scnprintf(events_path, PATH_MAX, "%s/tracing/events", + mountpoint); + } + + events_dir = opendir(events_path); + + TEST_ASSERT_VAL("Can't open events dir", events_dir); + + while ((events_ent = readdir(events_dir))) { + char sys_path[PATH_MAX]; + struct dirent *sys_ent; + DIR *sys_dir; + + if (!strcmp(events_ent->d_name, ".") + || !strcmp(events_ent->d_name, "..") + || !strcmp(events_ent->d_name, "enable") + || !strcmp(events_ent->d_name, "header_event") + || !strcmp(events_ent->d_name, "header_page")) + continue; + + scnprintf(sys_path, PATH_MAX, "%s/%s", + events_path, events_ent->d_name); + + sys_dir = opendir(sys_path); + TEST_ASSERT_VAL("Can't open sys dir", sys_dir); + + while ((sys_ent = readdir(sys_dir))) { + if (!strcmp(sys_ent->d_name, ".") + || !strcmp(sys_ent->d_name, "..") + || !strcmp(sys_ent->d_name, "enable") + || !strcmp(sys_ent->d_name, "filter")) + continue; + + cnt++; + } + + closedir(sys_dir); + } + + closedir(events_dir); + return cnt; +} + +static int test__all_tracepoints(struct perf_evlist *evlist) +{ + TEST_ASSERT_VAL("wrong events count", + count_tracepoints() == evlist->nr_entries); + + return test__checkevent_tracepoint_multi(evlist); +} + +struct evlist_test { + const char *name; + __u32 type; + const int id; + int (*check)(struct perf_evlist *evlist); +}; + +static struct evlist_test test__events[] = { + { + .name = "syscalls:sys_enter_open", + .check = test__checkevent_tracepoint, + .id = 0, + }, + { + .name = "syscalls:*", + .check = test__checkevent_tracepoint_multi, + .id = 1, + }, + { + .name = "r1a", + .check = test__checkevent_raw, + .id = 2, + }, + { + .name = "1:1", + .check = test__checkevent_numeric, + .id = 3, + }, + { + .name = "instructions", + .check = test__checkevent_symbolic_name, + .id = 4, + }, + { + .name = "cycles/period=100000,config2/", + .check = test__checkevent_symbolic_name_config, + .id = 5, + }, + { + .name = "faults", + .check = test__checkevent_symbolic_alias, + .id = 6, + }, + { + .name = "L1-dcache-load-miss", + .check = test__checkevent_genhw, + .id = 7, + }, + { + .name = "mem:0", + .check = test__checkevent_breakpoint, + .id = 8, + }, + { + .name = "mem:0:x", + .check = test__checkevent_breakpoint_x, + .id = 9, + }, + { + .name = "mem:0:r", + .check = test__checkevent_breakpoint_r, + .id = 10, + }, + { + .name = "mem:0:w", + .check = test__checkevent_breakpoint_w, + .id = 11, + }, + { + .name = "syscalls:sys_enter_open:k", + .check = test__checkevent_tracepoint_modifier, + .id = 12, + }, + { + .name = "syscalls:*:u", + .check = test__checkevent_tracepoint_multi_modifier, + .id = 13, + }, + { + .name = "r1a:kp", + .check = test__checkevent_raw_modifier, + .id = 14, + }, + { + .name = "1:1:hp", + .check = test__checkevent_numeric_modifier, + .id = 15, + }, + { + .name = "instructions:h", + .check = test__checkevent_symbolic_name_modifier, + .id = 16, + }, + { + .name = "faults:u", + .check = test__checkevent_symbolic_alias_modifier, + .id = 17, + }, + { + .name = "L1-dcache-load-miss:kp", + .check = test__checkevent_genhw_modifier, + .id = 18, + }, + { + .name = "mem:0:u", + .check = test__checkevent_breakpoint_modifier, + .id = 19, + }, + { + .name = "mem:0:x:k", + .check = test__checkevent_breakpoint_x_modifier, + .id = 20, + }, + { + .name = "mem:0:r:hp", + .check = test__checkevent_breakpoint_r_modifier, + .id = 21, + }, + { + .name = "mem:0:w:up", + .check = test__checkevent_breakpoint_w_modifier, + .id = 22, + }, + { + .name = "r1,syscalls:sys_enter_open:k,1:1:hp", + .check = test__checkevent_list, + .id = 23, + }, + { + .name = "instructions:G", + .check = test__checkevent_exclude_host_modifier, + .id = 24, + }, + { + .name = "instructions:H", + .check = test__checkevent_exclude_guest_modifier, + .id = 25, + }, + { + .name = "mem:0:rw", + .check = test__checkevent_breakpoint_rw, + .id = 26, + }, + { + .name = "mem:0:rw:kp", + .check = test__checkevent_breakpoint_rw_modifier, + .id = 27, + }, + { + .name = "{instructions:k,cycles:upp}", + .check = test__group1, + .id = 28, + }, + { + .name = "{faults:k,cache-references}:u,cycles:k", + .check = test__group2, + .id = 29, + }, + { + .name = "group1{syscalls:sys_enter_open:H,cycles:kppp},group2{cycles,1:3}:G,instructions:u", + .check = test__group3, + .id = 30, + }, + { + .name = "{cycles:u,instructions:kp}:p", + .check = test__group4, + .id = 31, + }, + { + .name = "{cycles,instructions}:G,{cycles:G,instructions:G},cycles", + .check = test__group5, + .id = 32, + }, + { + .name = "*:*", + .check = test__all_tracepoints, + .id = 33, + }, + { + .name = "{cycles,cache-misses:G}:H", + .check = test__group_gh1, + .id = 34, + }, + { + .name = "{cycles,cache-misses:H}:G", + .check = test__group_gh2, + .id = 35, + }, + { + .name = "{cycles:G,cache-misses:H}:u", + .check = test__group_gh3, + .id = 36, + }, + { + .name = "{cycles:G,cache-misses:H}:uG", + .check = test__group_gh4, + .id = 37, + }, + { + .name = "{cycles,cache-misses,branch-misses}:S", + .check = test__leader_sample1, + .id = 38, + }, + { + .name = "{instructions,branch-misses}:Su", + .check = test__leader_sample2, + .id = 39, + }, + { + .name = "instructions:uDp", + .check = test__checkevent_pinned_modifier, + .id = 40, + }, + { + .name = "{cycles,cache-misses,branch-misses}:D", + .check = test__pinned_group, + .id = 41, + }, + { + .name = "mem:0/1", + .check = test__checkevent_breakpoint_len, + .id = 42, + }, + { + .name = "mem:0/2:w", + .check = test__checkevent_breakpoint_len_w, + .id = 43, + }, + { + .name = "mem:0/4:rw:u", + .check = test__checkevent_breakpoint_len_rw_modifier, + .id = 44 + }, +#if defined(__s390x__) + { + .name = "kvm-s390:kvm_s390_create_vm", + .check = test__checkevent_tracepoint, + .id = 100, + }, +#endif + { + .name = "instructions:I", + .check = test__checkevent_exclude_idle_modifier, + .id = 45, + }, + { + .name = "instructions:kIG", + .check = test__checkevent_exclude_idle_modifier_1, + .id = 46, + }, +}; + +static struct evlist_test test__events_pmu[] = { + { + .name = "cpu/config=10,config1,config2=3,period=1000/u", + .check = test__checkevent_pmu, + .id = 0, + }, + { + .name = "cpu/config=1,name=krava/u,cpu/config=2/u", + .check = test__checkevent_pmu_name, + .id = 1, + }, +}; + +struct terms_test { + const char *str; + __u32 type; + int (*check)(struct list_head *terms); +}; + +static struct terms_test test__terms[] = { + [0] = { + .str = "config=10,config1,config2=3,umask=1", + .check = test__checkterms_simple, + }, +}; + +static int test_event(struct evlist_test *e) +{ + struct perf_evlist *evlist; + int ret; + + evlist = perf_evlist__new(); + if (evlist == NULL) + return -ENOMEM; + + ret = parse_events(evlist, e->name); + if (ret) { + pr_debug("failed to parse event '%s', err %d\n", + e->name, ret); + } else { + ret = e->check(evlist); + } + + perf_evlist__delete(evlist); + + return ret; +} + +static int test_events(struct evlist_test *events, unsigned cnt) +{ + int ret1, ret2 = 0; + unsigned i; + + for (i = 0; i < cnt; i++) { + struct evlist_test *e = &events[i]; + + pr_debug("running test %d '%s'\n", e->id, e->name); + ret1 = test_event(e); + if (ret1) + ret2 = ret1; + } + + return ret2; +} + +static int test_term(struct terms_test *t) +{ + struct list_head terms; + int ret; + + INIT_LIST_HEAD(&terms); + + ret = parse_events_terms(&terms, t->str); + if (ret) { + pr_debug("failed to parse terms '%s', err %d\n", + t->str , ret); + return ret; + } + + ret = t->check(&terms); + parse_events__free_terms(&terms); + + return ret; +} + +static int test_terms(struct terms_test *terms, unsigned cnt) +{ + int ret = 0; + unsigned i; + + for (i = 0; i < cnt; i++) { + struct terms_test *t = &terms[i]; + + pr_debug("running test %d '%s'\n", i, t->str); + ret = test_term(t); + if (ret) + break; + } + + return ret; +} + +static int test_pmu(void) +{ + struct stat st; + char path[PATH_MAX]; + int ret; + + snprintf(path, PATH_MAX, "%s/bus/event_source/devices/cpu/format/", + sysfs__mountpoint()); + + ret = stat(path, &st); + if (ret) + pr_debug("omitting PMU cpu tests\n"); + return !ret; +} + +static int test_pmu_events(void) +{ + struct stat st; + char path[PATH_MAX]; + struct dirent *ent; + DIR *dir; + int ret; + + 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; + } + + dir = opendir(path); + if (!dir) { + pr_debug("can't open pmu event dir"); + return -1; + } + + while (!ret && (ent = readdir(dir))) { +#define MAX_NAME 100 + struct evlist_test e; + char name[MAX_NAME]; + + if (!strcmp(ent->d_name, ".") || + !strcmp(ent->d_name, "..")) + continue; + + snprintf(name, MAX_NAME, "cpu/event=%s/u", ent->d_name); + + e.name = name; + e.check = test__checkevent_pmu_events; + + ret = test_event(&e); + if (ret) + break; + snprintf(name, MAX_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); +#undef MAX_NAME + } + + closedir(dir); + return ret; +} + +int test__parse_events(void) +{ + int ret1, ret2 = 0; + +#define TEST_EVENTS(tests) \ +do { \ + ret1 = test_events(tests, ARRAY_SIZE(tests)); \ + if (!ret2) \ + ret2 = ret1; \ +} while (0) + + TEST_EVENTS(test__events); + + if (test_pmu()) + TEST_EVENTS(test__events_pmu); + + if (test_pmu()) { + int ret = test_pmu_events(); + if (ret) + return ret; + } + + ret1 = test_terms(test__terms, ARRAY_SIZE(test__terms)); + if (!ret2) + ret2 = ret1; + + return ret2; +} diff --git a/kernel/tools/perf/tests/parse-no-sample-id-all.c b/kernel/tools/perf/tests/parse-no-sample-id-all.c new file mode 100644 index 000000000..2c63ea658 --- /dev/null +++ b/kernel/tools/perf/tests/parse-no-sample-id-all.c @@ -0,0 +1,109 @@ +#include +#include + +#include "tests.h" + +#include "event.h" +#include "evlist.h" +#include "header.h" +#include "util.h" +#include "debug.h" + +static int process_event(struct perf_evlist **pevlist, union perf_event *event) +{ + struct perf_sample sample; + + if (event->header.type == PERF_RECORD_HEADER_ATTR) { + if (perf_event__process_attr(NULL, event, pevlist)) { + pr_debug("perf_event__process_attr failed\n"); + return -1; + } + return 0; + } + + if (event->header.type >= PERF_RECORD_USER_TYPE_START) + return -1; + + if (!*pevlist) + return -1; + + if (perf_evlist__parse_sample(*pevlist, event, &sample)) { + pr_debug("perf_evlist__parse_sample failed\n"); + return -1; + } + + return 0; +} + +static int process_events(union perf_event **events, size_t count) +{ + struct perf_evlist *evlist = NULL; + int err = 0; + size_t i; + + for (i = 0; i < count && !err; i++) + err = process_event(&evlist, events[i]); + + if (evlist) + perf_evlist__delete(evlist); + + return err; +} + +struct test_attr_event { + struct attr_event attr; + u64 id; +}; + +/** + * test__parse_no_sample_id_all - test parsing with no sample_id_all bit set. + * + * This function tests parsing data produced on kernel's that do not support the + * sample_id_all bit. Without the sample_id_all bit, non-sample events (such as + * mmap events) do not have an id sample appended, and consequently logic + * designed to determine the id will not work. That case happens when there is + * more than one selected event, so this test processes three events: 2 + * attributes representing the selected events and one mmap event. + * + * Return: %0 on success, %-1 if the test fails. + */ +int test__parse_no_sample_id_all(void) +{ + int err; + + struct test_attr_event event1 = { + .attr = { + .header = { + .type = PERF_RECORD_HEADER_ATTR, + .size = sizeof(struct test_attr_event), + }, + }, + .id = 1, + }; + struct test_attr_event event2 = { + .attr = { + .header = { + .type = PERF_RECORD_HEADER_ATTR, + .size = sizeof(struct test_attr_event), + }, + }, + .id = 2, + }; + struct mmap_event event3 = { + .header = { + .type = PERF_RECORD_MMAP, + .size = sizeof(struct mmap_event), + }, + }; + union perf_event *events[] = { + (union perf_event *)&event1, + (union perf_event *)&event2, + (union perf_event *)&event3, + }; + + err = process_events(events, ARRAY_SIZE(events)); + if (err) + return -1; + + return 0; +} diff --git a/kernel/tools/perf/tests/perf-record.c b/kernel/tools/perf/tests/perf-record.c new file mode 100644 index 000000000..7a228a2a0 --- /dev/null +++ b/kernel/tools/perf/tests/perf-record.c @@ -0,0 +1,314 @@ +#include +#include "evlist.h" +#include "evsel.h" +#include "perf.h" +#include "debug.h" +#include "tests.h" + +static int sched__get_first_possible_cpu(pid_t pid, cpu_set_t *maskp) +{ + int i, cpu = -1, nrcpus = 1024; +realloc: + CPU_ZERO(maskp); + + if (sched_getaffinity(pid, sizeof(*maskp), maskp) == -1) { + if (errno == EINVAL && nrcpus < (1024 << 8)) { + nrcpus = nrcpus << 2; + goto realloc; + } + perror("sched_getaffinity"); + return -1; + } + + for (i = 0; i < nrcpus; i++) { + if (CPU_ISSET(i, maskp)) { + if (cpu == -1) + cpu = i; + else + CPU_CLR(i, maskp); + } + } + + return cpu; +} + +int test__PERF_RECORD(void) +{ + struct record_opts opts = { + .target = { + .uid = UINT_MAX, + .uses_mmap = true, + }, + .no_buffering = true, + .freq = 10, + .mmap_pages = 256, + }; + cpu_set_t cpu_mask; + size_t cpu_mask_size = sizeof(cpu_mask); + struct perf_evlist *evlist = perf_evlist__new_default(); + struct perf_evsel *evsel; + struct perf_sample sample; + const char *cmd = "sleep"; + const char *argv[] = { cmd, "1", NULL, }; + char *bname, *mmap_filename; + u64 prev_time = 0; + bool found_cmd_mmap = false, + found_libc_mmap = false, + found_vdso_mmap = false, + found_ld_mmap = false; + int err = -1, errs = 0, i, wakeups = 0; + u32 cpu; + int total_events = 0, nr_events[PERF_RECORD_MAX] = { 0, }; + char sbuf[STRERR_BUFSIZE]; + + if (evlist == NULL || argv == NULL) { + pr_debug("Not enough memory to create evlist\n"); + goto out; + } + + /* + * Create maps of threads and cpus to monitor. In this case + * we start with all threads and cpus (-1, -1) but then in + * perf_evlist__prepare_workload we'll fill in the only thread + * we're monitoring, the one forked there. + */ + err = perf_evlist__create_maps(evlist, &opts.target); + if (err < 0) { + pr_debug("Not enough memory to create thread/cpu maps\n"); + goto out_delete_evlist; + } + + /* + * Prepare the workload in argv[] to run, it'll fork it, and then wait + * for perf_evlist__start_workload() to exec it. This is done this way + * so that we have time to open the evlist (calling sys_perf_event_open + * on all the fds) and then mmap them. + */ + err = perf_evlist__prepare_workload(evlist, &opts.target, argv, false, NULL); + if (err < 0) { + pr_debug("Couldn't run the workload!\n"); + goto out_delete_evlist; + } + + /* + * Config the evsels, setting attr->comm on the first one, etc. + */ + evsel = perf_evlist__first(evlist); + perf_evsel__set_sample_bit(evsel, CPU); + perf_evsel__set_sample_bit(evsel, TID); + perf_evsel__set_sample_bit(evsel, TIME); + perf_evlist__config(evlist, &opts); + + err = sched__get_first_possible_cpu(evlist->workload.pid, &cpu_mask); + if (err < 0) { + pr_debug("sched__get_first_possible_cpu: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + cpu = err; + + /* + * So that we can check perf_sample.cpu on all the samples. + */ + if (sched_setaffinity(evlist->workload.pid, cpu_mask_size, &cpu_mask) < 0) { + pr_debug("sched_setaffinity: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + /* + * Call sys_perf_event_open on all the fds on all the evsels, + * grouping them if asked to. + */ + err = perf_evlist__open(evlist); + if (err < 0) { + pr_debug("perf_evlist__open: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + /* + * mmap the first fd on a given CPU and ask for events for the other + * fds in the same CPU to be injected in the same mmap ring buffer + * (using ioctl(PERF_EVENT_IOC_SET_OUTPUT)). + */ + err = perf_evlist__mmap(evlist, opts.mmap_pages, false); + if (err < 0) { + pr_debug("perf_evlist__mmap: %s\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + /* + * Now that all is properly set up, enable the events, they will + * count just on workload.pid, which will start... + */ + perf_evlist__enable(evlist); + + /* + * Now! + */ + perf_evlist__start_workload(evlist); + + while (1) { + int before = total_events; + + for (i = 0; i < evlist->nr_mmaps; i++) { + union perf_event *event; + + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + const u32 type = event->header.type; + const char *name = perf_event__name(type); + + ++total_events; + if (type < PERF_RECORD_MAX) + nr_events[type]++; + + err = perf_evlist__parse_sample(evlist, event, &sample); + if (err < 0) { + if (verbose) + perf_event__fprintf(event, stderr); + pr_debug("Couldn't parse sample\n"); + goto out_delete_evlist; + } + + if (verbose) { + pr_info("%" PRIu64" %d ", sample.time, sample.cpu); + perf_event__fprintf(event, stderr); + } + + if (prev_time > sample.time) { + pr_debug("%s going backwards in time, prev=%" PRIu64 ", curr=%" PRIu64 "\n", + name, prev_time, sample.time); + ++errs; + } + + prev_time = sample.time; + + if (sample.cpu != cpu) { + pr_debug("%s with unexpected cpu, expected %d, got %d\n", + name, cpu, sample.cpu); + ++errs; + } + + if ((pid_t)sample.pid != evlist->workload.pid) { + pr_debug("%s with unexpected pid, expected %d, got %d\n", + name, evlist->workload.pid, sample.pid); + ++errs; + } + + if ((pid_t)sample.tid != evlist->workload.pid) { + pr_debug("%s with unexpected tid, expected %d, got %d\n", + name, evlist->workload.pid, sample.tid); + ++errs; + } + + if ((type == PERF_RECORD_COMM || + type == PERF_RECORD_MMAP || + type == PERF_RECORD_MMAP2 || + type == PERF_RECORD_FORK || + type == PERF_RECORD_EXIT) && + (pid_t)event->comm.pid != evlist->workload.pid) { + pr_debug("%s with unexpected pid/tid\n", name); + ++errs; + } + + if ((type == PERF_RECORD_COMM || + type == PERF_RECORD_MMAP || + type == PERF_RECORD_MMAP2) && + event->comm.pid != event->comm.tid) { + pr_debug("%s with different pid/tid!\n", name); + ++errs; + } + + switch (type) { + case PERF_RECORD_COMM: + if (strcmp(event->comm.comm, cmd)) { + pr_debug("%s with unexpected comm!\n", name); + ++errs; + } + break; + case PERF_RECORD_EXIT: + goto found_exit; + case PERF_RECORD_MMAP: + mmap_filename = event->mmap.filename; + goto check_bname; + case PERF_RECORD_MMAP2: + mmap_filename = event->mmap2.filename; + check_bname: + bname = strrchr(mmap_filename, '/'); + if (bname != NULL) { + if (!found_cmd_mmap) + found_cmd_mmap = !strcmp(bname + 1, cmd); + if (!found_libc_mmap) + found_libc_mmap = !strncmp(bname + 1, "libc", 4); + if (!found_ld_mmap) + found_ld_mmap = !strncmp(bname + 1, "ld", 2); + } else if (!found_vdso_mmap) + found_vdso_mmap = !strcmp(mmap_filename, "[vdso]"); + break; + + case PERF_RECORD_SAMPLE: + /* Just ignore samples for now */ + break; + default: + pr_debug("Unexpected perf_event->header.type %d!\n", + type); + ++errs; + } + + perf_evlist__mmap_consume(evlist, i); + } + } + + /* + * We don't use poll here because at least at 3.1 times the + * PERF_RECORD_{!SAMPLE} events don't honour + * perf_event_attr.wakeup_events, just PERF_EVENT_SAMPLE does. + */ + if (total_events == before && false) + perf_evlist__poll(evlist, -1); + + sleep(1); + if (++wakeups > 5) { + pr_debug("No PERF_RECORD_EXIT event!\n"); + break; + } + } + +found_exit: + if (nr_events[PERF_RECORD_COMM] > 1) { + pr_debug("Excessive number of PERF_RECORD_COMM events!\n"); + ++errs; + } + + if (nr_events[PERF_RECORD_COMM] == 0) { + pr_debug("Missing PERF_RECORD_COMM for %s!\n", cmd); + ++errs; + } + + if (!found_cmd_mmap) { + pr_debug("PERF_RECORD_MMAP for %s missing!\n", cmd); + ++errs; + } + + if (!found_libc_mmap) { + pr_debug("PERF_RECORD_MMAP for %s missing!\n", "libc"); + ++errs; + } + + if (!found_ld_mmap) { + pr_debug("PERF_RECORD_MMAP for %s missing!\n", "ld"); + ++errs; + } + + if (!found_vdso_mmap) { + pr_debug("PERF_RECORD_MMAP for %s missing!\n", "[vdso]"); + ++errs; + } +out_delete_evlist: + perf_evlist__delete(evlist); +out: + return (err < 0 || errs > 0) ? -1 : 0; +} diff --git a/kernel/tools/perf/tests/perf-targz-src-pkg b/kernel/tools/perf/tests/perf-targz-src-pkg new file mode 100755 index 000000000..238aa3927 --- /dev/null +++ b/kernel/tools/perf/tests/perf-targz-src-pkg @@ -0,0 +1,21 @@ +#!/bin/sh +# Test one of the main kernel Makefile targets to generate a perf sources tarball +# suitable for build outside the full kernel sources. +# +# This is to test that the tools/perf/MANIFEST file lists all the files needed to +# 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. + +PERF=$1 +cd ${PERF}/../.. +make perf-targz-src-pkg > /dev/null +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 2>&1 +RC=$? +rm -rf ${TMP_DEST} +exit $RC diff --git a/kernel/tools/perf/tests/perf-time-to-tsc.c b/kernel/tools/perf/tests/perf-time-to-tsc.c new file mode 100644 index 000000000..f238442b2 --- /dev/null +++ b/kernel/tools/perf/tests/perf-time-to-tsc.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include + +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tsc.h" +#include "tests.h" + +#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; \ + } \ +} + +/** + * 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 then the test passes but " (not supported)" is printed. + */ +int test__perf_time_to_tsc(void) +{ + struct record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .freq = 4000, + .target = { + .uses_mmap = true, + }, + .sample_time = true, + }; + struct thread_map *threads = NULL; + struct cpu_map *cpus = NULL; + struct perf_evlist *evlist = NULL; + struct perf_evsel *evsel = NULL; + int err = -1, 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; + + threads = thread_map__new(-1, getpid(), UINT_MAX); + CHECK_NOT_NULL__(threads); + + cpus = cpu_map__new(NULL); + CHECK_NOT_NULL__(cpus); + + evlist = perf_evlist__new(); + CHECK_NOT_NULL__(evlist); + + perf_evlist__set_maps(evlist, cpus, threads); + + CHECK__(parse_events(evlist, "cycles:u")); + + perf_evlist__config(evlist, &opts); + + evsel = perf_evlist__first(evlist); + + evsel->attr.comm = 1; + evsel->attr.disabled = 1; + evsel->attr.enable_on_exec = 0; + + CHECK__(perf_evlist__open(evlist)); + + CHECK__(perf_evlist__mmap(evlist, UINT_MAX, false)); + + pc = evlist->mmap[0].base; + ret = perf_read_tsc_conversion(pc, &tc); + if (ret) { + if (ret == -EOPNOTSUPP) { + fprintf(stderr, " (not supported)"); + return 0; + } + goto out_err; + } + + perf_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)); + + perf_evlist__disable(evlist); + + for (i = 0; i < evlist->nr_mmaps; i++) { + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + struct perf_sample sample; + + 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__(perf_evsel__parse_sample(evsel, event, + &sample)); + comm1_time = sample.time; + } + if (strcmp(event->comm.comm, comm2) == 0) { + CHECK__(perf_evsel__parse_sample(evsel, event, + &sample)); + comm2_time = sample.time; + } +next_event: + perf_evlist__mmap_consume(evlist, i); + } + } + + 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 = 0; + +out_err: + if (evlist) { + perf_evlist__disable(evlist); + perf_evlist__delete(evlist); + } + + return err; +} diff --git a/kernel/tools/perf/tests/pmu.c b/kernel/tools/perf/tests/pmu.c new file mode 100644 index 000000000..eeb68bb19 --- /dev/null +++ b/kernel/tools/perf/tests/pmu.c @@ -0,0 +1,173 @@ +#include "parse-events.h" +#include "pmu.h" +#include "util.h" +#include "tests.h" + +/* Simulated format definitions. */ +static struct test_format { + const char *name; + const char *value; +} test_formats[] = { + { "krava01", "config:0-1,62-63\n", }, + { "krava02", "config:10-17\n", }, + { "krava03", "config:5\n", }, + { "krava11", "config1:0,2,4,6,8,20-28\n", }, + { "krava12", "config1:63\n", }, + { "krava13", "config1:45-47\n", }, + { "krava21", "config2:0-3,10-13,20-23,30-33,40-43,50-53,60-63\n", }, + { "krava22", "config2:8,18,48,58\n", }, + { "krava23", "config2:28-29,38\n", }, +}; + +/* 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, + }, +}; + +/* + * Prepare format directory data, exported by kernel + * at /sys/bus/event_source/devices//format. + */ +static char *test_format_dir_get(void) +{ + static char dir[PATH_MAX]; + unsigned int i; + + snprintf(dir, PATH_MAX, "/tmp/perf-pmu-test-format-XXXXXX"); + if (!mkdtemp(dir)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(test_formats); i++) { + static char name[PATH_MAX]; + struct test_format *format = &test_formats[i]; + FILE *file; + + snprintf(name, PATH_MAX, "%s/%s", dir, format->name); + + file = fopen(name, "w"); + if (!file) + return NULL; + + if (1 != fwrite(format->value, strlen(format->value), 1, file)) + break; + + fclose(file); + } + + return dir; +} + +/* Cleanup format directory. */ +static int test_format_dir_put(char *dir) +{ + char buf[PATH_MAX]; + snprintf(buf, PATH_MAX, "rm -f %s/*\n", dir); + if (system(buf)) + return -1; + + snprintf(buf, PATH_MAX, "rmdir %s\n", dir); + return system(buf); +} + +static struct list_head *test_terms_list(void) +{ + static LIST_HEAD(terms); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(test_terms); i++) + list_add_tail(&test_terms[i].list, &terms); + + return &terms; +} + +int test__pmu(void) +{ + char *format = test_format_dir_get(); + LIST_HEAD(formats); + struct list_head *terms = test_terms_list(); + int ret; + + if (!format) + return -EINVAL; + + do { + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(attr)); + + ret = perf_pmu__format_parse(format, &formats); + if (ret) + break; + + ret = perf_pmu__config_terms(&formats, &attr, terms, false); + if (ret) + break; + + ret = -EINVAL; + + if (attr.config != 0xc00000000002a823) + break; + if (attr.config1 != 0x8000400000000145) + break; + if (attr.config2 != 0x0400000020041d07) + break; + + ret = 0; + } while (0); + + test_format_dir_put(format); + return ret; +} diff --git a/kernel/tools/perf/tests/python-use.c b/kernel/tools/perf/tests/python-use.c new file mode 100644 index 000000000..7760277c6 --- /dev/null +++ b/kernel/tools/perf/tests/python-use.c @@ -0,0 +1,23 @@ +/* + * Just test if we can load the python binding. + */ + +#include +#include +#include "tests.h" + +extern int verbose; + +int test__python_use(void) +{ + char *cmd; + int ret; + + if (asprintf(&cmd, "echo \"import sys ; sys.path.append('%s'); import perf\" | %s %s", + PYTHONPATH, PYTHON, verbose ? "" : "2> /dev/null") < 0) + return -1; + + ret = system(cmd) ? -1 : 0; + free(cmd); + return ret; +} diff --git a/kernel/tools/perf/tests/rdpmc.c b/kernel/tools/perf/tests/rdpmc.c new file mode 100644 index 000000000..d31f2c4d9 --- /dev/null +++ b/kernel/tools/perf/tests/rdpmc.c @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include "perf.h" +#include "debug.h" +#include "tests.h" +#include "cloexec.h" + +#if defined(__x86_64__) || defined(__i386__) + +static u64 rdpmc(unsigned int counter) +{ + unsigned int low, high; + + asm volatile("rdpmc" : "=a" (low), "=d" (high) : "c" (counter)); + + return low | ((u64)high) << 32; +} + +static u64 rdtsc(void) +{ + unsigned int low, high; + + asm volatile("rdtsc" : "=a" (low), "=d" (high)); + + return low | ((u64)high) << 32; +} + +static u64 mmap_read_self(void *addr) +{ + struct perf_event_mmap_page *pc = addr; + u32 seq, idx, time_mult = 0, time_shift = 0; + u64 count, cyc = 0, time_offset = 0, enabled, running, delta; + + do { + seq = pc->lock; + barrier(); + + enabled = pc->time_enabled; + running = pc->time_running; + + if (enabled != running) { + cyc = rdtsc(); + time_mult = pc->time_mult; + time_shift = pc->time_shift; + time_offset = pc->time_offset; + } + + idx = pc->index; + count = pc->offset; + if (idx) + count += rdpmc(idx - 1); + + barrier(); + } while (pc->lock != seq); + + if (enabled != running) { + u64 quot, rem; + + quot = (cyc >> time_shift); + rem = cyc & ((1 << time_shift) - 1); + delta = time_offset + quot * time_mult + + ((rem * time_mult) >> time_shift); + + enabled += delta; + if (idx) + running += delta; + + quot = count / running; + rem = count % running; + count = quot * enabled + (rem * enabled) / running; + } + + return count; +} + +/* + * If the RDPMC instruction faults then signal this back to the test parent task: + */ +static void segfault_handler(int sig __maybe_unused, + siginfo_t *info __maybe_unused, + void *uc __maybe_unused) +{ + exit(-1); +} + +static int __test__rdpmc(void) +{ + volatile int tmp = 0; + u64 i, loops = 1000; + int n; + int fd; + void *addr; + struct perf_event_attr attr = { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_INSTRUCTIONS, + .exclude_kernel = 1, + }; + u64 delta_sum = 0; + struct sigaction sa; + char sbuf[STRERR_BUFSIZE]; + + sigfillset(&sa.sa_mask); + sa.sa_sigaction = segfault_handler; + sigaction(SIGSEGV, &sa, NULL); + + fd = sys_perf_event_open(&attr, 0, -1, -1, + perf_event_open_cloexec_flag()); + if (fd < 0) { + pr_err("Error: sys_perf_event_open() syscall returned " + "with %d (%s)\n", fd, + strerror_r(errno, sbuf, sizeof(sbuf))); + return -1; + } + + addr = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0); + if (addr == (void *)(-1)) { + pr_err("Error: mmap() syscall returned with (%s)\n", + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_close; + } + + for (n = 0; n < 6; n++) { + u64 stamp, now, delta; + + stamp = mmap_read_self(addr); + + for (i = 0; i < loops; i++) + tmp++; + + now = mmap_read_self(addr); + loops *= 10; + + delta = now - stamp; + pr_debug("%14d: %14Lu\n", n, (long long)delta); + + delta_sum += delta; + } + + munmap(addr, page_size); + pr_debug(" "); +out_close: + close(fd); + + if (!delta_sum) + return -1; + + return 0; +} + +int test__rdpmc(void) +{ + int status = 0; + int wret = 0; + int ret; + int pid; + + pid = fork(); + if (pid < 0) + return -1; + + if (!pid) { + ret = __test__rdpmc(); + + exit(ret); + } + + wret = waitpid(pid, &status, 0); + if (wret < 0 || status) + return -1; + + return 0; +} + +#endif diff --git a/kernel/tools/perf/tests/sample-parsing.c b/kernel/tools/perf/tests/sample-parsing.c new file mode 100644 index 000000000..30c02181e --- /dev/null +++ b/kernel/tools/perf/tests/sample-parsing.c @@ -0,0 +1,346 @@ +#include +#include + +#include "util.h" +#include "event.h" +#include "evsel.h" +#include "debug.h" + +#include "tests.h" + +#define COMP(m) do { \ + if (s1->m != s2->m) { \ + pr_debug("Samples differ at '"#m"'\n"); \ + return false; \ + } \ +} while (0) + +#define MCOMP(m) do { \ + if (memcmp(&s1->m, &s2->m, sizeof(s1->m))) { \ + pr_debug("Samples differ at '"#m"'\n"); \ + return false; \ + } \ +} while (0) + +static bool samples_same(const struct perf_sample *s1, + const struct perf_sample *s2, + u64 type, u64 read_format) +{ + size_t i; + + if (type & PERF_SAMPLE_IDENTIFIER) + COMP(id); + + if (type & PERF_SAMPLE_IP) + COMP(ip); + + if (type & PERF_SAMPLE_TID) { + COMP(pid); + COMP(tid); + } + + if (type & PERF_SAMPLE_TIME) + COMP(time); + + if (type & PERF_SAMPLE_ADDR) + COMP(addr); + + if (type & PERF_SAMPLE_ID) + COMP(id); + + if (type & PERF_SAMPLE_STREAM_ID) + COMP(stream_id); + + if (type & PERF_SAMPLE_CPU) + COMP(cpu); + + if (type & PERF_SAMPLE_PERIOD) + COMP(period); + + if (type & PERF_SAMPLE_READ) { + if (read_format & PERF_FORMAT_GROUP) + COMP(read.group.nr); + else + COMP(read.one.value); + if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) + COMP(read.time_enabled); + if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) + 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]); + } else { + COMP(read.one.id); + } + } + + if (type & PERF_SAMPLE_CALLCHAIN) { + COMP(callchain->nr); + for (i = 0; i < s1->callchain->nr; i++) + COMP(callchain->ips[i]); + } + + if (type & PERF_SAMPLE_RAW) { + COMP(raw_size); + if (memcmp(s1->raw_data, s2->raw_data, s1->raw_size)) { + pr_debug("Samples differ at 'raw_data'\n"); + return false; + } + } + + if (type & PERF_SAMPLE_BRANCH_STACK) { + COMP(branch_stack->nr); + for (i = 0; i < s1->branch_stack->nr; i++) + 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))) { + pr_debug("Samples differ at 'user_regs'\n"); + return false; + } + } + + if (type & PERF_SAMPLE_STACK_USER) { + COMP(user_stack.size); + if (memcmp(s1->user_stack.data, s2->user_stack.data, + s1->user_stack.size)) { + pr_debug("Samples differ at 'user_stack'\n"); + return false; + } + } + + if (type & PERF_SAMPLE_WEIGHT) + COMP(weight); + + if (type & PERF_SAMPLE_DATA_SRC) + COMP(data_src); + + if (type & PERF_SAMPLE_TRANSACTION) + 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))) { + pr_debug("Samples differ at 'intr_regs'\n"); + return false; + } + } + + return true; +} + +static int do_test(u64 sample_type, u64 sample_regs, u64 read_format) +{ + struct perf_evsel evsel = { + .needs_swap = false, + .attr = { + .sample_type = sample_type, + .read_format = read_format, + }, + }; + union perf_event *event; + union { + struct ip_callchain callchain; + u64 data[64]; + } callchain = { + /* 3 ips */ + .data = {3, 201, 202, 203}, + }; + union { + struct branch_stack branch_stack; + u64 data[64]; + } branch_stack = { + /* 1 branch_entry */ + .data = {1, 211, 212, 213}, + }; + u64 regs[64]; + const u64 raw_data[] = {0x123456780a0b0c0dULL, 0x1102030405060708ULL}; + const u64 data[] = {0x2211443366558877ULL, 0, 0xaabbccddeeff4321ULL}; + struct perf_sample sample = { + .ip = 101, + .pid = 102, + .tid = 103, + .time = 104, + .addr = 105, + .id = 106, + .stream_id = 107, + .period = 108, + .weight = 109, + .cpu = 110, + .raw_size = sizeof(raw_data), + .data_src = 111, + .transaction = 112, + .raw_data = (void *)raw_data, + .callchain = &callchain.callchain, + .branch_stack = &branch_stack.branch_stack, + .user_regs = { + .abi = PERF_SAMPLE_REGS_ABI_64, + .mask = sample_regs, + .regs = regs, + }, + .user_stack = { + .size = sizeof(data), + .data = (void *)data, + }, + .read = { + .time_enabled = 0x030a59d664fca7deULL, + .time_running = 0x011b6ae553eb98edULL, + }, + .intr_regs = { + .abi = PERF_SAMPLE_REGS_ABI_64, + .mask = sample_regs, + .regs = regs, + }, + }; + struct sample_read_value values[] = {{1, 5}, {9, 3}, {2, 7}, {6, 4},}; + struct perf_sample sample_out; + size_t i, sz, bufsz; + int err, ret = -1; + + if (sample_type & PERF_SAMPLE_REGS_USER) + evsel.attr.sample_regs_user = sample_regs; + + if (sample_type & PERF_SAMPLE_REGS_INTR) + evsel.attr.sample_regs_intr = sample_regs; + + for (i = 0; i < sizeof(regs); i++) + *(i + (u8 *)regs) = i & 0xfe; + + if (read_format & PERF_FORMAT_GROUP) { + sample.read.group.nr = 4; + sample.read.group.values = values; + } else { + sample.read.one.value = 0x08789faeb786aa87ULL; + sample.read.one.id = 99; + } + + sz = perf_event__sample_event_size(&sample, sample_type, read_format); + bufsz = sz + 4096; /* Add a bit for overrun checking */ + event = malloc(bufsz); + if (!event) { + pr_debug("malloc failed\n"); + return -1; + } + + memset(event, 0xff, bufsz); + event->header.type = PERF_RECORD_SAMPLE; + event->header.misc = 0; + event->header.size = sz; + + err = perf_event__synthesize_sample(event, sample_type, read_format, + &sample, false); + if (err) { + pr_debug("%s failed for sample_type %#"PRIx64", error %d\n", + "perf_event__synthesize_sample", sample_type, err); + goto out_free; + } + + /* The data does not contain 0xff so we use that to check the size */ + for (i = bufsz; i > 0; i--) { + if (*(i - 1 + (u8 *)event) != 0xff) + break; + } + if (i != sz) { + pr_debug("Event size mismatch: actual %zu vs expected %zu\n", + i, sz); + goto out_free; + } + + evsel.sample_size = __perf_evsel__sample_size(sample_type); + + err = perf_evsel__parse_sample(&evsel, event, &sample_out); + if (err) { + pr_debug("%s failed for sample_type %#"PRIx64", error %d\n", + "perf_evsel__parse_sample", sample_type, err); + goto out_free; + } + + if (!samples_same(&sample, &sample_out, sample_type, read_format)) { + pr_debug("parsing failed for sample_type %#"PRIx64"\n", + sample_type); + goto out_free; + } + + ret = 0; +out_free: + free(event); + if (ret && read_format) + pr_debug("read_format %#"PRIx64"\n", read_format); + return ret; +} + +/** + * test__sample_parsing - test sample parsing. + * + * This function implements a test that synthesizes a sample event, parses it + * and then checks that the parsed sample matches the original sample. The test + * checks sample format bits separately and together. If the test passes %0 is + * returned, otherwise %-1 is returned. + */ +int test__sample_parsing(void) +{ + const u64 rf[] = {4, 5, 6, 7, 12, 13, 14, 15}; + u64 sample_type; + u64 sample_regs; + size_t i; + int err; + + /* + * Fail the test if it has not been updated when new sample format bits + * were added. Please actually update the test rather than just change + * the condition below. + */ + if (PERF_SAMPLE_MAX > PERF_SAMPLE_REGS_INTR << 1) { + pr_debug("sample format has changed, some new PERF_SAMPLE_ bit was introduced - test needs updating\n"); + return -1; + } + + /* Test each sample format bit separately */ + for (sample_type = 1; sample_type != PERF_SAMPLE_MAX; + sample_type <<= 1) { + /* Test read_format variations */ + if (sample_type == PERF_SAMPLE_READ) { + for (i = 0; i < ARRAY_SIZE(rf); i++) { + err = do_test(sample_type, 0, rf[i]); + if (err) + return err; + } + continue; + } + sample_regs = 0; + + if (sample_type == PERF_SAMPLE_REGS_USER) + sample_regs = 0x3fff; + + if (sample_type == PERF_SAMPLE_REGS_INTR) + sample_regs = 0xff0fff; + + err = do_test(sample_type, sample_regs, 0); + if (err) + return err; + } + + /* Test all sample format bits together */ + sample_type = PERF_SAMPLE_MAX - 1; + 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]); + if (err) + return err; + } + + return 0; +} diff --git a/kernel/tools/perf/tests/sw-clock.c b/kernel/tools/perf/tests/sw-clock.c new file mode 100644 index 000000000..1aa21c907 --- /dev/null +++ b/kernel/tools/perf/tests/sw-clock.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include + +#include "tests.h" +#include "util/evsel.h" +#include "util/evlist.h" +#include "util/cpumap.h" +#include "util/thread_map.h" + +#define NR_LOOPS 10000000 + +/* + * This test will open software clock events (cpu-clock, task-clock) + * then check their frequency -> period conversion has no artifact of + * setting period to 1 forcefully. + */ +static int __test__sw_clock_freq(enum perf_sw_ids clock_id) +{ + int i, err = -1; + volatile int tmp = 0; + u64 total_periods = 0; + int nr_samples = 0; + char sbuf[STRERR_BUFSIZE]; + union perf_event *event; + struct perf_evsel *evsel; + struct perf_evlist *evlist; + struct perf_event_attr attr = { + .type = PERF_TYPE_SOFTWARE, + .config = clock_id, + .sample_type = PERF_SAMPLE_PERIOD, + .exclude_kernel = 1, + .disabled = 1, + .freq = 1, + }; + + attr.sample_freq = 500; + + evlist = perf_evlist__new(); + if (evlist == NULL) { + pr_debug("perf_evlist__new\n"); + return -1; + } + + evsel = perf_evsel__new(&attr); + if (evsel == NULL) { + pr_debug("perf_evsel__new\n"); + goto out_delete_evlist; + } + perf_evlist__add(evlist, evsel); + + evlist->cpus = cpu_map__dummy_new(); + evlist->threads = thread_map__new_by_tid(getpid()); + if (!evlist->cpus || !evlist->threads) { + err = -ENOMEM; + pr_debug("Not enough memory to create thread/cpu maps\n"); + goto out_delete_evlist; + } + + if (perf_evlist__open(evlist)) { + const char *knob = "/proc/sys/kernel/perf_event_max_sample_rate"; + + err = -errno; + pr_debug("Couldn't open evlist: %s\nHint: check %s, using %" PRIu64 " in this test.\n", + strerror_r(errno, sbuf, sizeof(sbuf)), + knob, (u64)attr.sample_freq); + goto out_delete_evlist; + } + + err = perf_evlist__mmap(evlist, 128, true); + if (err < 0) { + pr_debug("failed to mmap event: %d (%s)\n", errno, + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + perf_evlist__enable(evlist); + + /* collect samples */ + for (i = 0; i < NR_LOOPS; i++) + tmp++; + + perf_evlist__disable(evlist); + + while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) { + struct perf_sample sample; + + if (event->header.type != PERF_RECORD_SAMPLE) + goto next_event; + + err = perf_evlist__parse_sample(evlist, event, &sample); + if (err < 0) { + pr_debug("Error during parse sample\n"); + goto out_delete_evlist; + } + + total_periods += sample.period; + nr_samples++; +next_event: + perf_evlist__mmap_consume(evlist, 0); + } + + if ((u64) nr_samples == total_periods) { + pr_debug("All (%d) samples have period value of 1!\n", + nr_samples); + err = -1; + } + +out_delete_evlist: + perf_evlist__delete(evlist); + return err; +} + +int test__sw_clock_freq(void) +{ + int ret; + + ret = __test__sw_clock_freq(PERF_COUNT_SW_CPU_CLOCK); + if (!ret) + ret = __test__sw_clock_freq(PERF_COUNT_SW_TASK_CLOCK); + + return ret; +} diff --git a/kernel/tools/perf/tests/switch-tracking.c b/kernel/tools/perf/tests/switch-tracking.c new file mode 100644 index 000000000..cc68648c7 --- /dev/null +++ b/kernel/tools/perf/tests/switch-tracking.c @@ -0,0 +1,572 @@ +#include +#include +#include +#include + +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tests.h" + +static int spin_sleep(void) +{ + struct timeval start, now, diff, maxtime; + struct timespec ts; + int err, i; + + maxtime.tv_sec = 0; + maxtime.tv_usec = 50000; + + err = gettimeofday(&start, NULL); + if (err) + return err; + + /* Spin for 50ms */ + while (1) { + for (i = 0; i < 1000; i++) + barrier(); + + err = gettimeofday(&now, NULL); + if (err) + return err; + + timersub(&now, &start, &diff); + if (timercmp(&diff, &maxtime, > /* For checkpatch */)) + break; + } + + ts.tv_nsec = 50 * 1000 * 1000; + ts.tv_sec = 0; + + /* Sleep for 50ms */ + err = nanosleep(&ts, NULL); + if (err == EINTR) + err = 0; + + return err; +} + +struct switch_tracking { + struct perf_evsel *switch_evsel; + struct perf_evsel *cycles_evsel; + pid_t *tids; + int nr_tids; + int comm_seen[4]; + int cycles_before_comm_1; + int cycles_between_comm_2_and_comm_3; + int cycles_after_comm_4; +}; + +static int check_comm(struct switch_tracking *switch_tracking, + union perf_event *event, const char *comm, int nr) +{ + if (event->header.type == PERF_RECORD_COMM && + (pid_t)event->comm.pid == getpid() && + (pid_t)event->comm.tid == getpid() && + strcmp(event->comm.comm, comm) == 0) { + if (switch_tracking->comm_seen[nr]) { + pr_debug("Duplicate comm event\n"); + return -1; + } + switch_tracking->comm_seen[nr] = 1; + pr_debug3("comm event: %s nr: %d\n", event->comm.comm, nr); + return 1; + } + return 0; +} + +static int check_cpu(struct switch_tracking *switch_tracking, int cpu) +{ + int i, nr = cpu + 1; + + if (cpu < 0) + return -1; + + if (!switch_tracking->tids) { + switch_tracking->tids = calloc(nr, sizeof(pid_t)); + if (!switch_tracking->tids) + return -1; + for (i = 0; i < nr; i++) + switch_tracking->tids[i] = -1; + switch_tracking->nr_tids = nr; + return 0; + } + + if (cpu >= switch_tracking->nr_tids) { + void *addr; + + addr = realloc(switch_tracking->tids, nr * sizeof(pid_t)); + if (!addr) + return -1; + switch_tracking->tids = addr; + for (i = switch_tracking->nr_tids; i < nr; i++) + switch_tracking->tids[i] = -1; + switch_tracking->nr_tids = nr; + return 0; + } + + return 0; +} + +static int process_sample_event(struct perf_evlist *evlist, + union perf_event *event, + struct switch_tracking *switch_tracking) +{ + struct perf_sample sample; + struct perf_evsel *evsel; + 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; + } + + evsel = perf_evlist__id2evsel(evlist, sample.id); + if (evsel == switch_tracking->switch_evsel) { + next_tid = perf_evsel__intval(evsel, &sample, "next_pid"); + prev_tid = perf_evsel__intval(evsel, &sample, "prev_pid"); + cpu = sample.cpu; + pr_debug3("sched_switch: cpu: %d prev_tid %d next_tid %d\n", + cpu, prev_tid, next_tid); + err = check_cpu(switch_tracking, cpu); + if (err) + return err; + /* + * Check for no missing sched_switch events i.e. that the + * evsel->system_wide flag has worked. + */ + if (switch_tracking->tids[cpu] != -1 && + switch_tracking->tids[cpu] != prev_tid) { + pr_debug("Missing sched_switch events\n"); + return -1; + } + switch_tracking->tids[cpu] = next_tid; + } + + if (evsel == switch_tracking->cycles_evsel) { + pr_debug3("cycles event\n"); + if (!switch_tracking->comm_seen[0]) + switch_tracking->cycles_before_comm_1 = 1; + if (switch_tracking->comm_seen[1] && + !switch_tracking->comm_seen[2]) + switch_tracking->cycles_between_comm_2_and_comm_3 = 1; + if (switch_tracking->comm_seen[3]) + switch_tracking->cycles_after_comm_4 = 1; + } + + return 0; +} + +static int process_event(struct perf_evlist *evlist, union perf_event *event, + struct switch_tracking *switch_tracking) +{ + if (event->header.type == PERF_RECORD_SAMPLE) + return process_sample_event(evlist, event, switch_tracking); + + if (event->header.type == PERF_RECORD_COMM) { + int err, done = 0; + + err = check_comm(switch_tracking, event, "Test COMM 1", 0); + if (err < 0) + return -1; + done += err; + err = check_comm(switch_tracking, event, "Test COMM 2", 1); + if (err < 0) + return -1; + done += err; + err = check_comm(switch_tracking, event, "Test COMM 3", 2); + if (err < 0) + return -1; + done += err; + err = check_comm(switch_tracking, event, "Test COMM 4", 3); + if (err < 0) + return -1; + done += err; + if (done != 1) { + pr_debug("Unexpected comm event\n"); + return -1; + } + } + + return 0; +} + +struct event_node { + struct list_head list; + union perf_event *event; + u64 event_time; +}; + +static int add_event(struct perf_evlist *evlist, struct list_head *events, + union perf_event *event) +{ + struct perf_sample sample; + struct event_node *node; + + node = malloc(sizeof(struct event_node)); + if (!node) { + pr_debug("malloc failed\n"); + return -1; + } + node->event = event; + list_add(&node->list, events); + + if (perf_evlist__parse_sample(evlist, event, &sample)) { + pr_debug("perf_evlist__parse_sample failed\n"); + return -1; + } + + if (!sample.time) { + pr_debug("event with no time\n"); + return -1; + } + + node->event_time = sample.time; + + return 0; +} + +static void free_event_nodes(struct list_head *events) +{ + struct event_node *node; + + while (!list_empty(events)) { + node = list_entry(events->next, struct event_node, list); + list_del(&node->list); + free(node); + } +} + +static int compar(const void *a, const void *b) +{ + const struct event_node *nodea = a; + const struct event_node *nodeb = b; + s64 cmp = nodea->event_time - nodeb->event_time; + + return cmp; +} + +static int process_events(struct perf_evlist *evlist, + struct switch_tracking *switch_tracking) +{ + union perf_event *event; + unsigned pos, cnt = 0; + LIST_HEAD(events); + struct event_node *events_array, *node; + int i, ret; + + for (i = 0; i < evlist->nr_mmaps; i++) { + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + cnt += 1; + ret = add_event(evlist, &events, event); + perf_evlist__mmap_consume(evlist, i); + if (ret < 0) + goto out_free_nodes; + } + } + + events_array = calloc(cnt, sizeof(struct event_node)); + if (!events_array) { + pr_debug("calloc failed\n"); + ret = -1; + goto out_free_nodes; + } + + pos = 0; + list_for_each_entry(node, &events, list) + events_array[pos++] = *node; + + qsort(events_array, cnt, sizeof(struct event_node), compar); + + for (pos = 0; pos < cnt; pos++) { + ret = process_event(evlist, events_array[pos].event, + switch_tracking); + if (ret < 0) + goto out_free; + } + + ret = 0; +out_free: + pr_debug("%u events recorded\n", cnt); + free(events_array); +out_free_nodes: + free_event_nodes(&events); + return ret; +} + +/** + * test__switch_tracking - test using sched_switch and tracking events. + * + * This function implements a test that checks that sched_switch events and + * tracking events can be recorded for a workload (current process) using the + * evsel->system_wide and evsel->tracking flags (respectively) with other events + * sometimes enabled or disabled. + */ +int test__switch_tracking(void) +{ + const char *sched_switch = "sched:sched_switch"; + struct switch_tracking switch_tracking = { .tids = NULL, }; + struct record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .freq = 4000, + .target = { + .uses_mmap = true, + }, + }; + struct thread_map *threads = NULL; + struct cpu_map *cpus = NULL; + struct perf_evlist *evlist = NULL; + struct perf_evsel *evsel, *cpu_clocks_evsel, *cycles_evsel; + struct perf_evsel *switch_evsel, *tracking_evsel; + const char *comm; + int err = -1; + + threads = thread_map__new(-1, getpid(), UINT_MAX); + if (!threads) { + pr_debug("thread_map__new failed!\n"); + goto out_err; + } + + cpus = cpu_map__new(NULL); + if (!cpus) { + pr_debug("cpu_map__new failed!\n"); + goto out_err; + } + + evlist = perf_evlist__new(); + if (!evlist) { + pr_debug("perf_evlist__new failed!\n"); + goto out_err; + } + + perf_evlist__set_maps(evlist, cpus, threads); + + /* First event */ + err = parse_events(evlist, "cpu-clock:u"); + if (err) { + pr_debug("Failed to parse event dummy:u\n"); + goto out_err; + } + + cpu_clocks_evsel = perf_evlist__last(evlist); + + /* Second event */ + err = parse_events(evlist, "cycles:u"); + if (err) { + pr_debug("Failed to parse event cycles:u\n"); + goto out_err; + } + + cycles_evsel = perf_evlist__last(evlist); + + /* Third event */ + if (!perf_evlist__can_select_event(evlist, sched_switch)) { + fprintf(stderr, " (no sched_switch)"); + err = 0; + goto out; + } + + err = parse_events(evlist, sched_switch); + if (err) { + pr_debug("Failed to parse event %s\n", sched_switch); + goto out_err; + } + + switch_evsel = perf_evlist__last(evlist); + + perf_evsel__set_sample_bit(switch_evsel, CPU); + perf_evsel__set_sample_bit(switch_evsel, TIME); + + switch_evsel->system_wide = true; + switch_evsel->no_aux_samples = true; + switch_evsel->immediate = true; + + /* Test moving an event to the front */ + if (cycles_evsel == perf_evlist__first(evlist)) { + pr_debug("cycles event already at front"); + goto out_err; + } + perf_evlist__to_front(evlist, cycles_evsel); + if (cycles_evsel != perf_evlist__first(evlist)) { + pr_debug("Failed to move cycles event to front"); + goto out_err; + } + + perf_evsel__set_sample_bit(cycles_evsel, CPU); + perf_evsel__set_sample_bit(cycles_evsel, TIME); + + /* Fourth event */ + err = parse_events(evlist, "dummy:u"); + if (err) { + pr_debug("Failed to parse event dummy:u\n"); + goto out_err; + } + + tracking_evsel = perf_evlist__last(evlist); + + perf_evlist__set_tracking_event(evlist, tracking_evsel); + + tracking_evsel->attr.freq = 0; + tracking_evsel->attr.sample_period = 1; + + perf_evsel__set_sample_bit(tracking_evsel, TIME); + + /* Config events */ + perf_evlist__config(evlist, &opts); + + /* Check moved event is still at the front */ + if (cycles_evsel != perf_evlist__first(evlist)) { + pr_debug("Front event no longer at front"); + goto out_err; + } + + /* Check tracking event is tracking */ + if (!tracking_evsel->attr.mmap || !tracking_evsel->attr.comm) { + pr_debug("Tracking event not tracking\n"); + goto out_err; + } + + /* Check non-tracking events are not tracking */ + evlist__for_each(evlist, evsel) { + if (evsel != tracking_evsel) { + if (evsel->attr.mmap || evsel->attr.comm) { + pr_debug("Non-tracking event is tracking\n"); + goto out_err; + } + } + } + + if (perf_evlist__open(evlist) < 0) { + fprintf(stderr, " (not supported)"); + err = 0; + goto out; + } + + err = perf_evlist__mmap(evlist, UINT_MAX, false); + if (err) { + pr_debug("perf_evlist__mmap failed!\n"); + goto out_err; + } + + perf_evlist__enable(evlist); + + err = perf_evlist__disable_event(evlist, cpu_clocks_evsel); + if (err) { + pr_debug("perf_evlist__disable_event failed!\n"); + goto out_err; + } + + err = spin_sleep(); + if (err) { + pr_debug("spin_sleep failed!\n"); + goto out_err; + } + + comm = "Test COMM 1"; + err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); + if (err) { + pr_debug("PR_SET_NAME failed!\n"); + goto out_err; + } + + err = perf_evlist__disable_event(evlist, cycles_evsel); + if (err) { + pr_debug("perf_evlist__disable_event failed!\n"); + goto out_err; + } + + comm = "Test COMM 2"; + err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); + if (err) { + pr_debug("PR_SET_NAME failed!\n"); + goto out_err; + } + + err = spin_sleep(); + if (err) { + pr_debug("spin_sleep failed!\n"); + goto out_err; + } + + comm = "Test COMM 3"; + err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); + if (err) { + pr_debug("PR_SET_NAME failed!\n"); + goto out_err; + } + + err = perf_evlist__enable_event(evlist, cycles_evsel); + if (err) { + pr_debug("perf_evlist__disable_event failed!\n"); + goto out_err; + } + + comm = "Test COMM 4"; + err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); + if (err) { + pr_debug("PR_SET_NAME failed!\n"); + goto out_err; + } + + err = spin_sleep(); + if (err) { + pr_debug("spin_sleep failed!\n"); + goto out_err; + } + + perf_evlist__disable(evlist); + + switch_tracking.switch_evsel = switch_evsel; + switch_tracking.cycles_evsel = cycles_evsel; + + err = process_events(evlist, &switch_tracking); + + zfree(&switch_tracking.tids); + + if (err) + goto out_err; + + /* Check all 4 comm events were seen i.e. that evsel->tracking works */ + if (!switch_tracking.comm_seen[0] || !switch_tracking.comm_seen[1] || + !switch_tracking.comm_seen[2] || !switch_tracking.comm_seen[3]) { + pr_debug("Missing comm events\n"); + goto out_err; + } + + /* Check cycles event got enabled */ + if (!switch_tracking.cycles_before_comm_1) { + pr_debug("Missing cycles events\n"); + goto out_err; + } + + /* Check cycles event got disabled */ + if (switch_tracking.cycles_between_comm_2_and_comm_3) { + pr_debug("cycles events even though event was disabled\n"); + goto out_err; + } + + /* Check cycles event got enabled again */ + if (!switch_tracking.cycles_after_comm_4) { + pr_debug("Missing cycles events\n"); + goto out_err; + } +out: + if (evlist) { + perf_evlist__disable(evlist); + perf_evlist__delete(evlist); + } else { + cpu_map__delete(cpus); + thread_map__delete(threads); + } + + return err; + +out_err: + err = -1; + goto out; +} diff --git a/kernel/tools/perf/tests/task-exit.c b/kernel/tools/perf/tests/task-exit.c new file mode 100644 index 000000000..3a8fedef8 --- /dev/null +++ b/kernel/tools/perf/tests/task-exit.c @@ -0,0 +1,120 @@ +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tests.h" + +#include + +static int exited; +static int nr_exit; + +static void sig_handler(int sig __maybe_unused) +{ + exited = 1; +} + +/* + * perf_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, + siginfo_t *info __maybe_unused, + void *ucontext __maybe_unused) +{ + exited = 1; + nr_exit = -1; +} + +/* + * This test will start a workload that does nothing then it checks + * 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(void) +{ + int err = -1; + union perf_event *event; + struct perf_evsel *evsel; + struct perf_evlist *evlist; + struct target target = { + .uid = UINT_MAX, + .uses_mmap = true, + }; + const char *argv[] = { "true", NULL }; + char sbuf[STRERR_BUFSIZE]; + + signal(SIGCHLD, sig_handler); + + evlist = perf_evlist__new_default(); + if (evlist == NULL) { + pr_debug("perf_evlist__new_default\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 + * we're monitoring, the one forked there. + */ + evlist->cpus = cpu_map__dummy_new(); + evlist->threads = thread_map__new_by_tid(-1); + if (!evlist->cpus || !evlist->threads) { + err = -ENOMEM; + pr_debug("Not enough memory to create thread/cpu maps\n"); + goto out_delete_evlist; + } + + err = perf_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; + } + + evsel = perf_evlist__first(evlist); + evsel->attr.task = 1; + evsel->attr.sample_freq = 0; + evsel->attr.inherit = 0; + evsel->attr.watermark = 0; + evsel->attr.wakeup_events = 1; + evsel->attr.exclude_kernel = 1; + + err = perf_evlist__open(evlist); + if (err < 0) { + pr_debug("Couldn't open the evlist: %s\n", + strerror_r(-err, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + if (perf_evlist__mmap(evlist, 128, true) < 0) { + pr_debug("failed to mmap events: %d (%s)\n", errno, + strerror_r(errno, sbuf, sizeof(sbuf))); + goto out_delete_evlist; + } + + perf_evlist__start_workload(evlist); + +retry: + while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) { + if (event->header.type == PERF_RECORD_EXIT) + nr_exit++; + + perf_evlist__mmap_consume(evlist, 0); + } + + if (!exited || !nr_exit) { + perf_evlist__poll(evlist, -1); + goto retry; + } + + if (nr_exit != 1) { + pr_debug("received %d EXIT records\n", nr_exit); + err = -1; + } + +out_delete_evlist: + perf_evlist__delete(evlist); + return err; +} diff --git a/kernel/tools/perf/tests/tests.h b/kernel/tools/perf/tests/tests.h new file mode 100644 index 000000000..52758a33f --- /dev/null +++ b/kernel/tools/perf/tests/tests.h @@ -0,0 +1,64 @@ +#ifndef TESTS_H +#define TESTS_H + +#define TEST_ASSERT_VAL(text, cond) \ +do { \ + if (!(cond)) { \ + pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \ + return -1; \ + } \ +} while (0) + +enum { + TEST_OK = 0, + TEST_FAIL = -1, + TEST_SKIP = -2, +}; + +/* Tests */ +int test__vmlinux_matches_kallsyms(void); +int test__open_syscall_event(void); +int test__open_syscall_event_on_all_cpus(void); +int test__basic_mmap(void); +int test__PERF_RECORD(void); +int test__rdpmc(void); +int test__perf_evsel__roundtrip_name_test(void); +int test__perf_evsel__tp_sched_test(void); +int test__syscall_open_tp_fields(void); +int test__pmu(void); +int test__attr(void); +int test__dso_data(void); +int test__dso_data_cache(void); +int test__dso_data_reopen(void); +int test__parse_events(void); +int test__hists_link(void); +int test__python_use(void); +int test__bp_signal(void); +int test__bp_signal_overflow(void); +int test__task_exit(void); +int test__sw_clock_freq(void); +int test__perf_time_to_tsc(void); +int test__code_reading(void); +int test__sample_parsing(void); +int test__keep_tracking(void); +int test__parse_no_sample_id_all(void); +int test__dwarf_unwind(void); +int test__hists_filter(void); +int test__mmap_thread_lookup(void); +int test__thread_mg_share(void); +int test__hists_output(void); +int test__hists_cumulate(void); +int test__switch_tracking(void); +int test__fdarray__filter(void); +int test__fdarray__add(void); +int test__kmod_path__parse(void); + +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) +#ifdef HAVE_DWARF_UNWIND_SUPPORT +struct thread; +struct perf_sample; +int test__arch_unwind_sample(struct perf_sample *sample, + struct thread *thread); +#endif +#endif +#endif /* TESTS_H */ diff --git a/kernel/tools/perf/tests/thread-mg-share.c b/kernel/tools/perf/tests/thread-mg-share.c new file mode 100644 index 000000000..b028499dd --- /dev/null +++ b/kernel/tools/perf/tests/thread-mg-share.c @@ -0,0 +1,91 @@ +#include "tests.h" +#include "machine.h" +#include "thread.h" +#include "map.h" +#include "debug.h" + +int test__thread_mg_share(void) +{ + struct machines machines; + struct machine *machine; + + /* thread group */ + struct thread *leader; + struct thread *t1, *t2, *t3; + struct map_groups *mg; + + /* other process */ + struct thread *other, *other_leader; + struct map_groups *other_mg; + + /* + * This test create 2 processes abstractions (struct thread) + * with several threads and checks they properly share and + * maintain map groups info (struct map_groups). + * + * thread group (pid: 0, tids: 0, 1, 2, 3) + * other group (pid: 4, tids: 4, 5) + */ + + machines__init(&machines); + machine = &machines.host; + + /* create process with 4 threads */ + leader = machine__findnew_thread(machine, 0, 0); + t1 = machine__findnew_thread(machine, 0, 1); + t2 = machine__findnew_thread(machine, 0, 2); + t3 = machine__findnew_thread(machine, 0, 3); + + /* and create 1 separated process, without thread leader */ + other = machine__findnew_thread(machine, 4, 5); + + TEST_ASSERT_VAL("failed to create threads", + leader && t1 && t2 && t3 && other); + + mg = leader->mg; + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 4); + + /* test the map groups pointer is shared */ + TEST_ASSERT_VAL("map groups don't match", mg == t1->mg); + TEST_ASSERT_VAL("map groups don't match", mg == t2->mg); + TEST_ASSERT_VAL("map groups don't match", mg == t3->mg); + + /* + * Verify the other leader was created by previous call. + * It should have shared map groups with no change in + * refcnt. + */ + other_leader = machine__find_thread(machine, 4, 4); + TEST_ASSERT_VAL("failed to find other leader", other_leader); + + other_mg = other->mg; + TEST_ASSERT_VAL("wrong refcnt", other_mg->refcnt == 2); + + TEST_ASSERT_VAL("map groups don't match", other_mg == other_leader->mg); + + /* release thread group */ + thread__delete(leader); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 3); + + thread__delete(t1); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 2); + + thread__delete(t2); + TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 1); + + thread__delete(t3); + + /* release other group */ + thread__delete(other_leader); + TEST_ASSERT_VAL("wrong refcnt", other_mg->refcnt == 1); + + thread__delete(other); + + /* + * Cannot call machine__delete_threads(machine) now, + * because we've already released all the threads. + */ + + machines__exit(&machines); + return 0; +} diff --git a/kernel/tools/perf/tests/vmlinux-kallsyms.c b/kernel/tools/perf/tests/vmlinux-kallsyms.c new file mode 100644 index 000000000..3d9088003 --- /dev/null +++ b/kernel/tools/perf/tests/vmlinux-kallsyms.c @@ -0,0 +1,242 @@ +#include +#include +#include +#include "map.h" +#include "symbol.h" +#include "util.h" +#include "tests.h" +#include "debug.h" +#include "machine.h" + +static int vmlinux_matches_kallsyms_filter(struct map *map __maybe_unused, + struct symbol *sym) +{ + bool *visited = symbol__priv(sym); + *visited = true; + return 0; +} + +#define UM(x) kallsyms_map->unmap_ip(kallsyms_map, (x)) + +int test__vmlinux_matches_kallsyms(void) +{ + int err = -1; + struct rb_node *nd; + struct symbol *sym; + struct map *kallsyms_map, *vmlinux_map; + struct machine kallsyms, vmlinux; + enum map_type type = MAP__FUNCTION; + u64 mem_start, mem_end; + + /* + * Step 1: + * + * Init the machines that will hold kernel, modules obtained from + * both vmlinux + .ko files and from /proc/kallsyms split by modules. + */ + machine__init(&kallsyms, "", HOST_KERNEL_ID); + machine__init(&vmlinux, "", HOST_KERNEL_ID); + + /* + * Step 2: + * + * Create the kernel maps for kallsyms and the DSO where we will then + * load /proc/kallsyms. Also create the modules maps from /proc/modules + * and find the .ko files that match them in /lib/modules/`uname -r`/. + */ + if (machine__create_kernel_maps(&kallsyms) < 0) { + pr_debug("machine__create_kernel_maps "); + goto out; + } + + /* + * Step 3: + * + * Load and split /proc/kallsyms into multiple maps, one per module. + */ + if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms", type, NULL) <= 0) { + pr_debug("dso__load_kallsyms "); + goto out; + } + + /* + * Step 4: + * + * kallsyms will be internally on demand sorted by name so that we can + * find the reference relocation * symbol, i.e. the symbol we will use + * to see if the running kernel was relocated by checking if it has the + * same value in the vmlinux file we load. + */ + kallsyms_map = machine__kernel_map(&kallsyms, type); + + /* + * Step 5: + * + * Now repeat step 2, this time for the vmlinux file we'll auto-locate. + */ + if (machine__create_kernel_maps(&vmlinux) < 0) { + pr_debug("machine__create_kernel_maps "); + goto out; + } + + vmlinux_map = machine__kernel_map(&vmlinux, type); + + /* + * Step 6: + * + * Locate a vmlinux file in the vmlinux path that has a buildid that + * matches the one of the running kernel. + * + * While doing that look if we find the ref reloc symbol, if we find it + * we'll have its ref_reloc_symbol.unrelocated_addr and then + * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines + * to fixup the symbols. + */ + if (machine__load_vmlinux_path(&vmlinux, type, + vmlinux_matches_kallsyms_filter) <= 0) { + pr_debug("Couldn't find a vmlinux that matches the kernel running on this machine, skipping test\n"); + err = TEST_SKIP; + goto out; + } + + err = 0; + /* + * Step 7: + * + * Now look at the symbols in the vmlinux DSO and check if we find all of them + * in the kallsyms dso. For the ones that are in both, check its names and + * end addresses too. + */ + for (nd = rb_first(&vmlinux_map->dso->symbols[type]); nd; nd = rb_next(nd)) { + struct symbol *pair, *first_pair; + bool backwards = true; + + sym = rb_entry(nd, struct symbol, rb_node); + + if (sym->start == sym->end) + continue; + + mem_start = vmlinux_map->unmap_ip(vmlinux_map, sym->start); + mem_end = vmlinux_map->unmap_ip(vmlinux_map, sym->end); + + first_pair = machine__find_kernel_symbol(&kallsyms, type, + mem_start, NULL, NULL); + pair = first_pair; + + if (pair && UM(pair->start) == mem_start) { +next_pair: + if (strcmp(sym->name, pair->name) == 0) { + /* + * kallsyms don't have the symbol end, so we + * set that by using the next symbol start - 1, + * in some cases we get this up to a page + * wrong, trace_kmalloc when I was developing + * this code was one such example, 2106 bytes + * off the real size. More than that and we + * _really_ have a problem. + */ + s64 skew = mem_end - UM(pair->end); + if (llabs(skew) >= page_size) + pr_debug("%#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n", + mem_start, sym->name, mem_end, + UM(pair->end)); + + /* + * Do not count this as a failure, because we + * could really find a case where it's not + * possible to get proper function end from + * kallsyms. + */ + continue; + + } else { + struct rb_node *nnd; +detour: + nnd = backwards ? rb_prev(&pair->rb_node) : + rb_next(&pair->rb_node); + if (nnd) { + struct symbol *next = rb_entry(nnd, struct symbol, rb_node); + + if (UM(next->start) == mem_start) { + pair = next; + goto next_pair; + } + } + + if (backwards) { + backwards = false; + pair = first_pair; + goto detour; + } + + pr_debug("%#" PRIx64 ": diff name v: %s k: %s\n", + mem_start, sym->name, pair->name); + } + } else + pr_debug("%#" PRIx64 ": %s not on kallsyms\n", + mem_start, sym->name); + + err = -1; + } + + if (!verbose) + goto out; + + pr_info("Maps only in vmlinux:\n"); + + for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { + struct map *pos = rb_entry(nd, struct map, rb_node), *pair; + /* + * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while + * the kernel will have the path for the vmlinux file being used, + * so use the short name, less descriptive but the same ("[kernel]" in + * both cases. + */ + pair = map_groups__find_by_name(&kallsyms.kmaps, type, + (pos->dso->kernel ? + pos->dso->short_name : + pos->dso->name)); + if (pair) + pair->priv = 1; + else + map__fprintf(pos, stderr); + } + + pr_info("Maps in vmlinux with a different name in kallsyms:\n"); + + for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { + struct map *pos = rb_entry(nd, struct map, rb_node), *pair; + + mem_start = vmlinux_map->unmap_ip(vmlinux_map, pos->start); + mem_end = vmlinux_map->unmap_ip(vmlinux_map, pos->end); + + pair = map_groups__find(&kallsyms.kmaps, type, mem_start); + if (pair == NULL || pair->priv) + continue; + + if (pair->start == mem_start) { + pair->priv = 1; + pr_info(" %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as", + pos->start, pos->end, pos->pgoff, pos->dso->name); + if (mem_end != pair->end) + pr_info(":\n*%" PRIx64 "-%" PRIx64 " %" PRIx64, + pair->start, pair->end, pair->pgoff); + pr_info(" %s\n", pair->dso->name); + pair->priv = 1; + } + } + + pr_info("Maps only in kallsyms:\n"); + + for (nd = rb_first(&kallsyms.kmaps.maps[type]); + nd; nd = rb_next(nd)) { + struct map *pos = rb_entry(nd, struct map, rb_node); + + if (!pos->priv) + map__fprintf(pos, stderr); + } +out: + machine__exit(&kallsyms); + machine__exit(&vmlinux); + return err; +} -- cgit 1.2.3-korg