xref: /xnu-8019.80.24/tests/cpucount.c (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
1 /*
2  * Test to validate that we can schedule threads on all hw.ncpus cores according to _os_cpu_number
3  *
4  * <rdar://problem/29545645>
5  * <rdar://problem/30445216>
6  *
7  *  xcrun -sdk macosx.internal clang -o cpucount cpucount.c -ldarwintest -g -Weverything
8  *  xcrun -sdk iphoneos.internal clang -arch arm64 -o cpucount-ios cpucount.c -ldarwintest -g -Weverything
9  *  xcrun -sdk macosx.internal clang -o cpucount cpucount.c -ldarwintest -arch arm64e -Weverything
10  */
11 
12 #include <darwintest.h>
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <unistd.h>
17 #include <pthread.h>
18 #include <sys/sysctl.h>
19 #include <sys/proc_info.h>
20 #include <libproc.h>
21 
22 #include <mach/mach.h>
23 #include <mach/mach_time.h>
24 
25 #include <os/tsd.h> /* private header for _os_cpu_number */
26 
27 T_GLOBAL_META(
28 	T_META_RUN_CONCURRENTLY(false),
29 	T_META_BOOTARGS_SET("enable_skstb=1"),
30 	T_META_CHECK_LEAKS(false),
31 	T_META_ASROOT(true),
32 	T_META_ALL_VALID_ARCHS(true),
33 	T_META_RADAR_COMPONENT_NAME("xnu"),
34 	T_META_RADAR_COMPONENT_VERSION("scheduler")
35 	);
36 
37 #define KERNEL_BOOTARGS_MAX_SIZE 1024
38 static char kernel_bootargs[KERNEL_BOOTARGS_MAX_SIZE];
39 
40 #define KERNEL_VERSION_MAX_SIZE 1024
41 static char kernel_version[KERNEL_VERSION_MAX_SIZE];
42 
43 static mach_timebase_info_data_t timebase_info;
44 
45 static uint64_t
abs_to_nanos(uint64_t abs)46 abs_to_nanos(uint64_t abs)
47 {
48 	return abs * timebase_info.numer / timebase_info.denom;
49 }
50 
51 static int32_t
get_csw_count()52 get_csw_count()
53 {
54 	struct proc_taskinfo taskinfo;
55 	int rv;
56 
57 	rv = proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &taskinfo, sizeof(taskinfo));
58 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "PROC_PIDTASKINFO");
59 
60 	return taskinfo.pti_csw;
61 }
62 
63 // noinline hopefully keeps the optimizer from hoisting it out of the loop
64 // until rdar://68253516 is fixed.
65 __attribute__((noinline))
66 static uint32_t
fixed_os_cpu_number(void)67 fixed_os_cpu_number(void)
68 {
69 	uint32_t cpu_number = _os_cpu_number();
70 
71 	return cpu_number;
72 }
73 
74 
75 T_DECL(count_cpus, "Tests we can schedule bound threads on all hw.ncpus cores and that _os_cpu_number matches")
76 {
77 	int rv;
78 
79 	setvbuf(stdout, NULL, _IONBF, 0);
80 	setvbuf(stderr, NULL, _IONBF, 0);
81 
82 	/* Validate what kind of kernel we're on */
83 	size_t kernel_version_size = sizeof(kernel_version);
84 	rv = sysctlbyname("kern.version", kernel_version, &kernel_version_size, NULL, 0);
85 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "kern.version");
86 
87 	T_LOG("kern.version: %s\n", kernel_version);
88 
89 	/* Double check that darwintest set the boot arg we requested */
90 	size_t kernel_bootargs_size = sizeof(kernel_bootargs);
91 	rv = sysctlbyname("kern.bootargs", kernel_bootargs, &kernel_bootargs_size, NULL, 0);
92 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "kern.bootargs");
93 
94 	T_LOG("kern.bootargs: %s\n", kernel_bootargs);
95 
96 	if (NULL == strstr(kernel_bootargs, "enable_skstb=1")) {
97 		T_FAIL("enable_skstb=1 boot-arg is missing");
98 	}
99 
100 	kern_return_t kr;
101 	kr = mach_timebase_info(&timebase_info);
102 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_timebase_info");
103 
104 	int bound_cpu_out = 0;
105 	size_t bound_cpu_out_size = sizeof(bound_cpu_out);
106 	rv = sysctlbyname("kern.sched_thread_bind_cpu", &bound_cpu_out, &bound_cpu_out_size, NULL, 0);
107 
108 	if (rv == -1) {
109 		if (errno == ENOENT) {
110 			T_FAIL("kern.sched_thread_bind_cpu doesn't exist, must set enable_skstb=1 boot-arg on development kernel");
111 		}
112 		if (errno == EPERM) {
113 			T_FAIL("must run as root");
114 		}
115 	}
116 
117 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "read kern.sched_thread_bind_cpu");
118 	T_QUIET; T_ASSERT_EQ(bound_cpu_out, -1, "kern.sched_thread_bind_cpu should exist, start unbound");
119 
120 	struct sched_param param = {.sched_priority = 63};
121 
122 	rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
123 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_setschedparam");
124 
125 	uint32_t sysctl_ncpu = 0;
126 	size_t ncpu_size = sizeof(sysctl_ncpu);
127 	rv = sysctlbyname("hw.ncpu", &sysctl_ncpu, &ncpu_size, NULL, 0);
128 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "sysctlbyname(hw.ncpu)");
129 
130 	T_LOG("hw.ncpu: %2d\n", sysctl_ncpu);
131 
132 	T_ASSERT_GT(sysctl_ncpu, 0, "at least one CPU exists");
133 
134 	for (uint32_t cpu_to_bind = 0; cpu_to_bind < sysctl_ncpu; cpu_to_bind++) {
135 		int32_t before_csw_count = get_csw_count();
136 		T_LOG("(csw %4d) attempting to bind to cpu %2d\n", before_csw_count, cpu_to_bind);
137 
138 		uint64_t start =  mach_absolute_time();
139 
140 		rv = sysctlbyname("kern.sched_thread_bind_cpu", NULL, 0, &cpu_to_bind, sizeof(cpu_to_bind));
141 
142 		uint64_t end =  mach_absolute_time();
143 
144 		if (rv == -1 && errno == ENOTSUP) {
145 			T_SKIP("Binding is available, but this process doesn't support binding (e.g. Rosetta on Aruba)");
146 		}
147 
148 		T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "kern.sched_thread_bind_cpu(%u)", cpu_to_bind);
149 
150 		uint32_t os_cpu_number_reported = fixed_os_cpu_number();
151 
152 		bound_cpu_out = 0;
153 		rv = sysctlbyname("kern.sched_thread_bind_cpu", &bound_cpu_out, &bound_cpu_out_size, NULL, 0);
154 		T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "read kern.sched_thread_bind_cpu");
155 
156 		T_QUIET; T_EXPECT_EQ((int)cpu_to_bind, bound_cpu_out,
157 		    "should report bound cpu id matching requested bind target");
158 
159 		uint64_t delta_abs = end - start;
160 		uint64_t delta_ns = abs_to_nanos(delta_abs);
161 
162 		int32_t after_csw_count = get_csw_count();
163 
164 		T_LOG("(csw %4d) bound to cpu %2d in %f milliseconds\n",
165 		    after_csw_count, cpu_to_bind,
166 		    ((double)delta_ns / 1000000.0));
167 
168 		if (cpu_to_bind > 0) {
169 			T_QUIET; T_EXPECT_LT(before_csw_count, after_csw_count,
170 			    "should have had to context switch to execute the bind");
171 		}
172 
173 		T_LOG("cpu %2d reported id %2d\n",
174 		    cpu_to_bind, os_cpu_number_reported);
175 
176 		T_QUIET;
177 		T_EXPECT_EQ(cpu_to_bind, os_cpu_number_reported,
178 		    "should report same CPU number as was bound to");
179 	}
180 
181 	int unbind = -1; /* pass -1 in order to unbind the thread */
182 
183 	rv = sysctlbyname("kern.sched_thread_bind_cpu", NULL, 0, &unbind, sizeof(unbind));
184 
185 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "kern.sched_thread_bind_cpu(%u)", unbind);
186 
187 	rv = sysctlbyname("kern.sched_thread_bind_cpu", &bound_cpu_out, &bound_cpu_out_size, NULL, 0);
188 
189 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "read kern.sched_thread_bind_cpu");
190 	T_QUIET; T_ASSERT_EQ(bound_cpu_out, -1, "thread should be unbound at the end");
191 
192 	T_PASS("test has run threads on all CPUS");
193 }
194