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