]> git.saurik.com Git - apple/xnu.git/blob - tools/tests/zero-to-n/zero-to-n.c
xnu-3789.1.32.tar.gz
[apple/xnu.git] / tools / tests / zero-to-n / zero-to-n.c
1 /*
2 * Copyright (c) 2009 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <math.h>
31 #include <sys/kdebug.h>
32 #include <stdlib.h>
33 #include <pthread.h>
34 #include <errno.h>
35 #include <err.h>
36 #include <string.h>
37 #include <assert.h>
38 #include <sysexits.h>
39 #include <sys/sysctl.h>
40 #include <getopt.h>
41
42 #include <spawn.h>
43 #include <spawn_private.h>
44 #include <sys/spawn_internal.h>
45 #include <mach-o/dyld.h>
46
47 #include <libkern/OSAtomic.h>
48
49 #include <mach/mach_time.h>
50 #include <mach/mach.h>
51 #include <mach/task.h>
52 #include <mach/semaphore.h>
53
54 #include <pthread/qos_private.h>
55
56 #include <sys/resource.h>
57
58 typedef enum wake_type { WAKE_BROADCAST_ONESEM, WAKE_BROADCAST_PERTHREAD, WAKE_CHAIN, WAKE_HOP } wake_type_t;
59 typedef enum my_policy_type { MY_POLICY_REALTIME, MY_POLICY_TIMESHARE, MY_POLICY_FIXEDPRI } my_policy_type_t;
60
61 #define mach_assert_zero(error) do { if ((error) != 0) { fprintf(stderr, "[FAIL] error %d (%s) ", (error), mach_error_string(error)); assert(error == 0); } } while (0)
62 #define mach_assert_zero_t(tid, error) do { if ((error) != 0) { fprintf(stderr, "[FAIL] Thread %d error %d (%s) ", (tid), (error), mach_error_string(error)); assert(error == 0); } } while (0)
63 #define assert_zero_t(tid, error) do { if ((error) != 0) { fprintf(stderr, "[FAIL] Thread %d error %d ", (tid), (error)); assert(error == 0); } } while (0)
64
65 #define CONSTRAINT_NANOS (20000000ll) /* 20 ms */
66 #define COMPUTATION_NANOS (10000000ll) /* 10 ms */
67 #define TRACEWORTHY_NANOS (10000000ll) /* 10 ms */
68
69 #if DEBUG
70 #define debug_log(args...) printf(args)
71 #else
72 #define debug_log(args...) do { } while(0)
73 #endif
74
75 /* Declarations */
76 static void* worker_thread(void *arg);
77 static void usage();
78 static int thread_setup(uint32_t my_id);
79 static my_policy_type_t parse_thread_policy(const char *str);
80 static void selfexec_with_apptype(int argc, char *argv[]);
81 static void parse_args(int argc, char *argv[]);
82
83 /* Global variables (general) */
84 static uint32_t g_numcpus;
85 static uint32_t g_numthreads;
86 static wake_type_t g_waketype;
87 static policy_t g_policy;
88 static uint32_t g_iterations;
89 static struct mach_timebase_info g_mti;
90 static semaphore_t g_main_sem;
91 static uint64_t *g_thread_endtimes_abs;
92 static volatile uint32_t g_done_threads;
93 static boolean_t g_verbose = FALSE;
94 static boolean_t g_do_affinity = FALSE;
95 static uint64_t g_starttime_abs;
96 static uint32_t g_iteration_sleeptime_us = 0;
97 static uint32_t g_priority = 0;
98 static uint32_t g_churn_pri = 0;
99 static uint32_t g_churn_count = 0;
100 static uint64_t g_churn_stopped_at = 0;
101 static boolean_t g_churn_stop = FALSE;
102
103 static pthread_t* g_churn_threads = NULL;
104
105 /* Threshold for dropping a 'bad run' tracepoint */
106 static uint64_t g_traceworthy_latency_ns = TRACEWORTHY_NANOS;
107
108 /* Have we re-execed to set apptype? */
109 static boolean_t g_seen_apptype = FALSE;
110
111 /* usleep in betweeen iterations */
112 static boolean_t g_do_sleep = TRUE;
113
114 /* Every thread spins until all threads have checked in */
115 static boolean_t g_do_all_spin = FALSE;
116
117 /* Every thread backgrounds temporarily before parking */
118 static boolean_t g_drop_priority = FALSE;
119
120 /* One randomly chosen thread holds up the train for a certain duration. */
121 static boolean_t g_do_one_long_spin = FALSE;
122 static uint32_t g_one_long_spin_id = 0;
123 static uint64_t g_one_long_spin_length_abs = 0;
124 static uint64_t g_one_long_spin_length_ns = 0;
125
126 /* Each thread spins for a certain duration after waking up before blocking again. */
127 static boolean_t g_do_each_spin = FALSE;
128 static uint64_t g_each_spin_duration_abs = 0;
129 static uint64_t g_each_spin_duration_ns = 0;
130
131 /* Global variables (broadcast) */
132 static semaphore_t g_broadcastsem;
133 static semaphore_t g_leadersem;
134 static semaphore_t g_readysem;
135 static semaphore_t g_donesem;
136
137 /* Global variables (chain) */
138 static semaphore_t *g_semarr;
139
140 static uint64_t
141 abs_to_nanos(uint64_t abstime)
142 {
143 return (uint64_t)(abstime * (((double)g_mti.numer) / ((double)g_mti.denom)));
144 }
145
146 static uint64_t
147 nanos_to_abs(uint64_t ns)
148 {
149 return (uint64_t)(ns * (((double)g_mti.denom) / ((double)g_mti.numer)));
150 }
151
152 inline static void
153 yield(void)
154 {
155 #if defined(__x86_64__) || defined(__i386__)
156 asm volatile("pause");
157 #else
158 #error Unrecognized architecture
159 #endif
160 }
161
162 static void *
163 churn_thread(__unused void *arg)
164 {
165 uint64_t spin_count = 0;
166
167 /*
168 * As a safety measure to avoid wedging, we will bail on the spin if
169 * it's been more than 1s after the most recent run start
170 */
171
172 while (g_churn_stop == FALSE &&
173 mach_absolute_time() < (g_starttime_abs + NSEC_PER_SEC)) {
174 spin_count++;
175 yield();
176 }
177
178 /* This is totally racy, but only here to detect if anyone stops early */
179 g_churn_stopped_at += spin_count;
180
181 return NULL;
182 }
183
184 static void
185 create_churn_threads()
186 {
187 if (g_churn_count == 0)
188 g_churn_count = g_numcpus - 1;
189
190 errno_t err;
191
192 struct sched_param param = { .sched_priority = (int)g_churn_pri };
193 pthread_attr_t attr;
194
195 /* Array for churn threads */
196 g_churn_threads = (pthread_t*) valloc(sizeof(pthread_t) * g_churn_count);
197 assert(g_churn_threads);
198
199 if ((err = pthread_attr_init(&attr)))
200 errc(EX_OSERR, err, "pthread_attr_init");
201
202 if ((err = pthread_attr_setschedparam(&attr, &param)))
203 errc(EX_OSERR, err, "pthread_attr_setschedparam");
204
205 if ((err = pthread_attr_setschedpolicy(&attr, SCHED_RR)))
206 errc(EX_OSERR, err, "pthread_attr_setschedpolicy");
207
208 for (uint32_t i = 0 ; i < g_churn_count ; i++) {
209 pthread_t new_thread;
210
211 if ((err = pthread_create(&new_thread, &attr, churn_thread, NULL)))
212 errc(EX_OSERR, err, "pthread_create");
213 g_churn_threads[i] = new_thread;
214 }
215
216 if ((err = pthread_attr_destroy(&attr)))
217 errc(EX_OSERR, err, "pthread_attr_destroy");
218 }
219
220 static void
221 join_churn_threads(void)
222 {
223 if (g_churn_stopped_at != 0)
224 printf("Warning: Some of the churn threads may have stopped early: %lld\n",
225 g_churn_stopped_at);
226
227 OSMemoryBarrier();
228
229 g_churn_stop = TRUE;
230
231 /* Rejoin churn threads */
232 for (uint32_t i = 0; i < g_churn_count; i++) {
233 errno_t err = pthread_join(g_churn_threads[i], NULL);
234 if (err) errc(EX_OSERR, err, "pthread_join %d", i);
235 }
236 }
237
238 /*
239 * Figure out what thread policy to use
240 */
241 static my_policy_type_t
242 parse_thread_policy(const char *str)
243 {
244 if (strcmp(str, "timeshare") == 0) {
245 return MY_POLICY_TIMESHARE;
246 } else if (strcmp(str, "realtime") == 0) {
247 return MY_POLICY_REALTIME;
248 } else if (strcmp(str, "fixed") == 0) {
249 return MY_POLICY_FIXEDPRI;
250 } else {
251 errx(EX_USAGE, "Invalid thread policy \"%s\"", str);
252 }
253 }
254
255 /*
256 * Figure out what wakeup pattern to use
257 */
258 static wake_type_t
259 parse_wakeup_pattern(const char *str)
260 {
261 if (strcmp(str, "chain") == 0) {
262 return WAKE_CHAIN;
263 } else if (strcmp(str, "hop") == 0) {
264 return WAKE_HOP;
265 } else if (strcmp(str, "broadcast-single-sem") == 0) {
266 return WAKE_BROADCAST_ONESEM;
267 } else if (strcmp(str, "broadcast-per-thread") == 0) {
268 return WAKE_BROADCAST_PERTHREAD;
269 } else {
270 errx(EX_USAGE, "Invalid wakeup pattern \"%s\"", str);
271 }
272 }
273
274 /*
275 * Set policy
276 */
277 static int
278 thread_setup(uint32_t my_id)
279 {
280 kern_return_t kr;
281 errno_t ret;
282 thread_time_constraint_policy_data_t pol;
283
284 if (g_priority) {
285 int policy = SCHED_OTHER;
286 if (g_policy == MY_POLICY_FIXEDPRI)
287 policy = SCHED_RR;
288
289 struct sched_param param = {.sched_priority = (int)g_priority};
290 if ((ret = pthread_setschedparam(pthread_self(), policy, &param)))
291 errc(EX_OSERR, ret, "pthread_setschedparam: %d", my_id);
292 }
293
294 switch (g_policy) {
295 case MY_POLICY_TIMESHARE:
296 break;
297 case MY_POLICY_REALTIME:
298 /* Hard-coded realtime parameters (similar to what Digi uses) */
299 pol.period = 100000;
300 pol.constraint = (uint32_t) nanos_to_abs(CONSTRAINT_NANOS);
301 pol.computation = (uint32_t) nanos_to_abs(COMPUTATION_NANOS);
302 pol.preemptible = 0; /* Ignored by OS */
303
304 kr = thread_policy_set(mach_thread_self(), THREAD_TIME_CONSTRAINT_POLICY,
305 (thread_policy_t) &pol, THREAD_TIME_CONSTRAINT_POLICY_COUNT);
306 mach_assert_zero_t(my_id, kr);
307 break;
308 case MY_POLICY_FIXEDPRI:
309 ret = pthread_set_fixedpriority_self();
310 if (ret) errc(EX_OSERR, ret, "pthread_set_fixedpriority_self");
311 break;
312 default:
313 errx(EX_USAGE, "invalid policy type %d", g_policy);
314 }
315
316 if (g_do_affinity) {
317 thread_affinity_policy_data_t affinity;
318
319 affinity.affinity_tag = my_id % 2;
320
321 kr = thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY,
322 (thread_policy_t)&affinity, THREAD_AFFINITY_POLICY_COUNT);
323 mach_assert_zero_t(my_id, kr);
324 }
325
326 return 0;
327 }
328
329 /*
330 * Wait for a wakeup, potentially wake up another of the "0-N" threads,
331 * and notify the main thread when done.
332 */
333 static void*
334 worker_thread(void *arg)
335 {
336 uint32_t my_id = (uint32_t)(uintptr_t)arg;
337 kern_return_t kr;
338
339 volatile double x = 0.0;
340 volatile double y = 0.0;
341
342 /* Set policy and so forth */
343 thread_setup(my_id);
344
345 for (uint32_t i = 0; i < g_iterations; i++) {
346 if (my_id == 0) {
347 /*
348 * Leader thread either wakes everyone up or starts the chain going.
349 */
350
351 /* Give the worker threads undisturbed time to finish before waiting on them */
352 if (g_do_sleep)
353 usleep(g_iteration_sleeptime_us);
354
355 debug_log("%d Leader thread wait for ready\n", i);
356
357 /*
358 * Wait for everyone else to declare ready
359 * Is there a better way to do this that won't interfere with the rest of the chain?
360 * TODO: Invent 'semaphore wait for N signals'
361 */
362
363 for (uint32_t j = 0 ; j < g_numthreads - 1; j++) {
364 kr = semaphore_wait(g_readysem);
365 mach_assert_zero_t(my_id, kr);
366 }
367
368 debug_log("%d Leader thread wait\n", i);
369
370 /* Signal main thread and wait for start of iteration */
371
372 kr = semaphore_wait_signal(g_leadersem, g_main_sem);
373 mach_assert_zero_t(my_id, kr);
374
375 g_thread_endtimes_abs[my_id] = mach_absolute_time();
376
377 debug_log("%d Leader thread go\n", i);
378
379 assert_zero_t(my_id, g_done_threads);
380
381 switch (g_waketype) {
382 case WAKE_BROADCAST_ONESEM:
383 kr = semaphore_signal_all(g_broadcastsem);
384 mach_assert_zero_t(my_id, kr);
385 break;
386 case WAKE_BROADCAST_PERTHREAD:
387 for (uint32_t j = 1; j < g_numthreads; j++) {
388 kr = semaphore_signal(g_semarr[j]);
389 mach_assert_zero_t(my_id, kr);
390 }
391 break;
392 case WAKE_CHAIN:
393 kr = semaphore_signal(g_semarr[my_id + 1]);
394 mach_assert_zero_t(my_id, kr);
395 break;
396 case WAKE_HOP:
397 kr = semaphore_wait_signal(g_donesem, g_semarr[my_id + 1]);
398 mach_assert_zero_t(my_id, kr);
399 break;
400 }
401 } else {
402 /*
403 * Everyone else waits to be woken up,
404 * records when she wakes up, and possibly
405 * wakes up a friend.
406 */
407 switch(g_waketype) {
408 case WAKE_BROADCAST_ONESEM:
409 kr = semaphore_wait_signal(g_broadcastsem, g_readysem);
410 mach_assert_zero_t(my_id, kr);
411
412 g_thread_endtimes_abs[my_id] = mach_absolute_time();
413 break;
414
415 case WAKE_BROADCAST_PERTHREAD:
416 kr = semaphore_wait_signal(g_semarr[my_id], g_readysem);
417 mach_assert_zero_t(my_id, kr);
418
419 g_thread_endtimes_abs[my_id] = mach_absolute_time();
420 break;
421
422 case WAKE_CHAIN:
423 kr = semaphore_wait_signal(g_semarr[my_id], g_readysem);
424 mach_assert_zero_t(my_id, kr);
425
426 /* Signal the next thread *after* recording wake time */
427
428 g_thread_endtimes_abs[my_id] = mach_absolute_time();
429
430 if (my_id < (g_numthreads - 1)) {
431 kr = semaphore_signal(g_semarr[my_id + 1]);
432 mach_assert_zero_t(my_id, kr);
433 }
434
435 break;
436
437 case WAKE_HOP:
438 kr = semaphore_wait_signal(g_semarr[my_id], g_readysem);
439 mach_assert_zero_t(my_id, kr);
440
441 /* Signal the next thread *after* recording wake time */
442
443 g_thread_endtimes_abs[my_id] = mach_absolute_time();
444
445 if (my_id < (g_numthreads - 1)) {
446 kr = semaphore_wait_signal(g_donesem, g_semarr[my_id + 1]);
447 mach_assert_zero_t(my_id, kr);
448 } else {
449 kr = semaphore_signal_all(g_donesem);
450 mach_assert_zero_t(my_id, kr);
451 }
452
453 break;
454 }
455 }
456
457 debug_log("Thread %p woke up for iteration %d.\n", pthread_self(), i);
458
459 if (g_do_one_long_spin && g_one_long_spin_id == my_id) {
460 /* One randomly chosen thread holds up the train for a while. */
461
462 uint64_t endspin = g_starttime_abs + g_one_long_spin_length_abs;
463 while (mach_absolute_time() < endspin) {
464 y = y + 1.5 + x;
465 x = sqrt(y);
466 }
467 }
468
469 if (g_do_each_spin) {
470 /* Each thread spins for a certain duration after waking up before blocking again. */
471
472 uint64_t endspin = mach_absolute_time() + g_each_spin_duration_abs;
473 while (mach_absolute_time() < endspin) {
474 y = y + 1.5 + x;
475 x = sqrt(y);
476 }
477 }
478
479 int32_t new = OSAtomicIncrement32((volatile int32_t *)&g_done_threads);
480 (void)new;
481
482 debug_log("Thread %p new value is %d, iteration %d\n", pthread_self(), new, i);
483
484 if (g_drop_priority) {
485 /* Drop priority to BG momentarily */
486 errno_t ret = setpriority(PRIO_DARWIN_THREAD, 0, PRIO_DARWIN_BG);
487 if (ret) errc(EX_OSERR, ret, "setpriority PRIO_DARWIN_BG");
488 }
489
490 if (g_do_all_spin) {
491 /* Everyone spins until the last thread checks in. */
492
493 while (g_done_threads < g_numthreads) {
494 y = y + 1.5 + x;
495 x = sqrt(y);
496 }
497 }
498
499 if (g_drop_priority) {
500 /* Restore normal priority */
501 errno_t ret = setpriority(PRIO_DARWIN_THREAD, 0, 0);
502 if (ret) errc(EX_OSERR, ret, "setpriority 0");
503 }
504
505 debug_log("Thread %p done spinning, iteration %d\n", pthread_self(), i);
506 }
507
508 if (my_id == 0) {
509 /* Give the worker threads undisturbed time to finish before waiting on them */
510 if (g_do_sleep)
511 usleep(g_iteration_sleeptime_us);
512
513 /* Wait for the worker threads to finish */
514 for (uint32_t i = 0 ; i < g_numthreads - 1; i++) {
515 kr = semaphore_wait(g_readysem);
516 mach_assert_zero_t(my_id, kr);
517 }
518
519 /* Tell everyone and the main thread that the last iteration is done */
520 debug_log("%d Leader thread done\n", i);
521
522 kr = semaphore_signal_all(g_main_sem);
523 mach_assert_zero_t(my_id, kr);
524 } else {
525 /* Hold up thread teardown so it doesn't affect the last iteration */
526 kr = semaphore_wait_signal(g_main_sem, g_readysem);
527 mach_assert_zero_t(my_id, kr);
528 }
529
530 return 0;
531 }
532
533 /*
534 * Given an array of uint64_t values, compute average, max, min, and standard deviation
535 */
536 static void
537 compute_stats(uint64_t *values, uint64_t count, float *averagep, uint64_t *maxp, uint64_t *minp, float *stddevp)
538 {
539 uint32_t i;
540 uint64_t _sum = 0;
541 uint64_t _max = 0;
542 uint64_t _min = UINT64_MAX;
543 float _avg = 0;
544 float _dev = 0;
545
546 for (i = 0; i < count; i++) {
547 _sum += values[i];
548 _max = values[i] > _max ? values[i] : _max;
549 _min = values[i] < _min ? values[i] : _min;
550 }
551
552 _avg = ((float)_sum) / ((float)count);
553
554 _dev = 0;
555 for (i = 0; i < count; i++) {
556 _dev += powf((((float)values[i]) - _avg), 2);
557 }
558
559 _dev /= count;
560 _dev = sqrtf(_dev);
561
562 *averagep = _avg;
563 *maxp = _max;
564 *minp = _min;
565 *stddevp = _dev;
566 }
567
568 int
569 main(int argc, char **argv)
570 {
571 errno_t ret;
572 kern_return_t kr;
573
574 pthread_t *threads;
575 uint64_t *worst_latencies_ns;
576 uint64_t *worst_latencies_from_first_ns;
577 uint64_t max, min;
578 float avg, stddev;
579
580 for (int i = 0; i < argc; i++)
581 if (strcmp(argv[i], "--switched_apptype") == 0)
582 g_seen_apptype = TRUE;
583
584 if (!g_seen_apptype)
585 selfexec_with_apptype(argc, argv);
586
587 parse_args(argc, argv);
588
589 srand((unsigned int)time(NULL));
590
591 mach_timebase_info(&g_mti);
592
593 size_t ncpu_size = sizeof(g_numcpus);
594 ret = sysctlbyname("hw.ncpu", &g_numcpus, &ncpu_size, NULL, 0);
595 if (ret) err(EX_OSERR, "Failed sysctlbyname(hw.ncpu)");
596
597 if (g_do_each_spin)
598 g_each_spin_duration_abs = nanos_to_abs(g_each_spin_duration_ns);
599
600 /* Configure the long-spin thread to take up half of its computation */
601 if (g_do_one_long_spin) {
602 g_one_long_spin_length_ns = COMPUTATION_NANOS / 2;
603 g_one_long_spin_length_abs = nanos_to_abs(g_one_long_spin_length_ns);
604 }
605
606 /* Estimate the amount of time the cleanup phase needs to back off */
607 g_iteration_sleeptime_us = g_numthreads * 20;
608
609 uint32_t threads_per_core = (g_numthreads / g_numcpus) + 1;
610 if (g_do_each_spin)
611 g_iteration_sleeptime_us += threads_per_core * (g_each_spin_duration_ns / NSEC_PER_USEC);
612 if (g_do_one_long_spin)
613 g_iteration_sleeptime_us += g_one_long_spin_length_ns / NSEC_PER_USEC;
614
615 /* Arrays for threads and their wakeup times */
616 threads = (pthread_t*) valloc(sizeof(pthread_t) * g_numthreads);
617 assert(threads);
618
619 size_t endtimes_size = sizeof(uint64_t) * g_numthreads;
620
621 g_thread_endtimes_abs = (uint64_t*) valloc(endtimes_size);
622 assert(g_thread_endtimes_abs);
623
624 /* Ensure the allocation is pre-faulted */
625 ret = memset_s(g_thread_endtimes_abs, endtimes_size, 0, endtimes_size);
626 if (ret) errc(EX_OSERR, ret, "memset_s endtimes");
627
628 size_t latencies_size = sizeof(uint64_t) * g_iterations;
629
630 worst_latencies_ns = (uint64_t*) valloc(latencies_size);
631 assert(worst_latencies_ns);
632
633 /* Ensure the allocation is pre-faulted */
634 ret = memset_s(worst_latencies_ns, latencies_size, 0, latencies_size);
635 if (ret) errc(EX_OSERR, ret, "memset_s latencies");
636
637 worst_latencies_from_first_ns = (uint64_t*) valloc(latencies_size);
638 assert(worst_latencies_from_first_ns);
639
640 /* Ensure the allocation is pre-faulted */
641 ret = memset_s(worst_latencies_from_first_ns, latencies_size, 0, latencies_size);
642 if (ret) errc(EX_OSERR, ret, "memset_s latencies_from_first");
643
644 kr = semaphore_create(mach_task_self(), &g_main_sem, SYNC_POLICY_FIFO, 0);
645 mach_assert_zero(kr);
646
647 /* Either one big semaphore or one per thread */
648 if (g_waketype == WAKE_CHAIN ||
649 g_waketype == WAKE_BROADCAST_PERTHREAD ||
650 g_waketype == WAKE_HOP) {
651
652 g_semarr = valloc(sizeof(semaphore_t) * g_numthreads);
653 assert(g_semarr);
654
655 for (uint32_t i = 0; i < g_numthreads; i++) {
656 kr = semaphore_create(mach_task_self(), &g_semarr[i], SYNC_POLICY_FIFO, 0);
657 mach_assert_zero(kr);
658 }
659
660 g_leadersem = g_semarr[0];
661 } else {
662 kr = semaphore_create(mach_task_self(), &g_broadcastsem, SYNC_POLICY_FIFO, 0);
663 mach_assert_zero(kr);
664 kr = semaphore_create(mach_task_self(), &g_leadersem, SYNC_POLICY_FIFO, 0);
665 mach_assert_zero(kr);
666 }
667
668 if (g_waketype == WAKE_HOP) {
669 kr = semaphore_create(mach_task_self(), &g_donesem, SYNC_POLICY_FIFO, 0);
670 mach_assert_zero(kr);
671 }
672
673 kr = semaphore_create(mach_task_self(), &g_readysem, SYNC_POLICY_FIFO, 0);
674 mach_assert_zero(kr);
675
676 /* Create the threads */
677 g_done_threads = 0;
678 for (uint32_t i = 0; i < g_numthreads; i++) {
679 ret = pthread_create(&threads[i], NULL, worker_thread, (void*)(uintptr_t)i);
680 if (ret) errc(EX_OSERR, ret, "pthread_create %d", i);
681 }
682
683 ret = setpriority(PRIO_DARWIN_ROLE, 0, PRIO_DARWIN_ROLE_UI_FOCAL);
684 if (ret) errc(EX_OSERR, ret, "setpriority");
685
686 thread_setup(0);
687
688 g_starttime_abs = mach_absolute_time();
689
690 if (g_churn_pri)
691 create_churn_threads();
692
693 /* Let everyone get settled */
694 kr = semaphore_wait(g_main_sem);
695 mach_assert_zero(kr);
696
697 /* Give the system a bit more time to settle */
698 if (g_do_sleep)
699 usleep(g_iteration_sleeptime_us);
700
701 /* Go! */
702 for (uint32_t i = 0; i < g_iterations; i++) {
703 uint32_t j;
704 uint64_t worst_abs = 0, best_abs = UINT64_MAX;
705
706 if (g_do_one_long_spin)
707 g_one_long_spin_id = (uint32_t)rand() % g_numthreads;
708
709 debug_log("%d Main thread reset\n", i);
710
711 g_done_threads = 0;
712 OSMemoryBarrier();
713
714 g_starttime_abs = mach_absolute_time();
715
716 /* Fire them off and wait for worker threads to finish */
717 kr = semaphore_wait_signal(g_main_sem, g_leadersem);
718 mach_assert_zero(kr);
719
720 debug_log("%d Main thread return\n", i);
721
722 /*
723 * We report the worst latencies relative to start time
724 * and relative to the lead worker thread.
725 */
726 for (j = 0; j < g_numthreads; j++) {
727 uint64_t latency_abs;
728
729 latency_abs = g_thread_endtimes_abs[j] - g_starttime_abs;
730 worst_abs = worst_abs < latency_abs ? latency_abs : worst_abs;
731 }
732
733 worst_latencies_ns[i] = abs_to_nanos(worst_abs);
734
735 worst_abs = 0;
736 for (j = 1; j < g_numthreads; j++) {
737 uint64_t latency_abs;
738
739 latency_abs = g_thread_endtimes_abs[j] - g_thread_endtimes_abs[0];
740 worst_abs = worst_abs < latency_abs ? latency_abs : worst_abs;
741 best_abs = best_abs > latency_abs ? latency_abs : best_abs;
742 }
743
744 worst_latencies_from_first_ns[i] = abs_to_nanos(worst_abs);
745
746 /*
747 * In the event of a bad run, cut a trace point.
748 */
749 if (worst_latencies_from_first_ns[i] > g_traceworthy_latency_ns) {
750 /* Ariadne's ad-hoc test signpost */
751 kdebug_trace(ARIADNEDBG_CODE(0, 0), worst_latencies_from_first_ns[i], g_traceworthy_latency_ns, 0, 0);
752
753 if (g_verbose)
754 printf("Worst on this round was %.2f us.\n", ((float)worst_latencies_from_first_ns[i]) / 1000.0);
755 }
756
757 /* Give the system a bit more time to settle */
758 if (g_do_sleep)
759 usleep(g_iteration_sleeptime_us);
760 }
761
762 /* Rejoin threads */
763 for (uint32_t i = 0; i < g_numthreads; i++) {
764 ret = pthread_join(threads[i], NULL);
765 if (ret) errc(EX_OSERR, ret, "pthread_join %d", i);
766 }
767
768 if (g_churn_pri)
769 join_churn_threads();
770
771 compute_stats(worst_latencies_ns, g_iterations, &avg, &max, &min, &stddev);
772 printf("Results (from a stop):\n");
773 printf("Max:\t\t%.2f us\n", ((float)max) / 1000.0);
774 printf("Min:\t\t%.2f us\n", ((float)min) / 1000.0);
775 printf("Avg:\t\t%.2f us\n", avg / 1000.0);
776 printf("Stddev:\t\t%.2f us\n", stddev / 1000.0);
777
778 putchar('\n');
779
780 compute_stats(worst_latencies_from_first_ns, g_iterations, &avg, &max, &min, &stddev);
781 printf("Results (relative to first thread):\n");
782 printf("Max:\t\t%.2f us\n", ((float)max) / 1000.0);
783 printf("Min:\t\t%.2f us\n", ((float)min) / 1000.0);
784 printf("Avg:\t\t%.2f us\n", avg / 1000.0);
785 printf("Stddev:\t\t%.2f us\n", stddev / 1000.0);
786
787 #if 0
788 for (uint32_t i = 0; i < g_iterations; i++) {
789 printf("Iteration %d: %f us\n", i, worst_latencies_ns[i] / 1000.0);
790 }
791 #endif
792
793 free(threads);
794 free(g_thread_endtimes_abs);
795 free(worst_latencies_ns);
796 free(worst_latencies_from_first_ns);
797
798 return 0;
799 }
800
801 /*
802 * WARNING: This is SPI specifically intended for use by launchd to start UI
803 * apps. We use it here for a test tool only to opt into QoS using the same
804 * policies. Do not use this outside xnu or libxpc/launchd.
805 */
806 static void
807 selfexec_with_apptype(int argc, char *argv[])
808 {
809 int ret;
810 posix_spawnattr_t attr;
811 extern char **environ;
812 char *new_argv[argc + 1 + 1 /* NULL */];
813 int i;
814 char prog[PATH_MAX];
815 uint32_t prog_size = PATH_MAX;
816
817 ret = _NSGetExecutablePath(prog, &prog_size);
818 if (ret) err(EX_OSERR, "_NSGetExecutablePath");
819
820 for (i=0; i < argc; i++) {
821 new_argv[i] = argv[i];
822 }
823
824 new_argv[i] = "--switched_apptype";
825 new_argv[i+1] = NULL;
826
827 ret = posix_spawnattr_init(&attr);
828 if (ret) errc(EX_OSERR, ret, "posix_spawnattr_init");
829
830 ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETEXEC);
831 if (ret) errc(EX_OSERR, ret, "posix_spawnattr_setflags");
832
833 ret = posix_spawnattr_setprocesstype_np(&attr, POSIX_SPAWN_PROC_TYPE_APP_DEFAULT);
834 if (ret) errc(EX_OSERR, ret, "posix_spawnattr_setprocesstype_np");
835
836 ret = posix_spawn(NULL, prog, NULL, &attr, new_argv, environ);
837 if (ret) errc(EX_OSERR, ret, "posix_spawn");
838 }
839
840 /*
841 * Admittedly not very attractive.
842 */
843 static void __attribute__((noreturn))
844 usage()
845 {
846 errx(EX_USAGE, "Usage: %s <threads> <chain | hop | broadcast-single-sem | broadcast-per-thread> "
847 "<realtime | timeshare | fixed> <iterations>\n\t\t"
848 "[--trace <traceworthy latency in ns>] "
849 "[--verbose] [--spin-one] [--spin-all] [--spin-time <nanos>] [--affinity]\n\t\t"
850 "[--no-sleep] [--drop-priority] [--churn-pri <pri>] [--churn-count <n>]",
851 getprogname());
852 }
853
854 static struct option* g_longopts;
855 static int option_index;
856
857 static uint32_t
858 read_dec_arg()
859 {
860 char *cp;
861 /* char* optarg is a magic global */
862
863 uint32_t arg_val = (uint32_t)strtoull(optarg, &cp, 10);
864
865 if (cp == optarg || *cp)
866 errx(EX_USAGE, "arg --%s requires a decimal number, found \"%s\"",
867 g_longopts[option_index].name, optarg);
868
869 return arg_val;
870 }
871
872 static void
873 parse_args(int argc, char *argv[])
874 {
875 enum {
876 OPT_GETOPT = 0,
877 OPT_SPIN_TIME,
878 OPT_TRACE,
879 OPT_PRIORITY,
880 OPT_CHURN_PRI,
881 OPT_CHURN_COUNT,
882 };
883
884 static struct option longopts[] = {
885 { "spin-time", required_argument, NULL, OPT_SPIN_TIME },
886 { "trace", required_argument, NULL, OPT_TRACE },
887 { "priority", required_argument, NULL, OPT_PRIORITY },
888 { "churn-pri", required_argument, NULL, OPT_CHURN_PRI },
889 { "churn-count", required_argument, NULL, OPT_CHURN_COUNT },
890 { "switched_apptype", no_argument, (int*)&g_seen_apptype, TRUE },
891 { "spin-one", no_argument, (int*)&g_do_one_long_spin, TRUE },
892 { "spin-all", no_argument, (int*)&g_do_all_spin, TRUE },
893 { "affinity", no_argument, (int*)&g_do_affinity, TRUE },
894 { "no-sleep", no_argument, (int*)&g_do_sleep, FALSE },
895 { "drop-priority", no_argument, (int*)&g_drop_priority, TRUE },
896 { "verbose", no_argument, (int*)&g_verbose, TRUE },
897 { "help", no_argument, NULL, 'h' },
898 { NULL, 0, NULL, 0 }
899 };
900
901 g_longopts = longopts;
902 int ch = 0;
903
904 while ((ch = getopt_long(argc, argv, "h", longopts, &option_index)) != -1) {
905 switch (ch) {
906 case OPT_GETOPT:
907 /* getopt_long set a variable */
908 break;
909 case OPT_SPIN_TIME:
910 g_do_each_spin = TRUE;
911 g_each_spin_duration_ns = read_dec_arg();
912 break;
913 case OPT_TRACE:
914 g_traceworthy_latency_ns = read_dec_arg();
915 break;
916 case OPT_PRIORITY:
917 g_priority = read_dec_arg();
918 break;
919 case OPT_CHURN_PRI:
920 g_churn_pri = read_dec_arg();
921 break;
922 case OPT_CHURN_COUNT:
923 g_churn_count = read_dec_arg();
924 break;
925 case '?':
926 case 'h':
927 default:
928 usage();
929 /* NORETURN */
930 }
931 }
932
933 /*
934 * getopt_long reorders all the options to the beginning of the argv array.
935 * Jump past them to the non-option arguments.
936 */
937
938 argc -= optind;
939 argv += optind;
940
941 if (argc > 4) {
942 warnx("Too many non-option arguments passed");
943 usage();
944 }
945
946 if (argc != 4) {
947 warnx("Missing required <threads> <waketype> <policy> <iterations> arguments");
948 usage();
949 }
950
951 char *cp;
952
953 /* How many threads? */
954 g_numthreads = (uint32_t)strtoull(argv[0], &cp, 10);
955
956 if (cp == argv[0] || *cp)
957 errx(EX_USAGE, "numthreads requires a decimal number, found \"%s\"", argv[0]);
958
959 if (g_numthreads < 1)
960 errx(EX_USAGE, "Must use at least one thread");
961
962 /* What wakeup pattern? */
963 g_waketype = parse_wakeup_pattern(argv[1]);
964
965 /* Policy */
966 g_policy = parse_thread_policy(argv[2]);
967
968 /* Iterations */
969 g_iterations = (uint32_t)strtoull(argv[3], &cp, 10);
970
971 if (cp == argv[3] || *cp)
972 errx(EX_USAGE, "numthreads requires a decimal number, found \"%s\"", argv[3]);
973
974 if (g_iterations < 1)
975 errx(EX_USAGE, "Must have at least one iteration");
976
977 if (g_numthreads == 1 && g_waketype == WAKE_CHAIN)
978 errx(EX_USAGE, "chain mode requires more than one thread");
979
980 if (g_numthreads == 1 && g_waketype == WAKE_HOP)
981 errx(EX_USAGE, "hop mode requires more than one thread");
982 }
983
984