1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <errno.h> |
3 | #include <inttypes.h> |
4 | #include <stdlib.h> |
5 | #include <perf/cpumap.h> |
6 | |
7 | #include "debug.h" |
8 | #include "event.h" |
9 | #include "evlist.h" |
10 | #include "evsel.h" |
11 | #include "thread_map.h" |
12 | #include "tests.h" |
13 | #include "util/mmap.h" |
14 | #include "util/sample.h" |
15 | #include <linux/err.h> |
16 | #include <linux/kernel.h> |
17 | #include <linux/string.h> |
18 | #include <perf/evlist.h> |
19 | #include <perf/mmap.h> |
20 | |
21 | /* |
22 | * This test will generate random numbers of calls to some getpid syscalls, |
23 | * then establish an mmap for a group of events that are created to monitor |
24 | * the syscalls. |
25 | * |
26 | * It will receive the events, using mmap, use its PERF_SAMPLE_ID generated |
27 | * sample.id field to map back to its respective perf_evsel instance. |
28 | * |
29 | * Then it checks if the number of syscalls reported as perf events by |
30 | * the kernel corresponds to the number of syscalls made. |
31 | */ |
32 | static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest __maybe_unused) |
33 | { |
34 | int err = TEST_FAIL; |
35 | union perf_event *event; |
36 | struct perf_thread_map *threads; |
37 | struct perf_cpu_map *cpus; |
38 | struct evlist *evlist; |
39 | cpu_set_t cpu_set; |
40 | const char *syscall_names[] = { "getsid" , "getppid" , "getpgid" , }; |
41 | pid_t (*syscalls[])(void) = { (void *)getsid, getppid, (void*)getpgid }; |
42 | #define nsyscalls ARRAY_SIZE(syscall_names) |
43 | unsigned int nr_events[nsyscalls], |
44 | expected_nr_events[nsyscalls], i, j; |
45 | struct evsel *evsels[nsyscalls], *evsel; |
46 | char sbuf[STRERR_BUFSIZE]; |
47 | struct mmap *md; |
48 | |
49 | threads = thread_map__new(-1, getpid(), UINT_MAX); |
50 | if (threads == NULL) { |
51 | pr_debug("thread_map__new\n" ); |
52 | return -1; |
53 | } |
54 | |
55 | cpus = perf_cpu_map__new_online_cpus(); |
56 | if (cpus == NULL) { |
57 | pr_debug("perf_cpu_map__new\n" ); |
58 | goto out_free_threads; |
59 | } |
60 | |
61 | CPU_ZERO(&cpu_set); |
62 | CPU_SET(perf_cpu_map__cpu(cpus, 0).cpu, &cpu_set); |
63 | sched_setaffinity(0, sizeof(cpu_set), &cpu_set); |
64 | if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { |
65 | pr_debug("sched_setaffinity() failed on CPU %d: %s " , |
66 | perf_cpu_map__cpu(cpus, 0).cpu, |
67 | str_error_r(errno, sbuf, sizeof(sbuf))); |
68 | goto out_free_cpus; |
69 | } |
70 | |
71 | evlist = evlist__new(); |
72 | if (evlist == NULL) { |
73 | pr_debug("evlist__new\n" ); |
74 | goto out_free_cpus; |
75 | } |
76 | |
77 | perf_evlist__set_maps(&evlist->core, cpus, threads); |
78 | |
79 | for (i = 0; i < nsyscalls; ++i) { |
80 | char name[64]; |
81 | |
82 | snprintf(buf: name, size: sizeof(name), fmt: "sys_enter_%s" , syscall_names[i]); |
83 | evsels[i] = evsel__newtp("syscalls" , name); |
84 | if (IS_ERR(ptr: evsels[i])) { |
85 | pr_debug("evsel__new(%s)\n" , name); |
86 | if (PTR_ERR(ptr: evsels[i]) == -EACCES) { |
87 | /* Permissions failure, flag the failure as a skip. */ |
88 | err = TEST_SKIP; |
89 | } |
90 | goto out_delete_evlist; |
91 | } |
92 | |
93 | evsels[i]->core.attr.wakeup_events = 1; |
94 | evsel__set_sample_id(evsels[i], false); |
95 | |
96 | evlist__add(evlist, evsels[i]); |
97 | |
98 | if (evsel__open(evsels[i], cpus, threads) < 0) { |
99 | pr_debug("failed to open counter: %s, " |
100 | "tweak /proc/sys/kernel/perf_event_paranoid?\n" , |
101 | str_error_r(errno, sbuf, sizeof(sbuf))); |
102 | goto out_delete_evlist; |
103 | } |
104 | |
105 | nr_events[i] = 0; |
106 | expected_nr_events[i] = 1 + rand() % 127; |
107 | } |
108 | |
109 | if (evlist__mmap(evlist, 128) < 0) { |
110 | pr_debug("failed to mmap events: %d (%s)\n" , errno, |
111 | str_error_r(errno, sbuf, sizeof(sbuf))); |
112 | goto out_delete_evlist; |
113 | } |
114 | |
115 | for (i = 0; i < nsyscalls; ++i) |
116 | for (j = 0; j < expected_nr_events[i]; ++j) { |
117 | syscalls[i](); |
118 | } |
119 | |
120 | md = &evlist->mmap[0]; |
121 | if (perf_mmap__read_init(&md->core) < 0) |
122 | goto out_init; |
123 | |
124 | while ((event = perf_mmap__read_event(&md->core)) != NULL) { |
125 | struct perf_sample sample; |
126 | |
127 | if (event->header.type != PERF_RECORD_SAMPLE) { |
128 | pr_debug("unexpected %s event\n" , |
129 | perf_event__name(event->header.type)); |
130 | goto out_delete_evlist; |
131 | } |
132 | |
133 | err = evlist__parse_sample(evlist, event, &sample); |
134 | if (err) { |
135 | pr_err("Can't parse sample, err = %d\n" , err); |
136 | goto out_delete_evlist; |
137 | } |
138 | |
139 | err = -1; |
140 | evsel = evlist__id2evsel(evlist, sample.id); |
141 | if (evsel == NULL) { |
142 | pr_debug("event with id %" PRIu64 |
143 | " doesn't map to an evsel\n" , sample.id); |
144 | goto out_delete_evlist; |
145 | } |
146 | nr_events[evsel->core.idx]++; |
147 | perf_mmap__consume(&md->core); |
148 | } |
149 | perf_mmap__read_done(&md->core); |
150 | |
151 | out_init: |
152 | err = 0; |
153 | evlist__for_each_entry(evlist, evsel) { |
154 | if (nr_events[evsel->core.idx] != expected_nr_events[evsel->core.idx]) { |
155 | pr_debug("expected %d %s events, got %d\n" , |
156 | expected_nr_events[evsel->core.idx], |
157 | evsel__name(evsel), nr_events[evsel->core.idx]); |
158 | err = -1; |
159 | goto out_delete_evlist; |
160 | } |
161 | } |
162 | |
163 | out_delete_evlist: |
164 | evlist__delete(evlist); |
165 | out_free_cpus: |
166 | perf_cpu_map__put(cpus); |
167 | out_free_threads: |
168 | perf_thread_map__put(threads); |
169 | return err; |
170 | } |
171 | |
172 | static int test_stat_user_read(int event) |
173 | { |
174 | struct perf_counts_values counts = { .val = 0 }; |
175 | struct perf_thread_map *threads; |
176 | struct perf_evsel *evsel; |
177 | struct perf_event_mmap_page *pc; |
178 | struct perf_event_attr attr = { |
179 | .type = PERF_TYPE_HARDWARE, |
180 | .config = event, |
181 | #ifdef __aarch64__ |
182 | .config1 = 0x2, /* Request user access */ |
183 | #endif |
184 | }; |
185 | int err, i, ret = TEST_FAIL; |
186 | bool opened = false, mapped = false; |
187 | |
188 | threads = perf_thread_map__new_dummy(); |
189 | TEST_ASSERT_VAL("failed to create threads" , threads); |
190 | |
191 | perf_thread_map__set_pid(threads, 0, 0); |
192 | |
193 | evsel = perf_evsel__new(&attr); |
194 | TEST_ASSERT_VAL("failed to create evsel" , evsel); |
195 | |
196 | err = perf_evsel__open(evsel, NULL, threads); |
197 | if (err) { |
198 | pr_err("failed to open evsel: %s\n" , strerror(-err)); |
199 | ret = TEST_SKIP; |
200 | goto out; |
201 | } |
202 | opened = true; |
203 | |
204 | err = perf_evsel__mmap(evsel, 0); |
205 | if (err) { |
206 | pr_err("failed to mmap evsel: %s\n" , strerror(-err)); |
207 | goto out; |
208 | } |
209 | mapped = true; |
210 | |
211 | pc = perf_evsel__mmap_base(evsel, 0, 0); |
212 | if (!pc) { |
213 | pr_err("failed to get mmapped address\n" ); |
214 | goto out; |
215 | } |
216 | |
217 | if (!pc->cap_user_rdpmc || !pc->index) { |
218 | pr_err("userspace counter access not %s\n" , |
219 | !pc->cap_user_rdpmc ? "supported" : "enabled" ); |
220 | ret = TEST_SKIP; |
221 | goto out; |
222 | } |
223 | if (pc->pmc_width < 32) { |
224 | pr_err("userspace counter width not set (%d)\n" , pc->pmc_width); |
225 | goto out; |
226 | } |
227 | |
228 | perf_evsel__read(evsel, 0, 0, &counts); |
229 | if (counts.val == 0) { |
230 | pr_err("failed to read value for evsel\n" ); |
231 | goto out; |
232 | } |
233 | |
234 | for (i = 0; i < 5; i++) { |
235 | volatile int count = 0x10000 << i; |
236 | __u64 start, end, last = 0; |
237 | |
238 | pr_debug("\tloop = %u, " , count); |
239 | |
240 | perf_evsel__read(evsel, 0, 0, &counts); |
241 | start = counts.val; |
242 | |
243 | while (count--) ; |
244 | |
245 | perf_evsel__read(evsel, 0, 0, &counts); |
246 | end = counts.val; |
247 | |
248 | if ((end - start) < last) { |
249 | pr_err("invalid counter data: end=%llu start=%llu last= %llu\n" , |
250 | end, start, last); |
251 | goto out; |
252 | } |
253 | last = end - start; |
254 | pr_debug("count = %llu\n" , end - start); |
255 | } |
256 | ret = TEST_OK; |
257 | |
258 | out: |
259 | if (mapped) |
260 | perf_evsel__munmap(evsel); |
261 | if (opened) |
262 | perf_evsel__close(evsel); |
263 | perf_evsel__delete(evsel); |
264 | |
265 | perf_thread_map__put(threads); |
266 | return ret; |
267 | } |
268 | |
269 | static int test__mmap_user_read_instr(struct test_suite *test __maybe_unused, |
270 | int subtest __maybe_unused) |
271 | { |
272 | return test_stat_user_read(PERF_COUNT_HW_INSTRUCTIONS); |
273 | } |
274 | |
275 | static int test__mmap_user_read_cycles(struct test_suite *test __maybe_unused, |
276 | int subtest __maybe_unused) |
277 | { |
278 | return test_stat_user_read(PERF_COUNT_HW_CPU_CYCLES); |
279 | } |
280 | |
281 | static struct test_case tests__basic_mmap[] = { |
282 | TEST_CASE_REASON("Read samples using the mmap interface" , |
283 | basic_mmap, |
284 | "permissions" ), |
285 | TEST_CASE_REASON("User space counter reading of instructions" , |
286 | mmap_user_read_instr, |
287 | #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ |
288 | (defined(__riscv) && __riscv_xlen == 64) |
289 | "permissions" |
290 | #else |
291 | "unsupported" |
292 | #endif |
293 | ), |
294 | TEST_CASE_REASON("User space counter reading of cycles" , |
295 | mmap_user_read_cycles, |
296 | #if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ |
297 | (defined(__riscv) && __riscv_xlen == 64) |
298 | "permissions" |
299 | #else |
300 | "unsupported" |
301 | #endif |
302 | ), |
303 | { .name = NULL, } |
304 | }; |
305 | |
306 | struct test_suite suite__basic_mmap = { |
307 | .desc = "mmap interface tests" , |
308 | .test_cases = tests__basic_mmap, |
309 | }; |
310 | |