]> git.saurik.com Git - apple/xnu.git/blame - tests/cpucount.c
xnu-4903.241.1.tar.gz
[apple/xnu.git] / tests / cpucount.c
CommitLineData
5ba3f43e
A
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 *
6xcrun -sdk macosx.internal clang -o cpucount cpucount.c -ldarwintest -g -Weverything
7xcrun -sdk iphoneos.internal clang -arch arm64 -o cpucount-ios cpucount.c -ldarwintest -g -Weverything
8 */
9
10#include <darwintest.h>
11
12#include <stdio.h>
13#include <stdlib.h>
14#include <stdbool.h>
15#include <stdalign.h>
16#include <unistd.h>
17#include <assert.h>
18#include <pthread.h>
19#include <err.h>
20#include <errno.h>
21#include <sysexits.h>
22#include <sys/sysctl.h>
23#include <stdatomic.h>
24
25#include <mach/mach.h>
26#include <mach/mach_time.h>
27
28#include <os/tsd.h> /* private header for _os_cpu_number */
29
30/* const variables aren't constants, but enums are */
31enum { max_threads = 40 };
32
33#define CACHE_ALIGNED __attribute__((aligned(128)))
34
35static _Atomic CACHE_ALIGNED uint64_t g_ready_threads = 0;
36
37static _Atomic CACHE_ALIGNED bool g_cpu_seen[max_threads];
38
39static _Atomic CACHE_ALIGNED bool g_bail = false;
40
41static uint32_t g_threads; /* set by sysctl hw.ncpu */
42
43static uint64_t g_spin_ms = 50; /* it takes ~50ms of spinning for CLPC to deign to give us all cores */
44
45/*
46 * sometimes pageout scan can eat all of CPU 0 long enough to fail the test,
47 * so we run the test at RT priority
48 */
49static uint32_t g_thread_pri = 97;
50
51/*
52 * add in some extra low-pri threads to convince the amp scheduler to use E-cores consistently
53 * works around <rdar://problem/29636191>
54 */
55static uint32_t g_spin_threads = 2;
56static uint32_t g_spin_threads_pri = 20;
57
58static semaphore_t g_readysem, g_go_sem;
59
60static mach_timebase_info_data_t timebase_info;
61
62static uint64_t nanos_to_abs(uint64_t nanos) { return nanos * timebase_info.denom / timebase_info.numer; }
63
64static void set_realtime(pthread_t thread) {
65 kern_return_t kr;
66 thread_time_constraint_policy_data_t pol;
67
68 mach_port_t target_thread = pthread_mach_thread_np(thread);
69 T_QUIET; T_ASSERT_NOTNULL(target_thread, "pthread_mach_thread_np");
70
71 /* 1s 100ms 10ms */
72 pol.period = (uint32_t)nanos_to_abs(1000000000);
73 pol.constraint = (uint32_t)nanos_to_abs(100000000);
74 pol.computation = (uint32_t)nanos_to_abs(10000000);
75
76 pol.preemptible = 0; /* Ignored by OS */
77 kr = thread_policy_set(target_thread, THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t) &pol,
78 THREAD_TIME_CONSTRAINT_POLICY_COUNT);
79 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "thread_policy_set(THREAD_TIME_CONSTRAINT_POLICY)");
80}
81
82static pthread_t
83create_thread(void *(*start_routine)(void *), uint32_t priority)
84{
85 int rv;
86 pthread_t new_thread;
87 pthread_attr_t attr;
88
89 struct sched_param param = { .sched_priority = (int)priority };
90
91 rv = pthread_attr_init(&attr);
92 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_attr_init");
93
94 rv = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
95 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_attr_setdetachstate");
96
97 rv = pthread_attr_setschedparam(&attr, &param);
98 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_attr_setschedparam");
99
100 rv = pthread_create(&new_thread, &attr, start_routine, NULL);
101 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_create");
102
103 if (priority == 97)
104 set_realtime(new_thread);
105
106 rv = pthread_attr_destroy(&attr);
107 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_attr_destroy");
108
109 return new_thread;
110}
111
112static void *
113thread_fn(__unused void *arg)
114{
115 T_QUIET; T_EXPECT_TRUE(true, "initialize darwintest on this thread");
116
117 kern_return_t kr;
118
119 kr = semaphore_wait_signal(g_go_sem, g_readysem);
120 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait_signal");
121
122 /* atomic inc to say hello */
123 g_ready_threads++;
124
125 uint64_t timeout = nanos_to_abs(g_spin_ms * NSEC_PER_MSEC) + mach_absolute_time();
126
127 /*
128 * spin to force the other threads to spread out across the cores
129 * may take some time if cores are masked and CLPC needs to warm up to unmask them
130 */
131 while (g_ready_threads < g_threads && mach_absolute_time() < timeout);
132
133 T_QUIET; T_ASSERT_GE(timeout, mach_absolute_time(), "waiting for all threads took too long");
134
135 timeout = nanos_to_abs(g_spin_ms * NSEC_PER_MSEC) + mach_absolute_time();
136
137 int iteration = 0;
138 uint32_t cpunum = 0;
139
140 /* search for new CPUs for the duration */
141 while (mach_absolute_time() < timeout) {
142 cpunum = _os_cpu_number();
143
144 assert(cpunum < max_threads);
145
146 g_cpu_seen[cpunum] = true;
147
148 if (iteration++ % 10000) {
149 uint32_t cpus_seen = 0;
150
151 for (uint32_t i = 0 ; i < g_threads; i++) {
152 if (g_cpu_seen[i])
153 cpus_seen++;
154 }
155
156 /* bail out early if we saw all CPUs */
157 if (cpus_seen == g_threads)
158 break;
159 }
160 }
161
162 g_bail = true;
163
164 printf("thread cpunum: %d\n", cpunum);
165
166 kr = semaphore_wait_signal(g_go_sem, g_readysem);
167 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait_signal");
168
169 return NULL;
170}
171
172static void *
173spin_fn(__unused void *arg)
174{
175 T_QUIET; T_EXPECT_TRUE(true, "initialize darwintest on this thread");
176
177 kern_return_t kr;
178
179 kr = semaphore_wait_signal(g_go_sem, g_readysem);
180 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait_signal");
181
182 uint64_t timeout = nanos_to_abs(g_spin_ms * NSEC_PER_MSEC * 2) + mach_absolute_time();
183
184 /*
185 * run and sleep a bit to force some scheduler churn to get all the cores active
186 * needed to work around bugs in the amp scheduler
187 */
188 while (mach_absolute_time() < timeout && g_bail == false) {
189 usleep(500);
190
191 uint64_t inner_timeout = nanos_to_abs(1 * NSEC_PER_MSEC) + mach_absolute_time();
192
193 while (mach_absolute_time() < inner_timeout && g_bail == false);
194 }
195
196 kr = semaphore_wait_signal(g_go_sem, g_readysem);
197 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait_signal");
198
199 return NULL;
200}
201
202
203#pragma clang diagnostic push
204#pragma clang diagnostic ignored "-Wgnu-flexible-array-initializer"
205T_DECL(count_cpus, "Tests we can schedule threads on all hw.ncpus cores according to _os_cpu_number",
d9a64523 206 T_META_CHECK_LEAKS(false), T_META_ENABLED(false))
5ba3f43e
A
207#pragma clang diagnostic pop
208{
209 setvbuf(stdout, NULL, _IONBF, 0);
210 setvbuf(stderr, NULL, _IONBF, 0);
211
212 int rv;
213 kern_return_t kr;
214 kr = mach_timebase_info(&timebase_info);
215 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_timebase_info");
216
217 kr = semaphore_create(mach_task_self(), &g_readysem, SYNC_POLICY_FIFO, 0);
218 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_create");
219
220 kr = semaphore_create(mach_task_self(), &g_go_sem, SYNC_POLICY_FIFO, 0);
221 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_create");
222
223 size_t ncpu_size = sizeof(g_threads);
224 rv = sysctlbyname("hw.ncpu", &g_threads, &ncpu_size, NULL, 0);
225 T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "sysctlbyname(hw.ncpu)");
226
227 printf("hw.ncpu: %2d\n", g_threads);
228
229 assert(g_threads < max_threads);
230
231 for (uint32_t i = 0; i < g_threads; i++)
232 create_thread(&thread_fn, g_thread_pri);
233
234 for (uint32_t i = 0; i < g_spin_threads; i++)
235 create_thread(&spin_fn, g_spin_threads_pri);
236
237 for (uint32_t i = 0 ; i < g_threads + g_spin_threads; i++) {
238 kr = semaphore_wait(g_readysem);
239 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait");
240 }
241
242 uint64_t timeout = nanos_to_abs(g_spin_ms * NSEC_PER_MSEC) + mach_absolute_time();
243
244 /* spin to warm up CLPC :) */
245 while (mach_absolute_time() < timeout);
246
247 kr = semaphore_signal_all(g_go_sem);
248 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_signal_all");
249
250 for (uint32_t i = 0 ; i < g_threads + g_spin_threads; i++) {
251 kr = semaphore_wait(g_readysem);
252 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait");
253 }
254
255 uint32_t cpus_seen = 0;
256
257 for (uint32_t i = 0 ; i < g_threads; i++) {
258 if (g_cpu_seen[i])
259 cpus_seen++;
260
261 printf("cpu %2d: %d\n", i, g_cpu_seen[i]);
262 }
263
264 T_ASSERT_EQ(cpus_seen, g_threads, "test should have run threads on all CPUS");
265}
266