Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
# SPDX-License-Identifier: GPL-2.0
perf-test-y += builtin-test.o
perf-test-y += tests-scripts.o
perf-test-y += parse-events.o
perf-test-y += uncore-event-sorting.o
perf-test-y += dso-data.o
perf-test-y += vmlinux-kallsyms.o
perf-test-y += openat-syscall.o
perf-test-y += openat-syscall-all-cpus.o
perf-test-$(CONFIG_LIBTRACEEVENT) += openat-syscall-tp-fields.o
perf-test-y += mmap-basic.o
perf-test-y += perf-record.o
perf-test-y += evsel-roundtrip-name.o
perf-test-$(CONFIG_LIBTRACEEVENT) += evsel-tp-sched.o
perf-test-y += fdarray.o
perf-test-y += pmu.o
perf-test-y += pmu-events.o
perf-test-y += hists_common.o
perf-test-y += hists_link.o
perf-test-y += hists_filter.o
perf-test-y += hists_output.o
perf-test-y += hists_cumulate.o
perf-test-y += bp_signal.o
perf-test-y += bp_signal_overflow.o
perf-test-y += bp_account.o
perf-test-y += wp.o
perf-test-y += task-exit.o
perf-test-y += sw-clock.o
perf-test-y += mmap-thread-lookup.o
perf-test-y += thread-maps-share.o
perf-test-$(CONFIG_LIBTRACEEVENT) += switch-tracking.o
perf-test-y += keep-tracking.o
perf-test-y += code-reading.o
perf-test-y += sample-parsing.o
perf-test-y += parse-no-sample-id-all.o
perf-test-y += kmod-path.o
perf-test-y += thread-map.o
perf-test-y += topology.o
perf-test-y += mem.o
perf-test-y += cpumap.o
perf-test-y += stat.o
perf-test-y += event_update.o
perf-test-y += event-times.o
perf-test-y += expr.o
perf-test-y += backward-ring-buffer.o
perf-test-y += sdt.o
perf-test-y += is_printable_array.o
perf-test-y += bitmap.o
perf-test-y += perf-hooks.o
perf-test-y += unit_number__scnprintf.o
perf-test-y += mem2node.o
perf-test-y += maps.o
perf-test-y += time-utils-test.o
perf-test-y += genelf.o
perf-test-y += api-io.o
perf-test-y += demangle-java-test.o
perf-test-y += demangle-ocaml-test.o
perf-test-y += demangle-rust-v0-test.o
perf-test-y += pfm.o
perf-test-y += parse-metric.o
perf-test-y += pe-file-parsing.o
perf-test-y += expand-cgroup.o
perf-test-y += perf-time-to-tsc.o
perf-test-y += dlfilter-test.o
perf-test-y += sigtrap.o
perf-test-y += event_groups.o
perf-test-y += symbols.o
perf-test-y += util.o
perf-test-y += hwmon_pmu.o
perf-test-y += tool_pmu.o
perf-test-y += subcmd-help.o
perf-test-y += kallsyms-split.o
ifeq ($(SRCARCH),$(filter $(SRCARCH),x86 arm arm64 powerpc))
perf-test-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
endif
CFLAGS_dwarf-unwind.o += -fno-optimize-sibling-calls
perf-test-y += workloads/
ifdef SHELLCHECK
SHELL_TESTS := $(shell find tests/shell -executable -type f -name '*.sh')
SHELL_TEST_LOGS := $(SHELL_TESTS:tests/shell/%=shell/%.shellcheck_log)
else
SHELL_TESTS :=
SHELL_TEST_LOGS :=
endif
$(OUTPUT)%.shellcheck_log: %
$(call rule_mkdir)
$(Q)$(call echo-cmd,test)$(SHELLCHECK) "$<" > $@ || (cat $@ && rm $@ && false)
perf-test-y += $(SHELL_TEST_LOGS)
ifdef MYPY
PY_TESTS := $(shell find tests/shell -type f -name '*.py')
MYPY_TEST_LOGS := $(PY_TESTS:tests/shell/%=shell/%.mypy_log)
else
MYPY_TEST_LOGS :=
endif
$(OUTPUT)%.mypy_log: %
$(call rule_mkdir)
$(Q)$(call echo-cmd,test)mypy "$<" > $@ || (cat $@ && rm $@ && false)
perf-test-y += $(MYPY_TEST_LOGS)
ifdef PYLINT
PY_TESTS := $(shell find tests/shell -type f -name '*.py')
PYLINT_TEST_LOGS := $(PY_TESTS:tests/shell/%=shell/%.pylint_log)
else
PYLINT_TEST_LOGS :=
endif
$(OUTPUT)%.pylint_log: %
$(call rule_mkdir)
$(Q)$(call echo-cmd,test)pylint "$<" > $@ || (cat $@ && rm $@ && false)
perf-test-y += $(PYLINT_TEST_LOGS)
+343
View File
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "debug.h"
#include "tests.h"
#include <api/io.h>
#include <linux/kernel.h>
#include <linux/zalloc.h>
#define TEMPL "/tmp/perf-test-XXXXXX"
#define EXPECT_EQUAL(val, expected) \
do { \
if (val != expected) { \
pr_debug("%s:%d: %d != %d\n", \
__FILE__, __LINE__, val, expected); \
ret = -1; \
} \
} while (0)
#define EXPECT_EQUAL64(val, expected) \
do { \
if (val != expected) { \
pr_debug("%s:%d: %lld != %lld\n", \
__FILE__, __LINE__, val, expected); \
ret = -1; \
} \
} while (0)
static int make_test_file(char path[PATH_MAX], const char *contents)
{
ssize_t contents_len = strlen(contents);
int fd;
strcpy(path, TEMPL);
fd = mkstemp(path);
if (fd < 0) {
pr_debug("mkstemp failed");
return -1;
}
if (write(fd, contents, contents_len) < contents_len) {
pr_debug("short write");
close(fd);
unlink(path);
return -1;
}
close(fd);
return 0;
}
static int setup_test(char path[PATH_MAX], const char *contents,
size_t buf_size, struct io *io)
{
if (make_test_file(path, contents))
return -1;
io->fd = open(path, O_RDONLY);
if (io->fd < 0) {
pr_debug("Failed to open '%s'\n", path);
unlink(path);
return -1;
}
io->buf = malloc(buf_size);
if (io->buf == NULL) {
pr_debug("Failed to allocate memory");
close(io->fd);
unlink(path);
return -1;
}
io__init(io, io->fd, io->buf, buf_size);
return 0;
}
static void cleanup_test(char path[PATH_MAX], struct io *io)
{
zfree(&io->buf);
close(io->fd);
unlink(path);
}
static int do_test_get_char(const char *test_string, size_t buf_size)
{
char path[PATH_MAX];
struct io io;
int ch, ret = 0;
size_t i;
if (setup_test(path, test_string, buf_size, &io))
return -1;
for (i = 0; i < strlen(test_string); i++) {
ch = io__get_char(&io);
EXPECT_EQUAL(ch, test_string[i]);
EXPECT_EQUAL(io.eof, false);
}
ch = io__get_char(&io);
EXPECT_EQUAL(ch, -1);
EXPECT_EQUAL(io.eof, true);
cleanup_test(path, &io);
return ret;
}
static int test_get_char(void)
{
int i, ret = 0;
size_t j;
static const char *const test_strings[] = {
"12345678abcdef90",
"a\nb\nc\nd\n",
"\a\b\t\v\f\r",
};
for (i = 0; i <= 10; i++) {
for (j = 0; j < ARRAY_SIZE(test_strings); j++) {
if (do_test_get_char(test_strings[j], 1 << i))
ret = -1;
}
}
return ret;
}
static int do_test_get_hex(const char *test_string,
__u64 val1, int ch1,
__u64 val2, int ch2,
__u64 val3, int ch3,
bool end_eof)
{
char path[PATH_MAX];
struct io io;
int ch, ret = 0;
__u64 hex;
if (setup_test(path, test_string, 4, &io))
return -1;
ch = io__get_hex(&io, &hex);
EXPECT_EQUAL64(hex, val1);
EXPECT_EQUAL(ch, ch1);
ch = io__get_hex(&io, &hex);
EXPECT_EQUAL64(hex, val2);
EXPECT_EQUAL(ch, ch2);
ch = io__get_hex(&io, &hex);
EXPECT_EQUAL64(hex, val3);
EXPECT_EQUAL(ch, ch3);
EXPECT_EQUAL(io.eof, end_eof);
cleanup_test(path, &io);
return ret;
}
static int test_get_hex(void)
{
int ret = 0;
if (do_test_get_hex("12345678abcdef90",
0x12345678abcdef90, -1,
0, -1,
0, -1,
true))
ret = -1;
if (do_test_get_hex("1\n2\n3\n",
1, '\n',
2, '\n',
3, '\n',
false))
ret = -1;
if (do_test_get_hex("12345678ABCDEF90;a;b",
0x12345678abcdef90, ';',
0xa, ';',
0xb, -1,
true))
ret = -1;
if (do_test_get_hex("0x1x2x",
0, 'x',
1, 'x',
2, 'x',
false))
ret = -1;
if (do_test_get_hex("x1x",
0, -2,
1, 'x',
0, -1,
true))
ret = -1;
if (do_test_get_hex("10000000000000000000000000000abcdefgh99i",
0xabcdef, 'g',
0, -2,
0x99, 'i',
false))
ret = -1;
return ret;
}
static int do_test_get_dec(const char *test_string,
__u64 val1, int ch1,
__u64 val2, int ch2,
__u64 val3, int ch3,
bool end_eof)
{
char path[PATH_MAX];
struct io io;
int ch, ret = 0;
__u64 dec;
if (setup_test(path, test_string, 4, &io))
return -1;
ch = io__get_dec(&io, &dec);
EXPECT_EQUAL64(dec, val1);
EXPECT_EQUAL(ch, ch1);
ch = io__get_dec(&io, &dec);
EXPECT_EQUAL64(dec, val2);
EXPECT_EQUAL(ch, ch2);
ch = io__get_dec(&io, &dec);
EXPECT_EQUAL64(dec, val3);
EXPECT_EQUAL(ch, ch3);
EXPECT_EQUAL(io.eof, end_eof);
cleanup_test(path, &io);
return ret;
}
static int test_get_dec(void)
{
int ret = 0;
if (do_test_get_dec("12345678abcdef90",
12345678, 'a',
0, -2,
0, -2,
false))
ret = -1;
if (do_test_get_dec("1\n2\n3\n",
1, '\n',
2, '\n',
3, '\n',
false))
ret = -1;
if (do_test_get_dec("12345678;1;2",
12345678, ';',
1, ';',
2, -1,
true))
ret = -1;
if (do_test_get_dec("0x1x2x",
0, 'x',
1, 'x',
2, 'x',
false))
ret = -1;
if (do_test_get_dec("x1x",
0, -2,
1, 'x',
0, -1,
true))
ret = -1;
if (do_test_get_dec("10000000000000000000000000000000000000000000000000000000000123456789ab99c",
123456789, 'a',
0, -2,
99, 'c',
false))
ret = -1;
return ret;
}
static int test_get_line(void)
{
char path[PATH_MAX];
struct io io;
char test_string[1024];
char *line = NULL;
size_t i, line_len = 0;
size_t buf_size = 128;
int ret = 0;
for (i = 0; i < 512; i++)
test_string[i] = 'a';
test_string[512] = '\n';
for (i = 513; i < 1023; i++)
test_string[i] = 'b';
test_string[1023] = '\0';
if (setup_test(path, test_string, buf_size, &io))
return -1;
EXPECT_EQUAL((int)io__getline(&io, &line, &line_len), 513);
EXPECT_EQUAL((int)strlen(line), 513);
for (i = 0; i < 512; i++)
EXPECT_EQUAL(line[i], 'a');
EXPECT_EQUAL(line[512], '\n');
EXPECT_EQUAL((int)io__getline(&io, &line, &line_len), 510);
for (i = 0; i < 510; i++)
EXPECT_EQUAL(line[i], 'b');
free(line);
cleanup_test(path, &io);
return ret;
}
static int test__api_io(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int ret = 0;
if (test_get_char())
ret = TEST_FAIL;
if (test_get_hex())
ret = TEST_FAIL;
if (test_get_dec())
ret = TEST_FAIL;
if (test_get_line())
ret = TEST_FAIL;
return ret;
}
DEFINE_SUITE("Test api io", api_io);
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test backward bit in event attribute, read ring buffer from end to
* beginning
*/
#include <evlist.h>
#include <sys/prctl.h>
#include "record.h"
#include "tests.h"
#include "debug.h"
#include "parse-events.h"
#include "util/mmap.h"
#include <errno.h>
#include <linux/string.h>
#include <perf/mmap.h>
#define NR_ITERS 111
static void testcase(void)
{
int i;
for (i = 0; i < NR_ITERS; i++) {
char proc_name[15];
snprintf(proc_name, sizeof(proc_name), "p:%d\n", i);
prctl(PR_SET_NAME, proc_name);
}
}
static int count_samples(struct evlist *evlist, int *sample_count,
int *comm_count)
{
int i;
for (i = 0; i < evlist->core.nr_mmaps; i++) {
struct mmap *map = &evlist->overwrite_mmap[i];
union perf_event *event;
perf_mmap__read_init(&map->core);
while ((event = perf_mmap__read_event(&map->core)) != NULL) {
const u32 type = event->header.type;
switch (type) {
case PERF_RECORD_SAMPLE:
(*sample_count)++;
break;
case PERF_RECORD_COMM:
(*comm_count)++;
break;
default:
pr_err("Unexpected record of type %d\n", type);
return TEST_FAIL;
}
}
perf_mmap__read_done(&map->core);
}
return TEST_OK;
}
static int do_test(struct evlist *evlist, int mmap_pages,
int *sample_count, int *comm_count)
{
int err;
char sbuf[STRERR_BUFSIZE];
err = evlist__mmap(evlist, mmap_pages);
if (err < 0) {
pr_debug("evlist__mmap: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
return TEST_FAIL;
}
evlist__enable(evlist);
testcase();
evlist__disable(evlist);
err = count_samples(evlist, sample_count, comm_count);
evlist__munmap(evlist);
return err;
}
static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int ret = TEST_SKIP, err, sample_count = 0, comm_count = 0;
char pid[16], sbuf[STRERR_BUFSIZE];
struct evlist *evlist;
struct evsel *evsel __maybe_unused;
struct parse_events_error parse_error;
struct record_opts opts = {
.target = {
.uses_mmap = true,
},
.freq = 0,
.mmap_pages = 256,
.default_interval = 1,
};
snprintf(pid, sizeof(pid), "%d", getpid());
pid[sizeof(pid) - 1] = '\0';
opts.target.tid = opts.target.pid = pid;
evlist = evlist__new();
if (!evlist) {
pr_debug("Not enough memory to create evlist\n");
return TEST_FAIL;
}
err = evlist__create_maps(evlist, &opts.target);
if (err < 0) {
pr_debug("Not enough memory to create thread/cpu maps\n");
goto out_delete_evlist;
}
parse_events_error__init(&parse_error);
/*
* Set backward bit, ring buffer should be writing from end. Record
* it in aux evlist
*/
err = parse_events(evlist, "syscalls:sys_enter_prctl/overwrite/", &parse_error);
parse_events_error__exit(&parse_error);
if (err) {
pr_debug("Failed to parse tracepoint event, try use root\n");
ret = TEST_SKIP;
goto out_delete_evlist;
}
evlist__config(evlist, &opts, NULL);
err = evlist__open(evlist);
if (err < 0) {
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
goto out_delete_evlist;
}
ret = TEST_FAIL;
err = do_test(evlist, opts.mmap_pages, &sample_count,
&comm_count);
if (err != TEST_OK)
goto out_delete_evlist;
if ((sample_count != NR_ITERS) || (comm_count != NR_ITERS)) {
pr_err("Unexpected counter: sample_count=%d, comm_count=%d\n",
sample_count, comm_count);
goto out_delete_evlist;
}
evlist__close(evlist);
err = evlist__open(evlist);
if (err < 0) {
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
goto out_delete_evlist;
}
err = do_test(evlist, 1, &sample_count, &comm_count);
if (err != TEST_OK)
goto out_delete_evlist;
ret = TEST_OK;
out_delete_evlist:
evlist__delete(evlist);
return ret;
}
DEFINE_SUITE("Read backward ring buffer", backward_ring_buffer);
+56
View File
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <linux/bitmap.h>
#include <perf/cpumap.h>
#include <internal/cpumap.h>
#include "tests.h"
#include "debug.h"
#define NBITS 100
static unsigned long *get_bitmap(const char *str, int nbits)
{
struct perf_cpu_map *map = perf_cpu_map__new(str);
unsigned long *bm;
bm = bitmap_zalloc(nbits);
if (map && bm) {
unsigned int i;
struct perf_cpu cpu;
perf_cpu_map__for_each_cpu(cpu, i, map)
__set_bit(cpu.cpu, bm);
}
perf_cpu_map__put(map);
return bm;
}
static int test_bitmap(const char *str)
{
unsigned long *bm = get_bitmap(str, NBITS);
char buf[100];
int ret;
bitmap_scnprintf(bm, NBITS, buf, sizeof(buf));
pr_debug("bitmap: %s\n", buf);
ret = !strcmp(buf, str);
free(bm);
return ret;
}
static int test__bitmap_print(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
TEST_ASSERT_VAL("failed to convert map", test_bitmap("1"));
TEST_ASSERT_VAL("failed to convert map", test_bitmap("1,5"));
TEST_ASSERT_VAL("failed to convert map", test_bitmap("1,3,5,7,9,11,13,15,17,19,21-40"));
TEST_ASSERT_VAL("failed to convert map", test_bitmap("2-5"));
TEST_ASSERT_VAL("failed to convert map", test_bitmap("1,3-6,8-10,24,35-37"));
TEST_ASSERT_VAL("failed to convert map", test_bitmap("1,3-6,8-10,24,35-37"));
TEST_ASSERT_VAL("failed to convert map", test_bitmap("1-10,12-20,22-30,32-40"));
return 0;
}
DEFINE_SUITE("Print bitmap", bitmap_print);
+225
View File
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Powerpc needs __SANE_USERSPACE_TYPES__ before <linux/types.h> to select
* 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu.
*/
#define __SANE_USERSPACE_TYPES__
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/hw_breakpoint.h>
#include "tests.h"
#include "debug.h"
#include "event.h"
#include "parse-events.h"
#include "../perf-sys.h"
#include "cloexec.h"
/*
* PowerPC and S390 do not support creation of instruction breakpoints using the
* perf_event interface.
*
* Just disable the test for these architectures until these issues are
* resolved.
*/
#if defined(__powerpc__) || defined(__s390x__)
#define BP_ACCOUNT_IS_SUPPORTED 0
#else
#define BP_ACCOUNT_IS_SUPPORTED 1
#endif
static volatile long the_var;
static noinline int test_function(void)
{
return 0;
}
static int __event(bool is_x, void *addr, struct perf_event_attr *attr)
{
int fd;
memset(attr, 0, sizeof(struct perf_event_attr));
attr->type = PERF_TYPE_BREAKPOINT;
attr->size = sizeof(struct perf_event_attr);
attr->config = 0;
attr->bp_type = is_x ? HW_BREAKPOINT_X : HW_BREAKPOINT_W;
attr->bp_addr = (unsigned long) addr;
attr->bp_len = is_x ? default_breakpoint_len() : sizeof(long);
attr->sample_period = 1;
attr->sample_type = PERF_SAMPLE_IP;
attr->exclude_kernel = 1;
attr->exclude_hv = 1;
fd = sys_perf_event_open(attr, -1, 0, -1,
perf_event_open_cloexec_flag());
if (fd < 0) {
pr_debug("failed opening event %llx\n", attr->config);
return TEST_FAIL;
}
return fd;
}
static int wp_event(void *addr, struct perf_event_attr *attr)
{
return __event(false, addr, attr);
}
static int bp_event(void *addr, struct perf_event_attr *attr)
{
return __event(true, addr, attr);
}
static int bp_accounting(int wp_cnt, int share)
{
struct perf_event_attr attr, attr_mod, attr_new;
int i, fd[wp_cnt], fd_wp, ret;
for (i = 0; i < wp_cnt; i++) {
fd[i] = wp_event((void *)&the_var, &attr);
TEST_ASSERT_VAL("failed to create wp\n", fd[i] != -1);
pr_debug("wp %d created\n", i);
}
attr_mod = attr;
attr_mod.bp_type = HW_BREAKPOINT_X;
attr_mod.bp_addr = (unsigned long) test_function;
attr_mod.bp_len = default_breakpoint_len();
ret = ioctl(fd[0], PERF_EVENT_IOC_MODIFY_ATTRIBUTES, &attr_mod);
TEST_ASSERT_VAL("failed to modify wp\n", ret == 0);
pr_debug("wp 0 modified to bp\n");
if (!share) {
fd_wp = wp_event((void *)&the_var, &attr_new);
TEST_ASSERT_VAL("failed to create max wp\n", fd_wp != -1);
pr_debug("wp max created\n");
close(fd_wp);
}
for (i = 0; i < wp_cnt; i++)
close(fd[i]);
return 0;
}
static int detect_cnt(bool is_x)
{
struct perf_event_attr attr;
void *addr = is_x ? (void *)test_function : (void *)&the_var;
int fd[100], cnt = 0, i;
while (1) {
if (cnt == 100) {
pr_debug("way too many debug registers, fix the test\n");
return 0;
}
fd[cnt] = __event(is_x, addr, &attr);
if (fd[cnt] < 0)
break;
cnt++;
}
for (i = 0; i < cnt; i++)
close(fd[i]);
return cnt;
}
static int detect_ioctl(void)
{
struct perf_event_attr attr;
int fd, ret = 1;
fd = wp_event((void *) &the_var, &attr);
if (fd > 0) {
ret = ioctl(fd, PERF_EVENT_IOC_MODIFY_ATTRIBUTES, &attr);
close(fd);
}
return ret ? 0 : 1;
}
static int detect_share(int wp_cnt, int bp_cnt)
{
struct perf_event_attr attr;
int i, *fd = NULL, ret = -1;
if (wp_cnt + bp_cnt == 0)
return 0;
fd = malloc(sizeof(int) * (wp_cnt + bp_cnt));
if (!fd)
return -1;
for (i = 0; i < wp_cnt; i++) {
fd[i] = wp_event((void *)&the_var, &attr);
if (fd[i] == -1) {
pr_err("failed to create wp\n");
goto out;
}
}
for (; i < (bp_cnt + wp_cnt); i++) {
fd[i] = bp_event((void *)test_function, &attr);
if (fd[i] == -1)
break;
}
ret = i != (bp_cnt + wp_cnt);
out:
while (i--)
close(fd[i]);
free(fd);
return ret;
}
/*
* This test does following:
* - detects the number of watch/break-points,
* skip test if any is missing
* - detects PERF_EVENT_IOC_MODIFY_ATTRIBUTES ioctl,
* skip test if it's missing
* - detects if watchpoints and breakpoints share
* same slots
* - create all possible watchpoints on cpu 0
* - change one of it to breakpoint
* - in case wp and bp do not share slots,
* we create another watchpoint to ensure
* the slot accounting is correct
*/
static int test__bp_accounting(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int has_ioctl = detect_ioctl();
int wp_cnt = detect_cnt(false);
int bp_cnt = detect_cnt(true);
int share = detect_share(wp_cnt, bp_cnt);
if (!BP_ACCOUNT_IS_SUPPORTED) {
pr_debug("Test not supported on this architecture");
return TEST_SKIP;
}
pr_debug("watchpoints count %d, breakpoints count %d, has_ioctl %d, share %d\n",
wp_cnt, bp_cnt, has_ioctl, share);
if (!wp_cnt || !bp_cnt || !has_ioctl)
return TEST_SKIP;
return bp_accounting(wp_cnt, share);
}
DEFINE_SUITE("Breakpoint accounting", bp_accounting);
+294
View File
@@ -0,0 +1,294 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Inspired by breakpoint overflow test done by
* Vince Weaver <vincent.weaver@maine.edu> for perf_event_tests
* (git://github.com/deater/perf_event_tests)
*/
/*
* Powerpc needs __SANE_USERSPACE_TYPES__ before <linux/types.h> to select
* 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu.
*/
#define __SANE_USERSPACE_TYPES__
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/mman.h>
#include <linux/compiler.h>
#include <linux/hw_breakpoint.h>
#include "tests.h"
#include "debug.h"
#include "event.h"
#include "parse-events.h"
#include "perf-sys.h"
#include "cloexec.h"
static int fd1;
static int fd2;
static int fd3;
static int overflows;
static int overflows_2;
static volatile long the_var;
/*
* Use ASM to ensure watchpoint and breakpoint can be triggered
* at one instruction.
*/
#if defined (__x86_64__)
extern void __test_function(volatile long *ptr);
asm (
".pushsection .text;"
".globl __test_function\n"
".type __test_function, @function;"
"__test_function:\n"
"incq (%rdi)\n"
"ret\n"
".popsection\n");
#else
static void __test_function(volatile long *ptr)
{
*ptr = 0x1234;
}
#endif
static noinline int test_function(void)
{
__test_function(&the_var);
the_var++;
return time(NULL);
}
static void sig_handler_2(int signum __maybe_unused,
siginfo_t *oh __maybe_unused,
void *uc __maybe_unused)
{
overflows_2++;
if (overflows_2 > 10) {
ioctl(fd1, PERF_EVENT_IOC_DISABLE, 0);
ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0);
ioctl(fd3, PERF_EVENT_IOC_DISABLE, 0);
}
}
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);
ioctl(fd3, PERF_EVENT_IOC_DISABLE, 0);
}
}
static int __event(bool is_x, void *addr, int sig)
{
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 = is_x ? HW_BREAKPOINT_X : HW_BREAKPOINT_W;
pe.bp_addr = (unsigned long) addr;
pe.bp_len = is_x ? default_breakpoint_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;
}
fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC);
fcntl(fd, F_SETSIG, sig);
fcntl(fd, F_SETOWN, getpid());
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
return fd;
}
static int bp_event(void *addr, int sig)
{
return __event(true, addr, sig);
}
static int wp_event(void *addr, int sig)
{
return __event(false, addr, sig);
}
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;
}
static int test__bp_signal(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct sigaction sa;
long long count1, count2, count3;
if (!BP_SIGNAL_IS_SUPPORTED) {
pr_debug("Test not supported on this architecture");
return TEST_SKIP;
}
/* setup SIGIO signal handler */
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_sigaction = (void *) sig_handler;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGIO, &sa, NULL) < 0) {
pr_debug("failed setting up signal handler\n");
return TEST_FAIL;
}
sa.sa_sigaction = (void *) sig_handler_2;
if (sigaction(SIGUSR1, &sa, NULL) < 0) {
pr_debug("failed setting up signal handler 2\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 with SIGUSR1
* configured. We should get SIGUSR1 each time when
* breakpoint is hit
*
* fd3 - watchpoint event on __test_function with SIGIO
* configured.
*
* Following processing should happen:
* Exec: Action: Result:
* incq (%rdi) - fd1 event breakpoint hit -> count1 == 1
* - SIGIO is delivered
* sig_handler - fd2 event breakpoint hit -> count2 == 1
* - SIGUSR1 is delivered
* sig_handler_2 -> overflows_2 == 1 (nested signal)
* sys_rt_sigreturn - return from sig_handler_2
* overflows++ -> overflows = 1
* sys_rt_sigreturn - return from sig_handler
* incq (%rdi) - fd3 event watchpoint hit -> count3 == 1 (wp and bp in one insn)
* - SIGIO is delivered
* sig_handler - fd2 event breakpoint hit -> count2 == 2
* - SIGUSR1 is delivered
* sig_handler_2 -> overflows_2 == 2 (nested signal)
* sys_rt_sigreturn - return from sig_handler_2
* overflows++ -> overflows = 2
* sys_rt_sigreturn - return from sig_handler
* the_var++ - fd3 event watchpoint hit -> count3 == 2 (standalone watchpoint)
* - SIGIO is delivered
* sig_handler - fd2 event breakpoint hit -> count2 == 3
* - SIGUSR1 is delivered
* sig_handler_2 -> overflows_2 == 3 (nested signal)
* sys_rt_sigreturn - return from sig_handler_2
* overflows++ -> overflows == 3
* sys_rt_sigreturn - return from sig_handler
*
* The test case check following error conditions:
* - we get stuck in signal handler because of debug
* exception being triggered recursively due to
* the wrong RF EFLAG management
*
* - we never trigger the sig_handler breakpoint due
* to the wrong RF EFLAG management
*
*/
fd1 = bp_event(__test_function, SIGIO);
fd2 = bp_event(sig_handler, SIGUSR1);
fd3 = wp_event((void *)&the_var, SIGIO);
ioctl(fd1, PERF_EVENT_IOC_ENABLE, 0);
ioctl(fd2, PERF_EVENT_IOC_ENABLE, 0);
ioctl(fd3, PERF_EVENT_IOC_ENABLE, 0);
/*
* Kick off the test by triggering 'fd1'
* breakpoint.
*/
test_function();
ioctl(fd1, PERF_EVENT_IOC_DISABLE, 0);
ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0);
ioctl(fd3, PERF_EVENT_IOC_DISABLE, 0);
count1 = bp_count(fd1);
count2 = bp_count(fd2);
count3 = bp_count(fd3);
close(fd1);
close(fd2);
close(fd3);
pr_debug("count1 %lld, count2 %lld, count3 %lld, overflow %d, overflows_2 %d\n",
count1, count2, count3, overflows, overflows_2);
if (count1 != 1) {
if (count1 == 11)
pr_debug("failed: RF EFLAG recursion issue detected\n");
else
pr_debug("failed: wrong count for bp1: %lld, expected 1\n", count1);
}
if (overflows != 3)
pr_debug("failed: wrong overflow (%d) hit, expected 3\n", overflows);
if (overflows_2 != 3)
pr_debug("failed: wrong overflow_2 (%d) hit, expected 3\n", overflows_2);
if (count2 != 3)
pr_debug("failed: wrong count for bp2 (%lld), expected 3\n", count2);
if (count3 != 2)
pr_debug("failed: wrong count for bp3 (%lld), expected 2\n", count3);
return count1 == 1 && overflows == 3 && count2 == 3 && overflows_2 == 3 && count3 == 2 ?
TEST_OK : TEST_FAIL;
}
DEFINE_SUITE("Breakpoint overflow signal handler", bp_signal);
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Originally done by Vince Weaver <vincent.weaver@maine.edu> for
* perf_event_tests (git://github.com/deater/perf_event_tests)
*/
/*
* Powerpc needs __SANE_USERSPACE_TYPES__ before <linux/types.h> to select
* 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu.
*/
#define __SANE_USERSPACE_TYPES__
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/mman.h>
#include <linux/compiler.h>
#include <linux/hw_breakpoint.h>
#include "tests.h"
#include "debug.h"
#include "event.h"
#include "parse-events.h"
#include "../perf-sys.h"
#include "cloexec.h"
static int overflows;
static noinline 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
static int test__bp_signal_overflow(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct perf_event_attr pe;
struct sigaction sa;
long long count;
int fd, i, fails = 0;
if (!BP_SIGNAL_IS_SUPPORTED) {
pr_debug("Test not supported on this architecture");
return TEST_SKIP;
}
/* setup SIGIO signal handler */
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_sigaction = (void *) sig_handler;
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 = default_breakpoint_len();
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;
}
DEFINE_SUITE("Breakpoint overflow sampling", bp_signal_overflow);
File diff suppressed because it is too large Load Diff
+888
View File
@@ -0,0 +1,888 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <linux/kconfig.h>
#include <linux/kernel.h>
#include <linux/rbtree.h>
#include <linux/types.h>
#include <linux/zalloc.h>
#include <inttypes.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/utsname.h>
#include <perf/cpumap.h>
#include <perf/evlist.h>
#include <perf/mmap.h>
#include "debug.h"
#include "dso.h"
#include "env.h"
#include "parse-events.h"
#include "evlist.h"
#include "evsel.h"
#include "thread_map.h"
#include "machine.h"
#include "map.h"
#include "symbol.h"
#include "event.h"
#include "record.h"
#include "util/mmap.h"
#include "util/string2.h"
#include "util/synthetic-events.h"
#include "util/util.h"
#include "thread.h"
#include "tests.h"
#include <linux/ctype.h>
#define BUFSZ 1024
#define READLEN 128
struct tested_section {
struct rb_node rb_node;
u64 addr;
char *path;
};
static bool tested_code_insert_or_exists(const char *path, u64 addr,
struct rb_root *tested_sections)
{
struct rb_node **node = &tested_sections->rb_node;
struct rb_node *parent = NULL;
struct tested_section *data;
while (*node) {
int cmp;
parent = *node;
data = rb_entry(*node, struct tested_section, rb_node);
cmp = strcmp(path, data->path);
if (!cmp) {
if (addr < data->addr)
cmp = -1;
else if (addr > data->addr)
cmp = 1;
else
return true; /* already tested */
}
if (cmp < 0)
node = &(*node)->rb_left;
else
node = &(*node)->rb_right;
}
data = zalloc(sizeof(*data));
if (!data)
return true;
data->addr = addr;
data->path = strdup(path);
if (!data->path) {
free(data);
return true;
}
rb_link_node(&data->rb_node, parent, node);
rb_insert_color(&data->rb_node, tested_sections);
return false;
}
static void tested_sections__free(struct rb_root *root)
{
while (!RB_EMPTY_ROOT(root)) {
struct rb_node *node = rb_first(root);
struct tested_section *ts = rb_entry(node,
struct tested_section,
rb_node);
rb_erase(node, root);
free(ts->path);
free(ts);
}
}
static size_t read_objdump_chunk(const char **line, unsigned char **buf,
size_t *buf_len)
{
size_t bytes_read = 0;
unsigned char *chunk_start = *buf;
/* Read bytes */
while (*buf_len > 0) {
char c1, c2;
/* Get 2 hex digits */
c1 = *(*line)++;
if (!isxdigit(c1))
break;
c2 = *(*line)++;
if (!isxdigit(c2))
break;
/* Store byte and advance buf */
**buf = (hex(c1) << 4) | hex(c2);
(*buf)++;
(*buf_len)--;
bytes_read++;
/* End of chunk? */
if (isspace(**line))
break;
}
/*
* objdump will display raw insn as LE if code endian
* is LE and bytes_per_chunk > 1. In that case reverse
* the chunk we just read.
*
* see disassemble_bytes() at binutils/objdump.c for details
* how objdump chooses display endian)
*/
if (bytes_read > 1 && !host_is_bigendian()) {
unsigned char *chunk_end = chunk_start + bytes_read - 1;
unsigned char tmp;
while (chunk_start < chunk_end) {
tmp = *chunk_start;
*chunk_start = *chunk_end;
*chunk_end = tmp;
chunk_start++;
chunk_end--;
}
}
return bytes_read;
}
static size_t read_objdump_line(const char *line, unsigned char *buf,
size_t buf_len)
{
const char *p;
size_t ret, bytes_read = 0;
/* Skip to a colon */
p = strchr(line, ':');
if (!p)
return 0;
p++;
/* Skip initial spaces */
while (*p) {
if (!isspace(*p))
break;
p++;
}
do {
ret = read_objdump_chunk(&p, &buf, &buf_len);
bytes_read += ret;
p++;
} while (ret > 0);
/* return number of successfully read bytes */
return bytes_read;
}
static int read_objdump_output(FILE *f, void *buf, size_t *len, u64 start_addr)
{
char *line = NULL;
size_t line_len, off_last = 0;
ssize_t ret;
int err = 0;
u64 addr, last_addr = start_addr;
while (off_last < *len) {
size_t off, read_bytes, written_bytes;
unsigned char tmp[BUFSZ];
ret = getline(&line, &line_len, f);
if (feof(f))
break;
if (ret < 0) {
pr_debug("getline failed\n");
err = -1;
break;
}
/* read objdump data into temporary buffer */
read_bytes = read_objdump_line(line, tmp, sizeof(tmp));
if (!read_bytes)
continue;
if (sscanf(line, "%"PRIx64, &addr) != 1)
continue;
if (addr < last_addr) {
pr_debug("addr going backwards, read beyond section?\n");
break;
}
last_addr = addr;
/* copy it from temporary buffer to 'buf' according
* to address on current objdump line */
off = addr - start_addr;
if (off >= *len)
break;
written_bytes = MIN(read_bytes, *len - off);
memcpy(buf + off, tmp, written_bytes);
off_last = off + written_bytes;
}
/* len returns number of bytes that could not be read */
*len -= off_last;
free(line);
return err;
}
/*
* Only gets GNU objdump version. Returns 0 for llvm-objdump.
*/
static int objdump_version(void)
{
size_t line_len;
char cmd[PATH_MAX * 2];
char *line = NULL;
const char *fmt;
FILE *f;
int ret;
int version_tmp, version_num = 0;
char *version = 0, *token;
fmt = "%s --version";
ret = snprintf(cmd, sizeof(cmd), fmt, test_objdump_path);
if (ret <= 0 || (size_t)ret >= sizeof(cmd))
return -1;
/* Ignore objdump errors */
strcat(cmd, " 2>/dev/null");
f = popen(cmd, "r");
if (!f) {
pr_debug("popen failed\n");
return -1;
}
/* Get first line of objdump --version output */
ret = getline(&line, &line_len, f);
pclose(f);
if (ret < 0) {
pr_debug("getline failed\n");
return -1;
}
token = strsep(&line, " ");
if (token != NULL && !strcmp(token, "GNU")) {
// version is last part of first line of objdump --version output.
while ((token = strsep(&line, " ")))
version = token;
// Convert version into a format we can compare with
token = strsep(&version, ".");
version_num = atoi(token);
if (version_num)
version_num *= 10000;
token = strsep(&version, ".");
version_tmp = atoi(token);
if (token)
version_num += version_tmp * 100;
token = strsep(&version, ".");
version_tmp = atoi(token);
if (token)
version_num += version_tmp;
}
return version_num;
}
static int read_via_objdump(const char *filename, u64 addr, void *buf,
size_t len)
{
u64 stop_address = addr + len;
struct utsname uname_buf;
char cmd[PATH_MAX * 2];
const char *fmt;
FILE *f;
int ret;
ret = uname(&uname_buf);
if (ret) {
pr_debug("uname failed\n");
return -1;
}
if (!strncmp(uname_buf.machine, "riscv", 5)) {
int version = objdump_version();
/* Default to this workaround if version parsing fails */
if (version < 0 || version > 24100) {
/*
* Starting at riscv objdump version 2.41, dumping in
* the middle of an instruction is not supported. riscv
* instructions are aligned along 2-byte intervals and
* can be either 2-bytes or 4-bytes. This makes it
* possible that the stop-address lands in the middle of
* a 4-byte instruction. Increase the stop_address by
* two to ensure an instruction is not cut in half, but
* leave the len as-is so only the expected number of
* bytes are collected.
*/
stop_address += 2;
}
}
fmt = "%s -z -d --start-address=0x%"PRIx64" --stop-address=0x%"PRIx64" %s";
ret = snprintf(cmd, sizeof(cmd), fmt, test_objdump_path, addr, stop_address,
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, addr);
if (len) {
pr_debug("objdump read too few bytes: %zd\n", len);
if (!ret)
ret = len;
}
pclose(f);
return ret;
}
static void dump_buf(unsigned char *buf, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
pr_debug("0x%02x ", buf[i]);
if (i % 16 == 15)
pr_debug("\n");
}
pr_debug("\n");
}
static int read_object_code(u64 addr, size_t len, u8 cpumode,
struct thread *thread,
struct rb_root *tested_sections)
{
struct addr_location al;
unsigned char buf1[BUFSZ] = {0};
unsigned char buf2[BUFSZ] = {0};
size_t ret_len;
u64 objdump_addr;
u64 skip_addr;
const char *objdump_name;
char decomp_name[KMOD_DECOMP_LEN];
bool decomp = false;
int ret, err = 0;
struct dso *dso;
pr_debug("Reading object code for memory address: %#"PRIx64"\n", addr);
addr_location__init(&al);
if (!thread__find_map(thread, cpumode, addr, &al) || !map__dso(al.map)) {
if (cpumode == PERF_RECORD_MISC_HYPERVISOR) {
pr_debug("Hypervisor address can not be resolved - skipping\n");
goto out;
}
pr_debug("thread__find_map failed\n");
err = -1;
goto out;
}
dso = map__dso(al.map);
pr_debug("File is: %s\n", dso__long_name(dso));
if (dso__symtab_type(dso) == DSO_BINARY_TYPE__KALLSYMS && !dso__is_kcore(dso)) {
pr_debug("Unexpected kernel address - skipping\n");
goto out;
}
/*
* Don't retest the same addresses. objdump struggles with kcore - try
* each map only once even if the address is different.
*/
skip_addr = dso__is_kcore(dso) ? map__start(al.map) : al.addr;
if (tested_code_insert_or_exists(dso__long_name(dso), skip_addr,
tested_sections)) {
pr_debug("Already tested %s @ %#"PRIx64" - skipping\n",
dso__long_name(dso), skip_addr);
goto out;
}
pr_debug("On file address is: %#"PRIx64"\n", al.addr);
if (len > BUFSZ)
len = BUFSZ;
/* Do not go off the map */
if (addr + len > map__end(al.map))
len = map__end(al.map) - addr;
/*
* Some architectures (ex: powerpc) have stubs (trampolines) in kernel
* modules to manage long jumps. Check if the ip offset falls in stubs
* sections for kernel modules. And skip module address after text end
*/
if (dso__is_kmod(dso) && al.addr > dso__text_end(dso)) {
pr_debug("skipping the module address %#"PRIx64" after text end\n", al.addr);
goto out;
}
/* Read the object code using perf */
ret_len = dso__data_read_offset(dso, maps__machine(thread__maps(thread)),
al.addr, buf1, len);
if (ret_len != len) {
pr_debug("dso__data_read_offset failed\n");
err = -1;
goto out;
}
/*
* Converting addresses for use by objdump requires more information.
* map__load() does that. See map__rip_2objdump() for details.
*/
if (map__load(al.map)) {
err = -1;
goto out;
}
objdump_name = dso__long_name(dso);
if (dso__needs_decompress(dso)) {
if (dso__decompress_kmodule_path(dso, objdump_name,
decomp_name,
sizeof(decomp_name)) < 0) {
pr_debug("decompression failed\n");
err = -1;
goto out;
}
/* empty pathname means file wasn't actually compressed */
if (decomp_name[0] != '\0') {
decomp = true;
objdump_name = decomp_name;
}
}
/* Read the object code using objdump */
objdump_addr = map__rip_2objdump(al.map, al.addr);
ret = read_via_objdump(objdump_name, objdump_addr, buf2, len);
if (decomp)
unlink(objdump_name);
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(dso)) {
/*
* objdump cannot handle very large segments
* that may be found in kcore.
*/
pr_debug("objdump failed for kcore");
pr_debug(" - skipping\n");
} else {
err = -1;
}
goto out;
}
}
if (ret < 0) {
pr_debug("read_via_objdump failed\n");
err = -1;
goto out;
}
/* The results should be identical */
if (memcmp(buf1, buf2, len)) {
pr_debug("Bytes read differ from those read by objdump\n");
pr_debug("buf1 (dso):\n");
dump_buf(buf1, len);
pr_debug("buf2 (objdump):\n");
dump_buf(buf2, len);
err = -1;
goto out;
}
pr_debug("Bytes read match those read by objdump\n");
out:
addr_location__exit(&al);
return err;
}
static int process_sample_event(struct machine *machine, struct evlist *evlist,
union perf_event *event,
struct rb_root *tested_sections)
{
struct perf_sample sample;
struct thread *thread;
int ret;
perf_sample__init(&sample, /*all=*/false);
ret = evlist__parse_sample(evlist, event, &sample);
if (ret) {
pr_debug("evlist__parse_sample failed\n");
ret = -1;
goto out;
}
thread = machine__findnew_thread(machine, sample.pid, sample.tid);
if (!thread) {
pr_debug("machine__findnew_thread failed\n");
ret = -1;
goto out;
}
ret = read_object_code(sample.ip, READLEN, sample.cpumode, thread,
tested_sections);
thread__put(thread);
out:
perf_sample__exit(&sample);
return ret;
}
static int process_event(struct machine *machine, struct evlist *evlist,
union perf_event *event, struct rb_root *tested_sections)
{
if (event->header.type == PERF_RECORD_SAMPLE)
return process_sample_event(machine, evlist, event,
tested_sections);
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 evlist *evlist,
struct rb_root *tested_sections)
{
union perf_event *event;
struct mmap *md;
int i, ret;
for (i = 0; i < evlist->core.nr_mmaps; i++) {
md = &evlist->mmap[i];
if (perf_mmap__read_init(&md->core) < 0)
continue;
while ((event = perf_mmap__read_event(&md->core)) != NULL) {
ret = process_event(machine, evlist, event, tested_sections);
perf_mmap__consume(&md->core);
if (ret < 0)
return ret;
}
perf_mmap__read_done(&md->core);
}
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 machine *machine;
struct thread *thread;
struct record_opts opts = {
.mmap_pages = UINT_MAX,
.user_freq = UINT_MAX,
.user_interval = ULLONG_MAX,
.freq = 500,
.target = {
.uses_mmap = true,
},
};
struct rb_root tested_sections = RB_ROOT;
struct perf_thread_map *threads = NULL;
struct perf_cpu_map *cpus = NULL;
struct evlist *evlist = NULL;
struct evsel *evsel = NULL;
int err = -1, ret;
pid_t pid;
struct map *map;
bool have_vmlinux, have_kcore;
struct dso *dso;
const char *events[] = { "cpu-cycles", "cpu-cycles:u", "cpu-clock", "cpu-clock:u", NULL };
int evidx = 0;
struct perf_env host_env;
pid = getpid();
perf_env__init(&host_env);
machine = machine__new_host(&host_env);
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__kernel_map(machine);
ret = map__load(map);
if (ret < 0) {
pr_debug("map__load failed\n");
goto out_err;
}
dso = map__dso(map);
have_vmlinux = dso__is_vmlinux(dso);
have_kcore = dso__is_kcore(dso);
/* 2nd time through we just try kcore */
if (try_kcore && !have_kcore)
return TEST_CODE_READING_NO_KCORE;
/* No point getting kernel events if there is no kernel object */
if (!have_vmlinux && !have_kcore)
evidx++;
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,
true, 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_put;
}
cpus = perf_cpu_map__new_online_cpus();
if (!cpus) {
pr_debug("perf_cpu_map__new failed\n");
goto out_put;
}
while (events[evidx]) {
const char *str;
evlist = evlist__new();
if (!evlist) {
pr_debug("evlist__new failed\n");
goto out_put;
}
perf_evlist__set_maps(&evlist->core, cpus, threads);
str = events[evidx];
pr_debug("Parsing event '%s'\n", str);
ret = parse_event(evlist, str);
if (ret < 0) {
pr_debug("parse_events failed\n");
goto out_put;
}
evlist__config(evlist, &opts, NULL);
evlist__for_each_entry(evlist, evsel) {
evsel->core.attr.comm = 1;
evsel->core.attr.disabled = 1;
evsel->core.attr.enable_on_exec = 0;
}
ret = evlist__open(evlist);
if (ret < 0) {
evidx++;
if (events[evidx] == NULL && verbose > 0) {
char errbuf[512];
evlist__strerror_open(evlist, errno, errbuf, sizeof(errbuf));
pr_debug("perf_evlist__open() failed!\n%s\n", errbuf);
}
perf_evlist__set_maps(&evlist->core, NULL, NULL);
evlist__delete(evlist);
evlist = NULL;
continue;
}
break;
}
if (events[evidx] == NULL)
goto out_put;
ret = evlist__mmap(evlist, UINT_MAX);
if (ret < 0) {
pr_debug("evlist__mmap failed\n");
goto out_put;
}
evlist__enable(evlist);
do_something();
evlist__disable(evlist);
ret = process_events(machine, evlist, &tested_sections);
if (ret < 0)
goto out_put;
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 (strstr(events[evidx], ":u"))
err = TEST_CODE_READING_NO_ACCESS;
else
err = TEST_CODE_READING_OK;
out_put:
thread__put(thread);
out_err:
evlist__delete(evlist);
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
machine__delete(machine);
perf_env__exit(&host_env);
tested_sections__free(&tested_sections);
return err;
}
static int test__code_reading(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
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:
pr_debug("no vmlinux\n");
return 0;
case TEST_CODE_READING_NO_KCORE:
pr_debug("no kcore\n");
return 0;
case TEST_CODE_READING_NO_ACCESS:
pr_debug("no access\n");
return 0;
case TEST_CODE_READING_NO_KERNEL_OBJ:
pr_debug("no kernel obj\n");
return 0;
default:
return -1;
};
}
DEFINE_SUITE("Object code reading", code_reading);
@@ -0,0 +1,7 @@
This folder is for kernel config fragments that can be merged with
defconfig to give full test coverage of a perf test run. This is only
an optimistic set as some features require hardware support in order to
pass and not skip.
'config' is shared across all platforms, and for arch specific files,
the file name should match that used in the ARCH=... make option.
@@ -0,0 +1 @@
CONFIG_CORESIGHT_SOURCE_ETM4X=y
@@ -0,0 +1,14 @@
CONFIG_TRACEPOINTS=y
CONFIG_STACKTRACE=y
CONFIG_NOP_TRACER=y
CONFIG_RING_BUFFER=y
CONFIG_EVENT_TRACING=y
CONFIG_CONTEXT_SWITCH_TRACER=y
CONFIG_TRACING=y
CONFIG_GENERIC_TRACER=y
CONFIG_FTRACE=y
CONFIG_FTRACE_SYSCALLS=y
CONFIG_BRANCH_PROFILE_NONE=y
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENTS=y
CONFIG_UPROBE_EVENTS=y
+296
View File
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0
#include "tests.h"
#include <stdio.h>
#include "cpumap.h"
#include "event.h"
#include "util/synthetic-events.h"
#include <string.h>
#include <linux/bitops.h>
#include <internal/cpumap.h>
#include "debug.h"
struct machine;
static int process_event_mask(const struct perf_tool *tool __maybe_unused,
union perf_event *event,
struct perf_sample *sample __maybe_unused,
struct machine *machine __maybe_unused)
{
struct perf_record_cpu_map *map_event = &event->cpu_map;
struct perf_record_cpu_map_data *data;
struct perf_cpu_map *map;
unsigned int long_size;
data = &map_event->data;
TEST_ASSERT_VAL("wrong type", data->type == PERF_CPU_MAP__MASK);
long_size = data->mask32_data.long_size;
TEST_ASSERT_VAL("wrong long_size", long_size == 4 || long_size == 8);
TEST_ASSERT_VAL("wrong nr", data->mask32_data.nr == 1);
TEST_ASSERT_VAL("wrong cpu", perf_record_cpu_map_data__test_bit(0, data));
TEST_ASSERT_VAL("wrong cpu", !perf_record_cpu_map_data__test_bit(1, data));
for (int i = 2; i <= 20; i++)
TEST_ASSERT_VAL("wrong cpu", perf_record_cpu_map_data__test_bit(i, data));
map = cpu_map__new_data(data);
TEST_ASSERT_VAL("wrong nr", perf_cpu_map__nr(map) == 20);
TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, 0).cpu == 0);
for (int i = 2; i <= 20; i++)
TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, i - 1).cpu == i);
perf_cpu_map__put(map);
return 0;
}
static int process_event_cpus(const struct perf_tool *tool __maybe_unused,
union perf_event *event,
struct perf_sample *sample __maybe_unused,
struct machine *machine __maybe_unused)
{
struct perf_record_cpu_map *map_event = &event->cpu_map;
struct perf_record_cpu_map_data *data;
struct perf_cpu_map *map;
data = &map_event->data;
TEST_ASSERT_VAL("wrong type", data->type == PERF_CPU_MAP__CPUS);
TEST_ASSERT_VAL("wrong nr", data->cpus_data.nr == 2);
TEST_ASSERT_VAL("wrong cpu", data->cpus_data.cpu[0] == 1);
TEST_ASSERT_VAL("wrong cpu", data->cpus_data.cpu[1] == 256);
map = cpu_map__new_data(data);
TEST_ASSERT_VAL("wrong nr", perf_cpu_map__nr(map) == 2);
TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, 0).cpu == 1);
TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, 1).cpu == 256);
TEST_ASSERT_VAL("wrong refcnt", refcount_read(perf_cpu_map__refcnt(map)) == 1);
perf_cpu_map__put(map);
return 0;
}
static int process_event_range_cpus(const struct perf_tool *tool __maybe_unused,
union perf_event *event,
struct perf_sample *sample __maybe_unused,
struct machine *machine __maybe_unused)
{
struct perf_record_cpu_map *map_event = &event->cpu_map;
struct perf_record_cpu_map_data *data;
struct perf_cpu_map *map;
data = &map_event->data;
TEST_ASSERT_VAL("wrong type", data->type == PERF_CPU_MAP__RANGE_CPUS);
TEST_ASSERT_VAL("wrong any_cpu", data->range_cpu_data.any_cpu == 0);
TEST_ASSERT_VAL("wrong start_cpu", data->range_cpu_data.start_cpu == 1);
TEST_ASSERT_VAL("wrong end_cpu", data->range_cpu_data.end_cpu == 256);
map = cpu_map__new_data(data);
TEST_ASSERT_VAL("wrong nr", perf_cpu_map__nr(map) == 256);
TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__cpu(map, 0).cpu == 1);
TEST_ASSERT_VAL("wrong cpu", perf_cpu_map__max(map).cpu == 256);
TEST_ASSERT_VAL("wrong refcnt", refcount_read(perf_cpu_map__refcnt(map)) == 1);
perf_cpu_map__put(map);
return 0;
}
static int test__cpu_map_synthesize(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct perf_cpu_map *cpus;
/* This one is better stored in a mask. */
cpus = perf_cpu_map__new("0,2-20");
TEST_ASSERT_VAL("failed to synthesize map",
!perf_event__synthesize_cpu_map(NULL, cpus, process_event_mask, NULL));
perf_cpu_map__put(cpus);
/* This one is better stored in cpu values. */
cpus = perf_cpu_map__new("1,256");
TEST_ASSERT_VAL("failed to synthesize map",
!perf_event__synthesize_cpu_map(NULL, cpus, process_event_cpus, NULL));
perf_cpu_map__put(cpus);
/* This one is better stored as a range. */
cpus = perf_cpu_map__new("1-256");
TEST_ASSERT_VAL("failed to synthesize map",
!perf_event__synthesize_cpu_map(NULL, cpus, process_event_range_cpus, NULL));
perf_cpu_map__put(cpus);
return 0;
}
static int cpu_map_print(const char *str)
{
struct perf_cpu_map *map = perf_cpu_map__new(str);
char buf[100];
if (!map)
return -1;
cpu_map__snprint(map, buf, sizeof(buf));
perf_cpu_map__put(map);
return !strcmp(buf, str);
}
static int test__cpu_map_print(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
TEST_ASSERT_VAL("failed to convert map", cpu_map_print("1"));
TEST_ASSERT_VAL("failed to convert map", cpu_map_print("1,5"));
TEST_ASSERT_VAL("failed to convert map", cpu_map_print("1,3,5,7,9,11,13,15,17,19,21-40"));
TEST_ASSERT_VAL("failed to convert map", cpu_map_print("2-5"));
TEST_ASSERT_VAL("failed to convert map", cpu_map_print("1,3-6,8-10,24,35-37"));
TEST_ASSERT_VAL("failed to convert map", cpu_map_print("1,3-6,8-10,24,35-37"));
TEST_ASSERT_VAL("failed to convert map", cpu_map_print("1-10,12-20,22-30,32-40"));
return 0;
}
static int __test__cpu_map_merge(const char *lhs, const char *rhs, unsigned int nr,
const char *expected)
{
struct perf_cpu_map *a = perf_cpu_map__new(lhs);
struct perf_cpu_map *b = perf_cpu_map__new(rhs);
char buf[100];
perf_cpu_map__merge(&a, b);
TEST_ASSERT_VAL("failed to merge map: bad nr", perf_cpu_map__nr(a) == nr);
cpu_map__snprint(a, buf, sizeof(buf));
TEST_ASSERT_VAL("failed to merge map: bad result", !strcmp(buf, expected));
perf_cpu_map__put(b);
/*
* If 'b' is a superset of 'a', 'a' points to the same map with the
* map 'b'. In this case, the owner 'b' has released the resource above
* but 'a' still keeps the ownership, the reference counter should be 1.
*/
TEST_ASSERT_VAL("unexpected refcnt: bad result",
refcount_read(perf_cpu_map__refcnt(a)) == 1);
perf_cpu_map__put(a);
return 0;
}
static int test__cpu_map_merge(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int ret;
ret = __test__cpu_map_merge("4,2,1", "4,5,7", 5, "1-2,4-5,7");
if (ret)
return ret;
ret = __test__cpu_map_merge("1-8", "6-9", 9, "1-9");
if (ret)
return ret;
ret = __test__cpu_map_merge("1-8,12-20", "6-9,15", 18, "1-9,12-20");
if (ret)
return ret;
ret = __test__cpu_map_merge("4,2,1", "1", 3, "1-2,4");
if (ret)
return ret;
ret = __test__cpu_map_merge("1", "4,2,1", 3, "1-2,4");
if (ret)
return ret;
ret = __test__cpu_map_merge("1", "1", 1, "1");
return ret;
}
static int __test__cpu_map_intersect(const char *lhs, const char *rhs, unsigned int nr,
const char *expected)
{
struct perf_cpu_map *a = perf_cpu_map__new(lhs);
struct perf_cpu_map *b = perf_cpu_map__new(rhs);
struct perf_cpu_map *c = perf_cpu_map__intersect(a, b);
char buf[100];
TEST_ASSERT_EQUAL("failed to intersect map: bad nr", perf_cpu_map__nr(c), nr);
cpu_map__snprint(c, buf, sizeof(buf));
TEST_ASSERT_VAL("failed to intersect map: bad result", !strcmp(buf, expected));
perf_cpu_map__put(a);
perf_cpu_map__put(b);
perf_cpu_map__put(c);
return 0;
}
static int test__cpu_map_intersect(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int ret;
ret = __test__cpu_map_intersect("4,2,1", "4,5,7", 1, "4");
if (ret)
return ret;
ret = __test__cpu_map_intersect("1-8", "6-9", 3, "6-8");
if (ret)
return ret;
ret = __test__cpu_map_intersect("1-8,12-20", "6-9,15", 4, "6-8,15");
if (ret)
return ret;
ret = __test__cpu_map_intersect("4,2,1", "1", 1, "1");
if (ret)
return ret;
ret = __test__cpu_map_intersect("1", "4,2,1", 1, "1");
if (ret)
return ret;
ret = __test__cpu_map_intersect("1", "1", 1, "1");
return ret;
}
static int test__cpu_map_equal(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct perf_cpu_map *any = perf_cpu_map__new_any_cpu();
struct perf_cpu_map *one = perf_cpu_map__new("1");
struct perf_cpu_map *two = perf_cpu_map__new("2");
struct perf_cpu_map *empty = perf_cpu_map__intersect(one, two);
struct perf_cpu_map *pair = perf_cpu_map__new("1-2");
struct perf_cpu_map *tmp;
struct perf_cpu_map **maps[] = {&empty, &any, &one, &two, &pair};
for (size_t i = 0; i < ARRAY_SIZE(maps); i++) {
/* Maps equal themself. */
TEST_ASSERT_VAL("equal", perf_cpu_map__equal(*maps[i], *maps[i]));
for (size_t j = 0; j < ARRAY_SIZE(maps); j++) {
/* Maps dont't equal each other. */
if (i == j)
continue;
TEST_ASSERT_VAL("not equal", !perf_cpu_map__equal(*maps[i], *maps[j]));
}
}
/* Maps equal made maps. */
perf_cpu_map__merge(&two, one);
TEST_ASSERT_VAL("pair", perf_cpu_map__equal(pair, two));
tmp = perf_cpu_map__intersect(pair, one);
TEST_ASSERT_VAL("one", perf_cpu_map__equal(one, tmp));
perf_cpu_map__put(tmp);
for (size_t i = 0; i < ARRAY_SIZE(maps); i++)
perf_cpu_map__put(*maps[i]);
return TEST_OK;
}
static struct test_case tests__cpu_map[] = {
TEST_CASE("Synthesize cpu map", cpu_map_synthesize),
TEST_CASE("Print cpu map", cpu_map_print),
TEST_CASE("Merge cpu map", cpu_map_merge),
TEST_CASE("Intersect cpu map", cpu_map_intersect),
TEST_CASE("Equal cpu map", cpu_map_equal),
{ .name = NULL, }
};
struct test_suite suite__cpu_map = {
.desc = "CPU map",
.test_cases = tests__cpu_map,
};
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/kernel.h>
#include "debug.h"
#include "symbol.h"
#include "tests.h"
static int test__demangle_java(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int ret = TEST_OK;
char *buf = NULL;
size_t i;
struct {
const char *mangled, *demangled;
} test_cases[] = {
{ "Ljava/lang/StringLatin1;equals([B[B)Z",
"java.lang.StringLatin1.equals(byte[], byte[])" },
{ "Ljava/util/zip/ZipUtils;CENSIZ([BI)J",
"java.util.zip.ZipUtils.CENSIZ(byte[], int)" },
{ "Ljava/util/regex/Pattern$BmpCharProperty;match(Ljava/util/regex/Matcher;ILjava/lang/CharSequence;)Z",
"java.util.regex.Pattern$BmpCharProperty.match(java.util.regex.Matcher, int, java.lang.CharSequence)" },
{ "Ljava/lang/AbstractStringBuilder;appendChars(Ljava/lang/String;II)V",
"java.lang.AbstractStringBuilder.appendChars(java.lang.String, int, int)" },
{ "Ljava/lang/Object;<init>()V",
"java.lang.Object<init>()" },
};
for (i = 0; i < ARRAY_SIZE(test_cases); i++) {
buf = dso__demangle_sym(/*dso=*/NULL, /*kmodule=*/0, test_cases[i].mangled);
if (!buf) {
pr_debug("FAILED to demangle: \"%s\"\n \"%s\"\n", test_cases[i].mangled,
test_cases[i].demangled);
continue;
}
if (strcmp(buf, test_cases[i].demangled)) {
pr_debug("FAILED: %s: %s != %s\n", test_cases[i].mangled,
buf, test_cases[i].demangled);
ret = TEST_FAIL;
}
free(buf);
}
return ret;
}
DEFINE_SUITE("Demangle Java", demangle_java);
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "debug.h"
#include "symbol.h"
#include "tests.h"
static int test__demangle_ocaml(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int ret = TEST_OK;
char *buf = NULL;
size_t i;
struct {
const char *mangled, *demangled;
} test_cases[] = {
{ "main",
NULL },
{ "camlStdlib__array__map_154",
"Stdlib.array.map_154" },
{ "camlStdlib__anon_fn$5bstdlib$2eml$3a334$2c0$2d$2d54$5d_1453",
"Stdlib.anon_fn[stdlib.ml:334,0--54]_1453" },
{ "camlStdlib__bytes__$2b$2b_2205",
"Stdlib.bytes.++_2205" },
};
for (i = 0; i < ARRAY_SIZE(test_cases); i++) {
buf = dso__demangle_sym(/*dso=*/NULL, /*kmodule=*/0, test_cases[i].mangled);
if ((buf == NULL && test_cases[i].demangled != NULL)
|| (buf != NULL && test_cases[i].demangled == NULL)
|| (buf != NULL && strcmp(buf, test_cases[i].demangled))) {
pr_debug("FAILED: %s: %s != %s\n", test_cases[i].mangled,
buf == NULL ? "(null)" : buf,
test_cases[i].demangled == NULL ? "(null)" : test_cases[i].demangled);
ret = TEST_FAIL;
}
free(buf);
}
return ret;
}
DEFINE_SUITE("Demangle OCaml", demangle_ocaml);
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
#include "tests.h"
#include "debug.h"
#include "symbol.h"
#include <linux/kernel.h>
#include <stdlib.h>
#include <string.h>
static int test__demangle_rust(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int ret = TEST_OK;
char *buf = NULL;
size_t i;
struct {
const char *mangled, *demangled;
} test_cases[] = {
{ "_RNvMsr_NtCs3ssYzQotkvD_3std4pathNtB5_7PathBuf3newCs15kBYyAo9fc_7mycrate",
"<std::path::PathBuf>::new" },
{ "_RNvCs15kBYyAo9fc_7mycrate7example",
"mycrate::example" },
{ "_RNvMs_Cs4Cv8Wi1oAIB_7mycrateNtB4_7Example3foo",
"<mycrate::Example>::foo" },
{ "_RNvXCs15kBYyAo9fc_7mycrateNtB2_7ExampleNtB2_5Trait3foo",
"<mycrate::Example as mycrate::Trait>::foo" },
{ "_RNvMCs7qp2U7fqm6G_7mycrateNtB2_7Example3foo",
"<mycrate::Example>::foo" },
{ "_RNvMs_Cs7qp2U7fqm6G_7mycrateNtB4_7Example3bar",
"<mycrate::Example>::bar" },
{ "_RNvYNtCs15kBYyAo9fc_7mycrate7ExampleNtB4_5Trait7exampleB4_",
"<mycrate::Example as mycrate::Trait>::example" },
{ "_RNCNvCsgStHSCytQ6I_7mycrate4main0B3_",
"mycrate::main::{closure#0}" },
{ "_RNCNvCsgStHSCytQ6I_7mycrate4mains_0B3_",
"mycrate::main::{closure#1}" },
{ "_RINvCsgStHSCytQ6I_7mycrate7examplelKj1_EB2_",
"mycrate::example::<i32, 1>" },
{ "_RINvCs7qp2U7fqm6G_7mycrate7exampleFG0_RL1_hRL0_tEuEB2_",
"mycrate::example::<for<'a, 'b> fn(&'a u8, &'b u16)>",
},
{ "_RINvCs7qp2U7fqm6G_7mycrate7exampleKy12345678_EB2_",
"mycrate::example::<305419896>" },
{ "_RNvNvMCsd9PVOYlP1UU_7mycrateINtB4_7ExamplepKpE3foo14EXAMPLE_STATIC",
"<mycrate::Example<_, _>>::foo::EXAMPLE_STATIC",
},
{ "_RINvCs7qp2U7fqm6G_7mycrate7exampleAtj8_EB2_",
"mycrate::example::<[u16; 8]>" },
{ "_RINvCs7qp2U7fqm6G_7mycrate7exampleNtB2_7ExampleBw_EB2_",
"mycrate::example::<mycrate::Example, mycrate::Example>" },
{ "_RINvMsY_NtCseXNvpPnDBDp_3std4pathNtB6_4Path3neweECs7qp2U7fqm6G_7mycrate",
"<std::path::Path>::new::<str>" },
{ "_RNvNvNvCs7qp2U7fqm6G_7mycrate7EXAMPLE7___getit5___KEY",
"mycrate::EXAMPLE::__getit::__KEY" },
};
for (i = 0; i < ARRAY_SIZE(test_cases); i++) {
buf = dso__demangle_sym(/*dso=*/NULL, /*kmodule=*/0, test_cases[i].mangled);
if (!buf) {
pr_debug("FAILED to demangle: \"%s\"\n \"%s\"\n", test_cases[i].mangled,
test_cases[i].demangled);
continue;
}
if (strcmp(buf, test_cases[i].demangled)) {
pr_debug("FAILED: %s: %s != %s\n", test_cases[i].mangled,
buf, test_cases[i].demangled);
ret = TEST_FAIL;
}
free(buf);
}
return ret;
}
DEFINE_SUITE("Demangle Rust", demangle_rust);
@@ -0,0 +1,445 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test dlfilter C API. A perf.data file is synthesized and then processed
* by perf script with dlfilters named dlfilter-test-api-v*.so. Also a C file
* is compiled to provide a dso to match the synthesized perf.data file.
*/
#include <linux/compiler.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/perf_event.h>
#include <internal/lib.h>
#include <subcmd/exec-cmd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <libgen.h>
#include <string.h>
#include <errno.h>
#include "debug.h"
#include "tool.h"
#include "event.h"
#include "header.h"
#include "machine.h"
#include "dso.h"
#include "map.h"
#include "symbol.h"
#include "synthetic-events.h"
#include "util.h"
#include "dlfilter.h"
#include "tests.h"
#include "util/sample.h"
#define MAP_START 0x400000
#define DLFILTER_TEST_NAME_MAX 128
struct test_data {
struct perf_tool tool;
struct machine *machine;
int fd;
u64 foo;
u64 bar;
u64 ip;
u64 addr;
char name[DLFILTER_TEST_NAME_MAX];
char desc[DLFILTER_TEST_NAME_MAX];
char perf[PATH_MAX];
char perf_data_file_name[PATH_MAX];
char c_file_name[PATH_MAX];
char prog_file_name[PATH_MAX];
char dlfilters[PATH_MAX];
};
static int test_result(const char *msg, int ret)
{
pr_debug("%s\n", msg);
return ret;
}
static int process(const struct perf_tool *tool, union perf_event *event,
struct perf_sample *sample __maybe_unused,
struct machine *machine __maybe_unused)
{
struct test_data *td = container_of(tool, struct test_data, tool);
int fd = td->fd;
if (writen(fd, event, event->header.size) != event->header.size)
return -1;
return 0;
}
#define MAXCMD 4096
#define REDIRECT_TO_DEV_NULL " >/dev/null 2>&1"
static __printf(1, 2) int system_cmd(const char *fmt, ...)
{
char cmd[MAXCMD + sizeof(REDIRECT_TO_DEV_NULL)];
int ret;
va_list args;
va_start(args, fmt);
ret = vsnprintf(cmd, MAXCMD, fmt, args);
va_end(args);
if (ret <= 0 || ret >= MAXCMD)
return -1;
if (verbose <= 0)
strcat(cmd, REDIRECT_TO_DEV_NULL);
pr_debug("Command: %s\n", cmd);
ret = system(cmd);
if (ret)
pr_debug("Failed with return value %d\n", ret);
return ret;
}
static bool have_gcc(void)
{
pr_debug("Checking for gcc\n");
return !system_cmd("gcc --version");
}
static int write_attr(struct test_data *td, u64 sample_type, u64 *id)
{
struct perf_event_attr attr = {
.size = sizeof(attr),
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS,
.sample_type = sample_type,
.sample_period = 1,
};
return perf_event__synthesize_attr(&td->tool, &attr, 1, id, process);
}
static int write_comm(int fd, pid_t pid, pid_t tid, const char *comm_str)
{
struct perf_record_comm comm;
ssize_t sz = sizeof(comm);
comm.header.type = PERF_RECORD_COMM;
comm.header.misc = PERF_RECORD_MISC_USER;
comm.header.size = sz;
comm.pid = pid;
comm.tid = tid;
strncpy(comm.comm, comm_str, 16);
if (writen(fd, &comm, sz) != sz) {
pr_debug("%s failed\n", __func__);
return -1;
}
return 0;
}
static int write_mmap(int fd, pid_t pid, pid_t tid, u64 start, u64 len, u64 pgoff,
const char *filename)
{
char buf[PERF_SAMPLE_MAX_SIZE];
struct perf_record_mmap *mmap = (struct perf_record_mmap *)buf;
size_t fsz = roundup(strlen(filename) + 1, 8);
ssize_t sz = sizeof(*mmap) - sizeof(mmap->filename) + fsz;
mmap->header.type = PERF_RECORD_MMAP;
mmap->header.misc = PERF_RECORD_MISC_USER;
mmap->header.size = sz;
mmap->pid = pid;
mmap->tid = tid;
mmap->start = start;
mmap->len = len;
mmap->pgoff = pgoff;
strncpy(mmap->filename, filename, sizeof(mmap->filename));
if (writen(fd, mmap, sz) != sz) {
pr_debug("%s failed\n", __func__);
return -1;
}
return 0;
}
static int write_sample(struct test_data *td, u64 sample_type, u64 id, pid_t pid, pid_t tid)
{
char buf[PERF_SAMPLE_MAX_SIZE];
union perf_event *event = (union perf_event *)buf;
struct perf_sample sample = {
.ip = td->ip,
.addr = td->addr,
.id = id,
.time = 1234567890,
.cpu = 31,
.pid = pid,
.tid = tid,
.period = 543212345,
.stream_id = 101,
};
int err;
event->header.type = PERF_RECORD_SAMPLE;
event->header.misc = PERF_RECORD_MISC_USER;
event->header.size = perf_event__sample_event_size(&sample, sample_type,
/*read_format=*/0,
/*branch_sample_type=*/0);
err = perf_event__synthesize_sample(event, sample_type,
/*read_format=*/0,
/*branch_sample_type=*/0, &sample);
if (err)
return test_result("perf_event__synthesize_sample() failed", TEST_FAIL);
err = process(&td->tool, event, &sample, td->machine);
if (err)
return test_result("Failed to write sample", TEST_FAIL);
return TEST_OK;
}
static void close_fd(int fd)
{
if (fd >= 0)
close(fd);
}
static const char *prog = "int bar(){};int foo(){bar();};int main(){foo();return 0;}";
static int write_prog(char *file_name)
{
int fd = creat(file_name, 0644);
ssize_t n = strlen(prog);
bool err = fd < 0 || writen(fd, prog, n) != n;
close_fd(fd);
return err ? -1 : 0;
}
static int get_dlfilters_path(const char *name, char *buf, size_t sz)
{
char perf[PATH_MAX];
char path[PATH_MAX];
char *perf_path;
char *exec_path;
perf_exe(perf, sizeof(perf));
perf_path = dirname(perf);
snprintf(path, sizeof(path), "%s/dlfilters/%s", perf_path, name);
if (access(path, R_OK)) {
exec_path = get_argv_exec_path();
if (!exec_path)
return -1;
snprintf(path, sizeof(path), "%s/dlfilters/%s", exec_path, name);
free(exec_path);
if (access(path, R_OK))
return -1;
}
strlcpy(buf, dirname(path), sz);
return 0;
}
static int check_filter_desc(struct test_data *td)
{
char *long_desc = NULL;
char *desc = NULL;
int ret;
if (get_filter_desc(td->dlfilters, td->name, &desc, &long_desc) &&
long_desc && !strcmp(long_desc, "Filter used by the 'dlfilter C API' perf test") &&
desc && !strcmp(desc, td->desc))
ret = 0;
else
ret = -1;
free(desc);
free(long_desc);
return ret;
}
static int get_ip_addr(struct test_data *td)
{
struct map *map;
struct symbol *sym;
map = dso__new_map(td->prog_file_name);
if (!map)
return -1;
sym = map__find_symbol_by_name(map, "foo");
if (sym)
td->foo = sym->start;
sym = map__find_symbol_by_name(map, "bar");
if (sym)
td->bar = sym->start;
map__put(map);
td->ip = MAP_START + td->foo;
td->addr = MAP_START + td->bar;
return td->foo && td->bar ? 0 : -1;
}
static int do_run_perf_script(struct test_data *td, int do_early)
{
return system_cmd("%s script -i %s "
"--dlfilter %s/%s "
"--dlarg first "
"--dlarg %d "
"--dlarg %" PRIu64 " "
"--dlarg %" PRIu64 " "
"--dlarg %d "
"--dlarg last",
td->perf, td->perf_data_file_name, td->dlfilters,
td->name, verbose, td->ip, td->addr, do_early);
}
static int run_perf_script(struct test_data *td)
{
int do_early;
int err;
for (do_early = 0; do_early < 3; do_early++) {
err = do_run_perf_script(td, do_early);
if (err)
return err;
}
return 0;
}
#define TEST_SAMPLE_TYPE (PERF_SAMPLE_IP | PERF_SAMPLE_TID | \
PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TIME | \
PERF_SAMPLE_ADDR | PERF_SAMPLE_CPU | \
PERF_SAMPLE_PERIOD | PERF_SAMPLE_STREAM_ID)
static int test__dlfilter_test(struct test_data *td)
{
struct perf_env host_env;
u64 sample_type = TEST_SAMPLE_TYPE;
pid_t pid = 12345;
pid_t tid = 12346;
u64 id = 99;
int err = TEST_OK;
if (get_dlfilters_path(td->name, td->dlfilters, PATH_MAX))
return test_result("dlfilters not found", TEST_SKIP);
if (check_filter_desc(td))
return test_result("Failed to get expected filter description", TEST_FAIL);
if (!have_gcc())
return test_result("gcc not found", TEST_SKIP);
pr_debug("dlfilters path: %s\n", td->dlfilters);
if (write_prog(td->c_file_name))
return test_result("Failed to write test C file", TEST_FAIL);
if (verbose > 1)
system_cmd("cat %s ; echo", td->c_file_name);
if (system_cmd("gcc -g -o %s %s", td->prog_file_name, td->c_file_name))
return TEST_FAIL;
if (verbose > 2)
system_cmd("objdump -x -dS %s", td->prog_file_name);
if (get_ip_addr(td))
return test_result("Failed to find program symbols", TEST_FAIL);
pr_debug("Creating new host machine structure\n");
perf_env__init(&host_env);
td->machine = machine__new_host(&host_env);
td->fd = creat(td->perf_data_file_name, 0644);
if (td->fd < 0)
return test_result("Failed to create test perf.data file", TEST_FAIL);
err = perf_header__write_pipe(td->fd);
if (err < 0) {
err = test_result("perf_header__write_pipe() failed", TEST_FAIL);
goto out;
}
err = write_attr(td, sample_type, &id);
if (err) {
err = test_result("perf_event__synthesize_attr() failed", TEST_FAIL);
goto out;
}
if (write_comm(td->fd, pid, tid, "test-prog")) {
err = TEST_FAIL;
goto out;
}
if (write_mmap(td->fd, pid, tid, MAP_START, 0x10000, 0, td->prog_file_name)) {
err = TEST_FAIL;
goto out;
}
if (write_sample(td, sample_type, id, pid, tid) != TEST_OK) {
err = TEST_FAIL;
goto out;
}
if (verbose > 1)
system_cmd("%s script -i %s -D", td->perf, td->perf_data_file_name);
err = run_perf_script(td) ? TEST_FAIL : TEST_OK;
out:
perf_env__exit(&host_env);
return err;
}
static void unlink_path(const char *path)
{
if (*path)
unlink(path);
}
static void test_data__free(struct test_data *td)
{
machine__delete(td->machine);
close_fd(td->fd);
if (verbose <= 2) {
unlink_path(td->c_file_name);
unlink_path(td->prog_file_name);
unlink_path(td->perf_data_file_name);
}
}
static int test__dlfilter_ver(int ver)
{
struct test_data td = {.fd = -1};
int pid = getpid();
int err;
pr_debug("\n-- Testing version %d API --\n", ver);
perf_exe(td.perf, sizeof(td.perf));
snprintf(td.name, sizeof(td.name), "dlfilter-test-api-v%d.so", ver);
snprintf(td.desc, sizeof(td.desc), "dlfilter to test v%d C API", ver);
snprintf(td.perf_data_file_name, PATH_MAX, "/tmp/dlfilter-test-%u-perf-data", pid);
snprintf(td.c_file_name, PATH_MAX, "/tmp/dlfilter-test-%u-prog.c", pid);
snprintf(td.prog_file_name, PATH_MAX, "/tmp/dlfilter-test-%u-prog", pid);
err = test__dlfilter_test(&td);
test_data__free(&td);
return err;
}
static int test__dlfilter(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int err = test__dlfilter_ver(0);
if (err)
return err;
/* No test for version 1 */
return test__dlfilter_ver(2);
}
DEFINE_SUITE("dlfilter C API", dlfilter);
+407
View File
@@ -0,0 +1,407 @@
// SPDX-License-Identifier: GPL-2.0
#include <dirent.h>
#include <stdlib.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <api/fs/fs.h>
#include "dso.h"
#include "dsos.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;
};
static 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,
},
};
/* move it from util/dso.c for compatibility */
static int dso__data_fd(struct dso *dso, struct machine *machine)
{
int fd = -1;
if (dso__data_get_fd(dso, machine, &fd))
dso__data_put_fd(dso);
return fd;
}
static void dsos__delete(struct dsos *dsos)
{
for (unsigned int i = 0; i < dsos->cnt; i++) {
struct dso *dso = dsos->dsos[i];
dso__data_close(dso);
unlink(dso__name(dso));
}
dsos__exit(dsos);
}
static int test__dso_data(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct machine machine;
struct dso *dso;
char *file = test_file(TEST_FILE_SIZE);
size_t i;
TEST_ASSERT_VAL("No test file", file);
memset(&machine, 0, sizeof(machine));
dsos__init(&machine.dsos);
dso = dso__new(file);
TEST_ASSERT_VAL("Failed to add dso", !dsos__add(&machine.dsos, dso));
TEST_ASSERT_VAL("Failed to access to dso",
dso__data_fd(dso, &machine) >= 0);
/* 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__put(dso);
dsos__delete(&machine.dsos);
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 int dsos__create(int cnt, int size, struct dsos *dsos)
{
int i;
dsos__init(dsos);
for (i = 0; i < cnt; i++) {
struct dso *dso;
char *file = test_file(size);
TEST_ASSERT_VAL("failed to get dso file", file);
dso = dso__new(file);
TEST_ASSERT_VAL("failed to get dso", dso);
TEST_ASSERT_VAL("failed to add dso", !dsos__add(dsos, dso));
dso__put(dso);
}
return 0;
}
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);
}
static int test__dso_data_cache(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct machine machine;
long nr_end, nr = open_files_cnt();
int dso_cnt, limit, i, fd;
/* Rest the internal dso open counter limit. */
reset_fd_limit();
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, &machine.dsos));
for (i = 0; i < (dso_cnt - 1); i++) {
struct dso *dso = machine.dsos.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", dso__data(machine.dsos.dsos[0])->fd != -1);
/* open +1 dso to reach the allowed limit */
fd = dso__data_fd(machine.dsos.dsos[i], &machine);
TEST_ASSERT_VAL("failed to get fd", fd > 0);
/* should force the first one to be closed */
TEST_ASSERT_VAL("failed to close dsos[0]", dso__data(machine.dsos.dsos[0])->fd == -1);
/* cleanup everything */
dsos__delete(&machine.dsos);
/* 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 leaking files", nr == nr_end);
return 0;
}
static long new_limit(int count)
{
int fd = open("/dev/null", O_RDONLY);
long ret = fd;
if (count > 0)
ret = new_limit(--count);
close(fd);
return ret;
}
static int test__dso_data_reopen(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct machine machine;
long nr_end, nr = open_files_cnt(), lim = new_limit(3);
int fd, fd_extra;
#define dso_0 (machine.dsos.dsos[0])
#define dso_1 (machine.dsos.dsos[1])
#define dso_2 (machine.dsos.dsos[2])
/* Rest the internal dso open counter limit. */
reset_fd_limit();
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((lim)));
TEST_ASSERT_VAL("failed to create dsos\n",
!dsos__create(3, TEST_FILE_SIZE, &machine.dsos));
/* open dso_0 */
fd = dso__data_fd(dso_0, &machine);
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__data(dso_0)->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__data(dso_1)->fd == -1);
/* cleanup everything */
close(fd_extra);
dsos__delete(&machine.dsos);
/* 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 leaking files", nr == nr_end);
return 0;
}
static struct test_case tests__dso_data[] = {
TEST_CASE("read", dso_data),
TEST_CASE("cache", dso_data_cache),
TEST_CASE("reopen", dso_data_reopen),
{ .name = NULL, }
};
struct test_suite suite__dso_data = {
.desc = "DSO data tests",
.test_cases = tests__dso_data,
};
+223
View File
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <linux/types.h>
#include <linux/zalloc.h>
#include <inttypes.h>
#include <limits.h>
#include <unistd.h>
#include "tests.h"
#include "debug.h"
#include "env.h"
#include "machine.h"
#include "event.h"
#include "../util/unwind.h"
#include "perf_regs.h"
#include "map.h"
#include "symbol.h"
#include "thread.h"
#include "callchain.h"
/* For bsearch. We try to unwind functions in shared object. */
#include <stdlib.h>
/*
* The test will assert frames are on the stack but tail call optimizations lose
* the frame of the caller. Clang can disable this optimization on a called
* function but GCC currently (11/2020) lacks this attribute. The barrier is
* used to inhibit tail calls in these cases.
*/
#ifdef __has_attribute
#if __has_attribute(disable_tail_calls)
#define NO_TAIL_CALL_ATTRIBUTE __attribute__((disable_tail_calls))
#define NO_TAIL_CALL_BARRIER
#endif
#endif
#ifndef NO_TAIL_CALL_ATTRIBUTE
#define NO_TAIL_CALL_ATTRIBUTE
#define NO_TAIL_CALL_BARRIER __asm__ __volatile__("" : : : "memory");
#endif
/*
* We need to keep these functions global, despite the
* fact that they are used only locally in this object,
* in order to keep them around even if the binary is
* stripped. If they are gone, the unwind check for
* symbol fails.
*/
int test_dwarf_unwind__thread(struct thread *thread);
int test_dwarf_unwind__compare(void *p1, void *p2);
int test_dwarf_unwind__krava_3(struct thread *thread);
int test_dwarf_unwind__krava_2(struct thread *thread);
int test_dwarf_unwind__krava_1(struct thread *thread);
int test__dwarf_unwind(struct test_suite *test, int subtest);
#define MAX_STACK 8
static int unwind_entry(struct unwind_entry *entry, void *arg)
{
unsigned long *cnt = (unsigned long *) arg;
char *symbol = entry->ms.sym ? entry->ms.sym->name : NULL;
static const char *funcs[MAX_STACK] = {
"test__arch_unwind_sample",
"test_dwarf_unwind__thread",
"test_dwarf_unwind__compare",
"bsearch",
"test_dwarf_unwind__krava_3",
"test_dwarf_unwind__krava_2",
"test_dwarf_unwind__krava_1",
"test__dwarf_unwind"
};
/*
* The funcs[MAX_STACK] array index, based on the
* callchain order setup.
*/
int idx = callchain_param.order == ORDER_CALLER ?
MAX_STACK - *cnt - 1 : *cnt;
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;
}
(*cnt)++;
pr_debug("got: %s 0x%" PRIx64 ", expecting %s\n",
symbol, entry->ip, funcs[idx]);
return strcmp((const char *) symbol, funcs[idx]);
}
NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__thread(struct thread *thread)
{
struct perf_sample sample;
unsigned long cnt = 0;
int err = -1;
perf_sample__init(&sample, /*all=*/true);
if (test__arch_unwind_sample(&sample, thread)) {
pr_debug("failed to get unwind sample\n");
goto out;
}
err = unwind__get_entries(unwind_entry, &cnt, thread,
&sample, MAX_STACK, false);
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:
zfree(&sample.user_stack.data);
zfree(&sample.user_regs->regs);
perf_sample__exit(&sample);
return err;
}
static int global_unwind_retval = -INT_MAX;
NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__compare(void *p1, void *p2)
{
/* Any possible value should be 'thread' */
struct thread *thread = *(struct thread **)p1;
if (global_unwind_retval == -INT_MAX) {
/* Call unwinder twice for both callchain orders. */
callchain_param.order = ORDER_CALLER;
global_unwind_retval = test_dwarf_unwind__thread(thread);
if (!global_unwind_retval) {
callchain_param.order = ORDER_CALLEE;
global_unwind_retval = test_dwarf_unwind__thread(thread);
}
}
return p1 - p2;
}
NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__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 **),
test_dwarf_unwind__compare);
return global_unwind_retval;
}
NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__krava_2(struct thread *thread)
{
int ret;
ret = test_dwarf_unwind__krava_3(thread);
NO_TAIL_CALL_BARRIER;
return ret;
}
NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__krava_1(struct thread *thread)
{
int ret;
ret = test_dwarf_unwind__krava_2(thread);
NO_TAIL_CALL_BARRIER;
return ret;
}
noinline int test__dwarf_unwind(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct perf_env host_env;
struct machine *machine;
struct thread *thread;
int err = -1;
pid_t pid = getpid();
callchain_param.record_mode = CALLCHAIN_DWARF;
dwarf_callchain_users = true;
perf_env__init(&host_env);
machine = machine__new_live(&host_env, /*kernel_maps=*/true, pid);
if (!machine) {
pr_err("Could not get machine\n");
goto out;
}
if (machine__create_kernel_maps(machine)) {
pr_err("Failed to create kernel maps\n");
goto out;
}
if (verbose > 1)
machine__fprintf(machine, stderr);
thread = machine__find_thread(machine, pid, pid);
if (!thread) {
pr_err("Could not get thread\n");
goto out;
}
err = test_dwarf_unwind__krava_1(thread);
thread__put(thread);
out:
machine__delete(machine);
perf_env__exit(&host_env);
return err;
}
DEFINE_SUITE("Test dwarf unwind", dwarf_unwind);
+242
View File
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <linux/string.h>
#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <sys/wait.h>
#include <perf/cpumap.h>
#include "tests.h"
#include "evlist.h"
#include "evsel.h"
#include "debug.h"
#include "parse-events.h"
#include "thread_map.h"
#include "target.h"
static int attach__enable_on_exec(struct evlist *evlist)
{
struct evsel *evsel = evlist__last(evlist);
struct target target = {};
const char *argv[] = { "true", NULL, };
char sbuf[STRERR_BUFSIZE];
int err;
pr_debug("attaching to spawned child, enable on exec\n");
err = evlist__create_maps(evlist, &target);
if (err < 0) {
pr_debug("Not enough memory to create thread/cpu maps\n");
return err;
}
err = evlist__prepare_workload(evlist, &target, argv, false, NULL);
if (err < 0) {
pr_debug("Couldn't run the workload!\n");
return err;
}
evsel->core.attr.enable_on_exec = 1;
err = evlist__open(evlist);
if (err < 0) {
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
return err;
}
return evlist__start_workload(evlist) == 1 ? TEST_OK : TEST_FAIL;
}
static int detach__enable_on_exec(struct evlist *evlist)
{
waitpid(evlist->workload.pid, NULL, 0);
return 0;
}
static int attach__current_disabled(struct evlist *evlist)
{
struct evsel *evsel = evlist__last(evlist);
struct perf_thread_map *threads;
int err;
pr_debug("attaching to current thread as disabled\n");
threads = thread_map__new_by_tid(getpid());
if (threads == NULL) {
pr_debug("thread_map__new\n");
return -1;
}
evsel->core.attr.disabled = 1;
err = evsel__open_per_thread(evsel, threads);
if (err) {
pr_debug("Failed to open event cpu-clock:u\n");
return err;
}
perf_thread_map__put(threads);
return evsel__enable(evsel) == 0 ? TEST_OK : TEST_FAIL;
}
static int attach__current_enabled(struct evlist *evlist)
{
struct evsel *evsel = evlist__last(evlist);
struct perf_thread_map *threads;
int err;
pr_debug("attaching to current thread as enabled\n");
threads = thread_map__new_by_tid(getpid());
if (threads == NULL) {
pr_debug("failed to call thread_map__new\n");
return -1;
}
err = evsel__open_per_thread(evsel, threads);
perf_thread_map__put(threads);
return err == 0 ? TEST_OK : TEST_FAIL;
}
static int detach__disable(struct evlist *evlist)
{
struct evsel *evsel = evlist__last(evlist);
return evsel__enable(evsel);
}
static int attach__cpu_disabled(struct evlist *evlist)
{
struct evsel *evsel = evlist__last(evlist);
struct perf_cpu_map *cpus;
int err;
pr_debug("attaching to CPU 0 as enabled\n");
cpus = perf_cpu_map__new("0");
if (cpus == NULL) {
pr_debug("failed to call perf_cpu_map__new\n");
return -1;
}
evsel->core.attr.disabled = 1;
err = evsel__open_per_cpu(evsel, cpus, -1);
perf_cpu_map__put(cpus);
if (err) {
if (err == -EACCES)
return TEST_SKIP;
pr_debug("Failed to open event cpu-clock:u\n");
return err;
}
return evsel__enable(evsel);
}
static int attach__cpu_enabled(struct evlist *evlist)
{
struct evsel *evsel = evlist__last(evlist);
struct perf_cpu_map *cpus;
int err;
pr_debug("attaching to CPU 0 as enabled\n");
cpus = perf_cpu_map__new("0");
if (cpus == NULL) {
pr_debug("failed to call perf_cpu_map__new\n");
return -1;
}
err = evsel__open_per_cpu(evsel, cpus, -1);
perf_cpu_map__put(cpus);
if (err == -EACCES)
return TEST_SKIP;
return err ? TEST_FAIL : TEST_OK;
}
static int test_times(int (attach)(struct evlist *),
int (detach)(struct evlist *))
{
struct perf_counts_values count;
struct evlist *evlist = NULL;
struct evsel *evsel;
int err = -1, i;
evlist = evlist__new();
if (!evlist) {
pr_debug("failed to create event list\n");
goto out_err;
}
err = parse_event(evlist, "cpu-clock:u");
if (err) {
pr_debug("failed to parse event cpu-clock:u\n");
goto out_err;
}
evsel = evlist__last(evlist);
evsel->core.attr.read_format |=
PERF_FORMAT_TOTAL_TIME_ENABLED |
PERF_FORMAT_TOTAL_TIME_RUNNING;
err = attach(evlist);
if (err == TEST_SKIP) {
pr_debug(" SKIP : not enough rights\n");
evlist__delete(evlist);
return err;
}
TEST_ASSERT_VAL("failed to attach", !err);
for (i = 0; i < 100000000; i++) { }
TEST_ASSERT_VAL("failed to detach", !detach(evlist));
perf_evsel__read(&evsel->core, 0, 0, &count);
err = !(count.ena == count.run);
pr_debug(" %s: ena %" PRIu64", run %" PRIu64"\n",
!err ? "OK " : "FAILED",
count.ena, count.run);
out_err:
evlist__delete(evlist);
return !err ? TEST_OK : TEST_FAIL;
}
/*
* This test creates software event 'cpu-clock'
* attaches it in several ways (explained below)
* and checks that enabled and running times
* match.
*/
static int test__event_times(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int err, ret = 0;
#define _T(attach, detach) \
err = test_times(attach, detach); \
if (err && (ret == TEST_OK || ret == TEST_SKIP)) \
ret = err;
/* attach on newly spawned process after exec */
_T(attach__enable_on_exec, detach__enable_on_exec)
/* attach on current process as enabled */
_T(attach__current_enabled, detach__disable)
/* attach on current process as disabled */
_T(attach__current_disabled, detach__disable)
/* attach on cpu as disabled */
_T(attach__cpu_disabled, detach__disable)
/* attach on cpu as enabled */
_T(attach__cpu_enabled, detach__disable)
#undef _T
return ret;
}
DEFINE_SUITE("Event times", event_times);
+157
View File
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include "linux/perf_event.h"
#include "tests.h"
#include "debug.h"
#include "pmu.h"
#include "pmus.h"
#include "header.h"
#include "../perf-sys.h"
/* hw: cycles,instructions sw: context-switch, uncore: [arch dependent] */
static int types[] = {0, 1, -1};
static unsigned long configs[] = {0, 3, 0};
static unsigned long configs_hw[] = {1};
#define NR_UNCORE_PMUS 5
/* Uncore pmus that support more than 3 counters */
static struct uncore_pmus {
const char *name;
__u64 config;
} uncore_pmus[NR_UNCORE_PMUS] = {
{ "amd_l3", 0x0 },
{ "amd_df", 0x0 },
{ "uncore_imc_0", 0x1 }, /* Intel */
{ "core_imc", 0x318 }, /* PowerPC: core_imc/CPM_STCX_FIN/ */
{ "hv_24x7", 0x22000000003 }, /* PowerPC: hv_24x7/CPM_STCX_FIN/ */
};
static int event_open(int type, unsigned long config, int group_fd)
{
struct perf_event_attr attr;
memset(&attr, 0, sizeof(struct perf_event_attr));
attr.type = type;
attr.size = sizeof(struct perf_event_attr);
attr.config = config;
/*
* When creating an event group, typically the group leader is
* initialized with disabled set to 1 and any child events are
* initialized with disabled set to 0. Despite disabled being 0,
* the child events will not start until the group leader is
* enabled.
*/
attr.disabled = group_fd == -1 ? 1 : 0;
return sys_perf_event_open(&attr, -1, 0, group_fd, 0);
}
static int setup_uncore_event(void)
{
struct perf_pmu *pmu = NULL;
int i, fd;
while ((pmu = perf_pmus__scan(pmu)) != NULL) {
for (i = 0; i < NR_UNCORE_PMUS; i++) {
if (!strcmp(uncore_pmus[i].name, pmu->name)) {
pr_debug("Using %s for uncore pmu event\n", pmu->name);
types[2] = pmu->type;
configs[2] = uncore_pmus[i].config;
/*
* Check if the chosen uncore pmu event can be
* used in the test. For example, incase of accessing
* hv_24x7 pmu counters, partition should have
* additional permissions. If not, event open will
* fail. So check if the event open succeeds
* before proceeding.
*/
fd = event_open(types[2], configs[2], -1);
if (fd < 0)
return -1;
close(fd);
return 0;
}
}
}
return -1;
}
static int run_test(int i, int j, int k)
{
int erroneous = ((((1 << i) | (1 << j) | (1 << k)) & 5) == 5);
int group_fd, sibling_fd1, sibling_fd2;
group_fd = event_open(types[i], configs[i], -1);
if (group_fd == -1)
return -1;
sibling_fd1 = event_open(types[j], configs[j], group_fd);
if (sibling_fd1 == -1) {
close(group_fd);
return erroneous ? 0 : -1;
}
/*
* if all three events (leader and two sibling events)
* are hardware events, use instructions as one of the
* sibling event. There is event constraint in powerpc that
* events using same counter cannot be programmed in a group.
* Since PERF_COUNT_HW_INSTRUCTIONS is a generic hardware
* event and present in all platforms, lets use that.
*/
if (!i && !j && !k)
sibling_fd2 = event_open(types[k], configs_hw[k], group_fd);
else
sibling_fd2 = event_open(types[k], configs[k], group_fd);
if (sibling_fd2 == -1) {
close(sibling_fd1);
close(group_fd);
return erroneous ? 0 : -1;
}
close(sibling_fd2);
close(sibling_fd1);
close(group_fd);
return erroneous ? -1 : 0;
}
static int test__event_groups(struct test_suite *text __maybe_unused, int subtest __maybe_unused)
{
int i, j, k;
int ret;
int r;
ret = setup_uncore_event();
if (ret || types[2] == -1)
return TEST_SKIP;
ret = TEST_OK;
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
for (k = 0; k < 3; k++) {
r = run_test(i, j, k);
if (r)
ret = TEST_FAIL;
/*
* For all three events as HW events, second sibling
* event is picked from configs_hw. So print accordingly
*/
if (!i && !j && !k)
pr_debug("0x%x 0x%lx, 0x%x 0x%lx, 0x%x 0x%lx: %s\n",
types[i], configs[i], types[j], configs[j],
types[k], configs_hw[k], r ? "Fail" : "Pass");
else
pr_debug("0x%x 0x%lx, 0x%x 0x%lx, 0x%x 0x%lx: %s\n",
types[i], configs[i], types[j], configs[j],
types[k], configs[k], r ? "Fail" : "Pass");
}
}
}
return ret;
}
DEFINE_SUITE("Event groups", event_groups);
+124
View File
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <perf/cpumap.h>
#include <string.h>
#include "cpumap.h"
#include "evlist.h"
#include "evsel.h"
#include "header.h"
#include "machine.h"
#include "util/synthetic-events.h"
#include "target.h"
#include "tool.h"
#include "tests.h"
#include "debug.h"
static int process_event_unit(const struct perf_tool *tool __maybe_unused,
union perf_event *event,
struct perf_sample *sample __maybe_unused,
struct machine *machine __maybe_unused)
{
struct perf_record_event_update *ev = (struct perf_record_event_update *)event;
TEST_ASSERT_VAL("wrong id", ev->id == 123);
TEST_ASSERT_VAL("wrong id", ev->type == PERF_EVENT_UPDATE__UNIT);
TEST_ASSERT_VAL("wrong unit", !strcmp(ev->unit, "KRAVA"));
return 0;
}
static int process_event_scale(const struct perf_tool *tool __maybe_unused,
union perf_event *event,
struct perf_sample *sample __maybe_unused,
struct machine *machine __maybe_unused)
{
struct perf_record_event_update *ev = (struct perf_record_event_update *)event;
TEST_ASSERT_VAL("wrong id", ev->id == 123);
TEST_ASSERT_VAL("wrong id", ev->type == PERF_EVENT_UPDATE__SCALE);
TEST_ASSERT_VAL("wrong scale", ev->scale.scale == 0.123);
return 0;
}
struct event_name {
struct perf_tool tool;
const char *name;
};
static int process_event_name(const struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample __maybe_unused,
struct machine *machine __maybe_unused)
{
struct event_name *tmp = container_of(tool, struct event_name, tool);
struct perf_record_event_update *ev = (struct perf_record_event_update *)event;
TEST_ASSERT_VAL("wrong id", ev->id == 123);
TEST_ASSERT_VAL("wrong id", ev->type == PERF_EVENT_UPDATE__NAME);
TEST_ASSERT_VAL("wrong name", !strcmp(ev->name, tmp->name));
return 0;
}
static int process_event_cpus(const struct perf_tool *tool __maybe_unused,
union perf_event *event,
struct perf_sample *sample __maybe_unused,
struct machine *machine __maybe_unused)
{
struct perf_record_event_update *ev = (struct perf_record_event_update *)event;
struct perf_cpu_map *map;
map = cpu_map__new_data(&ev->cpus.cpus);
TEST_ASSERT_VAL("wrong id", ev->id == 123);
TEST_ASSERT_VAL("wrong type", ev->type == PERF_EVENT_UPDATE__CPUS);
TEST_ASSERT_VAL("wrong cpus", perf_cpu_map__nr(map) == 3);
TEST_ASSERT_VAL("wrong cpus", perf_cpu_map__cpu(map, 0).cpu == 1);
TEST_ASSERT_VAL("wrong cpus", perf_cpu_map__cpu(map, 1).cpu == 2);
TEST_ASSERT_VAL("wrong cpus", perf_cpu_map__cpu(map, 2).cpu == 3);
perf_cpu_map__put(map);
return 0;
}
static int test__event_update(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct evsel *evsel;
struct event_name tmp;
struct target target = {};
struct evlist *evlist = evlist__new_default(&target, /*sample_callchains=*/false);
TEST_ASSERT_VAL("failed to get evlist", evlist);
evsel = evlist__first(evlist);
TEST_ASSERT_VAL("failed to allocate ids",
!perf_evsel__alloc_id(&evsel->core, 1, 1));
perf_evlist__id_add(&evlist->core, &evsel->core, 0, 0, 123);
free((char *)evsel->unit);
evsel->unit = strdup("KRAVA");
TEST_ASSERT_VAL("failed to synthesize attr update unit",
!perf_event__synthesize_event_update_unit(NULL, evsel, process_event_unit));
evsel->scale = 0.123;
TEST_ASSERT_VAL("failed to synthesize attr update scale",
!perf_event__synthesize_event_update_scale(NULL, evsel, process_event_scale));
perf_tool__init(&tmp.tool, /*ordered_events=*/false);
tmp.name = evsel__name(evsel);
TEST_ASSERT_VAL("failed to synthesize attr update name",
!perf_event__synthesize_event_update_name(&tmp.tool, evsel, process_event_name));
perf_cpu_map__put(evsel->core.pmu_cpus);
evsel->core.pmu_cpus = perf_cpu_map__new("1,2,3");
TEST_ASSERT_VAL("failed to synthesize attr update cpus",
!perf_event__synthesize_event_update_cpus(&tmp.tool, evsel, process_event_cpus));
evlist__delete(evlist);
return 0;
}
DEFINE_SUITE("Synthesize attr update", event_update);
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0
#include "evlist.h"
#include "evsel.h"
#include "parse-events.h"
#include "tests.h"
#include "debug.h"
#include <linux/kernel.h>
static int perf_evsel__roundtrip_cache_name_test(void)
{
int ret = TEST_OK;
for (int type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) {
for (int op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) {
/* skip invalid cache type */
if (!evsel__is_cache_op_valid(type, op))
continue;
for (int res = 0; res < PERF_COUNT_HW_CACHE_RESULT_MAX; res++) {
char name[128];
struct evlist *evlist = evlist__new();
struct evsel *evsel;
int err;
if (evlist == NULL) {
pr_debug("Failed to alloc evlist");
return TEST_FAIL;
}
__evsel__hw_cache_type_op_res_name(type, op, res,
name, sizeof(name));
err = parse_event(evlist, name);
if (err) {
pr_debug("Failure to parse cache event '%s' possibly as PMUs don't support it",
name);
evlist__delete(evlist);
continue;
}
evlist__for_each_entry(evlist, evsel) {
if (!evsel__name_is(evsel, name)) {
pr_debug("%s != %s\n", evsel__name(evsel), name);
ret = TEST_FAIL;
}
}
evlist__delete(evlist);
}
}
}
return ret;
}
static int perf_evsel__name_array_test(const char *const names[], int nr_names)
{
int ret = TEST_OK;
for (int i = 0; i < nr_names; ++i) {
struct evlist *evlist = evlist__new();
struct evsel *evsel;
int err;
if (evlist == NULL) {
pr_debug("Failed to alloc evlist");
return TEST_FAIL;
}
err = parse_event(evlist, names[i]);
if (err) {
pr_debug("failed to parse event '%s', err %d\n",
names[i], err);
evlist__delete(evlist);
ret = TEST_FAIL;
continue;
}
evlist__for_each_entry(evlist, evsel) {
if (!evsel__name_is(evsel, names[i])) {
pr_debug("%s != %s\n", evsel__name(evsel), names[i]);
ret = TEST_FAIL;
}
}
evlist__delete(evlist);
}
return ret;
}
static int test__perf_evsel__roundtrip_name_test(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int err = 0, ret = TEST_OK;
err = perf_evsel__name_array_test(evsel__hw_names, PERF_COUNT_HW_MAX);
if (err)
ret = err;
err = perf_evsel__name_array_test(evsel__sw_names, PERF_COUNT_SW_DUMMY + 1);
if (err)
ret = err;
err = perf_evsel__roundtrip_cache_name_test();
if (err)
ret = err;
return ret;
}
DEFINE_SUITE("Roundtrip evsel->name", perf_evsel__roundtrip_name_test);
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/err.h>
#include <event-parse.h>
#include "evsel.h"
#include "tests.h"
#include "debug.h"
static int evsel__test_field(struct evsel *evsel, const char *name, int size, bool should_be_signed)
{
struct tep_format_field *field = evsel__field(evsel, name);
int is_signed;
int ret = 0;
if (field == NULL) {
pr_debug("%s: \"%s\" field not found!\n", evsel->name, name);
return -1;
}
is_signed = !!(field->flags & TEP_FIELD_IS_SIGNED);
if (should_be_signed && !is_signed) {
pr_debug("%s: \"%s\" signedness(%d) is wrong, should be %d\n",
evsel->name, name, is_signed, should_be_signed);
ret = -1;
}
if (field->size != size) {
pr_debug("%s: \"%s\" size (%d) should be %d!\n",
evsel->name, name, field->size, size);
ret = -1;
}
return ret;
}
static int test__perf_evsel__tp_sched_test(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct evsel *evsel = evsel__newtp("sched", "sched_switch");
int ret = TEST_OK;
if (IS_ERR(evsel)) {
pr_debug("evsel__newtp failed with %ld\n", PTR_ERR(evsel));
return PTR_ERR(evsel) == -EACCES ? TEST_SKIP : TEST_FAIL;
}
if (evsel__test_field(evsel, "prev_comm", 16, false))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "prev_pid", 4, true))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "prev_prio", 4, true))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "prev_state", sizeof(long), true))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "next_comm", 16, false))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "next_pid", 4, true))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "next_prio", 4, true))
ret = TEST_FAIL;
evsel__delete(evsel);
evsel = evsel__newtp("sched", "sched_wakeup");
if (IS_ERR(evsel)) {
pr_debug("evsel__newtp failed with %ld\n", PTR_ERR(evsel));
return TEST_FAIL;
}
if (evsel__test_field(evsel, "comm", 16, false))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "pid", 4, true))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "prio", 4, true))
ret = TEST_FAIL;
if (evsel__test_field(evsel, "target_cpu", 4, true))
ret = TEST_FAIL;
evsel__delete(evsel);
return ret;
}
static struct test_case tests__perf_evsel__tp_sched_test[] = {
TEST_CASE_REASON("Parse sched tracepoints fields",
perf_evsel__tp_sched_test,
"permissions"),
{ .name = NULL, }
};
struct test_suite suite__perf_evsel__tp_sched_test = {
.desc = "Parse sched tracepoints fields",
.test_cases = tests__perf_evsel__tp_sched_test,
};
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0
#include "tests.h"
#include "debug.h"
#include "evlist.h"
#include "cgroup.h"
#include "rblist.h"
#include "metricgroup.h"
#include "parse-events.h"
#include "pmu-events/pmu-events.h"
#include "pfm.h"
#include "target.h"
#include <subcmd/parse-options.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int test_expand_events(struct evlist *evlist)
{
int i, ret = TEST_FAIL;
int nr_events;
bool was_group_event;
int nr_members; /* for the first evsel only */
const char cgrp_str[] = "A,B,C";
const char *cgrp_name[] = { "A", "B", "C" };
int nr_cgrps = ARRAY_SIZE(cgrp_name);
char **ev_name;
struct evsel *evsel;
TEST_ASSERT_VAL("evlist is empty", !evlist__empty(evlist));
nr_events = evlist->core.nr_entries;
ev_name = calloc(nr_events, sizeof(*ev_name));
if (ev_name == NULL) {
pr_debug("memory allocation failure\n");
return TEST_FAIL;
}
i = 0;
evlist__for_each_entry(evlist, evsel) {
ev_name[i] = strdup(evsel->name);
if (ev_name[i] == NULL) {
pr_debug("memory allocation failure\n");
goto out;
}
i++;
}
/* remember grouping info */
was_group_event = evsel__is_group_event(evlist__first(evlist));
nr_members = evlist__first(evlist)->core.nr_members;
ret = evlist__expand_cgroup(evlist, cgrp_str, false);
if (ret < 0) {
pr_debug("failed to expand events for cgroups\n");
goto out;
}
ret = TEST_FAIL;
if (evlist->core.nr_entries != nr_events * nr_cgrps) {
pr_debug("event count doesn't match\n");
goto out;
}
i = 0;
evlist__for_each_entry(evlist, evsel) {
if (!evsel__name_is(evsel, ev_name[i % nr_events])) {
pr_debug("event name doesn't match:\n");
pr_debug(" evsel[%d]: %s\n expected: %s\n",
i, evsel->name, ev_name[i % nr_events]);
goto out;
}
if (strcmp(evsel->cgrp->name, cgrp_name[i / nr_events])) {
pr_debug("cgroup name doesn't match:\n");
pr_debug(" evsel[%d]: %s\n expected: %s\n",
i, evsel->cgrp->name, cgrp_name[i / nr_events]);
goto out;
}
if ((i % nr_events) == 0) {
if (evsel__is_group_event(evsel) != was_group_event) {
pr_debug("event group doesn't match: got %s, expect %s\n",
evsel__is_group_event(evsel) ? "true" : "false",
was_group_event ? "true" : "false");
goto out;
}
if (evsel->core.nr_members != nr_members) {
pr_debug("event group member doesn't match: %d vs %d\n",
evsel->core.nr_members, nr_members);
goto out;
}
}
i++;
}
ret = TEST_OK;
out: for (i = 0; i < nr_events; i++)
free(ev_name[i]);
free(ev_name);
return ret;
}
static int expand_default_events(void)
{
int ret;
struct target target = {};
struct evlist *evlist = evlist__new_default(&target, /*sample_callchains=*/false);
TEST_ASSERT_VAL("failed to get evlist", evlist);
ret = test_expand_events(evlist);
evlist__delete(evlist);
return ret;
}
static int expand_group_events(void)
{
int ret;
struct evlist *evlist;
struct parse_events_error err;
const char event_str[] = "{cycles,instructions}";
symbol_conf.event_group = true;
evlist = evlist__new();
TEST_ASSERT_VAL("failed to get evlist", evlist);
parse_events_error__init(&err);
ret = parse_events(evlist, event_str, &err);
if (ret < 0) {
pr_debug("failed to parse event '%s', err %d\n", event_str, ret);
parse_events_error__print(&err, event_str);
goto out;
}
ret = test_expand_events(evlist);
out:
parse_events_error__exit(&err);
evlist__delete(evlist);
return ret;
}
static int expand_libpfm_events(void)
{
int ret;
struct evlist *evlist;
const char event_str[] = "CYCLES";
struct option opt = {
.value = &evlist,
};
symbol_conf.event_group = true;
evlist = evlist__new();
TEST_ASSERT_VAL("failed to get evlist", evlist);
ret = parse_libpfm_events_option(&opt, event_str, 0);
if (ret < 0) {
pr_debug("failed to parse libpfm event '%s', err %d\n",
event_str, ret);
goto out;
}
if (evlist__empty(evlist)) {
pr_debug("libpfm was not enabled\n");
goto out;
}
ret = test_expand_events(evlist);
out:
evlist__delete(evlist);
return ret;
}
static int expand_metric_events(void)
{
int ret;
struct evlist *evlist;
const char metric_str[] = "CPI";
const struct pmu_metrics_table *pme_test;
evlist = evlist__new();
TEST_ASSERT_VAL("failed to get evlist", evlist);
pme_test = find_core_metrics_table("testarch", "testcpu");
ret = metricgroup__parse_groups_test(evlist, pme_test, metric_str);
if (ret < 0) {
pr_debug("failed to parse '%s' metric\n", metric_str);
goto out;
}
ret = test_expand_events(evlist);
out:
evlist__delete(evlist);
return ret;
}
static int test__expand_cgroup_events(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int ret;
ret = expand_default_events();
TEST_ASSERT_EQUAL("failed to expand default events", ret, 0);
ret = expand_group_events();
TEST_ASSERT_EQUAL("failed to expand event group", ret, 0);
ret = expand_libpfm_events();
TEST_ASSERT_EQUAL("failed to expand event group", ret, 0);
ret = expand_metric_events();
TEST_ASSERT_EQUAL("failed to expand metric events", ret, 0);
return ret;
}
DEFINE_SUITE("Event expansion for cgroups", expand_cgroup_events);
+298
View File
@@ -0,0 +1,298 @@
// SPDX-License-Identifier: GPL-2.0
#include "util/cputopo.h"
#include "util/debug.h"
#include "util/expr.h"
#include "util/hashmap.h"
#include "util/header.h"
#include "util/smt.h"
#include "tests.h"
#include <perf/cpumap.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <string2.h>
#include <linux/zalloc.h>
static int test_ids_union(void)
{
struct hashmap *ids1, *ids2;
/* Empty union. */
ids1 = ids__new();
TEST_ASSERT_VAL("ids__new", ids1);
ids2 = ids__new();
TEST_ASSERT_VAL("ids__new", ids2);
ids1 = ids__union(ids1, ids2);
TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 0);
/* Union {foo, bar} against {}. */
ids2 = ids__new();
TEST_ASSERT_VAL("ids__new", ids2);
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids1, strdup("foo")), 0);
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids1, strdup("bar")), 0);
ids1 = ids__union(ids1, ids2);
TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 2);
/* Union {foo, bar} against {foo}. */
ids2 = ids__new();
TEST_ASSERT_VAL("ids__new", ids2);
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("foo")), 0);
ids1 = ids__union(ids1, ids2);
TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 2);
/* Union {foo, bar} against {bar,baz}. */
ids2 = ids__new();
TEST_ASSERT_VAL("ids__new", ids2);
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("bar")), 0);
TEST_ASSERT_EQUAL("ids__insert", ids__insert(ids2, strdup("baz")), 0);
ids1 = ids__union(ids1, ids2);
TEST_ASSERT_EQUAL("union", (int)hashmap__size(ids1), 3);
ids__free(ids1);
return 0;
}
static int test(struct expr_parse_ctx *ctx, const char *e, double val2)
{
double val;
if (expr__parse(&val, ctx, e))
TEST_ASSERT_VAL("parse test failed", 0);
TEST_ASSERT_VAL("unexpected value", val == val2);
return 0;
}
static int test__expr(struct test_suite *t __maybe_unused, int subtest __maybe_unused)
{
struct expr_id_data *val_ptr;
const char *p;
double val, num_cpus_online, num_cpus, num_cores, num_dies, num_packages;
int ret;
struct expr_parse_ctx *ctx;
char strcmp_cpuid_buf[256];
struct perf_cpu cpu = {-1};
char *cpuid = get_cpuid_allow_env_override(cpu);
char *escaped_cpuid1, *escaped_cpuid2;
TEST_ASSERT_VAL("get_cpuid", cpuid);
TEST_ASSERT_EQUAL("ids_union", test_ids_union(), 0);
ctx = expr__ctx_new();
TEST_ASSERT_VAL("expr__ctx_new", ctx);
expr__add_id_val(ctx, strdup("FOO"), 1);
expr__add_id_val(ctx, strdup("BAR"), 2);
ret = test(ctx, "1+1", 2);
ret |= test(ctx, "FOO+BAR", 3);
ret |= test(ctx, "(BAR/2)%2", 1);
ret |= test(ctx, "1 - -4", 5);
ret |= test(ctx, "(FOO-1)*2 + (BAR/2)%2 - -4", 5);
ret |= test(ctx, "1-1 | 1", 1);
ret |= test(ctx, "1-1 & 1", 0);
ret |= test(ctx, "min(1,2) + 1", 2);
ret |= test(ctx, "max(1,2) + 1", 3);
ret |= test(ctx, "1+1 if 3*4 else 0", 2);
ret |= test(ctx, "100 if 1 else 200 if 1 else 300", 100);
ret |= test(ctx, "100 if 0 else 200 if 1 else 300", 200);
ret |= test(ctx, "100 if 1 else 200 if 0 else 300", 100);
ret |= test(ctx, "100 if 0 else 200 if 0 else 300", 300);
ret |= test(ctx, "1.1 + 2.1", 3.2);
ret |= test(ctx, ".1 + 2.", 2.1);
ret |= test(ctx, "d_ratio(1, 2)", 0.5);
ret |= test(ctx, "d_ratio(2.5, 0)", 0);
ret |= test(ctx, "1.1 < 2.2", 1);
ret |= test(ctx, "2.2 > 1.1", 1);
ret |= test(ctx, "1.1 < 1.1", 0);
ret |= test(ctx, "2.2 > 2.2", 0);
ret |= test(ctx, "2.2 < 1.1", 0);
ret |= test(ctx, "1.1 > 2.2", 0);
ret |= test(ctx, "1.1e10 < 1.1e100", 1);
ret |= test(ctx, "1.1e2 > 1.1e-2", 1);
if (ret) {
expr__ctx_free(ctx);
return ret;
}
p = "FOO/0";
ret = expr__parse(&val, ctx, p);
TEST_ASSERT_VAL("division by zero", ret == 0);
TEST_ASSERT_VAL("division by zero", isnan(val));
p = "BAR/";
ret = expr__parse(&val, ctx, p);
TEST_ASSERT_VAL("missing operand", ret == -1);
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("FOO + BAR + BAZ + BOZO", "FOO",
ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 3);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BAR", &val_ptr));
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BAZ", &val_ptr));
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "BOZO", &val_ptr));
expr__ctx_clear(ctx);
ctx->sctx.runtime = 3;
TEST_ASSERT_VAL("find ids",
expr__find_ids("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@",
NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 2);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1,param=3@", &val_ptr));
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT2,param=3@", &val_ptr));
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("dash\\-event1 - dash\\-event2",
NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 2);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "dash-event1", &val_ptr));
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "dash-event2", &val_ptr));
/* Only EVENT1 or EVENT2 need be measured depending on the value of smt_on. */
{
bool smton = smt_on();
bool corewide = core_wide(/*system_wide=*/false,
/*user_requested_cpus=*/false);
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("EVENT1 if #smt_on else EVENT2",
NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
smton ? "EVENT1" : "EVENT2",
&val_ptr));
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("EVENT1 if #core_wide else EVENT2",
NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
corewide ? "EVENT1" : "EVENT2",
&val_ptr));
}
/* The expression is a constant 1.0 without needing to evaluate EVENT1. */
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("1.0 if EVENT1 > 100.0 else 1.0",
NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0);
/* The expression is a constant 0.0 without needing to evaluate EVENT1. */
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("0 & EVENT1 > 0", NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0);
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("EVENT1 > 0 & 0", NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0);
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("1 & EVENT1 > 0", NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1", &val_ptr));
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("EVENT1 > 0 & 1", NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1", &val_ptr));
/* The expression is a constant 1.0 without needing to evaluate EVENT1. */
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("1 | EVENT1 > 0", NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0);
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("EVENT1 > 0 | 1", NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 0);
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("0 | EVENT1 > 0", NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1", &val_ptr));
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
expr__find_ids("EVENT1 > 0 | 0", NULL, ctx) == 0);
TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids, "EVENT1", &val_ptr));
/* Test toplogy constants appear well ordered. */
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("#num_cpus_online",
expr__parse(&num_cpus_online, ctx, "#num_cpus_online") == 0);
TEST_ASSERT_VAL("#num_cpus", expr__parse(&num_cpus, ctx, "#num_cpus") == 0);
TEST_ASSERT_VAL("#num_cpus >= #num_cpus_online", num_cpus >= num_cpus_online);
TEST_ASSERT_VAL("#num_cores", expr__parse(&num_cores, ctx, "#num_cores") == 0);
TEST_ASSERT_VAL("#num_cpus >= #num_cores", num_cpus >= num_cores);
TEST_ASSERT_VAL("#num_dies", expr__parse(&num_dies, ctx, "#num_dies") == 0);
TEST_ASSERT_VAL("#num_cores >= #num_dies", num_cores >= num_dies);
TEST_ASSERT_VAL("#num_packages", expr__parse(&num_packages, ctx, "#num_packages") == 0);
if (num_dies) // Some platforms do not have CPU die support, for example s390
TEST_ASSERT_VAL("#num_dies >= #num_packages", num_dies >= num_packages);
if (expr__parse(&val, ctx, "#system_tsc_freq") == 0) {
bool is_intel = strstr(cpuid, "Intel") != NULL;
if (is_intel)
TEST_ASSERT_VAL("#system_tsc_freq > 0", val > 0);
else
TEST_ASSERT_VAL("#system_tsc_freq == 0", fpclassify(val) == FP_ZERO);
} else {
#if defined(__i386__) || defined(__x86_64__)
TEST_ASSERT_VAL("#system_tsc_freq unsupported", 0);
#endif
}
/*
* Source count returns the number of events aggregating in a leader
* event including the leader. Check parsing yields an id.
*/
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("source count",
expr__find_ids("source_count(EVENT1)",
NULL, ctx) == 0);
TEST_ASSERT_VAL("source count", hashmap__size(ctx->ids) == 1);
TEST_ASSERT_VAL("source count", hashmap__find(ctx->ids, "EVENT1", &val_ptr));
/* Test no cpuid match */
ret = test(ctx, "strcmp_cpuid_str(0x0)", 0);
/*
* Test cpuid match with current cpuid. Special chars have to be
* escaped.
*/
escaped_cpuid1 = strreplace_chars('-', cpuid, "\\-");
free(cpuid);
escaped_cpuid2 = strreplace_chars(',', escaped_cpuid1, "\\,");
free(escaped_cpuid1);
escaped_cpuid1 = strreplace_chars('=', escaped_cpuid2, "\\=");
free(escaped_cpuid2);
scnprintf(strcmp_cpuid_buf, sizeof(strcmp_cpuid_buf),
"strcmp_cpuid_str(%s)", escaped_cpuid1);
free(escaped_cpuid1);
ret |= test(ctx, strcmp_cpuid_buf, 1);
/* has_event returns 1 when an event exists. */
expr__add_id_val(ctx, strdup("cycles"), 2);
ret |= test(ctx, "has_event(cycles)", 1);
expr__ctx_free(ctx);
return ret;
}
DEFINE_SUITE("Simple expression parser", expr);
+163
View File
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: GPL-2.0
#include <api/fd/array.h>
#include <poll.h>
#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].events = revents;
fda->entries[fd].revents = revents;
}
}
static int fdarray__fprintf_prefix(struct fdarray *fda, const char *prefix, FILE *fp)
{
int printed = 0;
if (verbose <= 0)
return 0;
printed += fprintf(fp, "\n%s: ", prefix);
return printed + fdarray__fprintf(fda, fp);
}
static int test__fdarray__filter(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int nr_fds, 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, 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, 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;
pr_debug("\nfiltering all but fda->entries[2]:");
fdarray__fprintf_prefix(fda, "before", stderr);
nr_fds = fdarray__filter(fda, POLLHUP, NULL, 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;
}
fdarray__init_revents(fda, POLLHUP);
fda->entries[0].revents = POLLIN;
fda->entries[3].revents = POLLIN;
pr_debug("\nfiltering all but (fda->entries[0], fda->entries[3]):");
fdarray__fprintf_prefix(fda, "before", stderr);
nr_fds = fdarray__filter(fda, POLLHUP, NULL, 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;
}
pr_debug("\n");
err = 0;
out_delete:
fdarray__delete(fda);
out:
return err;
}
static int test__fdarray__add(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int err = TEST_FAIL;
struct fdarray *fda = fdarray__new(2, 2);
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, fdarray_flag__default) < 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;
}
DEFINE_SUITE("Filter fds with revents mask in a fdarray", fdarray__filter);
DEFINE_SUITE("Add fd to a fdarray, making it autogrow", fdarray__add);
+53
View File
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/compiler.h>
#include "debug.h"
#include "tests.h"
#ifdef HAVE_JITDUMP
#include <libelf.h>
#include "../util/genelf.h"
#endif
#define TEMPL "/tmp/perf-test-XXXXXX"
static int test__jit_write_elf(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
#ifdef HAVE_JITDUMP
static unsigned char x86_code[] = {
0xBB, 0x2A, 0x00, 0x00, 0x00, /* movl $42, %ebx */
0xB8, 0x01, 0x00, 0x00, 0x00, /* movl $1, %eax */
0xCD, 0x80 /* int $0x80 */
};
char path[PATH_MAX];
int fd, ret;
strcpy(path, TEMPL);
fd = mkstemp(path);
if (fd < 0) {
perror("mkstemp failed");
return TEST_FAIL;
}
pr_info("Writing jit code to: %s\n", path);
ret = jit_write_elf(fd, 0, "main", x86_code, sizeof(x86_code),
NULL, 0, NULL, 0, 0);
close(fd);
unlink(path);
return ret ? TEST_FAIL : 0;
#else
return TEST_SKIP;
#endif
}
DEFINE_SUITE("Test jit_write_elf", jit_write_elf);
+223
View File
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0
#include <inttypes.h>
#include "util/debug.h"
#include "util/dso.h"
#include "util/event.h" // struct perf_sample
#include "util/map.h"
#include "util/symbol.h"
#include "util/sort.h"
#include "util/evsel.h"
#include "util/machine.h"
#include "util/thread.h"
#include "tests/hists_common.h"
#include <linux/kernel.h>
#include <linux/perf_event.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);
thread__put(thread);
}
for (i = 0; i < ARRAY_SIZE(fake_mmap_info); i++) {
struct perf_sample sample = {
.cpumode = PERF_RECORD_MISC_USER,
};
union perf_event fake_mmap_event = {
.mmap = {
.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, &sample);
}
for (i = 0; i < ARRAY_SIZE(fake_symbols); i++) {
size_t k;
struct dso *dso;
dso = machine__findnew_dso(machine, fake_symbols[i].dso_name);
if (dso == NULL)
goto out;
/* emulate dso__load() */
dso__set_loaded(dso);
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, STT_FUNC, fsym->name);
if (sym == NULL) {
dso__put(dso);
goto out;
}
symbols__insert(dso__symbols(dso), sym);
}
dso__put(dso);
}
return machine;
out:
pr_debug("Not enough memory for machine setup\n");
machine__delete_threads(machine);
return NULL;
}
void print_hists_in(struct hists *hists)
{
int i = 0;
struct rb_root_cached *root;
struct rb_node *node;
if (hists__has(hists, need_collapse))
root = &hists->entries_collapsed;
else
root = hists->entries_in;
pr_info("----- %s --------\n", __func__);
node = rb_first_cached(root);
while (node) {
struct hist_entry *he;
he = rb_entry(node, struct hist_entry, rb_node_in);
if (!he->filtered) {
struct dso *dso = map__dso(he->ms.map);
pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n",
i, thread__comm_str(he->thread),
dso__short_name(dso),
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_cached *root;
struct rb_node *node;
root = &hists->entries;
pr_info("----- %s --------\n", __func__);
node = rb_first_cached(root);
while (node) {
struct hist_entry *he;
he = rb_entry(node, struct hist_entry, rb_node);
if (!he->filtered) {
struct dso *dso = map__dso(he->ms.map);
pr_info("%2d: entry: %8s:%5d [%-8s] %20s: period = %"PRIu64"/%"PRIu64"\n",
i, thread__comm_str(he->thread), thread__tid(he->thread),
dso__short_name(dso),
he->ms.sym->name, he->stat.period,
he->stat_acc ? he->stat_acc->period : 0);
}
i++;
node = rb_next(node);
}
}
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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__ */
@@ -0,0 +1,754 @@
// SPDX-License-Identifier: GPL-2.0
#include "util/debug.h"
#include "util/dso.h"
#include "util/event.h"
#include "util/map.h"
#include "util/symbol.h"
#include "util/sort.h"
#include "util/evsel.h"
#include "util/evlist.h"
#include "util/machine.h"
#include "util/parse-events.h"
#include "util/thread.h"
#include "tests/tests.h"
#include "tests/hists_common.h"
#include <linux/kernel.h>
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 cast 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 evsel *evsel = hists_to_evsel(hists);
struct perf_sample sample = { .evsel = evsel, .period = 1000, };
size_t i;
addr_location__init(&al);
for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
struct hist_entry_iter iter = {
.sample = &sample,
.hide_unresolved = false,
};
if (symbol_conf.cumulate_callchain)
iter.ops = &hist_iter_cumulative;
else
iter.ops = &hist_iter_normal;
sample.cpumode = PERF_RECORD_MISC_USER;
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 (machine__resolve(machine, &al, &sample) < 0)
goto out;
if (hist_entry_iter__add(&iter, &al, sysctl_perf_event_max_stack,
NULL) < 0) {
goto out;
}
thread__put(fake_samples[i].thread);
fake_samples[i].thread = thread__get(al.thread);
map__put(fake_samples[i].map);
fake_samples[i].map = map__get(al.map);
fake_samples[i].sym = al.sym;
}
addr_location__exit(&al);
return TEST_OK;
out:
pr_debug("Not enough memory for adding a hist entry\n");
addr_location__exit(&al);
return TEST_FAIL;
}
static void del_hist_entries(struct hists *hists)
{
struct hist_entry *he;
struct rb_root_cached *root_in;
struct rb_root_cached *root_out;
struct rb_node *node;
if (hists__has(hists, need_collapse))
root_in = &hists->entries_collapsed;
else
root_in = hists->entries_in;
root_out = &hists->entries;
while (!RB_EMPTY_ROOT(&root_out->rb_root)) {
node = rb_first_cached(root_out);
he = rb_entry(node, struct hist_entry, rb_node);
rb_erase_cached(node, root_out);
rb_erase_cached(&he->rb_node_in, root_in);
hist_entry__delete(he);
}
}
static void put_fake_samples(void)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
map__zput(fake_samples[i].map);
thread__zput(fake_samples[i].thread);
}
}
typedef int (*test_fn_t)(struct evsel *, struct machine *);
#define COMM(he) (thread__comm_str(he->thread))
#define DSO(he) (dso__short_name(map__dso(he->ms.map)))
#define SYM(he) (he->ms.sym->name)
#define CPU(he) (he->cpu)
#define DEPTH(he) (he->callchain->max_depth)
#define CDSO(cl) (dso__short_name(map__dso(cl->ms.map)))
#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);
evsel__output_resort(hists_to_evsel(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.rb_root;
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;
TEST_ASSERT_VAL("callchains expected", !RB_EMPTY_ROOT(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 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;
evsel__reset_sample_bit(evsel, CALLCHAIN);
setup_sorting(/*evlist=*/NULL, machine->env);
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 + NO children */
static int test2(struct 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;
evsel__set_sample_bit(evsel, CALLCHAIN);
setup_sorting(/*evlist=*/NULL, machine->env);
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 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;
evsel__reset_sample_bit(evsel, CALLCHAIN);
setup_sorting(/*evlist=*/NULL, machine->env);
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 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;
evsel__set_sample_bit(evsel, CALLCHAIN);
setup_sorting(/*evlist=*/NULL, machine->env);
callchain_param = callchain_param_default;
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;
}
static int test__hists_cumulate(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int err = TEST_FAIL;
struct machines machines = { 0 };
struct machine *machine;
struct evsel *evsel;
struct evlist *evlist = evlist__new();
size_t i;
test_fn_t testcases[] = {
test1,
test2,
test3,
test4,
};
TEST_ASSERT_VAL("No memory", evlist);
err = parse_event(evlist, "cpu-clock");
if (err)
goto out;
err = TEST_FAIL;
if (machines__init(&machines))
goto out;
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
if (!machine)
goto out;
if (verbose > 1)
machine__fprintf(machine, stderr);
evsel = evlist__first(evlist);
for (i = 0; i < ARRAY_SIZE(testcases); i++) {
err = testcases[i](evsel, machine);
if (err < 0)
break;
}
out:
/* tear down everything */
evlist__delete(evlist);
machines__exit(&machines);
put_fake_samples();
return err;
}
DEFINE_SUITE("Cumulate child hist entries", hists_cumulate);
+343
View File
@@ -0,0 +1,343 @@
// SPDX-License-Identifier: GPL-2.0
#include "util/debug.h"
#include "util/map.h"
#include "util/symbol.h"
#include "util/sort.h"
#include "util/evsel.h"
#include "util/event.h"
#include "util/evlist.h"
#include "util/machine.h"
#include "util/parse-events.h"
#include "util/thread.h"
#include "tests/tests.h"
#include "tests/hists_common.h"
#include <linux/kernel.h>
struct sample {
u32 pid;
u64 ip;
struct thread *thread;
struct map *map;
struct symbol *sym;
int socket;
};
/* For the numbers, see hists_common.c */
static struct sample fake_samples[] = {
/* perf [kernel] schedule() */
{ .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, .socket = 0 },
/* perf [perf] main() */
{ .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, .socket = 0 },
/* perf [libc] malloc() */
{ .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, .socket = 0 },
/* perf [perf] main() */
{ .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, .socket = 0 }, /* will be merged */
/* perf [perf] cmd_record() */
{ .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, .socket = 1 },
/* perf [kernel] page_fault() */
{ .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, .socket = 1 },
/* bash [bash] main() */
{ .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, .socket = 2 },
/* bash [bash] xmalloc() */
{ .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, .socket = 2 },
/* bash [libc] malloc() */
{ .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, .socket = 3 },
/* bash [kernel] page_fault() */
{ .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, .socket = 3 },
};
static int add_hist_entries(struct evlist *evlist,
struct machine *machine)
{
struct evsel *evsel;
struct addr_location al;
struct perf_sample sample = { .period = 100, };
size_t i;
addr_location__init(&al);
/*
* each evsel will have 10 samples but the 4th sample
* (perf [perf] main) will be collapsed to an existing entry
* so total 9 entries will be in the tree.
*/
evlist__for_each_entry(evlist, evsel) {
for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
struct hist_entry_iter iter = {
.sample = &sample,
.ops = &hist_iter_normal,
.hide_unresolved = false,
};
struct hists *hists = evsel__hists(evsel);
sample.evsel = evsel;
/* make sure it has no filter at first */
hists->thread_filter = NULL;
hists->dso_filter = NULL;
hists->symbol_filter_str = NULL;
sample.cpumode = PERF_RECORD_MISC_USER;
sample.pid = fake_samples[i].pid;
sample.tid = fake_samples[i].pid;
sample.ip = fake_samples[i].ip;
if (machine__resolve(machine, &al, &sample) < 0)
goto out;
al.socket = fake_samples[i].socket;
if (hist_entry_iter__add(&iter, &al,
sysctl_perf_event_max_stack, NULL) < 0) {
goto out;
}
thread__put(fake_samples[i].thread);
fake_samples[i].thread = thread__get(al.thread);
map__put(fake_samples[i].map);
fake_samples[i].map = map__get(al.map);
fake_samples[i].sym = al.sym;
}
}
addr_location__exit(&al);
return 0;
out:
pr_debug("Not enough memory for adding a hist entry\n");
addr_location__exit(&al);
return TEST_FAIL;
}
static void put_fake_samples(void)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(fake_samples); i++)
map__put(fake_samples[i].map);
}
static int test__hists_filter(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int err = TEST_FAIL;
struct machines machines = { 0 };
struct machine *machine;
struct evsel *evsel;
struct evlist *evlist = evlist__new();
TEST_ASSERT_VAL("No memory", evlist);
err = parse_event(evlist, "cpu-clock");
if (err)
goto out;
err = parse_event(evlist, "task-clock");
if (err)
goto out;
err = TEST_FAIL;
if (machines__init(&machines))
goto out;
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
if (!machine)
goto out;
if (verbose > 1)
machine__fprintf(machine, stderr);
/* default sort order (comm,dso,sym) will be used */
if (setup_sorting(evlist, machine->env) < 0)
goto out;
/* process sample events */
err = add_hist_entries(evlist, machine);
if (err < 0)
goto out;
evlist__for_each_entry(evlist, evsel) {
struct hists *hists = evsel__hists(evsel);
hists__collapse_resort(hists, NULL);
evsel__output_resort(evsel, NULL);
if (verbose > 2) {
pr_info("Normal histogram\n");
print_hists_out(hists);
}
TEST_ASSERT_VAL("Invalid nr samples",
hists->stats.nr_samples == 10);
TEST_ASSERT_VAL("Invalid nr hist entries",
hists->nr_entries == 9);
TEST_ASSERT_VAL("Invalid total period",
hists->stats.total_period == 1000);
TEST_ASSERT_VAL("Unmatched nr samples",
hists->stats.nr_samples ==
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_samples == 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 = map__dso(fake_samples[0].map);
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_samples == 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_samples == 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);
/* remove symbol filter first */
hists->symbol_filter_str = NULL;
hists__filter_by_symbol(hists);
/* now applying socket filters */
hists->socket_filter = 2;
hists__filter_by_socket(hists);
if (verbose > 2) {
pr_info("Histogram for socket filters\n");
print_hists_out(hists);
}
/* normal stats should be invariant */
TEST_ASSERT_VAL("Invalid nr samples",
hists->stats.nr_samples == 10);
TEST_ASSERT_VAL("Invalid nr hist entries",
hists->nr_entries == 9);
TEST_ASSERT_VAL("Invalid total period",
hists->stats.total_period == 1000);
/* but filter stats are changed */
TEST_ASSERT_VAL("Unmatched nr samples for socket filter",
hists->stats.nr_non_filtered_samples == 2);
TEST_ASSERT_VAL("Unmatched nr hist entries for socket filter",
hists->nr_non_filtered_entries == 2);
TEST_ASSERT_VAL("Unmatched total period for socket filter",
hists->stats.total_non_filtered_period == 200);
/* remove socket filter first */
hists->socket_filter = -1;
hists__filter_by_socket(hists);
/* now applying all filters at once. */
hists->thread_filter = fake_samples[1].thread;
hists->dso_filter = map__dso(fake_samples[1].map);
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_samples == 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 */
evlist__delete(evlist);
reset_output_field();
machines__exit(&machines);
put_fake_samples();
return err;
}
DEFINE_SUITE("Filter hist entries", hists_filter);
+364
View File
@@ -0,0 +1,364 @@
// SPDX-License-Identifier: GPL-2.0
#include "tests.h"
#include "debug.h"
#include "symbol.h"
#include "sort.h"
#include "evsel.h"
#include "evlist.h"
#include "machine.h"
#include "map.h"
#include "parse-events.h"
#include "thread.h"
#include "hists_common.h"
#include "util/mmap.h"
#include <errno.h>
#include <linux/kernel.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 evlist *evlist, struct machine *machine)
{
struct evsel *evsel;
struct addr_location al;
struct hist_entry *he;
struct perf_sample sample = { .period = 1, .weight = 1, };
size_t i = 0, k;
addr_location__init(&al);
/*
* each evsel will have 10 samples - 5 common and 5 distinct.
* However the second evsel also has a collapsed entry for
* "bash [libc] malloc" so total 9 entries will be in the tree.
*/
evlist__for_each_entry(evlist, evsel) {
struct hists *hists = evsel__hists(evsel);
for (k = 0; k < ARRAY_SIZE(fake_common_samples); k++) {
sample.cpumode = 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 (machine__resolve(machine, &al, &sample) < 0)
goto out;
he = hists__add_entry(hists, &al, NULL,
NULL, NULL, NULL, &sample, true);
if (he == NULL) {
goto out;
}
thread__put(fake_common_samples[k].thread);
fake_common_samples[k].thread = thread__get(al.thread);
map__put(fake_common_samples[k].map);
fake_common_samples[k].map = map__get(al.map);
fake_common_samples[k].sym = al.sym;
}
for (k = 0; k < ARRAY_SIZE(fake_samples[i]); k++) {
sample.pid = fake_samples[i][k].pid;
sample.tid = fake_samples[i][k].pid;
sample.ip = fake_samples[i][k].ip;
if (machine__resolve(machine, &al, &sample) < 0)
goto out;
he = hists__add_entry(hists, &al, NULL,
NULL, NULL, NULL, &sample, true);
if (he == NULL) {
goto out;
}
thread__put(fake_samples[i][k].thread);
fake_samples[i][k].thread = thread__get(al.thread);
map__put(fake_samples[i][k].map);
fake_samples[i][k].map = map__get(al.map);
fake_samples[i][k].sym = al.sym;
}
i++;
}
addr_location__exit(&al);
return 0;
out:
addr_location__exit(&al);
pr_debug("Not enough memory for adding a hist entry\n");
return -1;
}
static void put_fake_samples(void)
{
size_t i, j;
for (i = 0; i < ARRAY_SIZE(fake_common_samples); i++)
map__put(fake_common_samples[i].map);
for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
for (j = 0; j < ARRAY_SIZE(fake_samples[0]); j++)
map__put(fake_samples[i][j].map);
}
}
static int find_sample(struct sample *samples, size_t nr_samples,
struct thread *t, struct map *m, struct symbol *s)
{
while (nr_samples--) {
if (RC_CHK_EQUAL(samples->thread, t) &&
RC_CHK_EQUAL(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_cached *root;
struct rb_node *node;
/*
* Only entries from fake_common_samples should have a pair.
*/
if (hists__has(hists, need_collapse))
root = &hists->entries_collapsed;
else
root = hists->entries_in;
node = rb_first_cached(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_cached *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 (hists__has(hists, need_collapse))
root = &hists->entries_collapsed;
else
root = hists->entries_in;
node = rb_first_cached(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);
}
static int test__hists_link(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int err = -1;
struct hists *hists, *first_hists;
struct machines machines = { 0 };
struct machine *machine = NULL;
struct evsel *evsel, *first;
struct evlist *evlist = evlist__new();
if (evlist == NULL)
return -ENOMEM;
err = parse_event(evlist, "cpu-clock");
if (err)
goto out;
err = parse_event(evlist, "task-clock");
if (err)
goto out;
err = TEST_FAIL;
if (machines__init(&machines))
goto out;
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
if (!machine)
goto out;
if (verbose > 1)
machine__fprintf(machine, stderr);
/* default sort order (comm,dso,sym) will be used */
if (setup_sorting(evlist, machine->env) < 0)
goto out;
/* process sample events */
err = add_hist_entries(evlist, machine);
if (err < 0)
goto out;
evlist__for_each_entry(evlist, evsel) {
hists = evsel__hists(evsel);
hists__collapse_resort(hists, NULL);
if (verbose > 2)
print_hists_in(hists);
}
first = evlist__first(evlist);
evsel = 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 */
evlist__delete(evlist);
reset_output_field();
machines__exit(&machines);
put_fake_samples();
return err;
}
DEFINE_SUITE("Match and link multiple hists", hists_link);
+641
View File
@@ -0,0 +1,641 @@
// SPDX-License-Identifier: GPL-2.0
#include "util/debug.h"
#include "util/dso.h"
#include "util/event.h"
#include "util/map.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"
#include <linux/kernel.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 evsel *evsel = hists_to_evsel(hists);
struct perf_sample sample = { .evsel = evsel, .period = 100, };
size_t i;
addr_location__init(&al);
for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
struct hist_entry_iter iter = {
.sample = &sample,
.ops = &hist_iter_normal,
.hide_unresolved = false,
};
sample.cpumode = PERF_RECORD_MISC_USER;
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 (machine__resolve(machine, &al, &sample) < 0)
goto out;
if (hist_entry_iter__add(&iter, &al, sysctl_perf_event_max_stack,
NULL) < 0) {
goto out;
}
fake_samples[i].thread = al.thread;
map__put(fake_samples[i].map);
fake_samples[i].map = map__get(al.map);
fake_samples[i].sym = al.sym;
}
addr_location__exit(&al);
return TEST_OK;
out:
pr_debug("Not enough memory for adding a hist entry\n");
addr_location__exit(&al);
return TEST_FAIL;
}
static void del_hist_entries(struct hists *hists)
{
struct hist_entry *he;
struct rb_root_cached *root_in;
struct rb_root_cached *root_out;
struct rb_node *node;
if (hists__has(hists, need_collapse))
root_in = &hists->entries_collapsed;
else
root_in = hists->entries_in;
root_out = &hists->entries;
while (!RB_EMPTY_ROOT(&root_out->rb_root)) {
node = rb_first_cached(root_out);
he = rb_entry(node, struct hist_entry, rb_node);
rb_erase_cached(node, root_out);
rb_erase_cached(&he->rb_node_in, root_in);
hist_entry__delete(he);
}
}
static void put_fake_samples(void)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
map__put(fake_samples[i].map);
fake_samples[i].map = NULL;
}
}
typedef int (*test_fn_t)(struct evsel *, struct machine *);
#define COMM(he) (thread__comm_str(he->thread))
#define DSO(he) (dso__short_name(map__dso(he->ms.map)))
#define SYM(he) (he->ms.sym->name)
#define CPU(he) (he->cpu)
#define PID(he) (thread__tid(he->thread))
/* default sort keys (no field) */
static int test1(struct evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = evsel__hists(evsel);
struct hist_entry *he;
struct rb_root_cached *root;
struct rb_node *node;
field_order = NULL;
sort_order = NULL; /* equivalent to sort_order = "comm,dso,sym" */
setup_sorting(/*evlist=*/NULL, machine->env);
/*
* 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);
evsel__output_resort(evsel, 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_cached(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 evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = evsel__hists(evsel);
struct hist_entry *he;
struct rb_root_cached *root;
struct rb_node *node;
field_order = "overhead,cpu";
sort_order = "pid";
setup_sorting(/*evlist=*/NULL, machine->env);
/*
* 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);
evsel__output_resort(evsel, 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_cached(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 evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = evsel__hists(evsel);
struct hist_entry *he;
struct rb_root_cached *root;
struct rb_node *node;
field_order = "comm,overhead,dso";
sort_order = NULL;
setup_sorting(/*evlist=*/NULL, machine->env);
/*
* 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);
evsel__output_resort(evsel, 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_cached(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 evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = evsel__hists(evsel);
struct hist_entry *he;
struct rb_root_cached *root;
struct rb_node *node;
field_order = "dso,sym,comm,overhead,dso";
sort_order = "sym";
setup_sorting(/*evlist=*/NULL, machine->env);
/*
* 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);
evsel__output_resort(evsel, 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_cached(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 evsel *evsel, struct machine *machine)
{
int err;
struct hists *hists = evsel__hists(evsel);
struct hist_entry *he;
struct rb_root_cached *root;
struct rb_node *node;
field_order = "cpu,pid,comm,dso,sym";
sort_order = "dso,pid";
setup_sorting(/*evlist=*/NULL, machine->env);
/*
* 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);
evsel__output_resort(evsel, 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_cached(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;
}
static int test__hists_output(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int err = TEST_FAIL;
struct machines machines = { 0 };
struct machine *machine;
struct evsel *evsel;
struct evlist *evlist = evlist__new();
size_t i;
test_fn_t testcases[] = {
test1,
test2,
test3,
test4,
test5,
};
TEST_ASSERT_VAL("No memory", evlist);
err = parse_event(evlist, "cpu-clock");
if (err)
goto out;
err = TEST_FAIL;
if (machines__init(&machines))
goto out;
/* setup threads/dso/map/symbols also */
machine = setup_fake_machine(&machines);
if (!machine)
goto out;
if (verbose > 1)
machine__fprintf(machine, stderr);
evsel = evlist__first(evlist);
for (i = 0; i < ARRAY_SIZE(testcases); i++) {
err = testcases[i](evsel, machine);
if (err < 0)
break;
}
out:
/* tear down everything */
evlist__delete(evlist);
machines__exit(&machines);
put_fake_samples();
return err;
}
DEFINE_SUITE("Sort output of hist entries", hists_output);
+362
View File
@@ -0,0 +1,362 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
#include "hwmon_pmu.h"
#include <errno.h>
#include <inttypes.h>
#include <fcntl.h>
#include <linux/compiler.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <sys/stat.h>
#include "debug.h"
#include "evlist.h"
#include "parse-events.h"
#include "pmus.h"
#include "tests.h"
static const struct test_event {
const char *name;
const char *alias;
union hwmon_pmu_event_key key;
} test_events[] = {
{
"temp_test_hwmon_event1",
"temp1",
.key = {
.num = 1,
.type = 10
},
},
{
"temp_test_hwmon_event2",
"temp2",
.key = {
.num = 2,
.type = 10
},
},
};
/* Cleanup test PMU directory. */
static int test_pmu_put(const char *dir, struct perf_pmu *hwm)
{
char buf[PATH_MAX + 20];
int ret;
if (scnprintf(buf, sizeof(buf), "rm -fr %s", dir) < 0) {
pr_err("Failure to set up buffer for \"%s\"\n", dir);
return -EINVAL;
}
ret = system(buf);
if (ret)
pr_err("Failure to \"%s\"\n", buf);
list_del(&hwm->list);
perf_pmu__delete(hwm);
return ret;
}
/*
* Prepare test PMU directory data, normally exported by kernel at
* /sys/class/hwmon/hwmon<number>/. Give as input a buffer to hold the file
* path, the result is PMU loaded using that directory.
*/
static struct perf_pmu *test_pmu_get(char *dir, size_t sz)
{
const char *test_hwmon_name_nl = "A test hwmon PMU\n";
const char *test_hwmon_name = "A test hwmon PMU";
/* Simulated hwmon items. */
const struct test_item {
const char *name;
const char *value;
} test_items[] = {
{ "temp1_label", "test hwmon event1\n", },
{ "temp1_input", "40000\n", },
{ "temp2_label", "test hwmon event2\n", },
{ "temp2_input", "50000\n", },
};
int hwmon_dirfd = -1, test_dirfd = -1, file;
struct perf_pmu *hwm = NULL;
ssize_t len;
/* Create equivalent of sysfs mount point. */
scnprintf(dir, sz, "/tmp/perf-hwmon-pmu-test-XXXXXX");
if (!mkdtemp(dir)) {
pr_err("mkdtemp failed\n");
dir[0] = '\0';
return NULL;
}
test_dirfd = open(dir, O_PATH|O_DIRECTORY);
if (test_dirfd < 0) {
pr_err("Failed to open test directory \"%s\"\n", dir);
goto err_out;
}
/* Create the test hwmon directory and give it a name. */
if (mkdirat(test_dirfd, "hwmon1234", 0755) < 0) {
pr_err("Failed to mkdir hwmon directory\n");
goto err_out;
}
strncat(dir, "/hwmon1234", sz - strlen(dir));
hwmon_dirfd = open(dir, O_PATH|O_DIRECTORY);
if (hwmon_dirfd < 0) {
pr_err("Failed to open test hwmon directory \"%s\"\n", dir);
goto err_out;
}
file = openat(hwmon_dirfd, "name", O_WRONLY | O_CREAT, 0600);
if (file < 0) {
pr_err("Failed to open for writing file \"name\"\n");
goto err_out;
}
len = strlen(test_hwmon_name_nl);
if (write(file, test_hwmon_name_nl, len) < len) {
close(file);
pr_err("Failed to write to 'name' file\n");
goto err_out;
}
close(file);
/* Create test hwmon files. */
for (size_t i = 0; i < ARRAY_SIZE(test_items); i++) {
const struct test_item *item = &test_items[i];
file = openat(hwmon_dirfd, item->name, O_WRONLY | O_CREAT, 0600);
if (file < 0) {
pr_err("Failed to open for writing file \"%s\"\n", item->name);
goto err_out;
}
if (write(file, item->value, strlen(item->value)) < 0) {
pr_err("Failed to write to file \"%s\"\n", item->name);
close(file);
goto err_out;
}
close(file);
}
/* Make the PMU reading the files created above. */
hwm = perf_pmus__add_test_hwmon_pmu(dir, "hwmon1234", test_hwmon_name);
if (!hwm)
pr_err("Test hwmon creation failed\n");
err_out:
if (!hwm) {
test_pmu_put(dir, hwm);
}
if (test_dirfd >= 0)
close(test_dirfd);
if (hwmon_dirfd >= 0)
close(hwmon_dirfd);
return hwm;
}
static int do_test(size_t i, bool with_pmu, bool with_alias)
{
const char *test_event = with_alias ? test_events[i].alias : test_events[i].name;
struct evlist *evlist = evlist__new();
struct evsel *evsel;
struct parse_events_error err;
int ret;
char str[128];
bool found = false;
if (!evlist) {
pr_err("evlist allocation failed\n");
return TEST_FAIL;
}
if (with_pmu)
snprintf(str, sizeof(str), "hwmon_a_test_hwmon_pmu/%s/", test_event);
else
strlcpy(str, test_event, sizeof(str));
pr_debug("Testing '%s'\n", str);
parse_events_error__init(&err);
ret = parse_events(evlist, str, &err);
if (ret) {
pr_debug("FAILED %s:%d failed to parse event '%s', err %d\n",
__FILE__, __LINE__, str, ret);
parse_events_error__print(&err, str);
ret = TEST_FAIL;
goto out;
}
ret = TEST_OK;
if (with_pmu ? (evlist->core.nr_entries != 1) : (evlist->core.nr_entries < 1)) {
pr_debug("FAILED %s:%d Unexpected number of events for '%s' of %d\n",
__FILE__, __LINE__, str, evlist->core.nr_entries);
ret = TEST_FAIL;
goto out;
}
evlist__for_each_entry(evlist, evsel) {
if (!evsel->pmu || !evsel->pmu->name ||
strcmp(evsel->pmu->name, "hwmon_a_test_hwmon_pmu"))
continue;
if (evsel->core.attr.config != (u64)test_events[i].key.type_and_num) {
pr_debug("FAILED %s:%d Unexpected config for '%s', %" PRIu64 " != %ld\n",
__FILE__, __LINE__, str,
(uint64_t)evsel->core.attr.config,
test_events[i].key.type_and_num);
ret = TEST_FAIL;
goto out;
}
found = true;
}
if (!found) {
pr_debug("FAILED %s:%d Didn't find hwmon event '%s' in parsed evsels\n",
__FILE__, __LINE__, str);
ret = TEST_FAIL;
}
out:
parse_events_error__exit(&err);
evlist__delete(evlist);
return ret;
}
static int test__hwmon_pmu(bool with_pmu)
{
char dir[PATH_MAX];
struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
int ret = TEST_OK;
if (!pmu)
return TEST_FAIL;
for (size_t i = 0; i < ARRAY_SIZE(test_events); i++) {
ret = do_test(i, with_pmu, /*with_alias=*/false);
if (ret != TEST_OK)
break;
ret = do_test(i, with_pmu, /*with_alias=*/true);
if (ret != TEST_OK)
break;
}
test_pmu_put(dir, pmu);
return ret;
}
static int test__hwmon_pmu_without_pmu(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return test__hwmon_pmu(/*with_pmu=*/false);
}
static int test__hwmon_pmu_with_pmu(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return test__hwmon_pmu(/*with_pmu=*/true);
}
static int test__parse_hwmon_filename(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
const struct hwmon_parse_test {
const char *filename;
enum hwmon_type type;
int number;
enum hwmon_item item;
bool alarm;
bool parse_ok;
} tests[] = {
{
.filename = "cpu0_accuracy",
.type = HWMON_TYPE_CPU,
.number = 0,
.item = HWMON_ITEM_ACCURACY,
.alarm = false,
.parse_ok = true,
},
{
.filename = "temp1_input",
.type = HWMON_TYPE_TEMP,
.number = 1,
.item = HWMON_ITEM_INPUT,
.alarm = false,
.parse_ok = true,
},
{
.filename = "fan2_vid",
.type = HWMON_TYPE_FAN,
.number = 2,
.item = HWMON_ITEM_VID,
.alarm = false,
.parse_ok = true,
},
{
.filename = "power3_crit_alarm",
.type = HWMON_TYPE_POWER,
.number = 3,
.item = HWMON_ITEM_CRIT,
.alarm = true,
.parse_ok = true,
},
{
.filename = "intrusion4_average_interval_min_alarm",
.type = HWMON_TYPE_INTRUSION,
.number = 4,
.item = HWMON_ITEM_AVERAGE_INTERVAL_MIN,
.alarm = true,
.parse_ok = true,
},
{
.filename = "badtype5_baditem",
.type = HWMON_TYPE_NONE,
.number = 5,
.item = HWMON_ITEM_NONE,
.alarm = false,
.parse_ok = false,
},
{
.filename = "humidity6_baditem",
.type = HWMON_TYPE_NONE,
.number = 6,
.item = HWMON_ITEM_NONE,
.alarm = false,
.parse_ok = false,
},
};
for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
enum hwmon_type type;
int number;
enum hwmon_item item;
bool alarm;
TEST_ASSERT_EQUAL("parse_hwmon_filename",
parse_hwmon_filename(
tests[i].filename,
&type,
&number,
&item,
&alarm),
tests[i].parse_ok
);
if (tests[i].parse_ok) {
TEST_ASSERT_EQUAL("parse_hwmon_filename type", type, tests[i].type);
TEST_ASSERT_EQUAL("parse_hwmon_filename number", number, tests[i].number);
TEST_ASSERT_EQUAL("parse_hwmon_filename item", item, tests[i].item);
TEST_ASSERT_EQUAL("parse_hwmon_filename alarm", alarm, tests[i].alarm);
}
}
return TEST_OK;
}
static struct test_case tests__hwmon_pmu[] = {
TEST_CASE("Basic parsing test", parse_hwmon_filename),
TEST_CASE("Parsing without PMU name", hwmon_pmu_without_pmu),
TEST_CASE("Parsing with PMU name", hwmon_pmu_with_pmu),
{ .name = NULL, }
};
struct test_suite suite__hwmon_pmu = {
.desc = "Hwmon PMU",
.test_cases = tests__hwmon_pmu,
};
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <linux/kernel.h>
#include "tests.h"
#include "debug.h"
#include "print_binary.h"
static int test__is_printable_array(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
char buf1[] = { 'k', 'r', 4, 'v', 'a', 0 };
char buf2[] = { 'k', 'r', 'a', 'v', 4, 0 };
struct {
char *buf;
unsigned int len;
int ret;
} t[] = {
{ (char *) "krava", sizeof("krava"), 1 },
{ (char *) "krava", sizeof("krava") - 1, 0 },
{ (char *) "", sizeof(""), 1 },
{ (char *) "", 0, 0 },
{ NULL, 0, 0 },
{ buf1, sizeof(buf1), 0 },
{ buf2, sizeof(buf2), 0 },
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(t); i++) {
int ret;
ret = is_printable_array((char *) t[i].buf, t[i].len);
if (ret != t[i].ret) {
pr_err("failed: test %u\n", i);
return TEST_FAIL;
}
}
return TEST_OK;
}
DEFINE_SUITE("is_printable_array", is_printable_array);
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include "util/dso.h"
#include "util/map.h"
#include "util/symbol.h"
#include "util/debug.h"
#include "util/machine.h"
#include "tests.h"
/*
* This test is to check whether a bad symbol in a module won't split kallsyms maps.
* The main_symbol[1-3] should belong to the main [kernel.kallsyms] map even if the
* bad_symbol from the module is found in the middle.
*/
static char root_template[] = "/tmp/perf-test.XXXXXX";
static char *root_dir;
static const char proc_version[] = "Linux version X.Y.Z (just for perf test)\n";
static const char proc_modules[] = "module 4096 1 - Live 0xffffffffcd000000\n";
static const char proc_kallsyms[] =
"ffffffffab200000 T _stext\n"
"ffffffffab200010 T good_symbol\n"
"ffffffffab200020 t bad_symbol\n"
"ffffffffab200030 t main_symbol1\n"
"ffffffffab200040 t main_symbol2\n"
"ffffffffab200050 t main_symbol3\n"
"ffffffffab200060 T _etext\n"
"ffffffffcd000000 T start_module\t[module]\n"
"ffffffffab200020 u bad_symbol\t[module]\n"
"ffffffffcd000040 T end_module\t[module]\n";
static struct {
const char *name;
const char *contents;
long len;
} proc_files[] = {
{ "version", proc_version, sizeof(proc_version) - 1 },
{ "modules", proc_modules, sizeof(proc_modules) - 1 },
{ "kallsyms", proc_kallsyms, sizeof(proc_kallsyms) - 1 },
};
static void remove_proc_dir(int sig __maybe_unused)
{
char buf[128];
if (root_dir == NULL)
return;
for (unsigned i = 0; i < ARRAY_SIZE(proc_files); i++) {
scnprintf(buf, sizeof(buf), "%s/proc/%s", root_dir, proc_files[i].name);
remove(buf);
}
scnprintf(buf, sizeof(buf), "%s/proc", root_dir);
rmdir(buf);
rmdir(root_dir);
root_dir = NULL;
}
static int create_proc_dir(void)
{
char buf[128];
root_dir = mkdtemp(root_template);
if (root_dir == NULL)
return -1;
scnprintf(buf, sizeof(buf), "%s/proc", root_dir);
if (mkdir(buf, 0700) < 0)
goto err;
for (unsigned i = 0; i < ARRAY_SIZE(proc_files); i++) {
int fd, len;
scnprintf(buf, sizeof(buf), "%s/proc/%s", root_dir, proc_files[i].name);
fd = open(buf, O_RDWR | O_CREAT, 0600);
if (fd < 0)
goto err;
len = write(fd, proc_files[i].contents, proc_files[i].len);
close(fd);
if (len != proc_files[i].len)
goto err;
}
return 0;
err:
remove_proc_dir(0);
return -1;
}
static int test__kallsyms_split(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct machine m = { 0 };
struct map *map = NULL;
int ret = TEST_FAIL;
pr_debug("try to create fake root directory\n");
if (create_proc_dir() < 0) {
pr_debug("SKIP: cannot create a fake root directory\n");
return TEST_SKIP;
}
signal(SIGINT, remove_proc_dir);
signal(SIGPIPE, remove_proc_dir);
signal(SIGSEGV, remove_proc_dir);
signal(SIGTERM, remove_proc_dir);
pr_debug("create kernel maps from the fake root directory\n");
if (machine__init(&m, root_dir, HOST_KERNEL_ID)) {
pr_debug("FAIL: failed to init machine\n");
goto out;
}
if (machine__create_kernel_maps(&m) < 0) {
pr_debug("FAIL: failed to create kernel maps\n");
goto out;
}
/* force to use /proc/kallsyms */
symbol_conf.ignore_vmlinux = true;
symbol_conf.ignore_vmlinux_buildid = true;
symbol_conf.allow_aliases = true;
if (map__load(machine__kernel_map(&m)) < 0) {
pr_debug("FAIL: failed to load kallsyms\n");
goto out;
}
pr_debug("kernel map loaded - check symbol and map\n");
if (maps__nr_maps(machine__kernel_maps(&m)) != 2) {
pr_debug("FAIL: it should have the kernel and a module, but has %u maps\n",
maps__nr_maps(machine__kernel_maps(&m)));
goto out;
}
if (machine__find_kernel_symbol_by_name(&m, "main_symbol3", &map) == NULL) {
pr_debug("FAIL: failed to find a symbol\n");
goto out;
}
if (!RC_CHK_EQUAL(map, machine__kernel_map(&m))) {
pr_debug("FAIL: the symbol is not in the kernel map\n");
goto out;
}
ret = TEST_OK;
out:
map__put(map);
remove_proc_dir(0);
machine__exit(&m);
return ret;
}
DEFINE_SUITE("split kallsyms", kallsyms_split);
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/types.h>
#include <limits.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <perf/cpumap.h>
#include <perf/evlist.h>
#include <perf/mmap.h>
#include "debug.h"
#include "parse-events.h"
#include "evlist.h"
#include "evsel.h"
#include "record.h"
#include "thread_map.h"
#include "tests.h"
#include "util/mmap.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 evlist *evlist, const char *comm)
{
union perf_event *event;
struct mmap *md;
int i, found;
found = 0;
for (i = 0; i < evlist->core.nr_mmaps; i++) {
md = &evlist->mmap[i];
if (perf_mmap__read_init(&md->core) < 0)
continue;
while ((event = perf_mmap__read_event(&md->core)) != NULL) {
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_mmap__consume(&md->core);
}
perf_mmap__read_done(&md->core);
}
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.
*/
static int test__keep_tracking(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct record_opts opts = {
.mmap_pages = UINT_MAX,
.user_freq = UINT_MAX,
.user_interval = ULLONG_MAX,
.target = {
.uses_mmap = true,
},
};
struct perf_thread_map *threads = NULL;
struct perf_cpu_map *cpus = NULL;
struct evlist *evlist = NULL;
struct evsel *evsel = NULL;
int found, err = -1;
const char *comm;
threads = thread_map__new_by_tid(getpid());
CHECK_NOT_NULL__(threads);
cpus = perf_cpu_map__new_online_cpus();
CHECK_NOT_NULL__(cpus);
evlist = evlist__new();
CHECK_NOT_NULL__(evlist);
perf_evlist__set_maps(&evlist->core, cpus, threads);
CHECK__(parse_event(evlist, "dummy:u"));
CHECK__(parse_event(evlist, "cpu-cycles:u"));
evlist__config(evlist, &opts, NULL);
evsel = evlist__first(evlist);
evsel->core.attr.comm = 1;
evsel->core.attr.disabled = 1;
evsel->core.attr.enable_on_exec = 0;
if (evlist__open(evlist) < 0) {
pr_debug("Unable to open dummy and cycles event\n");
err = TEST_SKIP;
goto out_err;
}
CHECK__(evlist__mmap(evlist, UINT_MAX));
/*
* First, test that a 'comm' event can be found when the event is
* enabled.
*/
evlist__enable(evlist);
comm = "Test COMM 1";
CHECK__(prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0));
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.
*/
evlist__enable(evlist);
evsel = evlist__last(evlist);
CHECK__(evsel__disable(evsel));
comm = "Test COMM 2";
CHECK__(prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0));
evlist__disable(evlist);
found = find_comm(evlist, comm);
if (found != 1) {
pr_debug("Second time, failed to find tracking event.\n");
goto out_err;
}
err = 0;
out_err:
if (evlist) {
evlist__disable(evlist);
evlist__delete(evlist);
}
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
return err;
}
DEFINE_SUITE("Use a dummy software event to keep tracking", keep_tracking);
+163
View File
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "tests.h"
#include "dso.h"
#include "debug.h"
#include "event.h"
static int test(const char *path, bool alloc_name, bool kmod,
int comp, const char *name)
{
struct kmod_path m;
memset(&m, 0x0, sizeof(m));
TEST_ASSERT_VAL("kmod_path__parse",
!__kmod_path__parse(&m, path, alloc_name));
pr_debug("%s - alloc name %d, kmod %d, comp %d, name '%s'\n",
path, alloc_name, m.kmod, m.comp, m.name);
TEST_ASSERT_VAL("wrong kmod", m.kmod == kmod);
TEST_ASSERT_VAL("wrong comp", m.comp == comp);
if (name)
TEST_ASSERT_VAL("wrong name", m.name && !strcmp(name, m.name));
else
TEST_ASSERT_VAL("wrong name", !m.name);
free(m.name);
return 0;
}
static int test_is_kernel_module(const char *path, int cpumode, bool expect)
{
TEST_ASSERT_VAL("is_kernel_module",
(!!is_kernel_module(path, cpumode)) == (!!expect));
pr_debug("%s (cpumode: %d) - is_kernel_module: %s\n",
path, cpumode, expect ? "true" : "false");
return 0;
}
#define T(path, an, k, c, n) \
TEST_ASSERT_VAL("failed", !test(path, an, k, c, n))
#define M(path, c, e) \
TEST_ASSERT_VAL("failed", !test_is_kernel_module(path, c, e))
static int test__kmod_path__parse(struct test_suite *t __maybe_unused, int subtest __maybe_unused)
{
/* path alloc_name kmod comp name */
T("/xxxx/xxxx/x-x.ko", true , true, 0 , "[x_x]");
T("/xxxx/xxxx/x-x.ko", false , true, 0 , NULL );
T("/xxxx/xxxx/x-x.ko", true , true, 0 , "[x_x]");
T("/xxxx/xxxx/x-x.ko", false , true, 0 , NULL );
M("/xxxx/xxxx/x-x.ko", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true);
M("/xxxx/xxxx/x-x.ko", PERF_RECORD_MISC_KERNEL, true);
M("/xxxx/xxxx/x-x.ko", PERF_RECORD_MISC_USER, false);
#ifdef HAVE_ZLIB_SUPPORT
/* path alloc_name kmod comp name */
T("/xxxx/xxxx/x.ko.gz", true , true, 1 , "[x]");
T("/xxxx/xxxx/x.ko.gz", false , true, 1 , NULL );
T("/xxxx/xxxx/x.ko.gz", true , true, 1 , "[x]");
T("/xxxx/xxxx/x.ko.gz", false , true, 1 , NULL );
M("/xxxx/xxxx/x.ko.gz", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true);
M("/xxxx/xxxx/x.ko.gz", PERF_RECORD_MISC_KERNEL, true);
M("/xxxx/xxxx/x.ko.gz", PERF_RECORD_MISC_USER, false);
/* path alloc_name kmod comp name */
T("/xxxx/xxxx/x.gz", true , false, 1 , "x.gz");
T("/xxxx/xxxx/x.gz", false , false, 1 , NULL );
T("/xxxx/xxxx/x.gz", true , false, 1 , "x.gz");
T("/xxxx/xxxx/x.gz", false , false, 1 , NULL );
M("/xxxx/xxxx/x.gz", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false);
M("/xxxx/xxxx/x.gz", PERF_RECORD_MISC_KERNEL, false);
M("/xxxx/xxxx/x.gz", PERF_RECORD_MISC_USER, false);
/* path alloc_name kmod comp name */
T("x.gz", true , false, 1 , "x.gz");
T("x.gz", false , false, 1 , NULL );
T("x.gz", true , false, 1 , "x.gz");
T("x.gz", false , false, 1 , NULL );
M("x.gz", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false);
M("x.gz", PERF_RECORD_MISC_KERNEL, false);
M("x.gz", PERF_RECORD_MISC_USER, false);
/* path alloc_name kmod comp name */
T("x.ko.gz", true , true, 1 , "[x]");
T("x.ko.gz", false , true, 1 , NULL );
T("x.ko.gz", true , true, 1 , "[x]");
T("x.ko.gz", false , true, 1 , NULL );
M("x.ko.gz", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true);
M("x.ko.gz", PERF_RECORD_MISC_KERNEL, true);
M("x.ko.gz", PERF_RECORD_MISC_USER, false);
#endif
/* path alloc_name kmod comp name */
T("[test_module]", true , true, false, "[test_module]");
T("[test_module]", false , true, false, NULL );
T("[test_module]", true , true, false, "[test_module]");
T("[test_module]", false , true, false, NULL );
M("[test_module]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true);
M("[test_module]", PERF_RECORD_MISC_KERNEL, true);
M("[test_module]", PERF_RECORD_MISC_USER, false);
/* path alloc_name kmod comp name */
T("[test.module]", true , true, false, "[test.module]");
T("[test.module]", false , true, false, NULL );
T("[test.module]", true , true, false, "[test.module]");
T("[test.module]", false , true, false, NULL );
M("[test.module]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, true);
M("[test.module]", PERF_RECORD_MISC_KERNEL, true);
M("[test.module]", PERF_RECORD_MISC_USER, false);
/* path alloc_name kmod comp name */
T("[vdso]", true , false, false, "[vdso]");
T("[vdso]", false , false, false, NULL );
T("[vdso]", true , false, false, "[vdso]");
T("[vdso]", false , false, false, NULL );
M("[vdso]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false);
M("[vdso]", PERF_RECORD_MISC_KERNEL, false);
M("[vdso]", PERF_RECORD_MISC_USER, false);
T("[vdso32]", true , false, false, "[vdso32]");
T("[vdso32]", false , false, false, NULL );
T("[vdso32]", true , false, false, "[vdso32]");
T("[vdso32]", false , false, false, NULL );
M("[vdso32]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false);
M("[vdso32]", PERF_RECORD_MISC_KERNEL, false);
M("[vdso32]", PERF_RECORD_MISC_USER, false);
T("[vdsox32]", true , false, false, "[vdsox32]");
T("[vdsox32]", false , false, false, NULL );
T("[vdsox32]", true , false, false, "[vdsox32]");
T("[vdsox32]", false , false, false, NULL );
M("[vdsox32]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false);
M("[vdsox32]", PERF_RECORD_MISC_KERNEL, false);
M("[vdsox32]", PERF_RECORD_MISC_USER, false);
/* path alloc_name kmod comp name */
T("[vsyscall]", true , false, false, "[vsyscall]");
T("[vsyscall]", false , false, false, NULL );
T("[vsyscall]", true , false, false, "[vsyscall]");
T("[vsyscall]", false , false, false, NULL );
M("[vsyscall]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false);
M("[vsyscall]", PERF_RECORD_MISC_KERNEL, false);
M("[vsyscall]", PERF_RECORD_MISC_USER, false);
/* path alloc_name kmod comp name */
T("[kernel.kallsyms]", true , false, false, "[kernel.kallsyms]");
T("[kernel.kallsyms]", false , false, false, NULL );
T("[kernel.kallsyms]", true , false, false, "[kernel.kallsyms]");
T("[kernel.kallsyms]", false , false, false, NULL );
M("[kernel.kallsyms]", PERF_RECORD_MISC_CPUMODE_UNKNOWN, false);
M("[kernel.kallsyms]", PERF_RECORD_MISC_KERNEL, false);
M("[kernel.kallsyms]", PERF_RECORD_MISC_USER, false);
return 0;
}
DEFINE_SUITE("kmod_path__parse", kmod_path__parse);
+418
View File
@@ -0,0 +1,418 @@
include ../scripts/Makefile.include
ifndef MK
ifeq ($(MAKECMDGOALS),)
# no target specified, trigger the whole suite
all:
@echo "Testing Makefile"; $(MAKE) -sf tests/make MK=Makefile
@echo "Testing Makefile.perf"; $(MAKE) -sf tests/make MK=Makefile.perf SET_PARALLEL=1 SET_O=1
else
# run only specific test over 'Makefile'
%:
@echo "Testing Makefile"; $(MAKE) -sf tests/make MK=Makefile $@
endif
else
PERF := .
PERF_O := $(PERF)
O_OPT :=
FULL_O := $(shell readlink -f $(PERF_O) || echo $(PERF_O))
ifneq ($(O),)
FULL_O := $(shell readlink -f $(O) || echo $(O))
PERF_O := $(FULL_O)
ifeq ($(SET_O),1)
O_OPT := 'O=$(FULL_O)'
endif
K_O_OPT := 'O=$(FULL_O)'
endif
PARALLEL_OPT=
ifeq ($(SET_PARALLEL),1)
ifeq ($(JOBS),)
cores := $(shell (getconf _NPROCESSORS_ONLN || grep -E -c '^processor|^CPU[0-9]' /proc/cpuinfo) 2>/dev/null)
ifeq ($(cores),0)
cores := 1
endif
else
cores=$(JOBS)
endif
PARALLEL_OPT="-j$(cores)"
endif
# As per kernel Makefile, avoid funny character set dependencies
unexport LC_ALL
LC_COLLATE=C
LC_NUMERIC=C
export LC_COLLATE LC_NUMERIC
ifeq ($(srctree),)
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
#$(info Determined 'srctree' to be $(srctree))
endif
include $(srctree)/tools/scripts/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)
python_perf_so := $(shell $(MAKE) python_perf_target|grep "Target is:"|awk '{print $$3}')
# standard single make variable specified
make_clean_all := clean all
make_python_perf_so := $(python_perf_so)
make_debug := DEBUG=1
make_nondistro := BUILD_NONDISTRO=1
make_extra_tests := EXTRA_TESTS=1
make_no_jevents := NO_JEVENTS=1
make_jevents_all := JEVENTS_ARCH=all
make_no_bpf_skel := BUILD_BPF_SKEL=0
make_gen_vmlinux_h := GEN_VMLINUX_H=1
make_libperl := LIBPERL=1
make_no_libpython := NO_LIBPYTHON=1
make_no_scripts := NO_LIBPYTHON=1
make_no_slang := NO_SLANG=1
make_no_demangle := NO_DEMANGLE=1
make_no_libelf := NO_LIBELF=1
make_no_libdw := NO_LIBDW=1
make_libunwind := LIBUNWIND=1
make_no_backtrace := NO_BACKTRACE=1
make_no_libcapstone := NO_CAPSTONE=1
make_libcapstone_dlopen := LIBCAPSTONE_DLOPEN=1
make_no_libnuma := NO_LIBNUMA=1
make_no_libbionic := NO_LIBBIONIC=1
make_no_libbpf := NO_LIBBPF=1
make_libbpf_dynamic := LIBBPF_DYNAMIC=1
make_no_libbpf_DEBUG := NO_LIBBPF=1 DEBUG=1
make_no_libllvm := NO_LIBLLVM=1
make_no_babeltrace2 := NO_BABELTRACE2=1
make_with_coresight := CORESIGHT=1
make_no_sdt := NO_SDT=1
make_no_libpfm4 := NO_LIBPFM4=1
make_with_gtk2 := GTK2=1
make_refcnt_check := EXTRA_CFLAGS="-DREFCNT_CHECKING=1"
make_tags := tags
make_cscope := cscope
make_help := help
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_install_prefix := install prefix=/tmp/krava
make_install_prefix_slash := install prefix=/tmp/krava/
make_static := LDFLAGS=-static NO_PERF_READ_VDSO32=1 NO_PERF_READ_VDSOX32=1 NO_JVMTI=1 NO_LIBTRACEEVENT=1 NO_LIBELF=1
# all the NO_* variable combined
make_minimal := NO_LIBPYTHON=1
make_minimal += NO_DEMANGLE=1 NO_LIBELF=1 NO_BACKTRACE=1
make_minimal += NO_LIBNUMA=1 NO_LIBBIONIC=1 NO_LIBDW=1
make_minimal += NO_LIBBPF=1
make_minimal += NO_SDT=1 NO_JVMTI=1 NO_LIBZSTD=1
make_minimal += NO_CAPSTONE=1
# binutils 2_42 and newer have bfd_thread_init()
new_libbfd := $(shell echo '#include <bfd.h>' | $(CC) -E -x c - | grep bfd_thread_init)
# $(run) contains all available tests
run := make_pure
# Targets 'clean all' can be run together only through top level
# Makefile because we detect clean target in Makefile.perf and
# disable features detection
ifeq ($(MK),Makefile)
run += make_clean_all
MAKE_F := $(MAKE)
else
MAKE_F := $(MAKE) -f $(MK)
endif
run += make_python_perf_so
run += make_debug
ifneq ($(new_libbfd),)
run += make_nondistro
endif
run += make_extra_tests
run += make_no_jevents
run += make_jevents_all
run += make_no_bpf_skel
run += make_gen_vmlinux_h
run += make_libperl
run += make_no_libpython
run += make_no_scripts
run += make_no_slang
run += make_no_demangle
run += make_no_libelf
run += make_no_libdw
run += make_libunwind
run += make_no_libdw_dwarf_unwind
run += make_no_backtrace
run += make_no_libcapstone
run += make_libcapstone_dlopen
run += make_no_libnuma
run += make_no_libbionic
run += make_no_libbpf
run += make_no_libbpf_DEBUG
run += make_no_libllvm
run += make_no_sdt
run += make_no_syscall_tbl
run += make_no_babeltrace2
run += make_with_coresight
run += make_with_clangllvm
run += make_no_libpfm4
run += make_refcnt_check
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
run += make_install_prefix
run += make_install_prefix_slash
# 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
old_libbpf := $(shell echo '\#include <bpf/libbpf.h>' | $(CC) -E -dM -x c -| grep -q -E "define[[:space:]]+LIBBPF_MAJOR_VERSION[[:space:]]+0{1}")
ifneq ($(old_libbpf),)
run += make_libbpf_dynamic
endif
ifneq ($(call has,ctags),)
run += make_tags
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_O)/$(python_perf_so)
test_make_perf_o := test -f $(PERF_O)/perf.o
test_make_util_map_o := test -f $(PERF_O)/util/map.o
test_make_util_pmu_bison_o := test -f $(PERF_O)/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_all := $(installed_files_bin)
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))
# We prefix all installed files for make_install_prefix(_slash)
# with '/tmp/krava' to match installed/prefix-ed files.
installed_files_all_prefix := $(addprefix /tmp/krava/,$(installed_files_all))
test_make_install_prefix := $(call test_dest_files,$(installed_files_all_prefix))
test_make_install_prefix_O := $(call test_dest_files,$(installed_files_all_prefix))
test_make_install_prefix_slash := $(test_make_install_prefix)
test_make_install_prefix_slash_O := $(test_make_install_prefix_O)
# 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_libbpf_dynamic := ldd $(PERF_O)/perf | grep -q libbpf
test_make_libbpf_dynamic_O := ldd $$TMP_O/perf | grep -q libbpf
test_make_python_perf_so_O := test -f $$TMP_O/python/perf.so
test_make_perf_o_O := test -f $$TMP_O/perf.o
test_make_util_map_o_O := test -f $$TMP_O/util/map.o
test_make_util_pmu_bison_o_O := test -f $$TMP_O/util/pmu-bison.o
test_default = test -x $(PERF_O)/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 SHUF
run := $(shell shuf -e $(run))
run_O := $(shell shuf -e $(run_O))
endif
max_width := $(shell echo $(run_O) | sed 's/ /\n/g' | wc -L)
ifdef DEBUG
d := $(info run $(run))
d := $(info run_O $(run_O))
endif
MAKEFLAGS := --no-print-directory
clean := @(cd $(PERF); $(MAKE_F) -s $(O_OPT) clean >/dev/null && $(MAKE) -s $(O_OPT) -C ../build clean >/dev/null)
$(run):
$(call clean)
@TMP_DEST=$$(mktemp -d); \
cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \
printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \
( eval $$cmd ) >> $@ 2>&1; \
echo " test: $(call test,$@)" >> $@ 2>&1; \
$(call test,$@) && \
rm -rf $@ $$TMP_DEST || (cat $@ ; false)
make_with_gtk2:
$(call clean)
@TMP_DEST=$$(mktemp -d); \
cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \
printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \
( eval $$cmd ) >> $@ 2>&1; \
echo " test: $(call test,$@)" >> $@ 2>&1; \
$(call test,$@) && \
rm -rf $@ $$TMP_DEST || (cat $@ ; false)
make_static:
$(call clean)
@TMP_DEST=$$(mktemp -d); \
cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \
printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \
( eval $$cmd ) >> $@ 2>&1; \
echo " test: $(call test,$@)" >> $@ 2>&1; \
$(call test,$@) && \
rm -rf $@ $$TMP_DEST || (cat $@ ; false)
$(run_O):
$(call clean)
@TMP_O=$$(mktemp -d); \
TMP_DEST=$$(mktemp -d); \
cmd="cd $(PERF) && $(MAKE_F) $($(patsubst %_O,%,$@)) $(PARALLEL_OPT) O=$$TMP_O DESTDIR=$$TMP_DEST"; \
printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$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 && \
rm -f $@
KERNEL_O := ../..
ifneq ($(O),)
KERNEL_O := $(O)
endif
make_kernelsrc:
@echo "- make -C <kernelsrc> $(PARALLEL_OPT) $(K_O_OPT) tools/perf"
$(call clean); \
(make -C ../.. $(PARALLEL_OPT) $(K_O_OPT) tools/perf) > $@ 2>&1 && \
test -x $(KERNEL_O)/tools/perf/perf && rm -f $@ || (cat $@ ; false)
make_kernelsrc_tools:
@echo "- make -C <kernelsrc>/tools $(PARALLEL_OPT) $(K_O_OPT) perf"
$(call clean); \
(make -C ../../tools $(PARALLEL_OPT) $(K_O_OPT) perf) > $@ 2>&1 && \
test -x $(KERNEL_O)/tools/perf/perf && rm -f $@ || (cat $@ ; false)
make_libperf:
@echo "- make -C lib";
make -C lib clean >$@ 2>&1; make -C lib >>$@ 2>&1 && rm $@
FEATURES_DUMP_FILE := $(FULL_O)/BUILD_TEST_FEATURE_DUMP
FEATURES_DUMP_FILE_STATIC := $(FULL_O)/BUILD_TEST_FEATURE_DUMP_STATIC
all: $(run) $(run_O) tarpkg make_kernelsrc make_kernelsrc_tools
@echo OK
@rm -f $(FEATURES_DUMP_FILE) $(FEATURES_DUMP_FILE_STATIC)
out: $(run_O)
@echo OK
@rm -f $(FEATURES_DUMP_FILE) $(FEATURES_DUMP_FILE_STATIC)
ifeq ($(REUSE_FEATURES_DUMP),1)
$(FEATURES_DUMP_FILE):
$(call clean)
@cmd="cd $(PERF) && make FEATURE_DUMP_COPY=$@ $(O_OPT) feature-dump"; \
echo "- $@: $$cmd" && echo $$cmd && \
( eval $$cmd ) > /dev/null 2>&1
$(FEATURES_DUMP_FILE_STATIC):
$(call clean)
@cmd="cd $(PERF) && make FEATURE_DUMP_COPY=$@ $(O_OPT) LDFLAGS='-static' feature-dump"; \
echo "- $@: $$cmd" && echo $$cmd && \
( eval $$cmd ) > /dev/null 2>&1
# Add feature dump dependency for run/run_O targets
$(foreach t,$(run) $(run_O),$(eval \
$(t): $(if $(findstring make_static,$(t)),\
$(FEATURES_DUMP_FILE_STATIC),\
$(FEATURES_DUMP_FILE))))
# Append 'FEATURES_DUMP=' option to all test cases. For example:
# make_no_libbpf: NO_LIBBPF=1 --> NO_LIBBPF=1 FEATURES_DUMP=/a/b/BUILD_TEST_FEATURE_DUMP
# make_static: LDFLAGS=-static --> LDFLAGS=-static FEATURES_DUMP=/a/b/BUILD_TEST_FEATURE_DUMP_STATIC
$(foreach t,$(run),$(if $(findstring make_static,$(t)),\
$(eval $(t) := $($(t)) FEATURES_DUMP=$(FEATURES_DUMP_FILE_STATIC)),\
$(eval $(t) := $($(t)) FEATURES_DUMP=$(FEATURES_DUMP_FILE))))
endif
.PHONY: all $(run) $(run_O) tarpkg clean make_kernelsrc make_kernelsrc_tools make_libperf
endif # ifndef MK
+245
View File
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0
#include <inttypes.h>
#include <linux/compiler.h>
#include <linux/kernel.h>
#include "tests.h"
#include "map.h"
#include "maps.h"
#include "dso.h"
#include "debug.h"
struct map_def {
const char *name;
u64 start;
u64 end;
};
struct check_maps_cb_args {
struct map_def *merged;
unsigned int i;
};
static int check_maps_cb(struct map *map, void *data)
{
struct check_maps_cb_args *args = data;
struct map_def *merged = &args->merged[args->i];
if (map__start(map) != merged->start ||
map__end(map) != merged->end ||
strcmp(dso__name(map__dso(map)), merged->name) ||
refcount_read(map__refcnt(map)) != 1) {
return 1;
}
args->i++;
return 0;
}
static int failed_cb(struct map *map, void *data __maybe_unused)
{
pr_debug("\tstart: %" PRIu64 " end: %" PRIu64 " name: '%s' refcnt: %d\n",
map__start(map),
map__end(map),
dso__name(map__dso(map)),
refcount_read(map__refcnt(map)));
return 0;
}
static int check_maps(struct map_def *merged, unsigned int size, struct maps *maps)
{
bool failed = false;
if (maps__nr_maps(maps) != size) {
pr_debug("Expected %d maps, got %d", size, maps__nr_maps(maps));
failed = true;
} else {
struct check_maps_cb_args args = {
.merged = merged,
.i = 0,
};
failed = maps__for_each_map(maps, check_maps_cb, &args);
}
if (failed) {
pr_debug("Expected:\n");
for (unsigned int i = 0; i < size; i++) {
pr_debug("\tstart: %" PRIu64 " end: %" PRIu64 " name: '%s' refcnt: 1\n",
merged[i].start, merged[i].end, merged[i].name);
}
pr_debug("Got:\n");
maps__for_each_map(maps, failed_cb, NULL);
}
return failed ? TEST_FAIL : TEST_OK;
}
static int test__maps__merge_in(struct test_suite *t __maybe_unused, int subtest __maybe_unused)
{
unsigned int i;
struct map_def bpf_progs[] = {
{ "bpf_prog_1", 200, 300 },
{ "bpf_prog_2", 500, 600 },
{ "bpf_prog_3", 800, 900 },
};
struct map_def merged12[] = {
{ "kcore1", 100, 200 },
{ "bpf_prog_1", 200, 300 },
{ "kcore1", 300, 500 },
{ "bpf_prog_2", 500, 600 },
{ "kcore1", 600, 800 },
{ "bpf_prog_3", 800, 900 },
{ "kcore1", 900, 1000 },
};
struct map_def merged3[] = {
{ "kcore1", 100, 200 },
{ "bpf_prog_1", 200, 300 },
{ "kcore1", 300, 500 },
{ "bpf_prog_2", 500, 600 },
{ "kcore1", 600, 800 },
{ "bpf_prog_3", 800, 900 },
{ "kcore1", 900, 1000 },
{ "kcore3", 1000, 1100 },
};
struct map *map_kcore1, *map_kcore2, *map_kcore3;
int ret;
struct maps *maps = maps__new(NULL);
TEST_ASSERT_VAL("failed to create maps", maps);
for (i = 0; i < ARRAY_SIZE(bpf_progs); i++) {
struct map *map;
map = dso__new_map(bpf_progs[i].name);
TEST_ASSERT_VAL("failed to create map", map);
map__set_start(map, bpf_progs[i].start);
map__set_end(map, bpf_progs[i].end);
TEST_ASSERT_VAL("failed to insert map", maps__insert(maps, map) == 0);
map__put(map);
}
map_kcore1 = dso__new_map("kcore1");
TEST_ASSERT_VAL("failed to create map", map_kcore1);
map_kcore2 = dso__new_map("kcore2");
TEST_ASSERT_VAL("failed to create map", map_kcore2);
map_kcore3 = dso__new_map("kcore3");
TEST_ASSERT_VAL("failed to create map", map_kcore3);
/* kcore1 map overlaps over all bpf maps */
map__set_start(map_kcore1, 100);
map__set_end(map_kcore1, 1000);
/* kcore2 map hides behind bpf_prog_2 */
map__set_start(map_kcore2, 550);
map__set_end(map_kcore2, 570);
/* kcore3 map hides behind bpf_prog_3, kcore1 and adds new map */
map__set_start(map_kcore3, 880);
map__set_end(map_kcore3, 1100);
ret = maps__merge_in(maps, map_kcore1);
TEST_ASSERT_VAL("failed to merge map", !ret);
ret = check_maps(merged12, ARRAY_SIZE(merged12), maps);
TEST_ASSERT_VAL("merge check failed", !ret);
ret = maps__merge_in(maps, map_kcore2);
TEST_ASSERT_VAL("failed to merge map", !ret);
ret = check_maps(merged12, ARRAY_SIZE(merged12), maps);
TEST_ASSERT_VAL("merge check failed", !ret);
ret = maps__merge_in(maps, map_kcore3);
TEST_ASSERT_VAL("failed to merge map", !ret);
ret = check_maps(merged3, ARRAY_SIZE(merged3), maps);
TEST_ASSERT_VAL("merge check failed", !ret);
maps__zput(maps);
map__zput(map_kcore1);
map__zput(map_kcore2);
map__zput(map_kcore3);
return TEST_OK;
}
static int test__maps__fixup_overlap_and_insert(struct test_suite *t __maybe_unused,
int subtest __maybe_unused)
{
struct map_def initial_maps[] = {
{ "target_map", 1000, 2000 },
{ "next_map", 3000, 4000 },
};
struct map_def insert_split = { "split_map", 1400, 1600 };
struct map_def expected_after_split[] = {
{ "target_map", 1000, 1400 },
{ "split_map", 1400, 1600 },
{ "target_map", 1600, 2000 },
{ "next_map", 3000, 4000 },
};
struct map_def insert_eclipse = { "eclipse_map", 2500, 4500 };
struct map_def expected_final[] = {
{ "target_map", 1000, 1400 },
{ "split_map", 1400, 1600 },
{ "target_map", 1600, 2000 },
{ "eclipse_map", 2500, 4500 },
/* "next_map" (3000-4000) is removed */
};
struct map *map_split, *map_eclipse;
int ret;
unsigned int i;
struct maps *maps = maps__new(NULL);
TEST_ASSERT_VAL("failed to create maps", maps);
for (i = 0; i < ARRAY_SIZE(initial_maps); i++) {
struct map *map = dso__new_map(initial_maps[i].name);
TEST_ASSERT_VAL("failed to create map", map);
map__set_start(map, initial_maps[i].start);
map__set_end(map, initial_maps[i].end);
TEST_ASSERT_VAL("failed to insert map", maps__insert(maps, map) == 0);
map__put(map);
}
// Check splitting.
map_split = dso__new_map(insert_split.name);
TEST_ASSERT_VAL("failed to create split map", map_split);
map__set_start(map_split, insert_split.start);
map__set_end(map_split, insert_split.end);
ret = maps__fixup_overlap_and_insert(maps, map_split);
TEST_ASSERT_VAL("failed to fixup and insert split map", !ret);
map__zput(map_split);
ret = check_maps(expected_after_split, ARRAY_SIZE(expected_after_split), maps);
TEST_ASSERT_VAL("split check failed", !ret);
// Check cover 1 map with another.
map_eclipse = dso__new_map(insert_eclipse.name);
TEST_ASSERT_VAL("failed to create eclipse map", map_eclipse);
map__set_start(map_eclipse, insert_eclipse.start);
map__set_end(map_eclipse, insert_eclipse.end);
ret = maps__fixup_overlap_and_insert(maps, map_eclipse);
TEST_ASSERT_VAL("failed to fixup and insert eclipse map", !ret);
map__zput(map_eclipse);
ret = check_maps(expected_final, ARRAY_SIZE(expected_final), maps);
TEST_ASSERT_VAL("eclipse check failed", !ret);
maps__zput(maps);
return TEST_OK;
}
static struct test_case tests__maps[] = {
TEST_CASE("Test merge_in interface", maps__merge_in),
TEST_CASE("Test fix up overlap interface", maps__fixup_overlap_and_insert),
{ .name = NULL, }
};
struct test_suite suite__maps = {
.desc = "Maps - per process mmaps abstraction",
.test_cases = tests__maps,
};
+63
View File
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-2.0
#include "util/map_symbol.h"
#include "util/mem-events.h"
#include "util/mem-info.h"
#include "util/symbol.h"
#include "linux/perf_event.h"
#include "util/debug.h"
#include "tests.h"
#include <string.h>
static int check(union perf_mem_data_src data_src,
const char *string)
{
char out[100];
char failure[100];
struct mem_info *mi = mem_info__new();
int n;
TEST_ASSERT_VAL("Memory allocation failed", mi);
*mem_info__data_src(mi) = data_src;
n = perf_mem__snp_scnprintf(out, sizeof out, mi);
n += perf_mem__lvl_scnprintf(out + n, sizeof out - n, mi);
mem_info__put(mi);
scnprintf(failure, sizeof failure, "unexpected %s", out);
TEST_ASSERT_VAL(failure, !strcmp(string, out));
return 0;
}
static int test__mem(struct test_suite *text __maybe_unused, int subtest __maybe_unused)
{
int ret = 0;
union perf_mem_data_src src;
memset(&src, 0, sizeof(src));
src.mem_lvl = PERF_MEM_LVL_HIT;
src.mem_lvl_num = 4;
ret |= check(src, "N/AL4 hit");
src.mem_remote = 1;
ret |= check(src, "N/ARemote L4 hit");
src.mem_lvl = PERF_MEM_LVL_MISS;
src.mem_lvl_num = PERF_MEM_LVLNUM_PMEM;
src.mem_remote = 0;
ret |= check(src, "N/APMEM miss");
src.mem_remote = 1;
ret |= check(src, "N/ARemote PMEM miss");
src.mem_snoopx = PERF_MEM_SNOOPX_FWD;
src.mem_lvl_num = PERF_MEM_LVLNUM_RAM;
ret |= check(src , "FwdRemote RAM miss");
return ret;
}
DEFINE_SUITE("Test data source output", mem);
+82
View File
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <linux/bitmap.h>
#include <linux/kernel.h>
#include <linux/zalloc.h>
#include <perf/cpumap.h>
#include <internal/cpumap.h>
#include "debug.h"
#include "env.h"
#include "mem2node.h"
#include "tests.h"
static struct node {
int node;
const char *map;
} test_nodes[] = {
{ .node = 0, .map = "0" },
{ .node = 1, .map = "1-2" },
{ .node = 3, .map = "5-7,9" },
};
#define T TEST_ASSERT_VAL
static unsigned long *get_bitmap(const char *str, int nbits)
{
struct perf_cpu_map *map = perf_cpu_map__new(str);
unsigned long *bm = NULL;
bm = bitmap_zalloc(nbits);
if (map && bm) {
struct perf_cpu cpu;
unsigned int i;
perf_cpu_map__for_each_cpu(cpu, i, map)
__set_bit(cpu.cpu, bm);
}
if (map)
perf_cpu_map__put(map);
else
free(bm);
return bm && map ? bm : NULL;
}
static int test__mem2node(struct test_suite *t __maybe_unused, int subtest __maybe_unused)
{
struct mem2node map;
struct memory_node nodes[3];
struct perf_env env = {
.memory_nodes = (struct memory_node *) &nodes[0],
.nr_memory_nodes = ARRAY_SIZE(nodes),
.memory_bsize = 0x100,
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(nodes); i++) {
nodes[i].node = test_nodes[i].node;
nodes[i].size = 10;
T("failed: alloc bitmap",
(nodes[i].set = get_bitmap(test_nodes[i].map, 10)));
}
T("failed: mem2node__init", !mem2node__init(&map, &env));
T("failed: mem2node__node", 0 == mem2node__node(&map, 0x50));
T("failed: mem2node__node", 1 == mem2node__node(&map, 0x100));
T("failed: mem2node__node", 1 == mem2node__node(&map, 0x250));
T("failed: mem2node__node", 3 == mem2node__node(&map, 0x500));
T("failed: mem2node__node", 3 == mem2node__node(&map, 0x650));
T("failed: mem2node__node", -1 == mem2node__node(&map, 0x450));
T("failed: mem2node__node", -1 == mem2node__node(&map, 0x1050));
for (i = 0; i < ARRAY_SIZE(nodes); i++)
zfree(&nodes[i].set);
mem2node__exit(&map);
return 0;
}
DEFINE_SUITE("mem2node", mem2node);
+451
View File
@@ -0,0 +1,451 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <perf/cpumap.h>
#include <perf/evlist.h>
#include <perf/mmap.h>
#include "cpumap.h"
#include "debug.h"
#include "event.h"
#include "evlist.h"
#include "evsel.h"
#include "pmu.h"
#include "pmus.h"
#include "tests.h"
#include "thread_map.h"
#include "util/affinity.h"
#include "util/mmap.h"
#include "util/sample.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.
*/
static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int err = TEST_FAIL;
union perf_event *event;
struct perf_thread_map *threads;
struct perf_cpu_map *cpus;
struct evlist *evlist;
cpu_set_t cpu_set;
const char *syscall_names[] = { "getsid", "getppid", "getpgid", };
pid_t (*syscalls[])(void) = { (void *)getsid, getppid, (void*)getpgid };
#define nsyscalls ARRAY_SIZE(syscall_names)
unsigned int nr_events[nsyscalls],
expected_nr_events[nsyscalls], i, j;
struct evsel *evsels[nsyscalls], *evsel;
char sbuf[STRERR_BUFSIZE];
struct mmap *md;
threads = thread_map__new_by_tid(getpid());
if (threads == NULL) {
pr_debug("thread_map__new\n");
return -1;
}
cpus = perf_cpu_map__new_online_cpus();
if (cpus == NULL) {
pr_debug("perf_cpu_map__new\n");
goto out_free_threads;
}
CPU_ZERO(&cpu_set);
CPU_SET(perf_cpu_map__cpu(cpus, 0).cpu, &cpu_set);
sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) {
pr_debug("sched_setaffinity() failed on CPU %d: %s ",
perf_cpu_map__cpu(cpus, 0).cpu,
str_error_r(errno, sbuf, sizeof(sbuf)));
goto out_free_cpus;
}
evlist = evlist__new();
if (evlist == NULL) {
pr_debug("evlist__new\n");
goto out_free_cpus;
}
perf_evlist__set_maps(&evlist->core, cpus, threads);
for (i = 0; i < nsyscalls; ++i) {
char name[64];
snprintf(name, sizeof(name), "sys_enter_%s", syscall_names[i]);
evsels[i] = evsel__newtp("syscalls", name);
if (IS_ERR(evsels[i])) {
pr_debug("evsel__new(%s)\n", name);
if (PTR_ERR(evsels[i]) == -EACCES) {
/* Permissions failure, flag the failure as a skip. */
err = TEST_SKIP;
}
goto out_delete_evlist;
}
evsels[i]->core.attr.wakeup_events = 1;
evsel__set_sample_id(evsels[i], false);
evlist__add(evlist, evsels[i]);
if (evsel__open(evsels[i], cpus, threads) < 0) {
pr_debug("failed to open counter: %s, "
"tweak /proc/sys/kernel/perf_event_paranoid?\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
goto out_delete_evlist;
}
nr_events[i] = 0;
expected_nr_events[i] = 1 + rand() % 127;
}
if (evlist__mmap(evlist, 128) < 0) {
pr_debug("failed to mmap events: %d (%s)\n", errno,
str_error_r(errno, sbuf, sizeof(sbuf)));
goto out_delete_evlist;
}
for (i = 0; i < nsyscalls; ++i)
for (j = 0; j < expected_nr_events[i]; ++j) {
syscalls[i]();
}
md = &evlist->mmap[0];
if (perf_mmap__read_init(&md->core) < 0)
goto out_init;
while ((event = perf_mmap__read_event(&md->core)) != 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;
}
perf_sample__init(&sample, /*all=*/false);
err = evlist__parse_sample(evlist, event, &sample);
if (err) {
pr_err("Can't parse sample, err = %d\n", err);
perf_sample__exit(&sample);
goto out_delete_evlist;
}
err = -1;
evsel = sample.evsel;
if (!evsel)
evsel = evlist__id2evsel(evlist, sample.id);
perf_sample__exit(&sample);
if (evsel == NULL) {
pr_debug("event with id %" PRIu64
" doesn't map to an evsel\n", sample.id);
goto out_delete_evlist;
}
nr_events[evsel->core.idx]++;
perf_mmap__consume(&md->core);
}
perf_mmap__read_done(&md->core);
out_init:
err = 0;
evlist__for_each_entry(evlist, evsel) {
if (nr_events[evsel->core.idx] != expected_nr_events[evsel->core.idx]) {
pr_debug("expected %d %s events, got %d\n",
expected_nr_events[evsel->core.idx],
evsel__name(evsel), nr_events[evsel->core.idx]);
err = -1;
goto out_delete_evlist;
}
}
out_delete_evlist:
evlist__delete(evlist);
out_free_cpus:
perf_cpu_map__put(cpus);
out_free_threads:
perf_thread_map__put(threads);
return err;
}
enum user_read_state {
USER_READ_ENABLED,
USER_READ_DISABLED,
USER_READ_UNKNOWN,
};
static enum user_read_state set_user_read(struct perf_pmu *pmu, enum user_read_state enabled)
{
char buf[2] = {0, '\n'};
ssize_t len;
int events_fd, rdpmc_fd;
enum user_read_state old_user_read = USER_READ_UNKNOWN;
if (enabled == USER_READ_UNKNOWN)
return USER_READ_UNKNOWN;
events_fd = perf_pmu__event_source_devices_fd();
if (events_fd < 0)
return USER_READ_UNKNOWN;
rdpmc_fd = perf_pmu__pathname_fd(events_fd, pmu->name, "rdpmc", O_RDWR);
if (rdpmc_fd < 0) {
close(events_fd);
return USER_READ_UNKNOWN;
}
len = read(rdpmc_fd, buf, sizeof(buf));
if (len != sizeof(buf))
pr_debug("%s read failed\n", __func__);
// Note, on Intel hybrid disabling on 1 PMU will implicitly disable on
// all the core PMUs.
old_user_read = (buf[0] == '1') ? USER_READ_ENABLED : USER_READ_DISABLED;
if (enabled != old_user_read) {
buf[0] = (enabled == USER_READ_ENABLED) ? '1' : '0';
len = write(rdpmc_fd, buf, sizeof(buf));
if (len != sizeof(buf))
pr_debug("%s write failed\n", __func__);
}
close(rdpmc_fd);
close(events_fd);
return old_user_read;
}
static int test_stat_user_read(u64 event, enum user_read_state enabled)
{
struct perf_pmu *pmu = NULL;
struct perf_thread_map *threads = perf_thread_map__new_dummy();
int ret = TEST_OK;
pr_err("User space counter reading %" PRIu64 "\n", event);
if (!threads) {
pr_err("User space counter reading [Failed to create threads]\n");
return TEST_FAIL;
}
perf_thread_map__set_pid(threads, 0, 0);
while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
enum user_read_state saved_user_read_state = set_user_read(pmu, enabled);
struct perf_event_attr attr = {
.type = PERF_TYPE_HARDWARE,
.config = perf_pmus__supports_extended_type()
? event | ((u64)pmu->type << PERF_PMU_TYPE_SHIFT)
: event,
#ifdef __aarch64__
.config1 = 0x2, /* Request user access */
#endif
};
struct perf_evsel *evsel = NULL;
int err;
struct perf_event_mmap_page *pc;
bool mapped = false, opened = false, rdpmc_supported;
struct perf_counts_values counts = { .val = 0 };
pr_debug("User space counter reading for PMU %s\n", pmu->name);
/*
* Restrict scheduling to only use the rdpmc on the CPUs the
* event can be on. If the test doesn't run on the CPU of the
* event then the event will be disabled and the pc->index test
* will fail.
*/
if (pmu->cpus != NULL)
cpu_map__set_affinity(pmu->cpus);
/* Make the evsel. */
evsel = perf_evsel__new(&attr);
if (!evsel) {
pr_err("User space counter reading for PMU %s [Failed to allocate evsel]\n",
pmu->name);
ret = TEST_FAIL;
goto cleanup;
}
err = perf_evsel__open(evsel, NULL, threads);
if (err) {
pr_err("User space counter reading for PMU %s [Failed to open evsel]\n",
pmu->name);
ret = TEST_SKIP;
goto cleanup;
}
opened = true;
err = perf_evsel__mmap(evsel, 0);
if (err) {
pr_err("User space counter reading for PMU %s [Failed to mmap evsel]\n",
pmu->name);
ret = TEST_FAIL;
goto cleanup;
}
mapped = true;
pc = perf_evsel__mmap_base(evsel, 0, 0);
if (!pc) {
pr_err("User space counter reading for PMU %s [Failed to get mmaped address]\n",
pmu->name);
ret = TEST_FAIL;
goto cleanup;
}
if (saved_user_read_state == USER_READ_UNKNOWN)
rdpmc_supported = pc->cap_user_rdpmc && pc->index;
else
rdpmc_supported = (enabled == USER_READ_ENABLED);
if (rdpmc_supported && (!pc->cap_user_rdpmc || !pc->index)) {
pr_err("User space counter reading for PMU %s [Failed unexpected supported counter access %d %d]\n",
pmu->name, pc->cap_user_rdpmc, pc->index);
ret = TEST_FAIL;
goto cleanup;
}
if (!rdpmc_supported && pc->cap_user_rdpmc) {
pr_err("User space counter reading for PMU %s [Failed unexpected unsupported counter access %d]\n",
pmu->name, pc->cap_user_rdpmc);
ret = TEST_FAIL;
goto cleanup;
}
if (rdpmc_supported && pc->pmc_width < 32) {
pr_err("User space counter reading for PMU %s [Failed width not set %d]\n",
pmu->name, pc->pmc_width);
ret = TEST_FAIL;
goto cleanup;
}
perf_evsel__read(evsel, 0, 0, &counts);
if (rdpmc_supported && counts.val == 0) {
pr_err("User space counter reading for PMU %s [Failed read]\n", pmu->name);
ret = TEST_FAIL;
goto cleanup;
}
for (int i = 0; i < 5; i++) {
volatile int count = 0x10000 << i;
__u64 start, end, last = 0;
pr_debug("\tloop = %u, ", count);
perf_evsel__read(evsel, 0, 0, &counts);
start = counts.val;
while (count--) ;
perf_evsel__read(evsel, 0, 0, &counts);
end = counts.val;
if ((end - start) < last) {
pr_err("User space counter reading for PMU %s [Failed invalid counter data: end=%llu start=%llu last= %llu]\n",
pmu->name, end, start, last);
ret = TEST_FAIL;
goto cleanup;
}
last = end - start;
pr_debug("count = %llu\n", last);
}
pr_debug("User space counter reading for PMU %s [Success]\n", pmu->name);
cleanup:
if (mapped)
perf_evsel__munmap(evsel);
if (opened)
perf_evsel__close(evsel);
perf_evsel__delete(evsel);
/* If the affinity was changed, then put it back to all CPUs. */
if (pmu->cpus != NULL) {
struct perf_cpu_map *cpus = cpu_map__online();
cpu_map__set_affinity(cpus);
perf_cpu_map__put(cpus);
}
set_user_read(pmu, saved_user_read_state);
}
perf_thread_map__put(threads);
return ret;
}
static int test__mmap_user_read_instr(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return test_stat_user_read(PERF_COUNT_HW_INSTRUCTIONS, USER_READ_ENABLED);
}
static int test__mmap_user_read_cycles(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return test_stat_user_read(PERF_COUNT_HW_CPU_CYCLES, USER_READ_ENABLED);
}
static int test__mmap_user_read_instr_disabled(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return test_stat_user_read(PERF_COUNT_HW_INSTRUCTIONS, USER_READ_DISABLED);
}
static int test__mmap_user_read_cycles_disabled(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return test_stat_user_read(PERF_COUNT_HW_CPU_CYCLES, USER_READ_DISABLED);
}
static struct test_case tests__basic_mmap[] = {
TEST_CASE_REASON("Read samples using the mmap interface",
basic_mmap,
"permissions"),
TEST_CASE_REASON_EXCLUSIVE("User space counter reading of instructions",
mmap_user_read_instr,
#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \
(defined(__riscv) && __riscv_xlen == 64)
"permissions"
#else
"unsupported"
#endif
),
TEST_CASE_REASON_EXCLUSIVE("User space counter reading of cycles",
mmap_user_read_cycles,
#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \
(defined(__riscv) && __riscv_xlen == 64)
"permissions"
#else
"unsupported"
#endif
),
TEST_CASE_REASON_EXCLUSIVE("User space counter disabling instructions",
mmap_user_read_instr_disabled,
#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \
(defined(__riscv) && __riscv_xlen == 64)
"permissions"
#else
"unsupported"
#endif
),
TEST_CASE_REASON_EXCLUSIVE("User space counter disabling cycles",
mmap_user_read_cycles_disabled,
#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \
(defined(__riscv) && __riscv_xlen == 64)
"permissions"
#else
"unsupported"
#endif
),
{ .name = NULL, }
};
struct test_suite suite__basic_mmap = {
.desc = "mmap interface tests",
.test_cases = tests__basic_mmap,
};
@@ -0,0 +1,246 @@
// SPDX-License-Identifier: GPL-2.0
#include <inttypes.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include "debug.h"
#include "env.h"
#include "event.h"
#include "tests.h"
#include "machine.h"
#include "thread_map.h"
#include "map.h"
#include "symbol.h"
#include "util/synthetic-events.h"
#include "thread.h"
#include <internal/lib.h> // page_size
#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 = 0;
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, 1, 0, 1);
}
static int synth_process(struct machine *machine)
{
struct perf_thread_map *map;
int err;
map = thread_map__new_by_pid(getpid());
err = perf_event__synthesize_thread_map(NULL, map,
perf_event__process,
machine, 1, 0);
perf_thread_map__put(map);
return err;
}
static int mmap_events(synth_cb synth)
{
struct perf_env host_env;
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());
perf_env__init(&host_env);
machine = machine__new_host(&host_env);
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;
addr_location__init(&al);
thread = machine__findnew_thread(machine, getpid(), td->tid);
pr_debug("looking for map %p\n", td->map);
thread__find_map(thread, PERF_RECORD_MISC_USER,
(unsigned long) (td->map + 1), &al);
thread__put(thread);
if (!al.map) {
pr_debug("failed, couldn't find map\n");
err = -1;
addr_location__exit(&al);
break;
}
pr_debug("map %p, addr %" PRIx64 "\n", al.map, map__start(al.map));
addr_location__exit(&al);
}
machine__delete(machine);
perf_env__exit(&host_env);
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_map
*
* by using all thread objects.
*/
static int test__mmap_thread_lookup(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
/* perf_event__synthesize_threads synthesize */
TEST_ASSERT_VAL("failed with synthesizing all",
!mmap_events(synth_all));
/* perf_event__synthesize_thread_map synthesize */
TEST_ASSERT_VAL("failed with synthesizing process",
!mmap_events(synth_process));
return 0;
}
DEFINE_SUITE("Lookup mmap thread", mmap_thread_lookup);
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <inttypes.h>
/* For the CPU_* macros */
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <api/fs/fs.h>
#include <linux/err.h>
#include <linux/string.h>
#include <api/fs/tracing_path.h>
#include "evsel.h"
#include "tests.h"
#include "thread_map.h"
#include <perf/cpumap.h>
#include "debug.h"
#include "stat.h"
#include "util/counts.h"
static int test__openat_syscall_event_on_all_cpus(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int err = TEST_FAIL, fd;
unsigned int idx;
struct perf_cpu cpu;
struct perf_cpu_map *cpus;
struct evsel *evsel;
unsigned int nr_openat_calls = 111, i;
cpu_set_t cpu_set;
struct perf_thread_map *threads = thread_map__new_by_tid(getpid());
char sbuf[STRERR_BUFSIZE];
char errbuf[BUFSIZ];
if (threads == NULL) {
pr_debug("thread_map__new\n");
return -1;
}
cpus = perf_cpu_map__new_online_cpus();
if (cpus == NULL) {
pr_debug("perf_cpu_map__new\n");
goto out_thread_map_delete;
}
CPU_ZERO(&cpu_set);
evsel = evsel__newtp("syscalls", "sys_enter_openat");
if (IS_ERR(evsel)) {
tracing_path__strerror_open_tp(errno, errbuf, sizeof(errbuf), "syscalls", "sys_enter_openat");
pr_debug("%s\n", errbuf);
err = TEST_SKIP;
goto out_cpu_map_delete;
}
if (evsel__open(evsel, cpus, threads) < 0) {
pr_debug("failed to open counter: %s, "
"tweak /proc/sys/kernel/perf_event_paranoid?\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
err = TEST_SKIP;
goto out_evsel_delete;
}
perf_cpu_map__for_each_cpu(cpu, idx, cpus) {
unsigned int ncalls = nr_openat_calls + idx;
/*
* XXX eventually lift this restriction in a way that
* keeps perf building on older glibc installations
* without CPU_ALLOC. 1024 cpus in 2010 still seems
* a reasonable upper limit tho :-)
*/
if (cpu.cpu >= CPU_SETSIZE) {
pr_debug("Ignoring CPU %d\n", cpu.cpu);
continue;
}
CPU_SET(cpu.cpu, &cpu_set);
if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) {
pr_debug("sched_setaffinity() failed on CPU %d: %s ",
cpu.cpu,
str_error_r(errno, sbuf, sizeof(sbuf)));
goto out_close_fd;
}
for (i = 0; i < ncalls; ++i) {
fd = openat(0, "/etc/passwd", O_RDONLY);
close(fd);
}
CPU_CLR(cpu.cpu, &cpu_set);
}
evsel->core.cpus = perf_cpu_map__get(cpus);
err = TEST_OK;
perf_cpu_map__for_each_cpu(cpu, idx, cpus) {
unsigned int expected;
if (cpu.cpu >= CPU_SETSIZE)
continue;
if (evsel__read_on_cpu(evsel, idx, 0) < 0) {
pr_debug("evsel__read_on_cpu\n");
err = TEST_FAIL;
break;
}
expected = nr_openat_calls + idx;
if (perf_counts(evsel->counts, idx, 0)->val != expected) {
pr_debug("evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %" PRIu64 "\n",
expected, cpu.cpu, perf_counts(evsel->counts, idx, 0)->val);
err = TEST_FAIL;
}
}
evsel__free_counts(evsel);
out_close_fd:
perf_evsel__close_fd(&evsel->core);
out_evsel_delete:
evsel__delete(evsel);
out_cpu_map_delete:
perf_cpu_map__put(cpus);
out_thread_map_delete:
perf_thread_map__put(threads);
return err;
}
static struct test_case tests__openat_syscall_event_on_all_cpus[] = {
TEST_CASE_REASON("Detect openat syscall event on all cpus",
openat_syscall_event_on_all_cpus,
"permissions"),
{ .name = NULL, }
};
struct test_suite suite__openat_syscall_event_on_all_cpus = {
.desc = "Detect openat syscall event on all cpus",
.test_cases = tests__openat_syscall_event_on_all_cpus,
};
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdbool.h>
#include <linux/err.h>
#include <linux/string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "evlist.h"
#include "evsel.h"
#include "thread_map.h"
#include "record.h"
#include "tests.h"
#include "debug.h"
#include "util/mmap.h"
#include <errno.h>
#include <perf/mmap.h>
#include "util/sample.h"
#ifndef O_DIRECTORY
#define O_DIRECTORY 00200000
#endif
#ifndef AT_FDCWD
#define AT_FDCWD -100
#endif
static int test__syscall_openat_tp_fields(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct record_opts opts = {
.target = {
.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 evlist *evlist = evlist__new();
struct evsel *evsel;
int ret = TEST_FAIL, err, i, nr_events = 0, nr_polls = 0;
char sbuf[STRERR_BUFSIZE];
if (evlist == NULL) {
pr_debug("%s: evlist__new\n", __func__);
goto out;
}
evsel = evsel__newtp("syscalls", "sys_enter_openat");
if (IS_ERR(evsel)) {
pr_debug("%s: evsel__newtp\n", __func__);
ret = PTR_ERR(evsel) == -EACCES ? TEST_SKIP : TEST_FAIL;
goto out_delete_evlist;
}
evlist__add(evlist, evsel);
err = evlist__create_maps(evlist, &opts.target);
if (err < 0) {
pr_debug("%s: evlist__create_maps\n", __func__);
goto out_delete_evlist;
}
evsel__config(evsel, &opts, NULL);
perf_thread_map__set_pid(evlist->core.threads, 0, getpid());
err = evlist__open(evlist);
if (err < 0) {
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
goto out_delete_evlist;
}
err = evlist__mmap(evlist, UINT_MAX);
if (err < 0) {
pr_debug("evlist__mmap: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
goto out_delete_evlist;
}
evlist__enable(evlist);
/*
* Generate the event:
*/
openat(AT_FDCWD, filename, flags);
while (1) {
int before = nr_events;
for (i = 0; i < evlist->core.nr_mmaps; i++) {
union perf_event *event;
struct mmap *md;
md = &evlist->mmap[i];
if (perf_mmap__read_init(&md->core) < 0)
continue;
while ((event = perf_mmap__read_event(&md->core)) != NULL) {
const u32 type = event->header.type;
int tp_flags;
struct perf_sample sample;
++nr_events;
if (type != PERF_RECORD_SAMPLE) {
perf_mmap__consume(&md->core);
continue;
}
perf_sample__init(&sample, /*all=*/false);
err = evsel__parse_sample(evsel, event, &sample);
if (err) {
pr_debug("Can't parse sample, err = %d\n", err);
perf_sample__exit(&sample);
goto out_delete_evlist;
}
tp_flags = perf_sample__intval(&sample, "flags");
perf_sample__exit(&sample);
/* C library wrapper may set additional flags,
access mode must be unchanged */
if ((tp_flags & O_ACCMODE) != (flags & O_ACCMODE) ||
(tp_flags & flags) != flags) {
pr_debug("%s: Expected flags=%#x, got %#x\n",
__func__, flags, tp_flags);
goto out_delete_evlist;
}
goto out_ok;
}
perf_mmap__read_done(&md->core);
}
if (nr_events == before)
evlist__poll(evlist, 10);
if (++nr_polls > 5) {
pr_debug("%s: no events!\n", __func__);
goto out_delete_evlist;
}
}
out_ok:
ret = TEST_OK;
out_delete_evlist:
evlist__delete(evlist);
out:
return ret;
}
static struct test_case tests__syscall_openat_tp_fields[] = {
TEST_CASE_REASON("syscalls:sys_enter_openat event fields",
syscall_openat_tp_fields,
"permissions"),
{ .name = NULL, }
};
struct test_suite suite__syscall_openat_tp_fields = {
.desc = "syscalls:sys_enter_openat event fields",
.test_cases = tests__syscall_openat_tp_fields,
};
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <inttypes.h>
#include <api/fs/tracing_path.h>
#include <linux/err.h>
#include <linux/string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "thread_map.h"
#include "evsel.h"
#include "debug.h"
#include "tests.h"
#include "util/counts.h"
static int test__openat_syscall_event(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int err = TEST_FAIL, fd;
struct evsel *evsel;
unsigned int nr_openat_calls = 111, i;
struct perf_thread_map *threads = thread_map__new_by_tid(getpid());
char sbuf[STRERR_BUFSIZE];
char errbuf[BUFSIZ];
if (threads == NULL) {
pr_debug("thread_map__new\n");
return TEST_FAIL;
}
evsel = evsel__newtp("syscalls", "sys_enter_openat");
if (IS_ERR(evsel)) {
tracing_path__strerror_open_tp(errno, errbuf, sizeof(errbuf), "syscalls", "sys_enter_openat");
pr_debug("%s\n", errbuf);
err = TEST_SKIP;
goto out_thread_map_delete;
}
if (evsel__open_per_thread(evsel, threads) < 0) {
pr_debug("failed to open counter: %s, "
"tweak /proc/sys/kernel/perf_event_paranoid?\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
err = TEST_SKIP;
goto out_evsel_delete;
}
for (i = 0; i < nr_openat_calls; ++i) {
fd = openat(0, "/etc/passwd", O_RDONLY);
close(fd);
}
if (evsel__read_on_cpu(evsel, 0, 0) < 0) {
pr_debug("evsel__read_on_cpu\n");
goto out_close_fd;
}
if (perf_counts(evsel->counts, 0, 0)->val != nr_openat_calls) {
pr_debug("evsel__read_on_cpu: expected to intercept %d calls, got %" PRIu64 "\n",
nr_openat_calls, perf_counts(evsel->counts, 0, 0)->val);
goto out_close_fd;
}
err = TEST_OK;
out_close_fd:
perf_evsel__close_fd(&evsel->core);
out_evsel_delete:
evsel__delete(evsel);
out_thread_map_delete:
perf_thread_map__put(threads);
return err;
}
static struct test_case tests__openat_syscall_event[] = {
TEST_CASE_REASON("Detect openat syscall event",
openat_syscall_event,
"permissions"),
{ .name = NULL, }
};
struct test_suite suite__openat_syscall_event = {
.desc = "Detect openat syscall event",
.test_cases = tests__openat_syscall_event,
};
File diff suppressed because it is too large Load Diff
+305
View File
@@ -0,0 +1,305 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <errno.h>
#include <string.h>
#include <perf/cpumap.h>
#include <perf/evlist.h>
#include "metricgroup.h"
#include "tests.h"
#include "pmu-events/pmu-events.h"
#include "evlist.h"
#include "rblist.h"
#include "debug.h"
#include "expr.h"
#include "stat.h"
#include "pmus.h"
struct value {
const char *event;
u64 val;
};
static u64 find_value(const char *name, struct value *values)
{
struct value *v = values;
while (v->event) {
if (!strcmp(name, v->event))
return v->val;
v++;
}
return 0;
}
static void load_runtime_stat(struct evlist *evlist, struct value *vals)
{
struct evsel *evsel;
u64 count;
evlist__alloc_aggr_stats(evlist, 1);
evlist__for_each_entry(evlist, evsel) {
count = find_value(evsel->name, vals);
evsel->supported = true;
evsel->stats->aggr->counts.val = count;
evsel->stats->aggr->counts.ena = 1;
evsel->stats->aggr->counts.run = 1;
}
}
static double compute_single(struct evlist *evlist, const char *name)
{
struct metric_expr *mexp;
struct metric_event *me;
struct evsel *evsel;
evlist__for_each_entry(evlist, evsel) {
me = metricgroup__lookup(&evlist->metric_events, evsel, false);
if (me != NULL) {
list_for_each_entry (mexp, &me->head, nd) {
if (strcmp(mexp->metric_name, name))
continue;
return test_generic_metric(mexp, 0);
}
}
}
return 0.;
}
static int __compute_metric(const char *name, struct value *vals,
const char *name1, double *ratio1,
const char *name2, double *ratio2)
{
const struct pmu_metrics_table *pme_test;
struct perf_cpu_map *cpus;
struct evlist *evlist;
int err;
/*
* We need to prepare evlist for stat mode running on CPU 0
* because that's where all the stats are going to be created.
*/
evlist = evlist__new();
if (!evlist)
return -ENOMEM;
cpus = perf_cpu_map__new("0");
if (!cpus) {
evlist__delete(evlist);
return -ENOMEM;
}
perf_evlist__set_maps(&evlist->core, cpus, NULL);
/* Parse the metric into metric_events list. */
pme_test = find_core_metrics_table("testarch", "testcpu");
err = metricgroup__parse_groups_test(evlist, pme_test, name);
if (err)
goto out;
err = evlist__alloc_stats(/*config=*/NULL, evlist, /*alloc_raw=*/false);
if (err)
goto out;
/* Load the runtime stats with given numbers for events. */
load_runtime_stat(evlist, vals);
/* And execute the metric */
if (name1 && ratio1)
*ratio1 = compute_single(evlist, name1);
if (name2 && ratio2)
*ratio2 = compute_single(evlist, name2);
out:
/* ... cleanup. */
evlist__free_stats(evlist);
perf_cpu_map__put(cpus);
evlist__delete(evlist);
return err;
}
static int compute_metric(const char *name, struct value *vals, double *ratio)
{
return __compute_metric(name, vals, name, ratio, NULL, NULL);
}
static int compute_metric_group(const char *name, struct value *vals,
const char *name1, double *ratio1,
const char *name2, double *ratio2)
{
return __compute_metric(name, vals, name1, ratio1, name2, ratio2);
}
static int test_ipc(void)
{
double ratio;
struct value vals[] = {
{ .event = "inst_retired.any", .val = 300 },
{ .event = "cpu_clk_unhalted.thread", .val = 200 },
{ .event = NULL, },
};
TEST_ASSERT_VAL("failed to compute metric",
compute_metric("IPC", vals, &ratio) == 0);
TEST_ASSERT_VAL("IPC failed, wrong ratio",
ratio == 1.5);
return 0;
}
static int test_frontend(void)
{
double ratio;
struct value vals[] = {
{ .event = "idq_uops_not_delivered.core", .val = 300 },
{ .event = "cpu_clk_unhalted.thread", .val = 200 },
{ .event = "cpu_clk_unhalted.one_thread_active", .val = 400 },
{ .event = "cpu_clk_unhalted.ref_xclk", .val = 600 },
{ .event = NULL, },
};
TEST_ASSERT_VAL("failed to compute metric",
compute_metric("Frontend_Bound_SMT", vals, &ratio) == 0);
TEST_ASSERT_VAL("Frontend_Bound_SMT failed, wrong ratio",
ratio == 0.45);
return 0;
}
static int test_cache_miss_cycles(void)
{
double ratio;
struct value vals[] = {
{ .event = "l1d-loads-misses", .val = 300 },
{ .event = "l1i-loads-misses", .val = 200 },
{ .event = "inst_retired.any", .val = 400 },
{ .event = NULL, },
};
TEST_ASSERT_VAL("failed to compute metric",
compute_metric("cache_miss_cycles", vals, &ratio) == 0);
TEST_ASSERT_VAL("cache_miss_cycles failed, wrong ratio",
ratio == 1.25);
return 0;
}
/*
* DCache_L2_All_Hits = l2_rqsts.demand_data_rd_hit + l2_rqsts.pf_hit + l2_rqsts.rfo_hi
* DCache_L2_All_Miss = max(l2_rqsts.all_demand_data_rd - l2_rqsts.demand_data_rd_hit, 0) +
* l2_rqsts.pf_miss + l2_rqsts.rfo_miss
* DCache_L2_All = dcache_l2_all_hits + dcache_l2_all_miss
* DCache_L2_Hits = d_ratio(dcache_l2_all_hits, dcache_l2_all)
* DCache_L2_Misses = d_ratio(dcache_l2_all_miss, dcache_l2_all)
*
* l2_rqsts.demand_data_rd_hit = 100
* l2_rqsts.pf_hit = 200
* l2_rqsts.rfo_hi = 300
* l2_rqsts.all_demand_data_rd = 400
* l2_rqsts.pf_miss = 500
* l2_rqsts.rfo_miss = 600
*
* DCache_L2_All_Hits = 600
* DCache_L2_All_Miss = MAX(400 - 100, 0) + 500 + 600 = 1400
* DCache_L2_All = 600 + 1400 = 2000
* DCache_L2_Hits = 600 / 2000 = 0.3
* DCache_L2_Misses = 1400 / 2000 = 0.7
*/
static int test_dcache_l2(void)
{
double ratio;
struct value vals[] = {
{ .event = "l2_rqsts.demand_data_rd_hit", .val = 100 },
{ .event = "l2_rqsts.pf_hit", .val = 200 },
{ .event = "l2_rqsts.rfo_hit", .val = 300 },
{ .event = "l2_rqsts.all_demand_data_rd", .val = 400 },
{ .event = "l2_rqsts.pf_miss", .val = 500 },
{ .event = "l2_rqsts.rfo_miss", .val = 600 },
{ .event = NULL, },
};
TEST_ASSERT_VAL("failed to compute metric",
compute_metric("DCache_L2_Hits", vals, &ratio) == 0);
TEST_ASSERT_VAL("DCache_L2_Hits failed, wrong ratio",
ratio == 0.3);
TEST_ASSERT_VAL("failed to compute metric",
compute_metric("DCache_L2_Misses", vals, &ratio) == 0);
TEST_ASSERT_VAL("DCache_L2_Misses failed, wrong ratio",
ratio == 0.7);
return 0;
}
static int test_recursion_fail(void)
{
double ratio;
struct value vals[] = {
{ .event = "inst_retired.any", .val = 300 },
{ .event = "cpu_clk_unhalted.thread", .val = 200 },
{ .event = NULL, },
};
TEST_ASSERT_VAL("failed to find recursion",
compute_metric("M1", vals, &ratio) == -1);
TEST_ASSERT_VAL("failed to find recursion",
compute_metric("M3", vals, &ratio) == -1);
return 0;
}
static int test_memory_bandwidth(void)
{
double ratio;
struct value vals[] = {
{ .event = "l1d.replacement", .val = 4000000 },
{ .event = "duration_time", .val = 200000000 },
{ .event = NULL, },
};
TEST_ASSERT_VAL("failed to compute metric",
compute_metric("L1D_Cache_Fill_BW", vals, &ratio) == 0);
TEST_ASSERT_VAL("L1D_Cache_Fill_BW, wrong ratio",
1.28 == ratio);
return 0;
}
static int test_metric_group(void)
{
double ratio1, ratio2;
struct value vals[] = {
{ .event = "cpu_clk_unhalted.thread", .val = 200 },
{ .event = "l1d-loads-misses", .val = 300 },
{ .event = "l1i-loads-misses", .val = 200 },
{ .event = "inst_retired.any", .val = 400 },
{ .event = NULL, },
};
TEST_ASSERT_VAL("failed to find recursion",
compute_metric_group("group1", vals,
"IPC", &ratio1,
"cache_miss_cycles", &ratio2) == 0);
TEST_ASSERT_VAL("group IPC failed, wrong ratio",
ratio1 == 2.0);
TEST_ASSERT_VAL("group cache_miss_cycles failed, wrong ratio",
ratio2 == 1.25);
return 0;
}
static int test__parse_metric(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
TEST_ASSERT_VAL("IPC failed", test_ipc() == 0);
TEST_ASSERT_VAL("frontend failed", test_frontend() == 0);
TEST_ASSERT_VAL("DCache_L2 failed", test_dcache_l2() == 0);
TEST_ASSERT_VAL("recursion fail failed", test_recursion_fail() == 0);
TEST_ASSERT_VAL("Memory bandwidth", test_memory_bandwidth() == 0);
TEST_ASSERT_VAL("cache_miss_cycles failed", test_cache_miss_cycles() == 0);
TEST_ASSERT_VAL("test metric group", test_metric_group() == 0);
return 0;
}
DEFINE_SUITE("Parse and process metrics", parse_metric);
@@ -0,0 +1,119 @@
#include <linux/kernel.h>
#include <linux/types.h>
#include <stddef.h>
#include "tests.h"
#include "event.h"
#include "evlist.h"
#include "header.h"
#include "debug.h"
#include "util/sample.h"
static int process_event(struct evlist **pevlist, union perf_event *event)
{
struct perf_sample sample;
int ret;
if (event->header.type == PERF_RECORD_HEADER_ATTR) {
if (perf_event__process_attr(NULL, event, pevlist)) {
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;
perf_sample__init(&sample, /*all=*/false);
ret = evlist__parse_sample(*pevlist, event, &sample);
perf_sample__exit(&sample);
if (ret) {
pr_debug("evlist__parse_sample failed\n");
return -1;
}
return 0;
}
static int process_events(union perf_event **events, size_t count)
{
struct evlist *evlist = NULL;
int err = 0;
size_t i;
for (i = 0; i < count && !err; i++)
err = process_event(&evlist, events[i]);
evlist__delete(evlist);
return err;
}
struct test_attr_event {
struct perf_event_header header;
struct perf_event_attr 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.
*/
static int test__parse_no_sample_id_all(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int err;
struct test_attr_event event1 = {
.header = {
.type = PERF_RECORD_HEADER_ATTR,
.size = sizeof(struct test_attr_event),
},
.attr = {
.size = sizeof(struct perf_event_attr),
},
.id = 1,
};
struct test_attr_event event2 = {
.header = {
.type = PERF_RECORD_HEADER_ATTR,
.size = sizeof(struct test_attr_event),
},
.attr = {
.size = sizeof(struct perf_event_attr),
},
.id = 2,
};
struct perf_record_mmap event3 = {
.header = {
.type = PERF_RECORD_MMAP,
.size = sizeof(struct perf_record_mmap),
},
};
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;
}
DEFINE_SUITE("Parse with no sample_id_all bit set", parse_no_sample_id_all);
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdbool.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <subcmd/exec-cmd.h>
#include "debug.h"
#include "util/build-id.h"
#include "util/symbol.h"
#include "util/dso.h"
#include "tests.h"
#ifdef HAVE_LIBBFD_SUPPORT
static int run_dir(const char *d)
{
char filename[PATH_MAX];
char debugfile[PATH_MAX];
struct build_id bid = { .size = 0, };
char debuglink[PATH_MAX];
char expect_build_id[] = {
0x5a, 0x0f, 0xd8, 0x82, 0xb5, 0x30, 0x84, 0x22,
0x4b, 0xa4, 0x7b, 0x62, 0x4c, 0x55, 0xa4, 0x69,
};
char expect_debuglink[PATH_MAX] = "pe-file.exe.debug";
struct dso *dso;
struct symbol *sym;
int ret;
size_t idx;
scnprintf(filename, PATH_MAX, "%s/pe-file.exe", d);
ret = filename__read_build_id(filename, &bid);
TEST_ASSERT_VAL("Failed to read build_id",
ret == sizeof(expect_build_id));
TEST_ASSERT_VAL("Wrong build_id", !memcmp(bid.data, expect_build_id,
sizeof(expect_build_id)));
ret = filename__read_debuglink(filename, debuglink, PATH_MAX);
TEST_ASSERT_VAL("Failed to read debuglink", ret == 0);
TEST_ASSERT_VAL("Wrong debuglink",
!strcmp(debuglink, expect_debuglink));
scnprintf(debugfile, PATH_MAX, "%s/%s", d, debuglink);
ret = filename__read_build_id(debugfile, &bid);
TEST_ASSERT_VAL("Failed to read debug file build_id",
ret == sizeof(expect_build_id));
TEST_ASSERT_VAL("Wrong build_id", !memcmp(bid.data, expect_build_id,
sizeof(expect_build_id)));
dso = dso__new(filename);
TEST_ASSERT_VAL("Failed to get dso", dso);
ret = dso__load_bfd_symbols(dso, debugfile);
TEST_ASSERT_VAL("Failed to load symbols", ret == 0);
dso__sort_by_name(dso);
sym = dso__find_symbol_by_name(dso, "main", &idx);
TEST_ASSERT_VAL("Failed to find main", sym);
dso__delete(dso);
return TEST_OK;
}
static int test__pe_file_parsing(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct stat st;
char path_dir[PATH_MAX];
/* First try development tree tests. */
if (!lstat("./tests", &st))
return run_dir("./tests");
/* Then installed path. */
snprintf(path_dir, PATH_MAX, "%s/tests", get_argv_exec_path());
if (!lstat(path_dir, &st))
return run_dir(path_dir);
return TEST_SKIP;
}
#else
static int test__pe_file_parsing(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return TEST_SKIP;
}
#endif
DEFINE_SUITE("PE file support", pe_file_parsing);
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-2.0
// pe-file.exe and pe-file.exe.debug built with;
// x86_64-w64-mingw32-gcc -o pe-file.exe pe-file.c
// -Wl,--file-alignment,4096 -Wl,--build-id
// x86_64-w64-mingw32-objcopy --only-keep-debug
// --compress-debug-sections pe-file.exe pe-file.exe.debug
// x86_64-w64-mingw32-objcopy --strip-debug
// --add-gnu-debuglink=pe-file.exe.debug pe-file.exe
int main(int argc, char const *argv[])
{
return 0;
}
Binary file not shown.
Binary file not shown.
+49
View File
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0
#include <signal.h>
#include <stdlib.h>
#include "tests.h"
#include "debug.h"
#include "perf-hooks.h"
static void sigsegv_handler(int sig __maybe_unused)
{
pr_debug("SIGSEGV is observed as expected, try to recover.\n");
perf_hooks__recover();
signal(SIGSEGV, SIG_DFL);
raise(SIGSEGV);
exit(-1);
}
static void the_hook(void *_hook_flags)
{
int *hook_flags = _hook_flags;
*hook_flags = 1234;
/* Generate a segfault, test perf_hooks__recover */
raise(SIGSEGV);
}
static int test__perf_hooks(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int hook_flags = 0;
signal(SIGSEGV, sigsegv_handler);
perf_hooks__set_hook("test", the_hook, &hook_flags);
perf_hooks__invoke_test();
/* hook is triggered? */
if (hook_flags != 1234) {
pr_debug("Setting failed: %d (%p)\n", hook_flags, &hook_flags);
return TEST_FAIL;
}
/* the buggy hook is removed? */
if (perf_hooks__get_hook("test"))
return TEST_FAIL;
return TEST_OK;
}
DEFINE_SUITE("perf hooks", perf_hooks);
+375
View File
@@ -0,0 +1,375 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <inttypes.h>
#include <linux/string.h>
#include <sched.h>
#include <perf/mmap.h>
#include "event.h"
#include "evlist.h"
#include "evsel.h"
#include "debug.h"
#include "record.h"
#include "tests.h"
#include "util/mmap.h"
#include "util/sample.h"
#include "util/cpumap.h"
static int sched__get_first_possible_cpu(pid_t pid, cpu_set_t *maskp)
{
int i, cpu = -1;
int nrcpus = cpu__max_cpu().cpu;
size_t size = CPU_ALLOC_SIZE(nrcpus);
realloc:
CPU_ZERO_S(size, maskp);
if (sched_getaffinity(pid, size, maskp) == -1) {
if (errno == EINVAL && nrcpus < (cpu__max_cpu().cpu << 8)) {
nrcpus = nrcpus << 2;
goto realloc;
}
perror("sched_getaffinity");
return -1;
}
for (i = 0; i < nrcpus; i++) {
if (CPU_ISSET_S(i, size, maskp)) {
if (cpu == -1)
cpu = i;
else
CPU_CLR_S(i, size, maskp);
}
}
return cpu;
}
static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct record_opts opts = {
.target = {
.uses_mmap = true,
},
.no_buffering = true,
.mmap_pages = 256,
};
int nrcpus = cpu__max_cpu().cpu;
cpu_set_t *cpu_mask;
size_t cpu_mask_size;
struct evlist *evlist = evlist__new_dummy();
struct 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_coreutils_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];
cpu_mask = CPU_ALLOC(nrcpus);
if (!cpu_mask) {
pr_debug("failed to create cpumask\n");
goto out;
}
cpu_mask_size = CPU_ALLOC_SIZE(nrcpus);
CPU_ZERO_S(cpu_mask_size, cpu_mask);
perf_sample__init(&sample, /*all=*/false);
if (evlist == NULL) { /* Fallback for kernels lacking PERF_COUNT_SW_DUMMY */
struct target target = {};
evlist = evlist__new_default(&target, /*sample_callchains=*/false);
}
if (evlist == NULL) {
pr_debug("Not enough memory to create evlist\n");
CPU_FREE(cpu_mask);
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
* evlist__prepare_workload we'll fill in the only thread
* we're monitoring, the one forked there.
*/
err = evlist__create_maps(evlist, &opts.target);
if (err < 0) {
pr_debug("Not enough memory to create thread/cpu maps\n");
goto out_delete_evlist;
}
/*
* Prepare the workload in argv[] to run, it'll fork it, and then wait
* for evlist__start_workload() to exec it. This is done this way
* so that we have time to open the evlist (calling sys_perf_event_open
* on all the fds) and then mmap them.
*/
err = 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 = evlist__first(evlist);
evsel__set_sample_bit(evsel, CPU);
evsel__set_sample_bit(evsel, TID);
evsel__set_sample_bit(evsel, TIME);
evlist__config(evlist, &opts, NULL);
err = sched__get_first_possible_cpu(evlist->workload.pid, cpu_mask);
if (err < 0) {
pr_debug("sched__get_first_possible_cpu: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
evlist__cancel_workload(evlist);
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",
str_error_r(errno, sbuf, sizeof(sbuf)));
evlist__cancel_workload(evlist);
goto out_delete_evlist;
}
/*
* Call sys_perf_event_open on all the fds on all the evsels,
* grouping them if asked to.
*/
err = evlist__open(evlist);
if (err < 0) {
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
evlist__cancel_workload(evlist);
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 = evlist__mmap(evlist, opts.mmap_pages);
if (err < 0) {
pr_debug("evlist__mmap: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
evlist__cancel_workload(evlist);
goto out_delete_evlist;
}
/*
* Now that all is properly set up, enable the events, they will
* count just on workload.pid, which will start...
*/
evlist__enable(evlist);
/*
* Now!
*/
evlist__start_workload(evlist);
while (1) {
int before = total_events;
for (i = 0; i < evlist->core.nr_mmaps; i++) {
union perf_event *event;
struct mmap *md;
md = &evlist->mmap[i];
if (perf_mmap__read_init(&md->core) < 0)
continue;
while ((event = perf_mmap__read_event(&md->core)) != NULL) {
const u32 type = event->header.type;
const char *name = perf_event__name(type);
++total_events;
if (type < PERF_RECORD_MAX)
nr_events[type]++;
err = evlist__parse_sample(evlist, event, &sample);
if (err < 0) {
if (verbose > 0)
perf_event__fprintf(event, NULL, stderr);
pr_debug("Couldn't parse sample\n");
goto out_delete_evlist;
}
if (verbose > 0) {
pr_info("%" PRIu64" %d ", sample.time, sample.cpu);
perf_event__fprintf(event, NULL, 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_coreutils_mmap)
found_coreutils_mmap = !strcmp(bname + 1, "coreutils");
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_mmap__consume(&md->core);
perf_sample__exit(&sample);
}
perf_mmap__read_done(&md->core);
}
/*
* 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)
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 + !!found_coreutils_mmap) {
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 && !found_coreutils_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:
CPU_FREE(cpu_mask);
evlist__delete(evlist);
out:
perf_sample__exit(&sample);
if (err == -EACCES)
return TEST_SKIP;
if (err < 0 || errs != 0)
return TEST_FAIL;
return TEST_OK;
}
static struct test_case tests__PERF_RECORD[] = {
TEST_CASE_REASON("PERF_RECORD_* events & perf_sample fields",
PERF_RECORD,
"permissions"),
{ .name = NULL, }
};
struct test_suite suite__PERF_RECORD = {
.desc = "PERF_RECORD_* events & perf_sample fields",
.test_cases = tests__PERF_RECORD,
};
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# 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.
set -e
PERF=$1
cd ${PERF}/../..
make perf-targz-src-pkg
TARBALL=$(ls -rt perf-*.tar.gz)
TMP_DEST=$(mktemp -d)
tar xf ${TARBALL} -C $TMP_DEST
rm -f ${TARBALL}
cd - > /dev/null
make -C $TMP_DEST/perf*/tools/perf
RC=$?
rm -rf ${TMP_DEST}
exit $RC
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <linux/types.h>
#include <sys/prctl.h>
#include <perf/cpumap.h>
#include <perf/evlist.h>
#include <perf/mmap.h>
#include "debug.h"
#include "parse-events.h"
#include "evlist.h"
#include "evsel.h"
#include "thread_map.h"
#include "record.h"
#include "tsc.h"
#include "mmap.h"
#include "tests.h"
#include "util/sample.h"
/*
* Except x86_64/i386 and Arm64, other archs don't support TSC in perf. Just
* enable the test for x86_64/i386 and Arm64 archs.
*/
#if defined(__x86_64__) || defined(__i386__) || defined(__aarch64__)
#define TSC_IS_SUPPORTED 1
#else
#define TSC_IS_SUPPORTED 0
#endif
#define CHECK__(x) { \
while ((x) < 0) { \
pr_debug(#x " failed!\n"); \
goto out_err; \
} \
}
#define CHECK_NOT_NULL__(x) { \
while ((x) == NULL) { \
pr_debug(#x " failed!\n"); \
goto out_err; \
} \
}
static int test__tsc_is_supported(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
if (!TSC_IS_SUPPORTED) {
pr_debug("Test not supported on this architecture\n");
return TEST_SKIP;
}
return TEST_OK;
}
/**
* test__perf_time_to_tsc - test converting perf time to TSC.
*
* This function implements a test that checks that the conversion of perf time
* to and from TSC is consistent with the order of events. If the test passes
* %0 is returned, otherwise %-1 is returned. If TSC conversion is not
* supported then the test passes but " (not supported)" is printed.
*/
static int test__perf_time_to_tsc(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct record_opts opts = {
.mmap_pages = UINT_MAX,
.user_freq = UINT_MAX,
.user_interval = ULLONG_MAX,
.target = {
.uses_mmap = true,
},
.sample_time = true,
};
struct perf_thread_map *threads = NULL;
struct perf_cpu_map *cpus = NULL;
struct evlist *evlist = NULL;
struct evsel *evsel = NULL;
int err = TEST_FAIL, ret, i;
const char *comm1, *comm2;
struct perf_tsc_conversion tc;
struct perf_event_mmap_page *pc;
union perf_event *event;
u64 test_tsc, comm1_tsc, comm2_tsc;
u64 test_time, comm1_time = 0, comm2_time = 0;
struct mmap *md;
threads = thread_map__new_by_tid(getpid());
CHECK_NOT_NULL__(threads);
cpus = perf_cpu_map__new_online_cpus();
CHECK_NOT_NULL__(cpus);
evlist = evlist__new();
CHECK_NOT_NULL__(evlist);
perf_evlist__set_maps(&evlist->core, cpus, threads);
CHECK__(parse_event(evlist, "cpu-cycles:u"));
evlist__config(evlist, &opts, NULL);
/* For hybrid "cpu-cycles:u", it creates two events */
evlist__for_each_entry(evlist, evsel) {
evsel->core.attr.comm = 1;
evsel->core.attr.disabled = 1;
evsel->core.attr.enable_on_exec = 0;
}
ret = evlist__open(evlist);
if (ret < 0) {
if (ret == -ENOENT)
err = TEST_SKIP;
else
pr_debug("evlist__open() failed\n");
goto out_err;
}
CHECK__(evlist__mmap(evlist, UINT_MAX));
pc = evlist->mmap[0].core.base;
ret = perf_read_tsc_conversion(pc, &tc);
if (ret) {
if (ret == -EOPNOTSUPP) {
pr_debug("perf_read_tsc_conversion is not supported in current kernel\n");
err = TEST_SKIP;
}
goto out_err;
}
evlist__enable(evlist);
comm1 = "Test COMM 1";
CHECK__(prctl(PR_SET_NAME, (unsigned long)comm1, 0, 0, 0));
test_tsc = rdtsc();
comm2 = "Test COMM 2";
CHECK__(prctl(PR_SET_NAME, (unsigned long)comm2, 0, 0, 0));
evlist__disable(evlist);
for (i = 0; i < evlist->core.nr_mmaps; i++) {
md = &evlist->mmap[i];
if (perf_mmap__read_init(&md->core) < 0)
continue;
while ((event = perf_mmap__read_event(&md->core)) != NULL) {
struct perf_sample sample;
perf_sample__init(&sample, /*all=*/false);
if (event->header.type != PERF_RECORD_COMM ||
(pid_t)event->comm.pid != getpid() ||
(pid_t)event->comm.tid != getpid())
goto next_event;
if (strcmp(event->comm.comm, comm1) == 0) {
CHECK_NOT_NULL__(evsel = evlist__event2evsel(evlist, event));
CHECK__(evsel__parse_sample(evsel, event, &sample));
comm1_time = sample.time;
}
if (strcmp(event->comm.comm, comm2) == 0) {
CHECK_NOT_NULL__(evsel = evlist__event2evsel(evlist, event));
CHECK__(evsel__parse_sample(evsel, event, &sample));
comm2_time = sample.time;
}
next_event:
perf_mmap__consume(&md->core);
perf_sample__exit(&sample);
}
perf_mmap__read_done(&md->core);
}
if (!comm1_time || !comm2_time)
goto out_err;
test_time = tsc_to_perf_time(test_tsc, &tc);
comm1_tsc = perf_time_to_tsc(comm1_time, &tc);
comm2_tsc = perf_time_to_tsc(comm2_time, &tc);
pr_debug("1st event perf time %"PRIu64" tsc %"PRIu64"\n",
comm1_time, comm1_tsc);
pr_debug("rdtsc time %"PRIu64" tsc %"PRIu64"\n",
test_time, test_tsc);
pr_debug("2nd event perf time %"PRIu64" tsc %"PRIu64"\n",
comm2_time, comm2_tsc);
if (test_time <= comm1_time ||
test_time >= comm2_time)
goto out_err;
if (test_tsc <= comm1_tsc ||
test_tsc >= comm2_tsc)
goto out_err;
err = TEST_OK;
out_err:
evlist__delete(evlist);
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
return err;
}
static struct test_case time_to_tsc_tests[] = {
TEST_CASE_REASON("TSC support", tsc_is_supported,
"This architecture does not support"),
TEST_CASE_REASON("Perf time to TSC", perf_time_to_tsc,
"perf_read_tsc_conversion is not supported"),
{ .name = NULL, }
};
struct test_suite suite__perf_time_to_tsc = {
.desc = "Convert perf time to TSC",
.test_cases = time_to_tsc_tests,
};
+195
View File
@@ -0,0 +1,195 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test support for libpfm4 event encodings.
*
* Copyright 2020 Google LLC.
*/
#include <errno.h>
#include "tests.h"
#include "util/debug.h"
#include "util/evlist.h"
#include "util/pfm.h"
#include <linux/kernel.h>
#ifdef HAVE_LIBPFM
static int count_pfm_events(struct perf_evlist *evlist)
{
struct perf_evsel *evsel;
int count = 0;
perf_evlist__for_each_entry(evlist, evsel) {
count++;
}
return count;
}
static int test__pfm_events(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct evlist *evlist;
struct option opt;
size_t i;
const struct {
const char *events;
int nr_events;
} table[] = {
{
.events = "",
.nr_events = 0,
},
{
.events = "instructions",
.nr_events = 1,
},
{
.events = "instructions,cycles",
.nr_events = 2,
},
{
.events = "stereolab",
.nr_events = 0,
},
{
.events = "instructions,instructions",
.nr_events = 2,
},
{
.events = "stereolab,instructions",
.nr_events = 0,
},
{
.events = "instructions,stereolab",
.nr_events = 1,
},
};
for (i = 0; i < ARRAY_SIZE(table); i++) {
evlist = evlist__new();
if (evlist == NULL)
return -ENOMEM;
opt.value = evlist;
parse_libpfm_events_option(&opt,
table[i].events,
0);
TEST_ASSERT_EQUAL(table[i].events,
count_pfm_events(&evlist->core),
table[i].nr_events);
TEST_ASSERT_EQUAL(table[i].events,
evlist__nr_groups(evlist),
0);
evlist__delete(evlist);
}
return 0;
}
static int test__pfm_group(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct evlist *evlist;
struct option opt;
size_t i;
const struct {
const char *events;
int nr_events;
int nr_groups;
} table[] = {
{
.events = "{},",
.nr_events = 0,
.nr_groups = 0,
},
{
.events = "{instructions}",
.nr_events = 1,
.nr_groups = 0,
},
{
.events = "{instructions},{}",
.nr_events = 1,
.nr_groups = 0,
},
{
.events = "{},{instructions}",
.nr_events = 1,
.nr_groups = 0,
},
{
.events = "{instructions},{instructions}",
.nr_events = 2,
.nr_groups = 0,
},
{
.events = "{instructions,cycles},{instructions,cycles}",
.nr_events = 4,
.nr_groups = 2,
},
{
.events = "{stereolab}",
.nr_events = 0,
.nr_groups = 0,
},
{
.events =
"{instructions,cycles},{instructions,stereolab}",
.nr_events = 3,
.nr_groups = 1,
},
{
.events = "instructions}",
.nr_events = 1,
.nr_groups = 0,
},
{
.events = "{{instructions}}",
.nr_events = 0,
.nr_groups = 0,
},
};
for (i = 0; i < ARRAY_SIZE(table); i++) {
evlist = evlist__new();
if (evlist == NULL)
return -ENOMEM;
opt.value = evlist;
parse_libpfm_events_option(&opt,
table[i].events,
0);
TEST_ASSERT_EQUAL(table[i].events,
count_pfm_events(&evlist->core),
table[i].nr_events);
TEST_ASSERT_EQUAL(table[i].events,
evlist__nr_groups(evlist),
table[i].nr_groups);
evlist__delete(evlist);
}
return 0;
}
#else
static int test__pfm_events(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return TEST_SKIP;
}
static int test__pfm_group(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
return TEST_SKIP;
}
#endif
static struct test_case pfm_tests[] = {
TEST_CASE_REASON("test of individual --pfm-events", pfm_events, "not compiled in"),
TEST_CASE_REASON("test groups of --pfm-events", pfm_group, "not compiled in"),
{ .name = NULL, }
};
struct test_suite suite__pfm = {
.desc = "Test libpfm4 support",
.test_cases = pfm_tests,
};
File diff suppressed because it is too large Load Diff
+646
View File
@@ -0,0 +1,646 @@
// SPDX-License-Identifier: GPL-2.0
#include "evlist.h"
#include "evsel.h"
#include "parse-events.h"
#include "pmu.h"
#include "pmus.h"
#include "tests.h"
#include "debug.h"
#include "fncache.h"
#include <api/fs/fs.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
/* Cleanup test PMU directory. */
static int test_pmu_put(const char *dir, struct perf_pmu *pmu)
{
char buf[PATH_MAX + 20];
int ret;
if (scnprintf(buf, sizeof(buf), "rm -fr %s", dir) < 0) {
pr_err("Failure to set up buffer for \"%s\"\n", dir);
return -EINVAL;
}
ret = system(buf);
if (ret)
pr_err("Failure to \"%s\"\n", buf);
list_del(&pmu->list);
perf_pmu__delete(pmu);
return ret;
}
/*
* Prepare test PMU directory data, normally exported by kernel at
* /sys/bus/event_source/devices/<pmu>/. Give as input a buffer to hold the file
* path, the result is PMU loaded using that directory.
*/
static struct perf_pmu *test_pmu_get(char *dir, size_t sz)
{
/* Simulated format definitions. */
const struct test_format {
const char *name;
const char *value;
} test_formats[] = {
{ "krava01", "config:0-1,62-63\n", },
{ "krava02", "config:10-17\n", },
{ "krava03", "config:5\n", },
{ "krava11", "config1:0,2,4,6,8,20-28\n", },
{ "krava12", "config1:63\n", },
{ "krava13", "config1:45-47\n", },
{ "krava21", "config2:0-3,10-13,20-23,30-33,40-43,50-53,60-63\n", },
{ "krava22", "config2:8,18,48,58\n", },
{ "krava23", "config2:28-29,38\n", },
};
const char *test_event = "krava01=15,krava02=170,krava03=1,krava11=27,krava12=1,"
"krava13=2,krava21=119,krava22=11,krava23=2\n";
char name[PATH_MAX];
int dirfd, file;
struct perf_pmu *pmu = NULL;
ssize_t len;
/* Create equivalent of sysfs mount point. */
scnprintf(dir, sz, "/tmp/perf-pmu-test-XXXXXX");
if (!mkdtemp(dir)) {
pr_err("mkdtemp failed\n");
dir[0] = '\0';
return NULL;
}
dirfd = open(dir, O_DIRECTORY);
if (dirfd < 0) {
pr_err("Failed to open test directory \"%s\"\n", dir);
goto err_out;
}
/* Create the test PMU directory and give it a perf_event_attr type number. */
if (mkdirat(dirfd, "perf-pmu-test", 0755) < 0) {
pr_err("Failed to mkdir PMU directory\n");
goto err_out;
}
file = openat(dirfd, "perf-pmu-test/type", O_WRONLY | O_CREAT, 0600);
if (!file) {
pr_err("Failed to open for writing file \"type\"\n");
goto err_out;
}
len = strlen("9999");
if (write(file, "9999\n", len) < len) {
close(file);
pr_err("Failed to write to 'type' file\n");
goto err_out;
}
close(file);
/* Create format directory and files. */
if (mkdirat(dirfd, "perf-pmu-test/format", 0755) < 0) {
pr_err("Failed to mkdir PMU format directory\n)");
goto err_out;
}
for (size_t i = 0; i < ARRAY_SIZE(test_formats); i++) {
const struct test_format *format = &test_formats[i];
if (scnprintf(name, PATH_MAX, "perf-pmu-test/format/%s", format->name) < 0) {
pr_err("Failure to set up path for \"%s\"\n", format->name);
goto err_out;
}
file = openat(dirfd, name, O_WRONLY | O_CREAT, 0600);
if (!file) {
pr_err("Failed to open for writing file \"%s\"\n", name);
goto err_out;
}
if (write(file, format->value, strlen(format->value)) < 0) {
pr_err("Failed to write to file \"%s\"\n", name);
close(file);
goto err_out;
}
close(file);
}
/* Create test event. */
if (mkdirat(dirfd, "perf-pmu-test/events", 0755) < 0) {
pr_err("Failed to mkdir PMU events directory\n");
goto err_out;
}
file = openat(dirfd, "perf-pmu-test/events/test-event", O_WRONLY | O_CREAT, 0600);
if (!file) {
pr_err("Failed to open for writing file \"type\"\n");
goto err_out;
}
len = strlen(test_event);
if (write(file, test_event, len) < len) {
close(file);
pr_err("Failed to write to 'test-event' file\n");
goto err_out;
}
close(file);
/* Make the PMU reading the files created above. */
pmu = perf_pmus__add_test_pmu(dirfd, "perf-pmu-test");
if (!pmu)
pr_err("Test PMU creation failed\n");
err_out:
if (!pmu)
test_pmu_put(dir, pmu);
if (dirfd >= 0)
close(dirfd);
return pmu;
}
static int test__pmu_format(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
char dir[PATH_MAX];
struct perf_event_attr attr;
struct parse_events_terms terms;
int ret = TEST_FAIL;
struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
if (!pmu)
return TEST_FAIL;
parse_events_terms__init(&terms);
if (parse_events_terms(&terms,
"krava01=15,krava02=170,krava03=1,krava11=27,krava12=1,"
"krava13=2,krava21=119,krava22=11,krava23=2")) {
pr_err("Term parsing failed\n");
goto err_out;
}
memset(&attr, 0, sizeof(attr));
ret = perf_pmu__config_terms(pmu, &attr, &terms, /*zero=*/false,
/*apply_hardcoded=*/false, /*err=*/NULL);
if (ret) {
pr_err("perf_pmu__config_terms failed");
goto err_out;
}
if (attr.config != 0xc00000000002a823) {
pr_err("Unexpected config value %llx\n", attr.config);
goto err_out;
}
if (attr.config1 != 0x8000400000000145) {
pr_err("Unexpected config1 value %llx\n", attr.config1);
goto err_out;
}
if (attr.config2 != 0x0400000020041d07) {
pr_err("Unexpected config2 value %llx\n", attr.config2);
}
ret = TEST_OK;
err_out:
parse_events_terms__exit(&terms);
test_pmu_put(dir, pmu);
return ret;
}
static int test__pmu_config_helpers(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
const char *event = "perf-pmu-test/config=15,config1=4,krava02=170,"
"krava03=1,krava11=27,krava12=1/";
struct parse_events_terms terms;
struct parse_events_error err;
LIST_HEAD(config_terms);
struct evlist *evlist;
struct perf_pmu *pmu;
struct evsel *evsel;
int ret = TEST_FAIL;
char dir[PATH_MAX];
u64 val;
pmu = test_pmu_get(dir, sizeof(dir));
if (!pmu)
return TEST_FAIL;
evlist = evlist__new();
if (evlist == NULL) {
pr_err("Failed allocation");
goto err_out;
}
parse_events_terms__init(&terms);
ret = parse_events(evlist, event, &err);
if (ret) {
pr_debug("failed to parse event '%s', err %d\n", event, ret);
parse_events_error__print(&err, event);
if (parse_events_error__contains(&err, "can't access trace events"))
ret = TEST_SKIP;
goto err_out;
}
evsel = evlist__first(evlist);
/* Test evsel__config_exists() */
TEST_ASSERT_EQUAL("krava01 should exist",
evsel__config_exists(evsel, "krava01"), true);
TEST_ASSERT_EQUAL("krava99 should not exist",
evsel__config_exists(evsel, "krava99"), false);
/*
* Set via config=15, krava01 bits 0-1
* Set via config1=4, krava11 bit 1
* Set values: krava02=170, krava03=1, krava11=27, krava12=1
*
* Test that already set values aren't overwritten.
*/
evsel__set_config_if_unset(evsel, "krava01", 16);
evsel__get_config_val(evsel, "krava01", &val);
TEST_ASSERT_EQUAL("krava01 overwritten", (int) val, (15 & 0b11));
evsel__set_config_if_unset(evsel, "krava11", 45);
evsel__get_config_val(evsel, "krava11", &val);
TEST_ASSERT_EQUAL("krava11 overwritten", (int) val, (27 | (4 << 1)));
evsel__set_config_if_unset(evsel, "krava02", 32);
evsel__get_config_val(evsel, "krava02", &val);
TEST_ASSERT_EQUAL("krava02 overwritten", (int) val, 170);
evsel__set_config_if_unset(evsel, "krava03", 0);
evsel__get_config_val(evsel, "krava03", &val);
TEST_ASSERT_EQUAL("krava03 overwritten", (int) val, 1);
/*
* krava13 doesn't have any bits set by either krava13= or config1=
* but setting _any_ raw value for config1 implies that krava13
* shouldn't be overwritten. So it's value should remain as 0.
*/
evsel__set_config_if_unset(evsel, "krava13", 5);
evsel__get_config_val(evsel, "krava13", &val);
TEST_ASSERT_EQUAL("krava13 overwritten", (int) val, 0);
/*
* Unset values: krava21, krava22, krava23
*
* Test that unset values are overwritten.
*/
evsel__set_config_if_unset(evsel, "krava21", 13905);
evsel__get_config_val(evsel, "krava21", &val);
TEST_ASSERT_EQUAL("krava21 not overwritten", (int) val, 13905);
evsel__set_config_if_unset(evsel, "krava22", 11);
evsel__get_config_val(evsel, "krava22", &val);
TEST_ASSERT_EQUAL("krava22 not overwritten", (int) val, 11);
evsel__set_config_if_unset(evsel, "krava23", 0);
evsel__get_config_val(evsel, "krava23", &val);
TEST_ASSERT_EQUAL("krava23 not overwritten", (int) val, 0);
ret = TEST_OK;
err_out:
parse_events_terms__exit(&terms);
evlist__delete(evlist);
test_pmu_put(dir, pmu);
return ret;
}
static int test__pmu_events(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
char dir[PATH_MAX];
struct parse_events_error err;
struct evlist *evlist;
struct evsel *evsel;
struct perf_event_attr *attr;
int ret = TEST_FAIL;
struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
const char *event = "perf-pmu-test/test-event/";
if (!pmu)
return TEST_FAIL;
evlist = evlist__new();
if (evlist == NULL) {
pr_err("Failed allocation");
goto err_out;
}
parse_events_error__init(&err);
ret = parse_events(evlist, event, &err);
if (ret) {
pr_debug("failed to parse event '%s', err %d\n", event, ret);
parse_events_error__print(&err, event);
if (parse_events_error__contains(&err, "can't access trace events"))
ret = TEST_SKIP;
goto err_out;
}
evsel = evlist__first(evlist);
attr = &evsel->core.attr;
if (attr->config != 0xc00000000002a823) {
pr_err("Unexpected config value %llx\n", attr->config);
goto err_out;
}
if (attr->config1 != 0x8000400000000145) {
pr_err("Unexpected config1 value %llx\n", attr->config1);
goto err_out;
}
if (attr->config2 != 0x0400000020041d07) {
pr_err("Unexpected config2 value %llx\n", attr->config2);
goto err_out;
}
ret = TEST_OK;
err_out:
parse_events_error__exit(&err);
evlist__delete(evlist);
test_pmu_put(dir, pmu);
return ret;
}
static bool permitted_event_name(const char *name)
{
bool has_lower = false, has_upper = false;
__u64 config;
for (size_t i = 0; i < strlen(name); i++) {
char c = name[i];
if (islower(c)) {
if (has_upper)
goto check_legacy;
has_lower = true;
continue;
}
if (isupper(c)) {
if (has_lower)
goto check_legacy;
has_upper = true;
continue;
}
if (!isdigit(c) && c != '.' && c != '_' && c != '-')
goto check_legacy;
}
return true;
check_legacy:
/*
* If the event name matches a legacy cache name the legacy encoding
* will still be used. This isn't quite WAI as sysfs events should take
* priority, but this case happens on PowerPC and matches the behavior
* in older perf tools where legacy events were the priority. Be
* permissive and assume later PMU drivers will use all lower or upper
* case names.
*/
if (parse_events__decode_legacy_cache(name, /*extended_pmu_type=*/0, &config) == 0) {
pr_warning("sysfs event '%s' should be all lower/upper case, it will be matched using legacy encoding.",
name);
return true;
}
return false;
}
static int test__pmu_event_names(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
char path[PATH_MAX];
DIR *pmu_dir, *event_dir;
struct dirent *pmu_dent, *event_dent;
const char *sysfs = sysfs__mountpoint();
int ret = TEST_OK;
if (!sysfs) {
pr_err("Sysfs not mounted\n");
return TEST_FAIL;
}
snprintf(path, sizeof(path), "%s/bus/event_source/devices/", sysfs);
pmu_dir = opendir(path);
if (!pmu_dir) {
pr_err("Error opening \"%s\"\n", path);
return TEST_FAIL;
}
while ((pmu_dent = readdir(pmu_dir))) {
if (!strcmp(pmu_dent->d_name, ".") ||
!strcmp(pmu_dent->d_name, ".."))
continue;
snprintf(path, sizeof(path), "%s/bus/event_source/devices/%s/type",
sysfs, pmu_dent->d_name);
/* Does it look like a PMU? */
if (!file_available(path))
continue;
/* Process events. */
snprintf(path, sizeof(path), "%s/bus/event_source/devices/%s/events",
sysfs, pmu_dent->d_name);
event_dir = opendir(path);
if (!event_dir) {
pr_debug("Skipping as no event directory \"%s\"\n", path);
continue;
}
while ((event_dent = readdir(event_dir))) {
const char *event_name = event_dent->d_name;
if (!strcmp(event_name, ".") || !strcmp(event_name, ".."))
continue;
if (!permitted_event_name(event_name)) {
pr_err("Invalid sysfs event name: %s/%s\n",
pmu_dent->d_name, event_name);
ret = TEST_FAIL;
}
}
closedir(event_dir);
}
closedir(pmu_dir);
return ret;
}
static const char * const uncore_chas[] = {
"uncore_cha_0",
"uncore_cha_1",
"uncore_cha_2",
"uncore_cha_3",
"uncore_cha_4",
"uncore_cha_5",
"uncore_cha_6",
"uncore_cha_7",
"uncore_cha_8",
"uncore_cha_9",
"uncore_cha_10",
"uncore_cha_11",
"uncore_cha_12",
"uncore_cha_13",
"uncore_cha_14",
"uncore_cha_15",
"uncore_cha_16",
"uncore_cha_17",
"uncore_cha_18",
"uncore_cha_19",
"uncore_cha_20",
"uncore_cha_21",
"uncore_cha_22",
"uncore_cha_23",
"uncore_cha_24",
"uncore_cha_25",
"uncore_cha_26",
"uncore_cha_27",
"uncore_cha_28",
"uncore_cha_29",
"uncore_cha_30",
"uncore_cha_31",
};
static const char * const mrvl_ddrs[] = {
"mrvl_ddr_pmu_87e1b0000000",
"mrvl_ddr_pmu_87e1b1000000",
"mrvl_ddr_pmu_87e1b2000000",
"mrvl_ddr_pmu_87e1b3000000",
"mrvl_ddr_pmu_87e1b4000000",
"mrvl_ddr_pmu_87e1b5000000",
"mrvl_ddr_pmu_87e1b6000000",
"mrvl_ddr_pmu_87e1b7000000",
"mrvl_ddr_pmu_87e1b8000000",
"mrvl_ddr_pmu_87e1b9000000",
"mrvl_ddr_pmu_87e1ba000000",
"mrvl_ddr_pmu_87e1bb000000",
"mrvl_ddr_pmu_87e1bc000000",
"mrvl_ddr_pmu_87e1bd000000",
"mrvl_ddr_pmu_87e1be000000",
"mrvl_ddr_pmu_87e1bf000000",
};
static int test__name_len(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
TEST_ASSERT_VAL("cpu", pmu_name_len_no_suffix("cpu") == strlen("cpu"));
TEST_ASSERT_VAL("i915", pmu_name_len_no_suffix("i915") == strlen("i915"));
TEST_ASSERT_VAL("cpum_cf", pmu_name_len_no_suffix("cpum_cf") == strlen("cpum_cf"));
for (size_t i = 0; i < ARRAY_SIZE(uncore_chas); i++) {
TEST_ASSERT_VAL("Strips uncore_cha suffix",
pmu_name_len_no_suffix(uncore_chas[i]) ==
strlen("uncore_cha"));
}
for (size_t i = 0; i < ARRAY_SIZE(mrvl_ddrs); i++) {
TEST_ASSERT_VAL("Strips mrvl_ddr_pmu suffix",
pmu_name_len_no_suffix(mrvl_ddrs[i]) ==
strlen("mrvl_ddr_pmu"));
}
return TEST_OK;
}
static int test__name_cmp(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
TEST_ASSERT_EQUAL("cpu", pmu_name_cmp("cpu", "cpu"), 0);
TEST_ASSERT_EQUAL("i915", pmu_name_cmp("i915", "i915"), 0);
TEST_ASSERT_EQUAL("cpum_cf", pmu_name_cmp("cpum_cf", "cpum_cf"), 0);
TEST_ASSERT_VAL("i915", pmu_name_cmp("cpu", "i915") < 0);
TEST_ASSERT_VAL("i915", pmu_name_cmp("i915", "cpu") > 0);
TEST_ASSERT_VAL("cpum_cf", pmu_name_cmp("cpum_cf", "cpum_ce") > 0);
TEST_ASSERT_VAL("cpum_cf", pmu_name_cmp("cpum_cf", "cpum_d0") < 0);
for (size_t i = 1; i < ARRAY_SIZE(uncore_chas); i++) {
TEST_ASSERT_VAL("uncore_cha suffixes ordered lt",
pmu_name_cmp(uncore_chas[i-1], uncore_chas[i]) < 0);
TEST_ASSERT_VAL("uncore_cha suffixes ordered gt",
pmu_name_cmp(uncore_chas[i], uncore_chas[i-1]) > 0);
}
for (size_t i = 1; i < ARRAY_SIZE(mrvl_ddrs); i++) {
TEST_ASSERT_VAL("mrvl_ddr_pmu suffixes ordered lt",
pmu_name_cmp(mrvl_ddrs[i-1], mrvl_ddrs[i]) < 0);
TEST_ASSERT_VAL("mrvl_ddr_pmu suffixes ordered gt",
pmu_name_cmp(mrvl_ddrs[i], mrvl_ddrs[i-1]) > 0);
}
return TEST_OK;
}
/**
* Test perf_pmu__wildcard_match() that's used to search for a PMU given a name passed
* on the command line. The name that's passed may also be a filename type glob
* match. If the name does not match, perf_pmu__wildcard_match() attempts to match the
* alias of the PMU, if provided.
*/
static int test__pmu_match(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
struct perf_pmu test_pmu = {
.name = "pmuname",
};
#define TEST_PMU_MATCH(msg, to_match, expect) \
TEST_ASSERT_EQUAL(msg, perf_pmu__wildcard_match(&test_pmu, to_match), expect)
TEST_PMU_MATCH("Exact match", "pmuname", true);
TEST_PMU_MATCH("Longer token", "longertoken", false);
TEST_PMU_MATCH("Shorter token", "pmu", false);
test_pmu.name = "pmuname_10";
TEST_PMU_MATCH("Diff suffix_", "pmuname_2", false);
TEST_PMU_MATCH("Sub suffix_", "pmuname_1", true);
TEST_PMU_MATCH("Same suffix_", "pmuname_10", true);
TEST_PMU_MATCH("No suffix_", "pmuname", true);
TEST_PMU_MATCH("Underscore_", "pmuname_", true);
TEST_PMU_MATCH("Substring_", "pmuna", false);
test_pmu.name = "pmuname_ab23";
TEST_PMU_MATCH("Diff suffix hex_", "pmuname_2", false);
TEST_PMU_MATCH("Sub suffix hex_", "pmuname_ab", true);
TEST_PMU_MATCH("Same suffix hex_", "pmuname_ab23", true);
TEST_PMU_MATCH("No suffix hex_", "pmuname", true);
TEST_PMU_MATCH("Underscore hex_", "pmuname_", true);
TEST_PMU_MATCH("Substring hex_", "pmuna", false);
test_pmu.name = "pmuname10";
TEST_PMU_MATCH("Diff suffix", "pmuname2", false);
TEST_PMU_MATCH("Sub suffix", "pmuname1", true);
TEST_PMU_MATCH("Same suffix", "pmuname10", true);
TEST_PMU_MATCH("No suffix", "pmuname", true);
TEST_PMU_MATCH("Underscore", "pmuname_", false);
TEST_PMU_MATCH("Substring", "pmuna", false);
test_pmu.name = "pmunameab23";
TEST_PMU_MATCH("Diff suffix hex", "pmuname2", false);
TEST_PMU_MATCH("Sub suffix hex", "pmunameab", true);
TEST_PMU_MATCH("Same suffix hex", "pmunameab23", true);
TEST_PMU_MATCH("No suffix hex", "pmuname", true);
TEST_PMU_MATCH("Underscore hex", "pmuname_", false);
TEST_PMU_MATCH("Substring hex", "pmuna", false);
/*
* 2 hex chars or less are not considered suffixes so it shouldn't be
* possible to wildcard by skipping the suffix. Therefore there are more
* false results here than above.
*/
test_pmu.name = "pmuname_a3";
TEST_PMU_MATCH("Diff suffix 2 hex_", "pmuname_2", false);
/*
* This one should be false, but because pmuname_a3 ends in 3 which is
* decimal, it's not possible to determine if it's a short hex suffix or
* a normal decimal suffix following text. And we want to match on any
* length of decimal suffix. Run the test anyway and expect the wrong
* result. And slightly fuzzy matching shouldn't do too much harm.
*/
TEST_PMU_MATCH("Sub suffix 2 hex_", "pmuname_a", true);
TEST_PMU_MATCH("Same suffix 2 hex_", "pmuname_a3", true);
TEST_PMU_MATCH("No suffix 2 hex_", "pmuname", false);
TEST_PMU_MATCH("Underscore 2 hex_", "pmuname_", false);
TEST_PMU_MATCH("Substring 2 hex_", "pmuna", false);
test_pmu.name = "pmuname_5";
TEST_PMU_MATCH("Glob 1", "pmu*", true);
TEST_PMU_MATCH("Glob 2", "nomatch*", false);
TEST_PMU_MATCH("Seq 1", "pmuname_[12345]", true);
TEST_PMU_MATCH("Seq 2", "pmuname_[67890]", false);
TEST_PMU_MATCH("? 1", "pmuname_?", true);
TEST_PMU_MATCH("? 2", "pmuname_1?", false);
return TEST_OK;
}
static struct test_case tests__pmu[] = {
TEST_CASE("Parsing with PMU format directory", pmu_format),
TEST_CASE("Parsing with PMU event", pmu_events),
TEST_CASE("PMU event names", pmu_event_names),
TEST_CASE("PMU name combining", name_len),
TEST_CASE("PMU name comparison", name_cmp),
TEST_CASE("PMU cmdline match", pmu_match),
TEST_CASE("PMU config helpers", pmu_config_helpers),
{ .name = NULL, }
};
struct test_suite suite__pmu = {
.desc = "Sysfs PMU tests",
.test_cases = tests__pmu,
};
@@ -0,0 +1,461 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdbool.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include "map_symbol.h"
#include "branch.h"
#include "event.h"
#include "evsel.h"
#include "debug.h"
#include "util/synthetic-events.h"
#include "util/util.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)
/*
* Hardcode the expected values for branch_entry flags.
* These are based on the input value (213) specified
* in branch_stack variable.
*/
#define BS_EXPECTED_BE 0xa000d00000000000
#define BS_EXPECTED_LE 0x1aa00000000
#define FLAG(s) s->branch_stack->entries[i].flags
static bool samples_same(struct perf_sample *s1,
struct perf_sample *s2,
u64 type, u64 read_format, bool needs_swap)
{
size_t i;
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++) {
/* FIXME: check values without LOST */
if (read_format & PERF_FORMAT_LOST)
MCOMP(read.group.values[i]);
}
} else {
COMP(read.one.id);
if (read_format & PERF_FORMAT_LOST)
COMP(read.one.lost);
}
}
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);
COMP(branch_stack->hw_idx);
for (i = 0; i < s1->branch_stack->nr; i++) {
if (needs_swap)
return ((host_is_bigendian()) ?
(FLAG(s2).value == BS_EXPECTED_BE) :
(FLAG(s2).value == BS_EXPECTED_LE));
else
MCOMP(branch_stack->entries[i]);
}
}
if (type & PERF_SAMPLE_REGS_USER) {
struct regs_dump *s1_regs = perf_sample__user_regs(s1);
struct regs_dump *s2_regs = perf_sample__user_regs(s2);
size_t sz = hweight_long(s1_regs->mask) * sizeof(u64);
COMP(user_regs->mask);
COMP(user_regs->abi);
if (s1_regs->abi &&
(!s1_regs->regs || !s2_regs->regs ||
memcmp(s1_regs->regs, s2_regs->regs, sz))) {
pr_debug("Samples differ at 'user_regs'\n");
return false;
}
}
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_WEIGHT_STRUCT) {
COMP(weight);
COMP(ins_lat);
COMP(weight3);
}
if (type & PERF_SAMPLE_DATA_SRC)
COMP(data_src);
if (type & PERF_SAMPLE_TRANSACTION)
COMP(transaction);
if (type & PERF_SAMPLE_REGS_INTR) {
struct regs_dump *s1_regs = perf_sample__intr_regs(s1);
struct regs_dump *s2_regs = perf_sample__intr_regs(s2);
size_t sz = hweight_long(s1_regs->mask) * sizeof(u64);
COMP(intr_regs->mask);
COMP(intr_regs->abi);
if (s1_regs->abi &&
(!s1_regs->regs || !s2_regs->regs ||
memcmp(s1_regs->regs, s2_regs->regs, sz))) {
pr_debug("Samples differ at 'intr_regs'\n");
return false;
}
}
if (type & PERF_SAMPLE_PHYS_ADDR)
COMP(phys_addr);
if (type & PERF_SAMPLE_CGROUP)
COMP(cgroup);
if (type & PERF_SAMPLE_DATA_PAGE_SIZE)
COMP(data_page_size);
if (type & PERF_SAMPLE_CODE_PAGE_SIZE)
COMP(code_page_size);
if (type & PERF_SAMPLE_AUX) {
COMP(aux_sample.size);
if (memcmp(s1->aux_sample.data, s2->aux_sample.data,
s1->aux_sample.size)) {
pr_debug("Samples differ at 'aux_sample'\n");
return false;
}
}
return true;
}
static int do_test(u64 sample_type, u64 sample_regs, u64 read_format)
{
struct evsel evsel = {
.needs_swap = false,
.core = {
. 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, -1ULL, 211, 212, 213},
};
u64 regs[64];
const u32 raw_data[] = {0x12345678, 0x0a0b0c0d, 0x11020304, 0x05060708, 0 };
const u64 data[] = {0x2211443366558877ULL, 0, 0xaabbccddeeff4321ULL};
const u64 aux_data[] = {0xa55a, 0, 0xeeddee, 0x0282028202820282};
struct regs_dump user_regs = {
.abi = PERF_SAMPLE_REGS_ABI_64,
.mask = sample_regs,
.regs = regs,
};
struct regs_dump intr_regs = {
.abi = PERF_SAMPLE_REGS_ABI_64,
.mask = sample_regs,
.regs = regs,
};
struct perf_sample sample = {
.ip = 101,
.pid = 102,
.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,
.no_hw_idx = false,
.branch_stack = &branch_stack.branch_stack,
.user_regs = &user_regs,
.user_stack = {
.size = sizeof(data),
.data = (void *)data,
},
.read = {
.time_enabled = 0x030a59d664fca7deULL,
.time_running = 0x011b6ae553eb98edULL,
},
.intr_regs = &intr_regs,
.phys_addr = 113,
.cgroup = 114,
.data_page_size = 115,
.code_page_size = 116,
.ins_lat = 117,
.weight3 = 118,
.aux_sample = {
.size = sizeof(aux_data),
.data = (void *)aux_data,
},
};
struct sample_read_value values[] = {{1, 5, 0}, {9, 3, 0}, {2, 7, 0}, {6, 4, 1},};
struct perf_sample sample_out, sample_out_endian;
size_t i, sz, bufsz;
int err, ret = -1;
perf_sample__init(&sample_out, /*all=*/false);
perf_sample__init(&sample_out_endian, /*all=*/false);
if (sample_type & PERF_SAMPLE_REGS_USER)
evsel.core.attr.sample_regs_user = sample_regs;
if (sample_type & PERF_SAMPLE_REGS_INTR)
evsel.core.attr.sample_regs_intr = sample_regs;
if (sample_type & PERF_SAMPLE_BRANCH_STACK)
evsel.core.attr.branch_sample_type |= PERF_SAMPLE_BRANCH_HW_INDEX;
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;
sample.read.one.lost = 1;
}
sz = perf_event__sample_event_size(&sample, sample_type, read_format,
evsel.core.attr.branch_sample_type);
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,
evsel.core.attr.branch_sample_type, &sample);
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 = __evsel__sample_size(sample_type);
err = evsel__parse_sample(&evsel, event, &sample_out);
if (err) {
pr_debug("%s failed for sample_type %#"PRIx64", error %d\n",
"evsel__parse_sample", sample_type, err);
goto out_free;
}
if (!samples_same(&sample, &sample_out, sample_type, read_format, evsel.needs_swap)) {
pr_debug("parsing failed for sample_type %#"PRIx64"\n",
sample_type);
goto out_free;
}
if (sample_type == PERF_SAMPLE_BRANCH_STACK) {
evsel.needs_swap = true;
evsel.sample_size = __evsel__sample_size(sample_type);
err = evsel__parse_sample(&evsel, event, &sample_out_endian);
if (err) {
pr_debug("%s failed for sample_type %#"PRIx64", error %d\n",
"evsel__parse_sample", sample_type, err);
goto out_free;
}
if (!samples_same(&sample, &sample_out_endian, sample_type, read_format, evsel.needs_swap)) {
pr_debug("parsing failed for sample_type %#"PRIx64"\n",
sample_type);
goto out_free;
}
}
ret = 0;
out_free:
free(event);
perf_sample__exit(&sample_out_endian);
perf_sample__exit(&sample_out);
if (ret && read_format)
pr_debug("read_format %#"PRIx64"\n", read_format);
return ret;
}
/**
* 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.
*/
static int test__sample_parsing(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
const u64 rf[] = {4, 5, 6, 7, 12, 13, 14, 15, 20, 21, 22, 28, 29, 30, 31};
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_WEIGHT_STRUCT << 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
* Note: PERF_SAMPLE_WEIGHT and PERF_SAMPLE_WEIGHT_STRUCT cannot
* be set simultaneously.
*/
sample_type = (PERF_SAMPLE_MAX - 1) & ~PERF_SAMPLE_WEIGHT;
sample_regs = 0x3fff; /* shared yb intr and user regs */
for (i = 0; i < ARRAY_SIZE(rf); i++) {
err = do_test(sample_type, sample_regs, rf[i]);
if (err)
return err;
}
sample_type = (PERF_SAMPLE_MAX - 1) & ~PERF_SAMPLE_WEIGHT_STRUCT;
for (i = 0; i < ARRAY_SIZE(rf); i++) {
err = do_test(sample_type, sample_regs, rf[i]);
if (err)
return err;
}
return 0;
}
DEFINE_SUITE("Sample parsing", sample_parsing);
+124
View File
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <util/symbol.h>
#include <linux/filter.h>
#include "tests.h"
#include "debug.h"
#include "probe-file.h"
#include "build-id.h"
#include "util.h"
/* To test SDT event, we need libelf support to scan elf binary */
#if defined(HAVE_SDT_EVENT) && defined(HAVE_LIBELF_SUPPORT)
#include <sys/sdt.h>
static int target_function(void)
{
DTRACE_PROBE(perf, test_target);
return TEST_OK;
}
/* Copied from builtin-buildid-cache.c */
static int build_id_cache__add_file(const char *filename)
{
char sbuild_id[SBUILD_ID_SIZE];
struct build_id bid = { .size = 0, };
int err;
err = filename__read_build_id(filename, &bid);
if (err < 0) {
pr_debug("Failed to read build id of %s\n", filename);
return err;
}
build_id__snprintf(&bid, sbuild_id, sizeof(sbuild_id));
err = build_id_cache__add_s(sbuild_id, filename, NULL, false, false);
if (err < 0)
pr_debug("Failed to add build id cache of %s\n", filename);
return err;
}
static char *get_self_path(void)
{
char *buf = calloc(PATH_MAX, sizeof(char));
if (buf && readlink("/proc/self/exe", buf, PATH_MAX - 1) < 0) {
pr_debug("Failed to get correct path of perf\n");
free(buf);
return NULL;
}
return buf;
}
static int search_cached_probe(const char *target,
const char *group, const char *event)
{
struct probe_cache *cache = probe_cache__new(target, NULL);
int ret = 0;
if (!cache) {
pr_debug("Failed to open probe cache of %s\n", target);
return -EINVAL;
}
if (!probe_cache__find_by_name(cache, group, event)) {
pr_debug("Failed to find %s:%s in the cache\n", group, event);
ret = -ENOENT;
}
probe_cache__delete(cache);
return ret;
}
static int test__sdt_event(struct test_suite *test __maybe_unused, int subtests __maybe_unused)
{
int ret = TEST_FAIL;
char __tempdir[] = "./test-buildid-XXXXXX";
char *tempdir = NULL, *myself = get_self_path();
if (myself == NULL || mkdtemp(__tempdir) == NULL) {
pr_debug("Failed to make a tempdir for build-id cache\n");
goto error;
}
/* Note that buildid_dir must be an absolute path */
tempdir = realpath(__tempdir, NULL);
if (tempdir == NULL)
goto error_rmdir;
/* At first, scan itself */
set_buildid_dir(tempdir);
if (build_id_cache__add_file(myself) < 0)
goto error_rmdir;
/* Open a cache and make sure the SDT is stored */
if (search_cached_probe(myself, "sdt_perf", "test_target") < 0)
goto error_rmdir;
/* TBD: probing on the SDT event and collect logs */
/* Call the target and get an event */
ret = target_function();
error_rmdir:
/* Cleanup temporary buildid dir */
rm_rf(__tempdir);
error:
free(tempdir);
free(myself);
return ret;
}
#else
static int test__sdt_event(struct test_suite *test __maybe_unused, int subtests __maybe_unused)
{
pr_debug("Skip SDT event test because SDT support is not compiled\n");
return TEST_SKIP;
}
#endif
DEFINE_SUITE("Probe SDT events", sdt_event);
+96
View File
@@ -0,0 +1,96 @@
#!/bin/bash
# test addr2line inline unwinding
# SPDX-License-Identifier: GPL-2.0
set -e
err=0
test_dir=$(mktemp -d /tmp/perf-test-inline-addr2line.XXXXXXXXXX)
perf_data="${test_dir}/perf.data"
perf_script_txt="${test_dir}/perf_script.txt"
cleanup() {
rm -rf "${test_dir}"
trap - EXIT TERM INT
}
trap_cleanup() {
echo "Unexpected signal in ${FUNCNAME[1]}"
cleanup
exit 1
}
trap trap_cleanup EXIT TERM INT
test_fp() {
echo "Inline unwinding fp verification test"
# Record data. Currently only dwarf callchains support inlined functions.
perf record --call-graph fp -e task-clock:u -o "${perf_data}" -- perf test -w inlineloop 1
# Check output with inline (default) and srcline
perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
# Expect the leaf and middle functions to occur on lines in the 20s, with
# the non-inlined parent function on a line in the 30s.
if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
grep -q "inlineloop.c:3.$" "${perf_script_txt}"
then
echo "Inline unwinding fp verification test [Success]"
else
echo "Inline unwinding fp verification test [Failed missing inlined functions]"
err=1
fi
}
test_dwarf() {
echo "Inline unwinding dwarf verification test"
# Record data. Currently only dwarf callchains support inlined functions.
perf record --call-graph dwarf -e task-clock:u -o "${perf_data}" -- perf test -w inlineloop 1
# Check output with inline (default) and srcline
perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
# Expect the leaf and middle functions to occur on lines in the 20s, with
# the non-inlined parent function on a line in the 30s.
if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
grep -q "inlineloop.c:3.$" "${perf_script_txt}"
then
echo "Inline unwinding dwarf verification test [Success]"
else
echo "Inline unwinding dwarf verification test [Failed missing inlined functions]"
err=1
fi
}
test_lbr() {
echo "Inline unwinding LBR verification test"
if [ ! -f /sys/bus/event_source/devices/cpu/caps/branches ] &&
[ ! -f /sys/bus/event_source/devices/cpu_core/caps/branches ]
then
echo "Skip: only x86 CPUs support LBR"
return
fi
# Record data. Currently only dwarf callchains support inlined functions.
perf record --call-graph lbr -e cycles:u -o "${perf_data}" -- perf test -w inlineloop 1
# Check output with inline (default) and srcline
perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
# Expect the leaf and middle functions to occur on lines in the 20s, with
# the non-inlined parent function on a line in the 30s.
if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
grep -q "inlineloop.c:3.$" "${perf_script_txt}"
then
echo "Inline unwinding lbr verification test [Success]"
else
echo "Inline unwinding lbr verification test [Failed missing inlined functions]"
err=1
fi
}
test_fp
test_dwarf
test_lbr
cleanup
exit $err
+121
View File
@@ -0,0 +1,121 @@
#!/bin/bash
# AMD IBS software filtering
cpu_family() {
grep -m1 '^cpu family[[:space:]]*:' /proc/cpuinfo \
| awk -F: '{gsub(/^[ \t]+/, "", $2); print $2}'
}
cpu_model() {
grep -m1 '^model[[:space:]]*:' /proc/cpuinfo \
| awk -F: '{gsub(/^[ \t]+/, "", $2); print $2}'
}
# IBS PMUs does not advertize privilege filtering capability. Rely
# on Family / Model check.
hw_priv_filter_supported() {
family=$(cpu_family)
model=$(cpu_model)
if (( family > 0x1a )) ||
{ (( family == 0x1a )) &&
{ { (( model >= 0x50 && model <= 0x5f )) ||
(( model >= 0x80 && model <= 0xaf )) ||
(( model >= 0xc0 && model <= 0xcf )); }; }; }; then
return 0 # True
else
return 1 # False
fi
}
ParanoidAndNotRoot() {
[ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
}
echo "check availability of IBS swfilt"
# check if IBS PMU is available
if [ ! -d /sys/bus/event_source/devices/ibs_op ]; then
echo "[SKIP] IBS PMU does not exist"
exit 2
fi
# check if IBS PMU has swfilt format
if [ ! -f /sys/bus/event_source/devices/ibs_op/format/swfilt ]; then
echo "[SKIP] IBS PMU does not have swfilt"
exit 2
fi
echo "run perf record with modifier and swfilt"
err=0
# setting any modifiers should fail
if ! hw_priv_filter_supported; then
perf record -B -e ibs_op//u -o /dev/null true 2> /dev/null
if [ $? -eq 0 ]; then
echo "[FAIL] IBS PMU should not accept exclude_kernel"
exit 1
fi
fi
# setting it with swfilt should be fine
perf record -B -e ibs_op/swfilt/u -o /dev/null true
if [ $? -ne 0 ]; then
echo "[FAIL] IBS op PMU cannot handle swfilt for exclude_kernel"
exit 1
fi
if ! ParanoidAndNotRoot 1
then
# setting it with swfilt=1 should be fine
perf record -B -e ibs_op/swfilt=1/k -o /dev/null true
if [ $? -ne 0 ]; then
echo "[FAIL] IBS op PMU cannot handle swfilt for exclude_user"
exit 1
fi
else
echo "[SKIP] not root and perf_event_paranoid too high for exclude_user"
err=2
fi
# check ibs_fetch PMU as well
perf record -B -e ibs_fetch/swfilt/u -o /dev/null true
if [ $? -ne 0 ]; then
echo "[FAIL] IBS fetch PMU cannot handle swfilt for exclude_kernel"
exit 1
fi
# check system wide recording
if ! ParanoidAndNotRoot 0
then
perf record -aB --synth=no -e ibs_op/swfilt/k -o /dev/null true
if [ $? -ne 0 ]; then
echo "[FAIL] IBS op PMU cannot handle swfilt in system-wide mode"
exit 1
fi
else
echo "[SKIP] not root and perf_event_paranoid too high for system-wide/exclude_user"
err=2
fi
echo "check number of samples with swfilt"
kernel_sample=$(perf record -e ibs_op/swfilt/u -o- true | perf script -i- -F misc | grep -c ^K)
if [ ${kernel_sample} -ne 0 ]; then
echo "[FAIL] unexpected kernel samples: " ${kernel_sample}
exit 1
fi
if ! ParanoidAndNotRoot 1
then
user_sample=$(perf record -e ibs_fetch/swfilt/k -o- true | perf script -i- -F misc | grep -c ^U)
if [ ${user_sample} -ne 0 ]; then
echo "[FAIL] unexpected user samples: " ${user_sample}
exit 1
fi
else
echo "[SKIP] not root and perf_event_paranoid too high for exclude_user"
err=2
fi
exit $err
+113
View File
@@ -0,0 +1,113 @@
#!/bin/bash
# perf annotate basic tests
# SPDX-License-Identifier: GPL-2.0
set -e
shelldir=$(dirname "$0")
# shellcheck source=lib/perf_has_symbol.sh
. "${shelldir}"/lib/perf_has_symbol.sh
testsym="noploop"
skip_test_missing_symbol ${testsym}
err=0
perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX)
testprog="perf test -w noploop"
# disassembly format: "percent : offset: instruction (operands ...)"
disasm_regex="[0-9]*\.[0-9]* *: *\w*: *\w*"
cleanup() {
rm -rf "${perfdata}" "${perfout}"
rm -rf "${perfdata}".old
trap - EXIT TERM INT
}
trap_cleanup() {
echo "Unexpected signal in ${FUNCNAME[1]}"
cleanup
exit 1
}
trap trap_cleanup EXIT TERM INT
test_basic() {
mode=$1
echo "${mode} perf annotate test"
if [ "x${mode}" == "xBasic" ]
then
perf record -o "${perfdata}" ${testprog} 2> /dev/null
else
perf record -o - ${testprog} 2> /dev/null > "${perfdata}"
fi
if [ "x$?" != "x0" ]
then
echo "${mode} annotate [Failed: perf record]"
err=1
return
fi
# Generate the annotated output file
if [ "x${mode}" == "xBasic" ]
then
perf annotate --no-demangle -i "${perfdata}" --stdio --percent-limit 10 2> /dev/null > "${perfout}"
else
perf annotate --no-demangle -i - --stdio 2> /dev/null --percent-limit 10 < "${perfdata}" > "${perfout}"
fi
# check if it has the target symbol
if ! grep -q "${testsym}" "${perfout}"
then
echo "${mode} annotate [Failed: missing target symbol]"
cat "${perfout}"
err=1
return
fi
# check if it has the disassembly lines
if ! grep -q "${disasm_regex}" "${perfout}"
then
echo "${mode} annotate [Failed: missing disasm output from default disassembler]"
err=1
return
fi
# check again with a target symbol name
if [ "x${mode}" == "xBasic" ]
then
perf annotate --no-demangle -i "${perfdata}" "${testsym}" 2> /dev/null > "${perfout}"
else
perf annotate --no-demangle -i - "${testsym}" 2> /dev/null < "${perfdata}" > "${perfout}"
fi
if ! head -250 "${perfout}"| grep -q -m 3 "${disasm_regex}"
then
echo "${mode} annotate [Failed: missing disasm output when specifying the target symbol]"
err=1
return
fi
# check one more with external objdump tool (forced by --objdump option)
if [ "x${mode}" == "xBasic" ]
then
perf annotate --no-demangle -i "${perfdata}" --percent-limit 10 --objdump=objdump 2> /dev/null > "${perfout}"
else
perf annotate --no-demangle -i - "${testsym}" --percent-limit 10 --objdump=objdump 2> /dev/null < "${perfdata}" > "${perfout}"
fi
if ! grep -q -m 3 "${disasm_regex}" "${perfout}"
then
echo "${mode} annotate [Failed: missing disasm output from non default disassembler (using --objdump)]"
err=1
return
fi
echo "${mode} annotate test [Success]"
}
test_basic Basic
test_basic Pipe
cleanup
exit $err
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# Perf attribute expectations test
# SPDX-License-Identifier: GPL-2.0
err=0
cleanup() {
trap - EXIT TERM INT
}
trap_cleanup() {
echo "Unexpected signal in ${FUNCNAME[1]}"
cleanup
exit 1
}
trap trap_cleanup EXIT TERM INT
shelldir=$(dirname "$0")
perf_path=$(which perf)
python "${shelldir}"/lib/attr.py -d "${shelldir}"/attr -v -p "$perf_path"
cleanup
exit $err
@@ -0,0 +1,71 @@
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 -g kill (test-record-graph-default-aarch64)
perf record --call-graph dwarf kill (test-record-graph-dwarf)
perf record --call-graph fp kill (test-record-graph-fp)
perf record --call-graph fp kill (test-record-graph-fp-aarch64)
perf record -e '{cycles,instructions}' kill (test-record-group1)
perf record -e '{cycles/period=1/,instructions/period=2/}:S' kill (test-record-group2)
perf record -e '{cycles,cache-misses}:S' kill (test-record-group-sampling1)
perf record -c 10000 -e '{cycles,cache-misses}:S' kill (test-record-group-sampling2)
perf record -D kill (test-record-no-delay)
perf record -i kill (test-record-no-inherit)
perf record -n kill (test-record-no-samples)
perf record -c 100 -P kill (test-record-period)
perf record -c 1 --pfm-events=cycles:period=2 (test-record-pfm-period)
perf record -R kill (test-record-raw)
perf record -c 2 -e arm_spe_0// -- kill (test-record-spe-period)
perf record -e arm_spe_0/period=3/ -- kill (test-record-spe-period-term)
perf record -e arm_spe_0/pa_enable=1/ -- kill (test-record-spe-physical-address)
perf stat -e cycles kill (test-stat-basic)
perf stat kill (test-stat-default)
perf stat -d kill (test-stat-detailed-1)
perf stat -dd kill (test-stat-detailed-2)
perf stat -ddd kill (test-stat-detailed-3)
perf stat -e '{cycles,instructions}' kill (test-stat-group1)
perf stat -i -e cycles kill (test-stat-no-inherit)
@@ -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=136
config=0|1
sample_period=*
sample_type=263
read_format=0|4|20
disabled=1
inherit=1
pinned=0
exclusive=0
exclude_user=0
exclude_kernel=0|1
exclude_hv=0|1
exclude_idle=0
mmap=1
comm=1
freq=1
inherit_stat=0
enable_on_exec=1
task=1
watermark=0
precise_ip=0|1|2|3
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
@@ -0,0 +1,40 @@
[event]
fd=*
group_fd=-1
flags=*
cpu=*
type=*
size=*
config=*
sample_period=*
sample_type=*
read_format=*
disabled=*
inherit=*
pinned=*
exclusive=*
exclude_user=*
exclude_kernel=*
exclude_hv=*
exclude_idle=*
mmap=*
comm=*
freq=*
inherit_stat=*
enable_on_exec=*
task=*
watermark=*
precise_ip=*
mmap_data=*
sample_id_all=*
exclude_host=*
exclude_guest=*
exclude_callchain_kernel=*
exclude_callchain_user=*
wakeup_events=*
bp_type=*
config1=*
config2=*
branch_sample_type=*
sample_regs_user=*
sample_stack_user=*
@@ -0,0 +1,41 @@
[event]
fd=1
group_fd=-1
# 0 or PERF_FLAG_FD_CLOEXEC flag
flags=0|8
cpu=*
type=0
size=136
config=0
sample_period=0
sample_type=65536
read_format=3
disabled=1
inherit=1
pinned=0
exclusive=0
exclude_user=0
exclude_kernel=0|1
exclude_hv=0|1
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
@@ -0,0 +1,52 @@
# Event added by system-wide or CPU perf-record to handle the race of
# processes starting while /proc is processed.
[event]
fd=1
group_fd=-1
cpu=*
pid=-1
flags=8
type=1
size=136
config=9
sample_period=1
# PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
# PERF_SAMPLE_CPU | PERF_SAMPLE_IDENTIFIER
sample_type=65671
read_format=4|20
# Event will be enabled right away.
disabled=0
inherit=1
pinned=0
exclusive=0
exclude_user=0
exclude_kernel=1
exclude_hv=1
exclude_idle=0
mmap=1
comm=1
freq=0
inherit_stat=0
enable_on_exec=0
task=1
watermark=0
precise_ip=0
mmap_data=0
sample_id_all=1
exclude_host=0
exclude_guest=1
exclude_callchain_kernel=0
exclude_callchain_user=0
mmap2=1
comm_exec=1
context_switch=0
write_backward=0
namespaces=0
use_clockid=0
wakeup_events=0
bp_type=0
config1=0
config2=0
branch_sample_type=0
sample_regs_user=0
sample_stack_user=0
@@ -0,0 +1,24 @@
[config]
command = record
args = --no-bpf-event -C 0 kill >/dev/null 2>&1
ret = 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_IDENTIFIER
# + PERF_SAMPLE_CPU added by -C 0
sample_type=65927
# Dummy event handles mmaps, comm and task.
mmap=0
comm=0
task=0
inherit=0
[event:system-wide-dummy]
inherit=0
@@ -0,0 +1,6 @@
[config]
command = record
args = --no-bpf-event kill >/dev/null 2>&1
ret = 1
[event:base-record]
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -b kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=2311
branch_sample_type=8
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -j any kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=2311
branch_sample_type=8
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -j any_call kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=2311
branch_sample_type=16
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -j any_ret kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=2311
branch_sample_type=32
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -j hv kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=2311
branch_sample_type=8
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -j ind_call kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=2311
branch_sample_type=64
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -j k kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=2311
branch_sample_type=8
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -j u kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=2311
branch_sample_type=8
@@ -0,0 +1,9 @@
[config]
command = record
args = --no-bpf-event -c 123 kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_period=123
sample_type=7
freq=0
@@ -0,0 +1,10 @@
[config]
command = record
args = --no-bpf-event -d kill >/dev/null 2>&1
ret = 1
[event:base-record]
# 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
@@ -0,0 +1,55 @@
[config]
command = record
args = --no-bpf-event -e dummy -C 0 kill >/dev/null 2>&1
ret = 1
[event]
fd=1
group_fd=-1
cpu=0
pid=-1
flags=8
type=1
size=136
config=9
sample_period=4000
# PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
# PERF_SAMPLE_PERIOD
# + PERF_SAMPLE_CPU added by -C 0
sample_type=391
read_format=4|20
disabled=0
inherit=0
pinned=0
exclusive=0
exclude_user=0
exclude_kernel=0
exclude_hv=0
exclude_idle=0
mmap=1
comm=1
freq=1
inherit_stat=0
enable_on_exec=0
task=1
watermark=0
precise_ip=0
mmap_data=0
sample_id_all=1
exclude_host=0
exclude_guest=0
exclude_callchain_kernel=0
exclude_callchain_user=0
mmap2=1
comm_exec=1
context_switch=0
write_backward=0
namespaces=0
use_clockid=0
wakeup_events=0
bp_type=0
config1=0
config2=0
branch_sample_type=0
sample_regs_user=0
sample_stack_user=0
@@ -0,0 +1,7 @@
[config]
command = record
args = --no-bpf-event -F 100 kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_period=100
@@ -0,0 +1,9 @@
[config]
command = record
args = --no-bpf-event -g kill >/dev/null 2>&1
ret = 1
# arm64 enables registers in the default mode (fp)
arch = !aarch64
[event:base-record]
sample_type=295
@@ -0,0 +1,9 @@
[config]
command = record
args = --no-bpf-event -g kill >/dev/null 2>&1
ret = 1
arch = aarch64
[event:base-record]
sample_type=4391
sample_regs_user=1073741824
@@ -0,0 +1,12 @@
[config]
command = record
args = --no-bpf-event --call-graph dwarf -- kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=45359
exclude_callchain_user=1
sample_stack_user=8192
# TODO different for each arch, no support for that now
sample_regs_user=*
mmap_data=1
@@ -0,0 +1,9 @@
[config]
command = record
args = --no-bpf-event --call-graph fp kill >/dev/null 2>&1
ret = 1
# arm64 enables registers in fp mode
arch = !aarch64
[event:base-record]
sample_type=295
@@ -0,0 +1,9 @@
[config]
command = record
args = --no-bpf-event --call-graph fp kill >/dev/null 2>&1
ret = 1
arch = aarch64
[event:base-record]
sample_type=4391
sample_regs_user=1073741824
@@ -0,0 +1,40 @@
[config]
command = record
args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
ret = 1
kernel_until = 6.12
[event-1:base-record]
fd=1
group_fd=-1
sample_type=343
read_format=12|28
inherit=0
[event-2:base-record]
fd=2
group_fd=1
# cache-misses
type=0
config=3
# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD
sample_type=343
# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST
read_format=12|28
task=0
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
freq=0
write_backward=0
@@ -0,0 +1,50 @@
[config]
command = record
args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
ret = 1
kernel_since = 6.12
[event-1:base-record]
fd=1
group_fd=-1
# cycles
type=0
config=0
# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD
sample_type=343
# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING
read_format=28|31
task=1
mmap=1
comm=1
enable_on_exec=1
disabled=1
# inherit is enabled for group sampling
inherit=1
[event-2:base-record]
fd=2
group_fd=1
# cache-misses
type=0
config=3
# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD
sample_type=343
# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING
read_format=28|31
task=0
mmap=0
comm=0
enable_on_exec=0
disabled=0
freq=0
# inherit is enabled for group sampling
inherit=1
@@ -0,0 +1,61 @@
[config]
command = record
args = --no-bpf-event -c 10000 -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
ret = 1
kernel_since = 6.12
[event-1:base-record]
fd=1
group_fd=-1
# cycles
type=0
config=0
# default | PERF_SAMPLE_READ
sample_type=87
# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING
read_format=28|31
task=1
mmap=1
comm=1
enable_on_exec=1
disabled=1
# inherit is enabled for group sampling
inherit=1
# sampling disabled
sample_freq=0
sample_period=10000
freq=0
write_backward=0
[event-2:base-record]
fd=2
group_fd=1
# cache-misses
type=0
config=3
# default | PERF_SAMPLE_READ
sample_type=87
# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING
read_format=28|31
task=0
mmap=0
comm=0
enable_on_exec=0
disabled=0
# inherit is enabled for group sampling
inherit=1
# sampling disabled
sample_freq=0
sample_period=0
freq=0
write_backward=0
@@ -0,0 +1,23 @@
[config]
command = record
args = --no-bpf-event -e '{cycles,instructions}' kill >/dev/null 2>&1
ret = 1
[event-1:base-record]
fd=1
group_fd=-1
sample_type=327
read_format=4|20
[event-2:base-record]
fd=2
group_fd=1
type=0
config=1
sample_type=327
read_format=4|20
mmap=0
comm=0
task=0
enable_on_exec=0
disabled=0
@@ -0,0 +1,30 @@
[config]
command = record
args = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1
ret = 1
kernel_until = 6.12
[event-1:base-record]
fd=1
group_fd=-1
config=0|1
sample_period=1234000
sample_type=87
read_format=12|28
inherit=0
freq=0
[event-2:base-record]
fd=2
group_fd=1
config=0|1
sample_period=6789000
sample_type=87
read_format=12|28
disabled=0
inherit=0
mmap=0
comm=0
freq=0
enable_on_exec=0
task=0
@@ -0,0 +1,31 @@
[config]
command = record
args = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1
ret = 1
kernel_since = 6.12
[event-1:base-record]
fd=1
group_fd=-1
config=0|1
sample_period=1234000
sample_type=87
read_format=28|31
disabled=1
inherit=1
freq=0
[event-2:base-record]
fd=2
group_fd=1
config=0|1
sample_period=6789000
sample_type=87
read_format=28|31
disabled=0
inherit=1
mmap=0
comm=0
freq=0
enable_on_exec=0
task=0
@@ -0,0 +1,9 @@
[config]
command = record
args = --no-bpf-event --no-buffering kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=263
watermark=0
wakeup_events=1
@@ -0,0 +1,8 @@
[config]
command = record
args = --no-bpf-event -i kill >/dev/null 2>&1
ret = 1
[event:base-record]
sample_type=263
inherit=0

Some files were not shown because too many files have changed in this diff Show More