diff options
author | Michael Ellerman <michael@ellerman.id.au> | 2013-08-06 03:42:37 -0400 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2013-08-14 00:57:10 -0400 |
commit | cb96143defbd5516c351595d56b608ed915b525e (patch) | |
tree | 427dc6a4231f1c3b1ed9f7a327676223487d2b07 /tools/testing/selftests | |
parent | 2fae0d7ced53ef2e7b4d87d84986ec3ff7cf798f (diff) |
selftests: Add test of PMU instruction counting on powerpc
This commit adds a test of instruction counting using the PMU on powerpc.
Although the bulk of the code is architecture agnostic, the code needs to
run a precisely sized loop which is implemented in assembler.
Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'tools/testing/selftests')
-rw-r--r-- | tools/testing/selftests/powerpc/Makefile | 2 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/Makefile | 23 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/count_instructions.c | 135 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/event.c | 105 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/event.h | 39 | ||||
-rw-r--r-- | tools/testing/selftests/powerpc/pmu/loop.S | 46 |
6 files changed, 349 insertions, 1 deletions
diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile index b315740e4cd9..bd24ae5aaeab 100644 --- a/tools/testing/selftests/powerpc/Makefile +++ b/tools/testing/selftests/powerpc/Makefile | |||
@@ -13,7 +13,7 @@ CFLAGS := -Wall -O2 -flto -Wall -Werror -DGIT_VERSION='"$(GIT_VERSION)"' -I$(CUR | |||
13 | 13 | ||
14 | export CC CFLAGS | 14 | export CC CFLAGS |
15 | 15 | ||
16 | TARGETS = | 16 | TARGETS = pmu |
17 | 17 | ||
18 | endif | 18 | endif |
19 | 19 | ||
diff --git a/tools/testing/selftests/powerpc/pmu/Makefile b/tools/testing/selftests/powerpc/pmu/Makefile new file mode 100644 index 000000000000..7216f0091655 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/Makefile | |||
@@ -0,0 +1,23 @@ | |||
1 | noarg: | ||
2 | $(MAKE) -C ../ | ||
3 | |||
4 | PROGS := count_instructions | ||
5 | EXTRA_SOURCES := ../harness.c event.c | ||
6 | |||
7 | all: $(PROGS) | ||
8 | |||
9 | $(PROGS): $(EXTRA_SOURCES) | ||
10 | |||
11 | # loop.S can only be built 64-bit | ||
12 | count_instructions: loop.S count_instructions.c $(EXTRA_SOURCES) | ||
13 | $(CC) $(CFLAGS) -m64 -o $@ $^ | ||
14 | |||
15 | run_tests: all | ||
16 | @-for PROG in $(PROGS); do \ | ||
17 | ./$$PROG; \ | ||
18 | done; | ||
19 | |||
20 | clean: | ||
21 | rm -f $(PROGS) loop.o | ||
22 | |||
23 | .PHONY: all run_tests clean | ||
diff --git a/tools/testing/selftests/powerpc/pmu/count_instructions.c b/tools/testing/selftests/powerpc/pmu/count_instructions.c new file mode 100644 index 000000000000..312b4f0fd27c --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/count_instructions.c | |||
@@ -0,0 +1,135 @@ | |||
1 | /* | ||
2 | * Copyright 2013, Michael Ellerman, IBM Corp. | ||
3 | * Licensed under GPLv2. | ||
4 | */ | ||
5 | |||
6 | #define _GNU_SOURCE | ||
7 | |||
8 | #include <stdio.h> | ||
9 | #include <stdbool.h> | ||
10 | #include <string.h> | ||
11 | #include <sys/prctl.h> | ||
12 | |||
13 | #include "event.h" | ||
14 | #include "utils.h" | ||
15 | |||
16 | extern void thirty_two_instruction_loop(u64 loops); | ||
17 | |||
18 | static void setup_event(struct event *e, u64 config, char *name) | ||
19 | { | ||
20 | event_init_opts(e, config, PERF_TYPE_HARDWARE, name); | ||
21 | |||
22 | e->attr.disabled = 1; | ||
23 | e->attr.exclude_kernel = 1; | ||
24 | e->attr.exclude_hv = 1; | ||
25 | e->attr.exclude_idle = 1; | ||
26 | } | ||
27 | |||
28 | static int do_count_loop(struct event *events, u64 instructions, | ||
29 | u64 overhead, bool report) | ||
30 | { | ||
31 | s64 difference, expected; | ||
32 | double percentage; | ||
33 | |||
34 | prctl(PR_TASK_PERF_EVENTS_ENABLE); | ||
35 | |||
36 | /* Run for 1M instructions */ | ||
37 | thirty_two_instruction_loop(instructions >> 5); | ||
38 | |||
39 | prctl(PR_TASK_PERF_EVENTS_DISABLE); | ||
40 | |||
41 | event_read(&events[0]); | ||
42 | event_read(&events[1]); | ||
43 | |||
44 | expected = instructions + overhead; | ||
45 | difference = events[0].result.value - expected; | ||
46 | percentage = (double)difference / events[0].result.value * 100; | ||
47 | |||
48 | if (report) { | ||
49 | event_report(&events[0]); | ||
50 | event_report(&events[1]); | ||
51 | |||
52 | printf("Looped for %llu instructions, overhead %llu\n", instructions, overhead); | ||
53 | printf("Expected %llu\n", expected); | ||
54 | printf("Actual %llu\n", events[0].result.value); | ||
55 | printf("Delta %lld, %f%%\n", difference, percentage); | ||
56 | } | ||
57 | |||
58 | event_reset(&events[0]); | ||
59 | event_reset(&events[1]); | ||
60 | |||
61 | if (difference < 0) | ||
62 | difference = -difference; | ||
63 | |||
64 | /* Tolerate a difference below 0.0001 % */ | ||
65 | difference *= 10000 * 100; | ||
66 | if (difference / events[0].result.value) | ||
67 | return -1; | ||
68 | |||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | /* Count how many instructions it takes to do a null loop */ | ||
73 | static u64 determine_overhead(struct event *events) | ||
74 | { | ||
75 | u64 current, overhead; | ||
76 | int i; | ||
77 | |||
78 | do_count_loop(events, 0, 0, false); | ||
79 | overhead = events[0].result.value; | ||
80 | |||
81 | for (i = 0; i < 100; i++) { | ||
82 | do_count_loop(events, 0, 0, false); | ||
83 | current = events[0].result.value; | ||
84 | if (current < overhead) { | ||
85 | printf("Replacing overhead %llu with %llu\n", overhead, current); | ||
86 | overhead = current; | ||
87 | } | ||
88 | } | ||
89 | |||
90 | return overhead; | ||
91 | } | ||
92 | |||
93 | static int count_instructions(void) | ||
94 | { | ||
95 | struct event events[2]; | ||
96 | u64 overhead; | ||
97 | |||
98 | setup_event(&events[0], PERF_COUNT_HW_INSTRUCTIONS, "instructions"); | ||
99 | setup_event(&events[1], PERF_COUNT_HW_CPU_CYCLES, "cycles"); | ||
100 | |||
101 | if (event_open(&events[0])) { | ||
102 | perror("perf_event_open"); | ||
103 | return -1; | ||
104 | } | ||
105 | |||
106 | if (event_open_with_group(&events[1], events[0].fd)) { | ||
107 | perror("perf_event_open"); | ||
108 | return -1; | ||
109 | } | ||
110 | |||
111 | overhead = determine_overhead(events); | ||
112 | printf("Overhead of null loop: %llu instructions\n", overhead); | ||
113 | |||
114 | /* Run for 1M instructions */ | ||
115 | FAIL_IF(do_count_loop(events, 0x100000, overhead, true)); | ||
116 | |||
117 | /* Run for 10M instructions */ | ||
118 | FAIL_IF(do_count_loop(events, 0xa00000, overhead, true)); | ||
119 | |||
120 | /* Run for 100M instructions */ | ||
121 | FAIL_IF(do_count_loop(events, 0x6400000, overhead, true)); | ||
122 | |||
123 | /* Run for 1G instructions */ | ||
124 | FAIL_IF(do_count_loop(events, 0x40000000, overhead, true)); | ||
125 | |||
126 | event_close(&events[0]); | ||
127 | event_close(&events[1]); | ||
128 | |||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | int main(void) | ||
133 | { | ||
134 | return test_harness(count_instructions, "count_instructions"); | ||
135 | } | ||
diff --git a/tools/testing/selftests/powerpc/pmu/event.c b/tools/testing/selftests/powerpc/pmu/event.c new file mode 100644 index 000000000000..2b2d11df2450 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/event.c | |||
@@ -0,0 +1,105 @@ | |||
1 | /* | ||
2 | * Copyright 2013, Michael Ellerman, IBM Corp. | ||
3 | * Licensed under GPLv2. | ||
4 | */ | ||
5 | |||
6 | #define _GNU_SOURCE | ||
7 | #include <unistd.h> | ||
8 | #include <sys/syscall.h> | ||
9 | #include <string.h> | ||
10 | #include <stdio.h> | ||
11 | #include <sys/ioctl.h> | ||
12 | |||
13 | #include "event.h" | ||
14 | |||
15 | |||
16 | int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu, | ||
17 | int group_fd, unsigned long flags) | ||
18 | { | ||
19 | return syscall(__NR_perf_event_open, attr, pid, cpu, | ||
20 | group_fd, flags); | ||
21 | } | ||
22 | |||
23 | void event_init_opts(struct event *e, u64 config, int type, char *name) | ||
24 | { | ||
25 | memset(e, 0, sizeof(*e)); | ||
26 | |||
27 | e->name = name; | ||
28 | |||
29 | e->attr.type = type; | ||
30 | e->attr.config = config; | ||
31 | e->attr.size = sizeof(e->attr); | ||
32 | /* This has to match the structure layout in the header */ | ||
33 | e->attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | \ | ||
34 | PERF_FORMAT_TOTAL_TIME_RUNNING; | ||
35 | } | ||
36 | |||
37 | void event_init_named(struct event *e, u64 config, char *name) | ||
38 | { | ||
39 | event_init_opts(e, config, PERF_TYPE_RAW, name); | ||
40 | } | ||
41 | |||
42 | #define PERF_CURRENT_PID 0 | ||
43 | #define PERF_NO_CPU -1 | ||
44 | #define PERF_NO_GROUP -1 | ||
45 | |||
46 | int event_open_with_options(struct event *e, pid_t pid, int cpu, int group_fd) | ||
47 | { | ||
48 | e->fd = perf_event_open(&e->attr, pid, cpu, group_fd, 0); | ||
49 | if (e->fd == -1) { | ||
50 | perror("perf_event_open"); | ||
51 | return -1; | ||
52 | } | ||
53 | |||
54 | return 0; | ||
55 | } | ||
56 | |||
57 | int event_open_with_group(struct event *e, int group_fd) | ||
58 | { | ||
59 | return event_open_with_options(e, PERF_CURRENT_PID, PERF_NO_CPU, group_fd); | ||
60 | } | ||
61 | |||
62 | int event_open(struct event *e) | ||
63 | { | ||
64 | return event_open_with_options(e, PERF_CURRENT_PID, PERF_NO_CPU, PERF_NO_GROUP); | ||
65 | } | ||
66 | |||
67 | void event_close(struct event *e) | ||
68 | { | ||
69 | close(e->fd); | ||
70 | } | ||
71 | |||
72 | int event_reset(struct event *e) | ||
73 | { | ||
74 | return ioctl(e->fd, PERF_EVENT_IOC_RESET); | ||
75 | } | ||
76 | |||
77 | int event_read(struct event *e) | ||
78 | { | ||
79 | int rc; | ||
80 | |||
81 | rc = read(e->fd, &e->result, sizeof(e->result)); | ||
82 | if (rc != sizeof(e->result)) { | ||
83 | fprintf(stderr, "read error on event %p!\n", e); | ||
84 | return -1; | ||
85 | } | ||
86 | |||
87 | return 0; | ||
88 | } | ||
89 | |||
90 | void event_report_justified(struct event *e, int name_width, int result_width) | ||
91 | { | ||
92 | printf("%*s: result %*llu ", name_width, e->name, result_width, | ||
93 | e->result.value); | ||
94 | |||
95 | if (e->result.running == e->result.enabled) | ||
96 | printf("running/enabled %llu\n", e->result.running); | ||
97 | else | ||
98 | printf("running %llu enabled %llu\n", e->result.running, | ||
99 | e->result.enabled); | ||
100 | } | ||
101 | |||
102 | void event_report(struct event *e) | ||
103 | { | ||
104 | event_report_justified(e, 0, 0); | ||
105 | } | ||
diff --git a/tools/testing/selftests/powerpc/pmu/event.h b/tools/testing/selftests/powerpc/pmu/event.h new file mode 100644 index 000000000000..e6993192ff34 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/event.h | |||
@@ -0,0 +1,39 @@ | |||
1 | /* | ||
2 | * Copyright 2013, Michael Ellerman, IBM Corp. | ||
3 | * Licensed under GPLv2. | ||
4 | */ | ||
5 | |||
6 | #ifndef _SELFTESTS_POWERPC_PMU_EVENT_H | ||
7 | #define _SELFTESTS_POWERPC_PMU_EVENT_H | ||
8 | |||
9 | #include <unistd.h> | ||
10 | #include <linux/perf_event.h> | ||
11 | |||
12 | #include "utils.h" | ||
13 | |||
14 | |||
15 | struct event { | ||
16 | struct perf_event_attr attr; | ||
17 | char *name; | ||
18 | int fd; | ||
19 | /* This must match the read_format we use */ | ||
20 | struct { | ||
21 | u64 value; | ||
22 | u64 running; | ||
23 | u64 enabled; | ||
24 | } result; | ||
25 | }; | ||
26 | |||
27 | void event_init(struct event *e, u64 config); | ||
28 | void event_init_named(struct event *e, u64 config, char *name); | ||
29 | void event_init_opts(struct event *e, u64 config, int type, char *name); | ||
30 | int event_open_with_options(struct event *e, pid_t pid, int cpu, int group_fd); | ||
31 | int event_open_with_group(struct event *e, int group_fd); | ||
32 | int event_open(struct event *e); | ||
33 | void event_close(struct event *e); | ||
34 | int event_reset(struct event *e); | ||
35 | int event_read(struct event *e); | ||
36 | void event_report_justified(struct event *e, int name_width, int result_width); | ||
37 | void event_report(struct event *e); | ||
38 | |||
39 | #endif /* _SELFTESTS_POWERPC_PMU_EVENT_H */ | ||
diff --git a/tools/testing/selftests/powerpc/pmu/loop.S b/tools/testing/selftests/powerpc/pmu/loop.S new file mode 100644 index 000000000000..8820e3df1444 --- /dev/null +++ b/tools/testing/selftests/powerpc/pmu/loop.S | |||
@@ -0,0 +1,46 @@ | |||
1 | /* | ||
2 | * Copyright 2013, Michael Ellerman, IBM Corp. | ||
3 | * Licensed under GPLv2. | ||
4 | */ | ||
5 | |||
6 | .text | ||
7 | |||
8 | .global thirty_two_instruction_loop | ||
9 | .type .thirty_two_instruction_loop,@function | ||
10 | .section ".opd","aw",@progbits | ||
11 | thirty_two_instruction_loop: | ||
12 | .quad .thirty_two_instruction_loop, .TOC.@tocbase, 0 | ||
13 | .previous | ||
14 | .thirty_two_instruction_loop: | ||
15 | cmpwi %r3,0 | ||
16 | beqlr | ||
17 | addi %r4,%r3,1 | ||
18 | addi %r4,%r4,1 | ||
19 | addi %r4,%r4,1 | ||
20 | addi %r4,%r4,1 | ||
21 | addi %r4,%r4,1 | ||
22 | addi %r4,%r4,1 | ||
23 | addi %r4,%r4,1 | ||
24 | addi %r4,%r4,1 | ||
25 | addi %r4,%r4,1 | ||
26 | addi %r4,%r4,1 | ||
27 | addi %r4,%r4,1 | ||
28 | addi %r4,%r4,1 | ||
29 | addi %r4,%r4,1 | ||
30 | addi %r4,%r4,1 | ||
31 | addi %r4,%r4,1 | ||
32 | addi %r4,%r4,1 | ||
33 | addi %r4,%r4,1 | ||
34 | addi %r4,%r4,1 | ||
35 | addi %r4,%r4,1 | ||
36 | addi %r4,%r4,1 | ||
37 | addi %r4,%r4,1 | ||
38 | addi %r4,%r4,1 | ||
39 | addi %r4,%r4,1 | ||
40 | addi %r4,%r4,1 | ||
41 | addi %r4,%r4,1 | ||
42 | addi %r4,%r4,1 | ||
43 | addi %r4,%r4,1 | ||
44 | addi %r4,%r4,1 # 28 addi's | ||
45 | subi %r3,%r3,1 | ||
46 | b .thirty_two_instruction_loop | ||