1 // SPDX-License-Identifier: GPL-2.0
2 #include <errno.h>
3 #include <inttypes.h>
4 #include <linux/string.h>
5
6 #include <sched.h>
7 #include <perf/mmap.h>
8 #include "event.h"
9 #include "evlist.h"
10 #include "evsel.h"
11 #include "debug.h"
12 #include "record.h"
13 #include "tests.h"
14 #include "util/mmap.h"
15 #include "util/sample.h"
16
sched__get_first_possible_cpu(pid_t pid,cpu_set_t * maskp)17 static int sched__get_first_possible_cpu(pid_t pid, cpu_set_t *maskp)
18 {
19 int i, cpu = -1, nrcpus = 1024;
20 realloc:
21 CPU_ZERO(maskp);
22
23 if (sched_getaffinity(pid, sizeof(*maskp), maskp) == -1) {
24 if (errno == EINVAL && nrcpus < (1024 << 8)) {
25 nrcpus = nrcpus << 2;
26 goto realloc;
27 }
28 perror("sched_getaffinity");
29 return -1;
30 }
31
32 for (i = 0; i < nrcpus; i++) {
33 if (CPU_ISSET(i, maskp)) {
34 if (cpu == -1)
35 cpu = i;
36 else
37 CPU_CLR(i, maskp);
38 }
39 }
40
41 return cpu;
42 }
43
test__PERF_RECORD(struct test_suite * test __maybe_unused,int subtest __maybe_unused)44 static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
45 {
46 struct record_opts opts = {
47 .target = {
48 .uid = UINT_MAX,
49 .uses_mmap = true,
50 },
51 .no_buffering = true,
52 .mmap_pages = 256,
53 };
54 cpu_set_t cpu_mask;
55 size_t cpu_mask_size = sizeof(cpu_mask);
56 struct evlist *evlist = evlist__new_dummy();
57 struct evsel *evsel;
58 struct perf_sample sample;
59 const char *cmd = "sleep";
60 const char *argv[] = { cmd, "1", NULL, };
61 char *bname, *mmap_filename;
62 u64 prev_time = 0;
63 bool found_cmd_mmap = false,
64 found_coreutils_mmap = false,
65 found_libc_mmap = false,
66 found_vdso_mmap = false,
67 found_ld_mmap = false;
68 int err = -1, errs = 0, i, wakeups = 0;
69 u32 cpu;
70 int total_events = 0, nr_events[PERF_RECORD_MAX] = { 0, };
71 char sbuf[STRERR_BUFSIZE];
72
73 perf_sample__init(&sample, /*all=*/false);
74 if (evlist == NULL) /* Fallback for kernels lacking PERF_COUNT_SW_DUMMY */
75 evlist = evlist__new_default();
76
77 if (evlist == NULL) {
78 pr_debug("Not enough memory to create evlist\n");
79 goto out;
80 }
81
82 /*
83 * Create maps of threads and cpus to monitor. In this case
84 * we start with all threads and cpus (-1, -1) but then in
85 * evlist__prepare_workload we'll fill in the only thread
86 * we're monitoring, the one forked there.
87 */
88 err = evlist__create_maps(evlist, &opts.target);
89 if (err < 0) {
90 pr_debug("Not enough memory to create thread/cpu maps\n");
91 goto out_delete_evlist;
92 }
93
94 /*
95 * Prepare the workload in argv[] to run, it'll fork it, and then wait
96 * for evlist__start_workload() to exec it. This is done this way
97 * so that we have time to open the evlist (calling sys_perf_event_open
98 * on all the fds) and then mmap them.
99 */
100 err = evlist__prepare_workload(evlist, &opts.target, argv, false, NULL);
101 if (err < 0) {
102 pr_debug("Couldn't run the workload!\n");
103 goto out_delete_evlist;
104 }
105
106 /*
107 * Config the evsels, setting attr->comm on the first one, etc.
108 */
109 evsel = evlist__first(evlist);
110 evsel__set_sample_bit(evsel, CPU);
111 evsel__set_sample_bit(evsel, TID);
112 evsel__set_sample_bit(evsel, TIME);
113 evlist__config(evlist, &opts, NULL);
114
115 err = sched__get_first_possible_cpu(evlist->workload.pid, &cpu_mask);
116 if (err < 0) {
117 pr_debug("sched__get_first_possible_cpu: %s\n",
118 str_error_r(errno, sbuf, sizeof(sbuf)));
119 goto out_delete_evlist;
120 }
121
122 cpu = err;
123
124 /*
125 * So that we can check perf_sample.cpu on all the samples.
126 */
127 if (sched_setaffinity(evlist->workload.pid, cpu_mask_size, &cpu_mask) < 0) {
128 pr_debug("sched_setaffinity: %s\n",
129 str_error_r(errno, sbuf, sizeof(sbuf)));
130 goto out_delete_evlist;
131 }
132
133 /*
134 * Call sys_perf_event_open on all the fds on all the evsels,
135 * grouping them if asked to.
136 */
137 err = evlist__open(evlist);
138 if (err < 0) {
139 pr_debug("perf_evlist__open: %s\n",
140 str_error_r(errno, sbuf, sizeof(sbuf)));
141 goto out_delete_evlist;
142 }
143
144 /*
145 * mmap the first fd on a given CPU and ask for events for the other
146 * fds in the same CPU to be injected in the same mmap ring buffer
147 * (using ioctl(PERF_EVENT_IOC_SET_OUTPUT)).
148 */
149 err = evlist__mmap(evlist, opts.mmap_pages);
150 if (err < 0) {
151 pr_debug("evlist__mmap: %s\n",
152 str_error_r(errno, sbuf, sizeof(sbuf)));
153 goto out_delete_evlist;
154 }
155
156 /*
157 * Now that all is properly set up, enable the events, they will
158 * count just on workload.pid, which will start...
159 */
160 evlist__enable(evlist);
161
162 /*
163 * Now!
164 */
165 evlist__start_workload(evlist);
166
167 while (1) {
168 int before = total_events;
169
170 for (i = 0; i < evlist->core.nr_mmaps; i++) {
171 union perf_event *event;
172 struct mmap *md;
173
174 md = &evlist->mmap[i];
175 if (perf_mmap__read_init(&md->core) < 0)
176 continue;
177
178 while ((event = perf_mmap__read_event(&md->core)) != NULL) {
179 const u32 type = event->header.type;
180 const char *name = perf_event__name(type);
181
182 ++total_events;
183 if (type < PERF_RECORD_MAX)
184 nr_events[type]++;
185
186 err = evlist__parse_sample(evlist, event, &sample);
187 if (err < 0) {
188 if (verbose > 0)
189 perf_event__fprintf(event, NULL, stderr);
190 pr_debug("Couldn't parse sample\n");
191 goto out_delete_evlist;
192 }
193
194 if (verbose > 0) {
195 pr_info("%" PRIu64" %d ", sample.time, sample.cpu);
196 perf_event__fprintf(event, NULL, stderr);
197 }
198
199 if (prev_time > sample.time) {
200 pr_debug("%s going backwards in time, prev=%" PRIu64 ", curr=%" PRIu64 "\n",
201 name, prev_time, sample.time);
202 ++errs;
203 }
204
205 prev_time = sample.time;
206
207 if (sample.cpu != cpu) {
208 pr_debug("%s with unexpected cpu, expected %d, got %d\n",
209 name, cpu, sample.cpu);
210 ++errs;
211 }
212
213 if ((pid_t)sample.pid != evlist->workload.pid) {
214 pr_debug("%s with unexpected pid, expected %d, got %d\n",
215 name, evlist->workload.pid, sample.pid);
216 ++errs;
217 }
218
219 if ((pid_t)sample.tid != evlist->workload.pid) {
220 pr_debug("%s with unexpected tid, expected %d, got %d\n",
221 name, evlist->workload.pid, sample.tid);
222 ++errs;
223 }
224
225 if ((type == PERF_RECORD_COMM ||
226 type == PERF_RECORD_MMAP ||
227 type == PERF_RECORD_MMAP2 ||
228 type == PERF_RECORD_FORK ||
229 type == PERF_RECORD_EXIT) &&
230 (pid_t)event->comm.pid != evlist->workload.pid) {
231 pr_debug("%s with unexpected pid/tid\n", name);
232 ++errs;
233 }
234
235 if ((type == PERF_RECORD_COMM ||
236 type == PERF_RECORD_MMAP ||
237 type == PERF_RECORD_MMAP2) &&
238 event->comm.pid != event->comm.tid) {
239 pr_debug("%s with different pid/tid!\n", name);
240 ++errs;
241 }
242
243 switch (type) {
244 case PERF_RECORD_COMM:
245 if (strcmp(event->comm.comm, cmd)) {
246 pr_debug("%s with unexpected comm!\n", name);
247 ++errs;
248 }
249 break;
250 case PERF_RECORD_EXIT:
251 goto found_exit;
252 case PERF_RECORD_MMAP:
253 mmap_filename = event->mmap.filename;
254 goto check_bname;
255 case PERF_RECORD_MMAP2:
256 mmap_filename = event->mmap2.filename;
257 check_bname:
258 bname = strrchr(mmap_filename, '/');
259 if (bname != NULL) {
260 if (!found_cmd_mmap)
261 found_cmd_mmap = !strcmp(bname + 1, cmd);
262 if (!found_coreutils_mmap)
263 found_coreutils_mmap = !strcmp(bname + 1, "coreutils");
264 if (!found_libc_mmap)
265 found_libc_mmap = !strncmp(bname + 1, "libc", 4);
266 if (!found_ld_mmap)
267 found_ld_mmap = !strncmp(bname + 1, "ld", 2);
268 } else if (!found_vdso_mmap)
269 found_vdso_mmap = !strcmp(mmap_filename, "[vdso]");
270 break;
271
272 case PERF_RECORD_SAMPLE:
273 /* Just ignore samples for now */
274 break;
275 default:
276 pr_debug("Unexpected perf_event->header.type %d!\n",
277 type);
278 ++errs;
279 }
280
281 perf_mmap__consume(&md->core);
282 }
283 perf_mmap__read_done(&md->core);
284 }
285
286 /*
287 * We don't use poll here because at least at 3.1 times the
288 * PERF_RECORD_{!SAMPLE} events don't honour
289 * perf_event_attr.wakeup_events, just PERF_EVENT_SAMPLE does.
290 */
291 if (total_events == before && false)
292 evlist__poll(evlist, -1);
293
294 sleep(1);
295 if (++wakeups > 5) {
296 pr_debug("No PERF_RECORD_EXIT event!\n");
297 break;
298 }
299 }
300
301 found_exit:
302 if (nr_events[PERF_RECORD_COMM] > 1 + !!found_coreutils_mmap) {
303 pr_debug("Excessive number of PERF_RECORD_COMM events!\n");
304 ++errs;
305 }
306
307 if (nr_events[PERF_RECORD_COMM] == 0) {
308 pr_debug("Missing PERF_RECORD_COMM for %s!\n", cmd);
309 ++errs;
310 }
311
312 if (!found_cmd_mmap && !found_coreutils_mmap) {
313 pr_debug("PERF_RECORD_MMAP for %s missing!\n", cmd);
314 ++errs;
315 }
316
317 if (!found_libc_mmap) {
318 pr_debug("PERF_RECORD_MMAP for %s missing!\n", "libc");
319 ++errs;
320 }
321
322 if (!found_ld_mmap) {
323 pr_debug("PERF_RECORD_MMAP for %s missing!\n", "ld");
324 ++errs;
325 }
326
327 if (!found_vdso_mmap) {
328 pr_debug("PERF_RECORD_MMAP for %s missing!\n", "[vdso]");
329 ++errs;
330 }
331 out_delete_evlist:
332 evlist__delete(evlist);
333 out:
334 perf_sample__exit(&sample);
335 if (err == -EACCES)
336 return TEST_SKIP;
337 if (err < 0 || errs != 0)
338 return TEST_FAIL;
339 return TEST_OK;
340 }
341
342 static struct test_case tests__PERF_RECORD[] = {
343 TEST_CASE_REASON("PERF_RECORD_* events & perf_sample fields",
344 PERF_RECORD,
345 "permissions"),
346 { .name = NULL, }
347 };
348
349 struct test_suite suite__PERF_RECORD = {
350 .desc = "PERF_RECORD_* events & perf_sample fields",
351 .test_cases = tests__PERF_RECORD,
352 };
353