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