xref: /linux/tools/testing/selftests/sched_ext/peek_dsq.c (revision 02baaa67d9afc2e56c6e1ac6a1fb1f1dd2be366f)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Test for DSQ operations including create, destroy, and peek operations.
4  *
5  * Copyright (c) 2025 Meta Platforms, Inc. and affiliates.
6  * Copyright (c) 2025 Ryan Newton <ryan.newton@alum.mit.edu>
7  */
8 #include <bpf/bpf.h>
9 #include <scx/common.h>
10 #include <sys/wait.h>
11 #include <unistd.h>
12 #include <pthread.h>
13 #include <string.h>
14 #include <sched.h>
15 #include "peek_dsq.bpf.skel.h"
16 #include "scx_test.h"
17 
18 #define NUM_WORKERS 4
19 
20 static bool workload_running = true;
21 static pthread_t workload_threads[NUM_WORKERS];
22 
23 /**
24  * Background workload thread that sleeps and wakes rapidly to exercise
25  * the scheduler's enqueue operations and ensure DSQ operations get tested.
26  */
workload_thread_fn(void * arg)27 static void *workload_thread_fn(void *arg)
28 {
29 	while (workload_running) {
30 		/* Sleep for a very short time to trigger scheduler activity */
31 		usleep(1000); /* 1ms sleep */
32 		/* Yield to ensure we go through the scheduler */
33 		sched_yield();
34 	}
35 	return NULL;
36 }
37 
setup(void ** ctx)38 static enum scx_test_status setup(void **ctx)
39 {
40 	struct peek_dsq *skel;
41 
42 	skel = peek_dsq__open();
43 	SCX_FAIL_IF(!skel, "Failed to open");
44 	SCX_ENUM_INIT(skel);
45 	SCX_FAIL_IF(peek_dsq__load(skel), "Failed to load skel");
46 
47 	*ctx = skel;
48 
49 	return SCX_TEST_PASS;
50 }
51 
print_observed_pids(struct bpf_map * map,int max_samples,const char * dsq_name)52 static int print_observed_pids(struct bpf_map *map, int max_samples, const char *dsq_name)
53 {
54 	long count = 0;
55 
56 	printf("Observed %s DSQ peek pids:\n", dsq_name);
57 	for (int i = 0; i < max_samples; i++) {
58 		long pid;
59 		int err;
60 
61 		err = bpf_map_lookup_elem(bpf_map__fd(map), &i, &pid);
62 		if (err == 0) {
63 			if (pid == 0) {
64 				printf("  Sample %d: NULL peek\n", i);
65 			} else if (pid > 0) {
66 				printf("  Sample %d: pid %ld\n", i, pid);
67 				count++;
68 			}
69 		} else {
70 			printf("  Sample %d: error reading pid (err=%d)\n", i, err);
71 		}
72 	}
73 	printf("Observed ~%ld pids in the %s DSQ(s)\n", count, dsq_name);
74 	return count;
75 }
76 
run(void * ctx)77 static enum scx_test_status run(void *ctx)
78 {
79 	struct peek_dsq *skel = ctx;
80 	bool failed = false;
81 	int seconds = 3;
82 	int err;
83 
84 	/* Enable the scheduler to test DSQ operations */
85 	printf("Enabling scheduler to test DSQ insert operations...\n");
86 
87 	struct bpf_link *link =
88 		bpf_map__attach_struct_ops(skel->maps.peek_dsq_ops);
89 
90 	if (!link) {
91 		SCX_ERR("Failed to attach struct_ops");
92 		return SCX_TEST_FAIL;
93 	}
94 
95 	printf("Starting %d background workload threads...\n", NUM_WORKERS);
96 	workload_running = true;
97 	for (int i = 0; i < NUM_WORKERS; i++) {
98 		err = pthread_create(&workload_threads[i], NULL, workload_thread_fn, NULL);
99 		if (err) {
100 			SCX_ERR("Failed to create workload thread %d: %s", i, strerror(err));
101 			/* Stop already created threads */
102 			workload_running = false;
103 			for (int j = 0; j < i; j++)
104 				pthread_join(workload_threads[j], NULL);
105 			bpf_link__destroy(link);
106 			return SCX_TEST_FAIL;
107 		}
108 	}
109 
110 	printf("Waiting for enqueue events.\n");
111 	sleep(seconds);
112 	while (skel->data->enqueue_count <= 0) {
113 		printf(".");
114 		fflush(stdout);
115 		sleep(1);
116 		seconds++;
117 		if (seconds >= 30) {
118 			printf("\n\u2717 Timeout waiting for enqueue events\n");
119 			/* Stop workload threads and cleanup */
120 			workload_running = false;
121 			for (int i = 0; i < NUM_WORKERS; i++)
122 				pthread_join(workload_threads[i], NULL);
123 			bpf_link__destroy(link);
124 			return SCX_TEST_FAIL;
125 		}
126 	}
127 
128 	workload_running = false;
129 	for (int i = 0; i < NUM_WORKERS; i++) {
130 		err = pthread_join(workload_threads[i], NULL);
131 		if (err) {
132 			SCX_ERR("Failed to join workload thread %d: %s", i, strerror(err));
133 			bpf_link__destroy(link);
134 			return SCX_TEST_FAIL;
135 		}
136 	}
137 	printf("Background workload threads stopped.\n");
138 
139 	SCX_EQ(skel->data->uei.kind, EXIT_KIND(SCX_EXIT_NONE));
140 
141 	/* Detach the scheduler */
142 	bpf_link__destroy(link);
143 
144 	printf("Enqueue/dispatch count over %d seconds: %d / %d\n", seconds,
145 		skel->data->enqueue_count, skel->data->dispatch_count);
146 	printf("Debug: ksym_exists=%d\n",
147 	       skel->bss->debug_ksym_exists);
148 
149 	/* Check DSQ insert result */
150 	printf("DSQ insert test done on cpu: %d\n", skel->data->insert_test_cpu);
151 	if (skel->data->insert_test_cpu != -1)
152 		printf("\u2713 DSQ insert succeeded !\n");
153 	else {
154 		printf("\u2717 DSQ insert failed or not attempted\n");
155 		failed = true;
156 	}
157 
158 	/* Check DSQ peek results */
159 	printf("  DSQ peek result 1 (before insert): %d\n",
160 	       skel->data->dsq_peek_result1);
161 	if (skel->data->dsq_peek_result1 == 0)
162 		printf("\u2713 DSQ peek verification success: peek returned NULL!\n");
163 	else {
164 		printf("\u2717 DSQ peek verification failed\n");
165 		failed = true;
166 	}
167 
168 	printf("  DSQ peek result 2 (after insert): %ld\n",
169 	       skel->data->dsq_peek_result2);
170 	printf("  DSQ peek result 2, expected: %ld\n",
171 	       skel->data->dsq_peek_result2_expected);
172 	if (skel->data->dsq_peek_result2 ==
173 	    skel->data->dsq_peek_result2_expected)
174 		printf("\u2713 DSQ peek verification success: peek returned the inserted task!\n");
175 	else {
176 		printf("\u2717 DSQ peek verification failed\n");
177 		failed = true;
178 	}
179 
180 	printf("  Inserted test task -> pid: %ld\n", skel->data->dsq_inserted_pid);
181 	printf("  DSQ peek result 2 -> pid: %ld\n", skel->data->dsq_peek_result2_pid);
182 
183 	int pid_count;
184 
185 	pid_count = print_observed_pids(skel->maps.peek_results,
186 					skel->data->max_samples, "DSQ pool");
187 	printf("Total non-null peek observations: %ld out of %ld\n",
188 	       skel->data->successful_peeks, skel->data->total_peek_attempts);
189 
190 	if (skel->bss->debug_ksym_exists && pid_count == 0) {
191 		printf("\u2717 DSQ pool test failed: no successful peeks in native mode\n");
192 		failed = true;
193 	}
194 	if (skel->bss->debug_ksym_exists && pid_count > 0)
195 		printf("\u2713 DSQ pool test success: observed successful peeks in native mode\n");
196 
197 	if (failed)
198 		return SCX_TEST_FAIL;
199 	else
200 		return SCX_TEST_PASS;
201 }
202 
cleanup(void * ctx)203 static void cleanup(void *ctx)
204 {
205 	struct peek_dsq *skel = ctx;
206 
207 	if (workload_running) {
208 		workload_running = false;
209 		for (int i = 0; i < NUM_WORKERS; i++)
210 			pthread_join(workload_threads[i], NULL);
211 	}
212 
213 	peek_dsq__destroy(skel);
214 }
215 
216 struct scx_test peek_dsq = {
217 	.name = "peek_dsq",
218 	.description =
219 		"Test DSQ create/destroy operations and future peek functionality",
220 	.setup = setup,
221 	.run = run,
222 	.cleanup = cleanup,
223 };
224 REGISTER_SCX_TEST(&peek_dsq)
225