1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <sys/time.h> |
3 | #include <sys/prctl.h> |
4 | #include <errno.h> |
5 | #include <limits.h> |
6 | #include <time.h> |
7 | #include <stdlib.h> |
8 | #include <linux/zalloc.h> |
9 | #include <linux/err.h> |
10 | #include <perf/cpumap.h> |
11 | #include <perf/evlist.h> |
12 | #include <perf/mmap.h> |
13 | |
14 | #include "debug.h" |
15 | #include "parse-events.h" |
16 | #include "evlist.h" |
17 | #include "evsel.h" |
18 | #include "thread_map.h" |
19 | #include "record.h" |
20 | #include "tests.h" |
21 | #include "util/mmap.h" |
22 | #include "util/sample.h" |
23 | #include "pmus.h" |
24 | |
25 | static int spin_sleep(void) |
26 | { |
27 | struct timeval start, now, diff, maxtime; |
28 | struct timespec ts; |
29 | int err, i; |
30 | |
31 | maxtime.tv_sec = 0; |
32 | maxtime.tv_usec = 50000; |
33 | |
34 | err = gettimeofday(&start, NULL); |
35 | if (err) |
36 | return err; |
37 | |
38 | /* Spin for 50ms */ |
39 | while (1) { |
40 | for (i = 0; i < 1000; i++) |
41 | barrier(); |
42 | |
43 | err = gettimeofday(&now, NULL); |
44 | if (err) |
45 | return err; |
46 | |
47 | timersub(&now, &start, &diff); |
48 | if (timercmp(&diff, &maxtime, > /* For checkpatch */)) |
49 | break; |
50 | } |
51 | |
52 | ts.tv_nsec = 50 * 1000 * 1000; |
53 | ts.tv_sec = 0; |
54 | |
55 | /* Sleep for 50ms */ |
56 | err = nanosleep(&ts, NULL); |
57 | if (err == EINTR) |
58 | err = 0; |
59 | |
60 | return err; |
61 | } |
62 | |
63 | struct switch_tracking { |
64 | struct evsel *switch_evsel; |
65 | struct evsel *cycles_evsel; |
66 | pid_t *tids; |
67 | int nr_tids; |
68 | int comm_seen[4]; |
69 | int cycles_before_comm_1; |
70 | int cycles_between_comm_2_and_comm_3; |
71 | int cycles_after_comm_4; |
72 | }; |
73 | |
74 | static int check_comm(struct switch_tracking *switch_tracking, |
75 | union perf_event *event, const char *comm, int nr) |
76 | { |
77 | if (event->header.type == PERF_RECORD_COMM && |
78 | (pid_t)event->comm.pid == getpid() && |
79 | (pid_t)event->comm.tid == getpid() && |
80 | strcmp(event->comm.comm, comm) == 0) { |
81 | if (switch_tracking->comm_seen[nr]) { |
82 | pr_debug("Duplicate comm event\n" ); |
83 | return -1; |
84 | } |
85 | switch_tracking->comm_seen[nr] = 1; |
86 | pr_debug3("comm event: %s nr: %d\n" , event->comm.comm, nr); |
87 | return 1; |
88 | } |
89 | return 0; |
90 | } |
91 | |
92 | static int check_cpu(struct switch_tracking *switch_tracking, int cpu) |
93 | { |
94 | int i, nr = cpu + 1; |
95 | |
96 | if (cpu < 0) |
97 | return -1; |
98 | |
99 | if (!switch_tracking->tids) { |
100 | switch_tracking->tids = calloc(nr, sizeof(pid_t)); |
101 | if (!switch_tracking->tids) |
102 | return -1; |
103 | for (i = 0; i < nr; i++) |
104 | switch_tracking->tids[i] = -1; |
105 | switch_tracking->nr_tids = nr; |
106 | return 0; |
107 | } |
108 | |
109 | if (cpu >= switch_tracking->nr_tids) { |
110 | void *addr; |
111 | |
112 | addr = realloc(switch_tracking->tids, nr * sizeof(pid_t)); |
113 | if (!addr) |
114 | return -1; |
115 | switch_tracking->tids = addr; |
116 | for (i = switch_tracking->nr_tids; i < nr; i++) |
117 | switch_tracking->tids[i] = -1; |
118 | switch_tracking->nr_tids = nr; |
119 | return 0; |
120 | } |
121 | |
122 | return 0; |
123 | } |
124 | |
125 | static int process_sample_event(struct evlist *evlist, |
126 | union perf_event *event, |
127 | struct switch_tracking *switch_tracking) |
128 | { |
129 | struct perf_sample sample; |
130 | struct evsel *evsel; |
131 | pid_t next_tid, prev_tid; |
132 | int cpu, err; |
133 | |
134 | if (evlist__parse_sample(evlist, event, &sample)) { |
135 | pr_debug("evlist__parse_sample failed\n" ); |
136 | return -1; |
137 | } |
138 | |
139 | evsel = evlist__id2evsel(evlist, sample.id); |
140 | if (evsel == switch_tracking->switch_evsel) { |
141 | next_tid = evsel__intval(evsel, &sample, "next_pid" ); |
142 | prev_tid = evsel__intval(evsel, &sample, "prev_pid" ); |
143 | cpu = sample.cpu; |
144 | pr_debug3("sched_switch: cpu: %d prev_tid %d next_tid %d\n" , |
145 | cpu, prev_tid, next_tid); |
146 | err = check_cpu(switch_tracking, cpu); |
147 | if (err) |
148 | return err; |
149 | /* |
150 | * Check for no missing sched_switch events i.e. that the |
151 | * evsel->core.system_wide flag has worked. |
152 | */ |
153 | if (switch_tracking->tids[cpu] != -1 && |
154 | switch_tracking->tids[cpu] != prev_tid) { |
155 | pr_debug("Missing sched_switch events\n" ); |
156 | return -1; |
157 | } |
158 | switch_tracking->tids[cpu] = next_tid; |
159 | } |
160 | |
161 | if (evsel == switch_tracking->cycles_evsel) { |
162 | pr_debug3("cycles event\n" ); |
163 | if (!switch_tracking->comm_seen[0]) |
164 | switch_tracking->cycles_before_comm_1 = 1; |
165 | if (switch_tracking->comm_seen[1] && |
166 | !switch_tracking->comm_seen[2]) |
167 | switch_tracking->cycles_between_comm_2_and_comm_3 = 1; |
168 | if (switch_tracking->comm_seen[3]) |
169 | switch_tracking->cycles_after_comm_4 = 1; |
170 | } |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static int process_event(struct evlist *evlist, union perf_event *event, |
176 | struct switch_tracking *switch_tracking) |
177 | { |
178 | if (event->header.type == PERF_RECORD_SAMPLE) |
179 | return process_sample_event(evlist, event, switch_tracking); |
180 | |
181 | if (event->header.type == PERF_RECORD_COMM) { |
182 | int err, done = 0; |
183 | |
184 | err = check_comm(switch_tracking, event, comm: "Test COMM 1" , nr: 0); |
185 | if (err < 0) |
186 | return -1; |
187 | done += err; |
188 | err = check_comm(switch_tracking, event, comm: "Test COMM 2" , nr: 1); |
189 | if (err < 0) |
190 | return -1; |
191 | done += err; |
192 | err = check_comm(switch_tracking, event, comm: "Test COMM 3" , nr: 2); |
193 | if (err < 0) |
194 | return -1; |
195 | done += err; |
196 | err = check_comm(switch_tracking, event, comm: "Test COMM 4" , nr: 3); |
197 | if (err < 0) |
198 | return -1; |
199 | done += err; |
200 | if (done != 1) { |
201 | pr_debug("Unexpected comm event\n" ); |
202 | return -1; |
203 | } |
204 | } |
205 | |
206 | return 0; |
207 | } |
208 | |
209 | struct event_node { |
210 | struct list_head list; |
211 | union perf_event *event; |
212 | u64 event_time; |
213 | }; |
214 | |
215 | static int add_event(struct evlist *evlist, struct list_head *events, |
216 | union perf_event *event) |
217 | { |
218 | struct perf_sample sample; |
219 | struct event_node *node; |
220 | |
221 | node = malloc(sizeof(struct event_node)); |
222 | if (!node) { |
223 | pr_debug("malloc failed\n" ); |
224 | return -1; |
225 | } |
226 | node->event = event; |
227 | list_add(&node->list, events); |
228 | |
229 | if (evlist__parse_sample(evlist, event, &sample)) { |
230 | pr_debug("evlist__parse_sample failed\n" ); |
231 | return -1; |
232 | } |
233 | |
234 | if (!sample.time) { |
235 | pr_debug("event with no time\n" ); |
236 | return -1; |
237 | } |
238 | |
239 | node->event_time = sample.time; |
240 | |
241 | return 0; |
242 | } |
243 | |
244 | static void free_event_nodes(struct list_head *events) |
245 | { |
246 | struct event_node *node; |
247 | |
248 | while (!list_empty(events)) { |
249 | node = list_entry(events->next, struct event_node, list); |
250 | list_del_init(&node->list); |
251 | free(node); |
252 | } |
253 | } |
254 | |
255 | static int compar(const void *a, const void *b) |
256 | { |
257 | const struct event_node *nodea = a; |
258 | const struct event_node *nodeb = b; |
259 | s64 cmp = nodea->event_time - nodeb->event_time; |
260 | |
261 | return cmp; |
262 | } |
263 | |
264 | static int process_events(struct evlist *evlist, |
265 | struct switch_tracking *switch_tracking) |
266 | { |
267 | union perf_event *event; |
268 | unsigned pos, cnt = 0; |
269 | LIST_HEAD(events); |
270 | struct event_node *events_array, *node; |
271 | struct mmap *md; |
272 | int i, ret; |
273 | |
274 | for (i = 0; i < evlist->core.nr_mmaps; i++) { |
275 | md = &evlist->mmap[i]; |
276 | if (perf_mmap__read_init(&md->core) < 0) |
277 | continue; |
278 | |
279 | while ((event = perf_mmap__read_event(&md->core)) != NULL) { |
280 | cnt += 1; |
281 | ret = add_event(evlist, events: &events, event); |
282 | perf_mmap__consume(&md->core); |
283 | if (ret < 0) |
284 | goto out_free_nodes; |
285 | } |
286 | perf_mmap__read_done(&md->core); |
287 | } |
288 | |
289 | events_array = calloc(cnt, sizeof(struct event_node)); |
290 | if (!events_array) { |
291 | pr_debug("calloc failed\n" ); |
292 | ret = -1; |
293 | goto out_free_nodes; |
294 | } |
295 | |
296 | pos = 0; |
297 | list_for_each_entry(node, &events, list) |
298 | events_array[pos++] = *node; |
299 | |
300 | qsort(events_array, cnt, sizeof(struct event_node), compar); |
301 | |
302 | for (pos = 0; pos < cnt; pos++) { |
303 | ret = process_event(evlist, event: events_array[pos].event, |
304 | switch_tracking); |
305 | if (ret < 0) |
306 | goto out_free; |
307 | } |
308 | |
309 | ret = 0; |
310 | out_free: |
311 | pr_debug("%u events recorded\n" , cnt); |
312 | free(events_array); |
313 | out_free_nodes: |
314 | free_event_nodes(events: &events); |
315 | return ret; |
316 | } |
317 | |
318 | /** |
319 | * test__switch_tracking - test using sched_switch and tracking events. |
320 | * |
321 | * This function implements a test that checks that sched_switch events and |
322 | * tracking events can be recorded for a workload (current process) using the |
323 | * evsel->core.system_wide and evsel->tracking flags (respectively) with other events |
324 | * sometimes enabled or disabled. |
325 | */ |
326 | static int test__switch_tracking(struct test_suite *test __maybe_unused, int subtest __maybe_unused) |
327 | { |
328 | const char *sched_switch = "sched:sched_switch" ; |
329 | const char *cycles = "cycles:u" ; |
330 | struct switch_tracking switch_tracking = { .tids = NULL, }; |
331 | struct record_opts opts = { |
332 | .mmap_pages = UINT_MAX, |
333 | .user_freq = UINT_MAX, |
334 | .user_interval = ULLONG_MAX, |
335 | .freq = 4000, |
336 | .target = { |
337 | .uses_mmap = true, |
338 | }, |
339 | }; |
340 | struct perf_thread_map *threads = NULL; |
341 | struct perf_cpu_map *cpus = NULL; |
342 | struct evlist *evlist = NULL; |
343 | struct evsel *evsel, *cpu_clocks_evsel, *cycles_evsel; |
344 | struct evsel *switch_evsel, *tracking_evsel; |
345 | const char *comm; |
346 | int err = -1; |
347 | |
348 | threads = thread_map__new(-1, getpid(), UINT_MAX); |
349 | if (!threads) { |
350 | pr_debug("thread_map__new failed!\n" ); |
351 | goto out_err; |
352 | } |
353 | |
354 | cpus = perf_cpu_map__new_online_cpus(); |
355 | if (!cpus) { |
356 | pr_debug("perf_cpu_map__new failed!\n" ); |
357 | goto out_err; |
358 | } |
359 | |
360 | evlist = evlist__new(); |
361 | if (!evlist) { |
362 | pr_debug("evlist__new failed!\n" ); |
363 | goto out_err; |
364 | } |
365 | |
366 | perf_evlist__set_maps(&evlist->core, cpus, threads); |
367 | |
368 | /* First event */ |
369 | err = parse_event(evlist, "cpu-clock:u" ); |
370 | if (err) { |
371 | pr_debug("Failed to parse event dummy:u\n" ); |
372 | goto out_err; |
373 | } |
374 | |
375 | cpu_clocks_evsel = evlist__last(evlist); |
376 | |
377 | /* Second event */ |
378 | err = parse_event(evlist, cycles); |
379 | if (err) { |
380 | pr_debug("Failed to parse event %s\n" , cycles); |
381 | goto out_err; |
382 | } |
383 | |
384 | cycles_evsel = evlist__last(evlist); |
385 | |
386 | /* Third event */ |
387 | if (!evlist__can_select_event(evlist, sched_switch)) { |
388 | pr_debug("No sched_switch\n" ); |
389 | err = 0; |
390 | goto out; |
391 | } |
392 | |
393 | switch_evsel = evlist__add_sched_switch(evlist, true); |
394 | if (IS_ERR(ptr: switch_evsel)) { |
395 | err = PTR_ERR(ptr: switch_evsel); |
396 | pr_debug("Failed to create event %s\n" , sched_switch); |
397 | goto out_err; |
398 | } |
399 | |
400 | switch_evsel->immediate = true; |
401 | |
402 | /* Test moving an event to the front */ |
403 | if (cycles_evsel == evlist__first(evlist)) { |
404 | pr_debug("cycles event already at front" ); |
405 | goto out_err; |
406 | } |
407 | evlist__to_front(evlist, cycles_evsel); |
408 | if (cycles_evsel != evlist__first(evlist)) { |
409 | pr_debug("Failed to move cycles event to front" ); |
410 | goto out_err; |
411 | } |
412 | |
413 | evsel__set_sample_bit(cycles_evsel, CPU); |
414 | evsel__set_sample_bit(cycles_evsel, TIME); |
415 | |
416 | /* Fourth event */ |
417 | err = parse_event(evlist, "dummy:u" ); |
418 | if (err) { |
419 | pr_debug("Failed to parse event dummy:u\n" ); |
420 | goto out_err; |
421 | } |
422 | |
423 | tracking_evsel = evlist__last(evlist); |
424 | |
425 | evlist__set_tracking_event(evlist, tracking_evsel); |
426 | |
427 | tracking_evsel->core.attr.freq = 0; |
428 | tracking_evsel->core.attr.sample_period = 1; |
429 | |
430 | evsel__set_sample_bit(tracking_evsel, TIME); |
431 | |
432 | /* Config events */ |
433 | evlist__config(evlist, &opts, NULL); |
434 | |
435 | /* Check moved event is still at the front */ |
436 | if (cycles_evsel != evlist__first(evlist)) { |
437 | pr_debug("Front event no longer at front" ); |
438 | goto out_err; |
439 | } |
440 | |
441 | /* Check tracking event is tracking */ |
442 | if (!tracking_evsel->core.attr.mmap || !tracking_evsel->core.attr.comm) { |
443 | pr_debug("Tracking event not tracking\n" ); |
444 | goto out_err; |
445 | } |
446 | |
447 | /* Check non-tracking events are not tracking */ |
448 | evlist__for_each_entry(evlist, evsel) { |
449 | if (evsel != tracking_evsel) { |
450 | if (evsel->core.attr.mmap || evsel->core.attr.comm) { |
451 | pr_debug("Non-tracking event is tracking\n" ); |
452 | goto out_err; |
453 | } |
454 | } |
455 | } |
456 | |
457 | if (evlist__open(evlist) < 0) { |
458 | pr_debug("Not supported\n" ); |
459 | err = 0; |
460 | goto out; |
461 | } |
462 | |
463 | err = evlist__mmap(evlist, UINT_MAX); |
464 | if (err) { |
465 | pr_debug("evlist__mmap failed!\n" ); |
466 | goto out_err; |
467 | } |
468 | |
469 | evlist__enable(evlist); |
470 | |
471 | err = evsel__disable(cpu_clocks_evsel); |
472 | if (err) { |
473 | pr_debug("perf_evlist__disable_event failed!\n" ); |
474 | goto out_err; |
475 | } |
476 | |
477 | err = spin_sleep(); |
478 | if (err) { |
479 | pr_debug("spin_sleep failed!\n" ); |
480 | goto out_err; |
481 | } |
482 | |
483 | comm = "Test COMM 1" ; |
484 | err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); |
485 | if (err) { |
486 | pr_debug("PR_SET_NAME failed!\n" ); |
487 | goto out_err; |
488 | } |
489 | |
490 | err = evsel__disable(cycles_evsel); |
491 | if (err) { |
492 | pr_debug("perf_evlist__disable_event failed!\n" ); |
493 | goto out_err; |
494 | } |
495 | |
496 | comm = "Test COMM 2" ; |
497 | err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); |
498 | if (err) { |
499 | pr_debug("PR_SET_NAME failed!\n" ); |
500 | goto out_err; |
501 | } |
502 | |
503 | err = spin_sleep(); |
504 | if (err) { |
505 | pr_debug("spin_sleep failed!\n" ); |
506 | goto out_err; |
507 | } |
508 | |
509 | comm = "Test COMM 3" ; |
510 | err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); |
511 | if (err) { |
512 | pr_debug("PR_SET_NAME failed!\n" ); |
513 | goto out_err; |
514 | } |
515 | |
516 | err = evsel__enable(cycles_evsel); |
517 | if (err) { |
518 | pr_debug("perf_evlist__disable_event failed!\n" ); |
519 | goto out_err; |
520 | } |
521 | |
522 | comm = "Test COMM 4" ; |
523 | err = prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0); |
524 | if (err) { |
525 | pr_debug("PR_SET_NAME failed!\n" ); |
526 | goto out_err; |
527 | } |
528 | |
529 | err = spin_sleep(); |
530 | if (err) { |
531 | pr_debug("spin_sleep failed!\n" ); |
532 | goto out_err; |
533 | } |
534 | |
535 | evlist__disable(evlist); |
536 | |
537 | switch_tracking.switch_evsel = switch_evsel; |
538 | switch_tracking.cycles_evsel = cycles_evsel; |
539 | |
540 | err = process_events(evlist, switch_tracking: &switch_tracking); |
541 | |
542 | zfree(&switch_tracking.tids); |
543 | |
544 | if (err) |
545 | goto out_err; |
546 | |
547 | /* Check all 4 comm events were seen i.e. that evsel->tracking works */ |
548 | if (!switch_tracking.comm_seen[0] || !switch_tracking.comm_seen[1] || |
549 | !switch_tracking.comm_seen[2] || !switch_tracking.comm_seen[3]) { |
550 | pr_debug("Missing comm events\n" ); |
551 | goto out_err; |
552 | } |
553 | |
554 | /* Check cycles event got enabled */ |
555 | if (!switch_tracking.cycles_before_comm_1) { |
556 | pr_debug("Missing cycles events\n" ); |
557 | goto out_err; |
558 | } |
559 | |
560 | /* Check cycles event got disabled */ |
561 | if (switch_tracking.cycles_between_comm_2_and_comm_3) { |
562 | pr_debug("cycles events even though event was disabled\n" ); |
563 | goto out_err; |
564 | } |
565 | |
566 | /* Check cycles event got enabled again */ |
567 | if (!switch_tracking.cycles_after_comm_4) { |
568 | pr_debug("Missing cycles events\n" ); |
569 | goto out_err; |
570 | } |
571 | out: |
572 | if (evlist) { |
573 | evlist__disable(evlist); |
574 | evlist__delete(evlist); |
575 | } |
576 | perf_cpu_map__put(cpus); |
577 | perf_thread_map__put(threads); |
578 | |
579 | return err; |
580 | |
581 | out_err: |
582 | err = -1; |
583 | goto out; |
584 | } |
585 | |
586 | DEFINE_SUITE("Track with sched_switch" , switch_tracking); |
587 | |