1 // Copyright (c) 2024 Apple Inc.  All rights reserved.
2 
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <pthread.h>
7 #include <string.h>
8 #include <mach/mach.h>
9 #include <mach/mach_time.h>
10 #include <sys/resource_private.h>
11 #include <sys/stat.h>
12 #include <sys/sysctl.h>
13 #include <stdatomic.h>
14 #include <sys/work_interval.h>
15 #include <ktrace.h>
16 #include <sys/kdebug.h>
17 
18 #include <darwintest.h>
19 #include <darwintest_utils.h>
20 #include "test_utils.h"
21 
22 #define CONFIG_THREAD_GROUPS 1
23 typedef void *cluster_type_t;
24 #include "../../osfmk/kern/thread_group.h"
25 
26 #include "thread_group_flags_workload_config.h"
27 
28 
29 T_GLOBAL_META(T_META_NAMESPACE("xnu.scheduler"),
30     T_META_RADAR_COMPONENT_NAME("xnu"),
31     T_META_RADAR_COMPONENT_VERSION("scheduler"),
32     T_META_REQUIRES_SYSCTL_EQ("kern.thread_groups_supported", 1));
33 
34 
35 static void
workload_config_load(void)36 workload_config_load(void)
37 {
38 	int ret;
39 	size_t len = 0;
40 	ret = sysctlbyname("kern.workload_config", NULL, &len,
41 	    sched_thread_group_flags_workload_config_plist,
42 	    sched_thread_group_flags_workload_config_plist_len);
43 	if (ret == -1 && errno == ENOENT) {
44 		T_SKIP("kern.workload_config failed");
45 	}
46 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.workload_config");
47 }
48 
49 static void
workload_config_cleanup(void)50 workload_config_cleanup(void)
51 {
52 	size_t len = 0;
53 	sysctlbyname("kern.workload_config", NULL, &len, "", 1);
54 }
55 
56 static void
set_work_interval_id(work_interval_t * handle,uint32_t work_interval_flags,char * workload_id)57 set_work_interval_id(work_interval_t *handle, uint32_t work_interval_flags, char *workload_id)
58 {
59 	int ret;
60 	mach_port_t port = MACH_PORT_NULL;
61 
62 	ret = work_interval_copy_port(*handle, &port);
63 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "work_interval_copy_port");
64 
65 	struct work_interval_workload_id_params wlid_params = {
66 		.wlidp_flags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID,
67 		.wlidp_wicreate_flags = work_interval_flags,
68 		.wlidp_name = (uintptr_t)workload_id,
69 	};
70 
71 	ret = __work_interval_ctl(WORK_INTERVAL_OPERATION_SET_WORKLOAD_ID, port, &wlid_params, sizeof(wlid_params));
72 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "WORK_INTERVAL_OPERATION_SET_WORKLOAD_ID");
73 }
74 
75 static void
make_work_interval(work_interval_t * handle,uint32_t work_type_flags,char * workload_id)76 make_work_interval(work_interval_t *handle, uint32_t work_type_flags, char *workload_id)
77 {
78 	int ret;
79 	uint32_t work_interval_flags = WORK_INTERVAL_FLAG_JOINABLE | WORK_INTERVAL_FLAG_GROUP | work_type_flags;
80 	ret = work_interval_create(handle, work_interval_flags);
81 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "work_interval_create");
82 
83 	if (work_type_flags & WORK_INTERVAL_FLAG_HAS_WORKLOAD_ID) {
84 		set_work_interval_id(handle, work_interval_flags, workload_id);
85 	}
86 }
87 
88 static uint64_t
get_thread_group_id(void)89 get_thread_group_id(void)
90 {
91 	int ret;
92 	uint64_t tg_id;
93 	size_t tg_id_len = sizeof(tg_id);
94 	ret = sysctlbyname("kern.thread_group_id", &tg_id, &tg_id_len, NULL, 0);
95 	T_QUIET; T_WITH_ERRNO; T_ASSERT_POSIX_SUCCESS(ret, "kern.thread_group_id");
96 	return tg_id;
97 }
98 
99 struct thread_data {
100 	work_interval_t wi_handle;
101 	uint64_t tg_id;
102 };
103 
104 static void *
join_workload_fn(void * arg)105 join_workload_fn(void *arg)
106 {
107 	int ret;
108 	struct thread_data *data = (struct thread_data *)arg;
109 
110 	uint64_t old_tg_id = get_thread_group_id();
111 
112 	/* Join the thread group associated with the work interval handle */
113 	ret = work_interval_join(data->wi_handle);
114 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "work_interval_join");
115 
116 	data->tg_id = get_thread_group_id();
117 	T_LOG("Joined TG %llx", data->tg_id);
118 	T_QUIET; T_EXPECT_NE(data->tg_id, old_tg_id, "Thread failed to join new TG");
119 	return NULL;
120 }
121 
122 static pthread_t *
start_threads(void * (* func)(void *),struct thread_data * datas,int num_threads)123 start_threads(void *(*func)(void *), struct thread_data *datas, int num_threads)
124 {
125 	int ret;
126 	pthread_t *threads = (pthread_t *)malloc(sizeof(pthread_t) * num_threads);
127 	pthread_attr_t attr;
128 	ret = pthread_attr_init(&attr);
129 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_attr_init");
130 	for (int i = 0; i < num_threads; i++) {
131 		struct sched_param param = { .sched_priority = 31 };
132 		ret = pthread_attr_setschedparam(&attr, &param);
133 		T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_attr_setschedparam");
134 		ret = pthread_create(&threads[i], &attr, func, (void *)&datas[i]);
135 		T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_create");
136 	}
137 	return threads;
138 }
139 
140 static void
start_test(ktrace_session_t session,pthread_t * threads,int num_threads)141 start_test(ktrace_session_t session, pthread_t *threads, int num_threads)
142 {
143 	dispatch_async(dispatch_get_main_queue(), ^{
144 		/* Wait for threads to finish, as last test action */
145 		for (int i = 0; i < num_threads; i++) {
146 		        int ret = pthread_join(threads[i], NULL);
147 		        T_QUIET; T_ASSERT_POSIX_ZERO(ret, "pthread_join");
148 		}
149 		ktrace_end(session, 0);
150 	});
151 	dispatch_main();
152 }
153 
154 static char *trace_location = NULL;
155 
156 static void
delete_trace_file(void)157 delete_trace_file(void)
158 {
159 	if (T_FAILCOUNT == 0) {
160 		T_LOG("Test passed, so deleting \"%s\" to save memory", trace_location);
161 		int ret;
162 		/* Delete trace file in order to reclaim disk space on the test device */
163 		ret = remove(trace_location);
164 		T_QUIET; T_WITH_ERRNO; T_ASSERT_POSIX_SUCCESS(ret, "remove trace file");
165 	}
166 }
167 
168 static char *
make_ktrace_filepath(char * short_name)169 make_ktrace_filepath(char *short_name)
170 {
171 	int ret;
172 	char *filepath = (char *)malloc(sizeof(char) * MAXPATHLEN);
173 	snprintf(filepath, MAXPATHLEN, "%s/%s.ktrace", dt_tmpdir(), short_name);
174 	ret = remove(filepath);
175 	T_QUIET; T_WITH_ERRNO; T_ASSERT_TRUE((ret == 0) || (errno == ENOENT), "remove");
176 	return filepath;
177 }
178 
179 static const int num_workload_ids = 5;
180 static char *workload_ids[num_workload_ids] = {
181 	"com.test.myapp.efficient",
182 	"com.test.myapp.best_effort",
183 	"com.test.myapp.application",
184 	"com.test.myapp.critical",
185 	"com.test.myapp.shared_flags",
186 };
187 
188 static uint64_t expected_tg_flags[num_workload_ids] = {
189 	THREAD_GROUP_FLAGS_EFFICIENT,
190 	THREAD_GROUP_FLAGS_BEST_EFFORT,
191 	THREAD_GROUP_FLAGS_APPLICATION,
192 	THREAD_GROUP_FLAGS_CRITICAL,
193 #if TARGET_OS_XR
194 	THREAD_GROUP_FLAGS_MANAGED | THREAD_GROUP_FLAGS_STRICT_TIMERS | THREAD_GROUP_FLAGS_APPLICATION,
195 #else /* !TARGET_OS_XR */
196 	THREAD_GROUP_FLAGS_APPLICATION,
197 #endif /* !TARGET_OS_XR */
198 };
199 
200 static int
tg_id_to_index(struct thread_data * datas,int num_datas,uint64_t tg_id)201 tg_id_to_index(struct thread_data *datas, int num_datas, uint64_t tg_id)
202 {
203 	int index = -1;
204 	for (int i = 0; i < num_datas; i++) {
205 		if (tg_id == datas[i].tg_id) {
206 			index = i;
207 			break;
208 		}
209 	}
210 	return index;
211 }
212 
213 static void
search_for_workload_id_tg_flags_tracepoints(char * trace_path,int num_workload_ids,struct thread_data * datas)214 search_for_workload_id_tg_flags_tracepoints(char *trace_path, int num_workload_ids, struct thread_data *datas)
215 {
216 	__block int ret;
217 	trace_location = trace_path;
218 	T_ATEND(delete_trace_file);
219 	ktrace_session_t read_session = ktrace_session_create();
220 	ret = ktrace_set_file(read_session, trace_path);
221 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_set_file");
222 	__block int num_validated_new_tgs = 0;
223 	ktrace_events_single(read_session, MACHDBG_CODE(DBG_MACH_THREAD_GROUP, MACH_THREAD_GROUP_NEW), ^(ktrace_event_t e) {
224 		ret = ktrace_print_trace_point(stdout, read_session, e, KTP_KIND_CSV,
225 		KTP_FLAG_WALLTIME | KTP_FLAG_THREADNAME | KTP_FLAG_PID | KTP_FLAG_EVENTNAME | KTP_FLAG_EXECNAME);
226 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ktrace_print_trace_point output");
227 		printf("\n"); // Flush output from ktrace_print_trace_point
228 		uint64_t tg_id = e->arg1;
229 		uint64_t tg_flags = e->arg2;
230 		int workload_ind = tg_id_to_index(datas, num_workload_ids, tg_id);
231 		if (workload_ind != -1) {
232 		        T_LOG("MACH_THREAD_GROUP_NEW tracepoint from TG %llx with flags %llx, expecting %llx", tg_id, tg_flags, expected_tg_flags[workload_ind]);
233 		        T_EXPECT_EQ(tg_flags, expected_tg_flags[workload_ind], "Correct new TG flags for \"%s\"", workload_ids[workload_ind]);
234 		        num_validated_new_tgs++;
235 		}
236 	});
237 	__block int num_validated_flags = 0;
238 	ktrace_events_single(read_session, MACHDBG_CODE(DBG_MACH_THREAD_GROUP, MACH_THREAD_GROUP_FLAGS), ^(ktrace_event_t e) {
239 		ret = ktrace_print_trace_point(stdout, read_session, e, KTP_KIND_CSV,
240 		KTP_FLAG_WALLTIME | KTP_FLAG_THREADNAME | KTP_FLAG_PID | KTP_FLAG_EVENTNAME | KTP_FLAG_EXECNAME);
241 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ktrace_print_trace_point output");
242 		printf("\n"); // Flush output from ktrace_print_trace_point
243 		uint64_t tg_id = e->arg1;
244 		uint64_t tg_flags = e->arg2;
245 		int workload_ind = tg_id_to_index(datas, num_workload_ids, tg_id);
246 		if (workload_ind != -1) {
247 		        T_LOG("MACH_THREAD_GROUP_FLAGS tracepoint from TG %llx with flags %llx, expecting %llx", tg_id, tg_flags, expected_tg_flags[workload_ind]);
248 		        T_EXPECT_EQ(tg_flags, expected_tg_flags[workload_ind], "Correct TG flags for \"%s\"", workload_ids[workload_ind]);
249 		        T_QUIET; T_EXPECT_EQ(e->arg3, 0ULL, "tracepoint not dropped at TG creation time");
250 		        num_validated_flags++;
251 		}
252 	});
253 	__block int num_validated_joins = 0;
254 	ktrace_events_single(read_session, MACHDBG_CODE(DBG_MACH_THREAD_GROUP, MACH_THREAD_GROUP_SET), ^(ktrace_event_t e) {
255 		uint64_t new_tg_id = e->arg2;
256 		int workload_ind = tg_id_to_index(datas, num_workload_ids, new_tg_id);
257 		if (workload_ind != -1) {
258 		        ret = ktrace_print_trace_point(stdout, read_session, e, KTP_KIND_CSV,
259 		        KTP_FLAG_WALLTIME | KTP_FLAG_THREADNAME | KTP_FLAG_PID | KTP_FLAG_EVENTNAME | KTP_FLAG_EXECNAME);
260 		        T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ktrace_print_trace_point output");
261 		        printf("\n"); // Flush output from ktrace_print_trace_point
262 		        T_LOG("MACH_THREAD_GROUP_SET tracepoint for joining TG %llx", new_tg_id);
263 		        num_validated_joins++;
264 		}
265 	});
266 	ktrace_set_completion_handler(read_session, ^{
267 		T_EXPECT_EQ(num_validated_new_tgs, num_workload_ids, "Found all expected MACH_THREAD_GROUP_NEW tracepoints");
268 		T_EXPECT_EQ(num_validated_flags, num_workload_ids, "Found all expected MACH_THREAD_GROUP_FLAGS tracepoints");
269 		T_EXPECT_EQ(num_validated_joins, num_workload_ids, "Found all expected MACH_THREAD_GROUP_SET tracepoints");
270 		T_END;
271 	});
272 	ret = ktrace_start(read_session, dispatch_get_main_queue());
273 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_start");
274 }
275 
276 static const char *THREAD_GROUP_FILTER = "S0x01A6";
277 
278 T_DECL(thread_group_flags_from_workload_properties,
279     "Verify that workload properties correctly propagate thread group flags",
280     T_META_ASROOT(true))
281 {
282 	int ret;
283 	T_ATEND(workload_config_cleanup);
284 	workload_config_load();
285 
286 	ktrace_session_t session = ktrace_session_create();
287 	char *filepath = make_ktrace_filepath("thread_group_flags_from_workload_properties");
288 
289 	ret = ktrace_events_filter(session, THREAD_GROUP_FILTER, ^(__unused ktrace_event_t event){});
290 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_events_filter");
291 	__block struct thread_data *datas = (struct thread_data *)calloc(num_workload_ids, sizeof(struct thread_data));
292 	ktrace_set_completion_handler(session, ^{
293 		search_for_workload_id_tg_flags_tracepoints(filepath, num_workload_ids, datas);
294 	});
295 	ret = ktrace_start_writing_path(session, filepath, 0);
296 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_start_writing_path");
297 	T_LOG("Ktrace file being written to %s", filepath);
298 
299 	/* Create a work interval for each test workload id */
300 	work_interval_t wi_handles[num_workload_ids];
301 	for (int w = 0; w < num_workload_ids; w++) {
302 		make_work_interval(&wi_handles[w], WORK_INTERVAL_TYPE_DEFAULT | WORK_INTERVAL_FLAG_HAS_WORKLOAD_ID, workload_ids[w]);
303 	}
304 	for (int i = 0; i < num_workload_ids; i++) {
305 		datas[i].wi_handle = wi_handles[i];
306 	}
307 	__block pthread_t *threads = start_threads(join_workload_fn, datas, num_workload_ids);
308 	start_test(session, threads, num_workload_ids);
309 }
310 
311 static void *
join_leave_pid_based(void * arg)312 join_leave_pid_based(void *arg)
313 {
314 	int ret;
315 	struct thread_data *data = (struct thread_data *)arg;
316 	data->tg_id = get_thread_group_id();
317 
318 	ret = setpriority(PRIO_DARWIN_CARPLAY_MODE, 0, PRIO_DARWIN_CARPLAY_MODE_ON);
319 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set_priority(PRIO_DARWIN_CARPLAY_MODE_ON)");
320 
321 	ret = setpriority(PRIO_DARWIN_CARPLAY_MODE, 0, PRIO_DARWIN_CARPLAY_MODE_OFF);
322 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set_priority(PRIO_DARWIN_CARPLAY_MODE_OFF)");
323 
324 	ret = setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_ON);
325 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set_priority(PRIO_DARWIN_GAME_MODE_ON)");
326 
327 	ret = setpriority(PRIO_DARWIN_GAME_MODE, 0, PRIO_DARWIN_GAME_MODE_OFF);
328 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set_priority(PRIO_DARWIN_GAME_MODE_OFF)");
329 
330 	T_EXPECT_EQ(data->tg_id, get_thread_group_id(), "Unchanged TG");
331 	return NULL;
332 }
333 
334 static void
search_for_pid_based_tg_flags_tracepoints(char * trace_path,int num_threads,struct thread_data * datas)335 search_for_pid_based_tg_flags_tracepoints(char *trace_path, int num_threads, struct thread_data *datas)
336 {
337 	__block int ret;
338 	trace_location = trace_path;
339 	T_ATEND(delete_trace_file);
340 	ktrace_session_t read_session = ktrace_session_create();
341 	ret = ktrace_set_file(read_session, trace_path);
342 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_set_file");
343 	__block int tracepoint_idx = 0;
344 	ktrace_events_single(read_session, MACHDBG_CODE(DBG_MACH_THREAD_GROUP, MACH_THREAD_GROUP_FLAGS), ^(ktrace_event_t e) {
345 		ret = ktrace_print_trace_point(stdout, read_session, e, KTP_KIND_CSV,
346 		KTP_FLAG_WALLTIME | KTP_FLAG_THREADNAME | KTP_FLAG_PID | KTP_FLAG_EVENTNAME | KTP_FLAG_EXECNAME);
347 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ktrace_print_trace_point output");
348 		printf("\n"); // Flush output from ktrace_print_trace_point
349 		uint64_t tg_id = e->arg1;
350 		uint64_t new_tg_flags = e->arg2;
351 		uint64_t old_tg_flags = e->arg3;
352 		int data = tg_id_to_index(datas, num_workload_ids, tg_id);
353 		if (data != -1) {
354 		        T_LOG("MACH_THREAD_GROUP_FLAGS tracepoint from TG %llx with new flags %llx from old flags %llx", tg_id, new_tg_flags, old_tg_flags);
355 		        bool had_carplay = old_tg_flags & THREAD_GROUP_FLAGS_CARPLAY_MODE;
356 		        bool has_carplay = new_tg_flags & THREAD_GROUP_FLAGS_CARPLAY_MODE;
357 		        bool had_gamemode = old_tg_flags & THREAD_GROUP_FLAGS_GAME_MODE;
358 		        bool has_gamemode = new_tg_flags & THREAD_GROUP_FLAGS_GAME_MODE;
359 		        switch (tracepoint_idx) {
360 			case 0:
361 				T_QUIET; T_EXPECT_TRUE(!had_gamemode && !has_gamemode, "Game Mode on");
362 				T_EXPECT_TRUE(!had_carplay && has_carplay, "Correct flags for Car Play");
363 				break;
364 			case 1:
365 				T_QUIET; T_EXPECT_TRUE(!had_gamemode && !has_gamemode, "Game Mode on");
366 				T_EXPECT_TRUE(had_carplay && !has_carplay, "Correct flags for disabled Car Play");
367 				break;
368 			case 2:
369 				T_QUIET; T_EXPECT_TRUE(!had_carplay && !has_carplay, "Car Play on");
370 				T_EXPECT_TRUE(!had_gamemode && has_gamemode, "Correct flags for Game Mode");
371 				break;
372 			case 3:
373 				T_QUIET; T_EXPECT_TRUE(!had_carplay && !has_carplay, "Car Play on");
374 				T_EXPECT_TRUE(had_gamemode && !has_gamemode, "Correct flags for disabled Game Mode");
375 				break;
376 			}
377 		        T_QUIET; T_EXPECT_FALSE(new_tg_flags & THREAD_GROUP_FLAGS_EFFICIENT, "Test runner TG should not be efficient");
378 		        tracepoint_idx++;
379 		}
380 	});
381 	ktrace_set_completion_handler(read_session, ^{
382 		T_EXPECT_EQ(tracepoint_idx, 4, "Found all expected MACH_THREAD_GROUP_FLAGS tracepoints");
383 		T_END;
384 	});
385 	ret = ktrace_start(read_session, dispatch_get_main_queue());
386 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_start");
387 }
388 
389 T_DECL(thread_group_flags_from_pid_interfaces,
390     "Verify that Car Play and Game Mode correctly propagate thread group flags",
391     T_META_ASROOT(true))
392 {
393 	int ret;
394 	int num_threads = 1;
395 
396 	ktrace_session_t session = ktrace_session_create();
397 	char *filepath = make_ktrace_filepath("thread_group_flags_from_pid_interfaces");
398 
399 	ret = ktrace_events_filter(session, THREAD_GROUP_FILTER, ^(__unused ktrace_event_t event){});
400 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_events_filter");
401 	__block struct thread_data *datas = (struct thread_data *)calloc(num_threads, sizeof(struct thread_data));
402 	ktrace_set_completion_handler(session, ^{
403 		search_for_pid_based_tg_flags_tracepoints(filepath, num_threads, datas);
404 	});
405 	ret = ktrace_start_writing_path(session, filepath, 0);
406 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "ktrace_start_writing_path");
407 	T_LOG("Ktrace file being written to %s", filepath);
408 
409 	__block pthread_t *threads = start_threads(join_leave_pid_based, datas, num_threads);
410 	start_test(session, threads, num_threads);
411 }
412