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
;
62 static uint64_t nanos_to_abs(uint64_t nanos
) { return nanos
* timebase_info
.denom
/ timebase_info
.numer
; }
64 static void set_realtime(pthread_t thread
) {
66 thread_time_constraint_policy_data_t pol
;
68 mach_port_t target_thread
= pthread_mach_thread_np(thread
);
69 T_QUIET
; T_ASSERT_NOTNULL(target_thread
, "pthread_mach_thread_np");
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);
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)");
83 create_thread(void *(*start_routine
)(void *), uint32_t priority
)
89 struct sched_param param
= { .sched_priority
= (int)priority
};
91 rv
= pthread_attr_init(&attr
);
92 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_attr_init");
94 rv
= pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
95 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_attr_setdetachstate");
97 rv
= pthread_attr_setschedparam(&attr
, ¶m
);
98 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_attr_setschedparam");
100 rv
= pthread_create(&new_thread
, &attr
, start_routine
, NULL
);
101 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_create");
104 set_realtime(new_thread
);
106 rv
= pthread_attr_destroy(&attr
);
107 T_QUIET
; T_ASSERT_POSIX_SUCCESS(rv
, "pthread_attr_destroy");
113 thread_fn(__unused
void *arg
)
115 T_QUIET
; T_EXPECT_TRUE(true, "initialize darwintest on this thread");
119 kr
= semaphore_wait_signal(g_go_sem
, g_readysem
);
120 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait_signal");
122 /* atomic inc to say hello */
125 uint64_t timeout
= nanos_to_abs(g_spin_ms
* NSEC_PER_MSEC
) + mach_absolute_time();
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
131 while (g_ready_threads
< g_threads
&& mach_absolute_time() < timeout
);
133 T_QUIET
; T_ASSERT_GE(timeout
, mach_absolute_time(), "waiting for all threads took too long");
135 timeout
= nanos_to_abs(g_spin_ms
* NSEC_PER_MSEC
) + mach_absolute_time();
140 /* search for new CPUs for the duration */
141 while (mach_absolute_time() < timeout
) {
142 cpunum
= _os_cpu_number();
144 assert(cpunum
< max_threads
);
146 g_cpu_seen
[cpunum
] = true;
148 if (iteration
++ % 10000) {
149 uint32_t cpus_seen
= 0;
151 for (uint32_t i
= 0 ; i
< g_threads
; i
++) {
156 /* bail out early if we saw all CPUs */
157 if (cpus_seen
== g_threads
)
164 printf("thread cpunum: %d\n", cpunum
);
166 kr
= semaphore_wait_signal(g_go_sem
, g_readysem
);
167 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait_signal");
173 spin_fn(__unused
void *arg
)
175 T_QUIET
; T_EXPECT_TRUE(true, "initialize darwintest on this thread");
179 kr
= semaphore_wait_signal(g_go_sem
, g_readysem
);
180 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait_signal");
182 uint64_t timeout
= nanos_to_abs(g_spin_ms
* NSEC_PER_MSEC
* 2) + mach_absolute_time();
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
188 while (mach_absolute_time() < timeout
&& g_bail
== false) {
191 uint64_t inner_timeout
= nanos_to_abs(1 * NSEC_PER_MSEC
) + mach_absolute_time();
193 while (mach_absolute_time() < inner_timeout
&& g_bail
== false);
196 kr
= semaphore_wait_signal(g_go_sem
, g_readysem
);
197 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_wait_signal");
203 #pragma clang diagnostic push
204 #pragma clang diagnostic ignored "-Wgnu-flexible-array-initializer"
205 T_DECL(count_cpus
, "Tests we can schedule threads on all hw.ncpus cores according to _os_cpu_number",
206 T_META_CHECK_LEAKS(NO
))
207 #pragma clang diagnostic pop
209 setvbuf(stdout
, NULL
, _IONBF
, 0);
210 setvbuf(stderr
, NULL
, _IONBF
, 0);
214 kr
= mach_timebase_info(&timebase_info
);
215 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "mach_timebase_info");
217 kr
= semaphore_create(mach_task_self(), &g_readysem
, SYNC_POLICY_FIFO
, 0);
218 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_create");
220 kr
= semaphore_create(mach_task_self(), &g_go_sem
, SYNC_POLICY_FIFO
, 0);
221 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_create");
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)");
227 printf("hw.ncpu: %2d\n", g_threads
);
229 assert(g_threads
< max_threads
);
231 for (uint32_t i
= 0; i
< g_threads
; i
++)
232 create_thread(&thread_fn
, g_thread_pri
);
234 for (uint32_t i
= 0; i
< g_spin_threads
; i
++)
235 create_thread(&spin_fn
, g_spin_threads_pri
);
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");
242 uint64_t timeout
= nanos_to_abs(g_spin_ms
* NSEC_PER_MSEC
) + mach_absolute_time();
244 /* spin to warm up CLPC :) */
245 while (mach_absolute_time() < timeout
);
247 kr
= semaphore_signal_all(g_go_sem
);
248 T_QUIET
; T_ASSERT_MACH_SUCCESS(kr
, "semaphore_signal_all");
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");
255 uint32_t cpus_seen
= 0;
257 for (uint32_t i
= 0 ; i
< g_threads
; i
++) {
261 printf("cpu %2d: %d\n", i
, g_cpu_seen
[i
]);
264 T_ASSERT_EQ(cpus_seen
, g_threads
, "test should have run threads on all CPUS");