2 * Test to validate that we can schedule threads on all hw.ncpus cores according to _os_cpu_number
4 * <rdar://problem/29545645>
6 * xcrun -sdk macosx.internal clang -o cpucount cpucount.c -ldarwintest -g -Weverything
7 * xcrun -sdk iphoneos.internal clang -arch arm64 -o cpucount-ios cpucount.c -ldarwintest -g -Weverything
10 #include <darwintest.h>
22 #include <sys/sysctl.h>
23 #include <stdatomic.h>
25 #include <mach/mach.h>
26 #include <mach/mach_time.h>
28 #include <os/tsd.h> /* private header for _os_cpu_number */
30 /* const variables aren't constants, but enums are */
31 enum { max_threads
= 40 };
33 #define CACHE_ALIGNED __attribute__((aligned(128)))
35 static _Atomic CACHE_ALIGNED
uint64_t g_ready_threads
= 0;
37 static _Atomic CACHE_ALIGNED
bool g_cpu_seen
[max_threads
];
39 static _Atomic CACHE_ALIGNED
bool g_bail
= false;
41 static uint32_t g_threads
; /* set by sysctl hw.ncpu */
43 static uint64_t g_spin_ms
= 50; /* it takes ~50ms of spinning for CLPC to deign to give us all cores */
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
49 static uint32_t g_thread_pri
= 97;
52 * add in some extra low-pri threads to convince the amp scheduler to use E-cores consistently
53 * works around <rdar://problem/29636191>
55 static uint32_t g_spin_threads
= 2;
56 static uint32_t g_spin_threads_pri
= 20;
58 static semaphore_t g_readysem
, g_go_sem
;
60 static mach_timebase_info_data_t timebase_info
;
63 nanos_to_abs(uint64_t nanos
)
65 return nanos
* timebase_info
.denom
/ timebase_info
.numer
;
69 set_realtime(pthread_t thread
)
72 thread_time_constraint_policy_data_t pol
;
74 mach_port_t target_thread
= pthread_mach_thread_np(thread
);
75 T_QUIET
; T_ASSERT_NOTNULL(target_thread
, "pthread_mach_thread_np");
78 pol
.period
= (uint32_t)nanos_to_abs(1000000000);
79 pol
.constraint
= (uint32_t)nanos_to_abs(100000000);
80 pol
.computation
= (uint32_t)nanos_to_abs(10000000);
82 pol
.preemptible
= 0; /* Ignored by OS */
83 kr
= thread_policy_set(target_thread
, THREAD_TIME_CONSTRAINT_POLICY
, (thread_policy_t
) &pol
,
84 THREAD_TIME_CONSTRAINT_POLICY_COUNT
);
85 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "thread_policy_set(THREAD_TIME_CONSTRAINT_POLICY)");
89 create_thread(void *(*start_routine
)(void *), uint32_t priority
)
95 struct sched_param param
= { .sched_priority
= (int)priority
};
97 rv
= pthread_attr_init(&attr
);
98 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_attr_init");
100 rv
= pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
101 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_attr_setdetachstate");
103 rv
= pthread_attr_setschedparam(&attr
, ¶m
);
104 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_attr_setschedparam");
106 rv
= pthread_create(&new_thread
, &attr
, start_routine
, NULL
);
107 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_create");
109 if (priority
== 97) {
110 set_realtime(new_thread
);
113 rv
= pthread_attr_destroy(&attr
);
114 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_attr_destroy");
120 thread_fn(__unused
void *arg
)
122 T_QUIET
; T_EXPECT_TRUE(true, "initialize darwintest on this thread");
126 kr
= semaphore_wait_signal(g_go_sem
, g_readysem
);
127 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait_signal");
129 /* atomic inc to say hello */
132 uint64_t timeout
= nanos_to_abs(g_spin_ms
* NSEC_PER_MSEC
) + mach_absolute_time();
135 * spin to force the other threads to spread out across the cores
136 * may take some time if cores are masked and CLPC needs to warm up to unmask them
138 while (g_ready_threads
< g_threads
&& mach_absolute_time() < timeout
) {
142 T_QUIET
; T_ASSERT_GE(timeout
, mach_absolute_time(), "waiting for all threads took too long");
144 timeout
= nanos_to_abs(g_spin_ms
* NSEC_PER_MSEC
) + mach_absolute_time();
149 /* search for new CPUs for the duration */
150 while (mach_absolute_time() < timeout
) {
151 cpunum
= _os_cpu_number();
153 assert(cpunum
< max_threads
);
155 g_cpu_seen
[cpunum
] = true;
157 if (iteration
++ % 10000) {
158 uint32_t cpus_seen
= 0;
160 for (uint32_t i
= 0; i
< g_threads
; i
++) {
166 /* bail out early if we saw all CPUs */
167 if (cpus_seen
== g_threads
) {
175 printf("thread cpunum: %d\n", cpunum
);
177 kr
= semaphore_wait_signal(g_go_sem
, g_readysem
);
178 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait_signal");
184 spin_fn(__unused
void *arg
)
186 T_QUIET
; T_EXPECT_TRUE(true, "initialize darwintest on this thread");
190 kr
= semaphore_wait_signal(g_go_sem
, g_readysem
);
191 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait_signal");
193 uint64_t timeout
= nanos_to_abs(g_spin_ms
* NSEC_PER_MSEC
* 2) + mach_absolute_time();
196 * run and sleep a bit to force some scheduler churn to get all the cores active
197 * needed to work around bugs in the amp scheduler
199 while (mach_absolute_time() < timeout
&& g_bail
== false) {
202 uint64_t inner_timeout
= nanos_to_abs(1 * NSEC_PER_MSEC
) + mach_absolute_time();
204 while (mach_absolute_time() < inner_timeout
&& g_bail
== false) {
209 kr
= semaphore_wait_signal(g_go_sem
, g_readysem
);
210 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait_signal");
216 #pragma clang diagnostic push
217 #pragma clang diagnostic ignored "-Wgnu-flexible-array-initializer"
218 T_DECL(count_cpus
, "Tests we can schedule threads on all hw.ncpus cores according to _os_cpu_number",
219 T_META_CHECK_LEAKS(false), T_META_ENABLED(false))
220 #pragma clang diagnostic pop
222 setvbuf(stdout
, NULL
, _IONBF
, 0);
223 setvbuf(stderr
, NULL
, _IONBF
, 0);
227 kr
= mach_timebase_info(&timebase_info
);
228 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "mach_timebase_info");
230 kr
= semaphore_create(mach_task_self(), &g_readysem
, SYNC_POLICY_FIFO
, 0);
231 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_create");
233 kr
= semaphore_create(mach_task_self(), &g_go_sem
, SYNC_POLICY_FIFO
, 0);
234 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_create");
236 size_t ncpu_size
= sizeof(g_threads
);
237 rv
= sysctlbyname("hw.ncpu", &g_threads
, &ncpu_size
, NULL
, 0);
238 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "sysctlbyname(hw.ncpu)");
240 printf("hw.ncpu: %2d\n", g_threads
);
242 assert(g_threads
< max_threads
);
244 for (uint32_t i
= 0; i
< g_threads
; i
++) {
245 create_thread(&thread_fn
, g_thread_pri
);
248 for (uint32_t i
= 0; i
< g_spin_threads
; i
++) {
249 create_thread(&spin_fn
, g_spin_threads_pri
);
252 for (uint32_t i
= 0; i
< g_threads
+ g_spin_threads
; i
++) {
253 kr
= semaphore_wait(g_readysem
);
254 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait");
257 uint64_t timeout
= nanos_to_abs(g_spin_ms
* NSEC_PER_MSEC
) + mach_absolute_time();
259 /* spin to warm up CLPC :) */
260 while (mach_absolute_time() < timeout
) {
264 kr
= semaphore_signal_all(g_go_sem
);
265 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_signal_all");
267 for (uint32_t i
= 0; i
< g_threads
+ g_spin_threads
; i
++) {
268 kr
= semaphore_wait(g_readysem
);
269 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait");
272 uint32_t cpus_seen
= 0;
274 for (uint32_t i
= 0; i
< g_threads
; i
++) {
279 printf("cpu %2d: %d\n", i
, g_cpu_seen
[i
]);
282 T_ASSERT_EQ(cpus_seen
, g_threads
, "test should have run threads on all CPUS");