2 * Copyright (c) 2009 Apple Inc. All rights reserved.
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
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.
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
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.
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
32 #include <sys/param.h>
33 #include <sys/syscall.h>
34 #include <sys/types.h>
35 #include <sys/ptrace.h>
36 #include <semaphore.h>
45 #include <spawn_private.h>
46 #include <sys/spawn_internal.h>
47 #include <mach-o/dyld.h>
49 #include <libkern/OSAtomic.h>
51 #include <mach/mach_time.h>
52 #include <mach/mach.h>
53 #include <mach/task.h>
54 #include <mach/semaphore.h>
56 typedef enum wake_type
{ WAKE_BROADCAST_ONESEM
, WAKE_BROADCAST_PERTHREAD
, WAKE_CHAIN
} wake_type_t
;
57 typedef enum my_policy_type
{ MY_POLICY_REALTIME
, MY_POLICY_TIMESHARE
, MY_POLICY_FIXEDPRI
} my_policy_type_t
;
59 #define assert(truth, label) do { if(!(truth)) { printf("Thread %p: failure on line %d\n", pthread_self(), __LINE__); goto label; } } while (0)
61 #define CONSTRAINT_NANOS (20000000ll) /* 20 ms */
62 #define COMPUTATION_NANOS (10000000ll) /* 10 ms */
63 #define TRACEWORTHY_NANOS (10000000ll) /* 10 ms */
66 #define debug_log(args...) printf(args)
68 #define debug_log(args...) do { } while(0)
72 void* child_thread_func(void *arg
);
74 int thread_setup(int my_id
);
75 my_policy_type_t
parse_thread_policy(const char *str
);
76 int thread_finish_iteration();
77 void selfexec_with_apptype(int argc
, char *argv
[]);
79 /* Global variables (general) */
81 wake_type_t g_waketype
;
84 struct mach_timebase_info g_mti
;
85 semaphore_t g_main_sem
;
86 uint64_t *g_thread_endtimes_abs
;
87 volatile int32_t g_done_threads
;
88 boolean_t g_do_spin
= FALSE
;
89 boolean_t g_verbose
= FALSE
;
90 boolean_t g_do_affinity
= FALSE
;
91 uint64_t g_starttime_abs
;
92 #if MIMIC_DIGI_LEAD_TIME
94 uint64_t g_spinlength_abs
;
95 #endif /* MIMIC_DIGI_LEAD_TIME */
97 /* Global variables (broadcast) */
98 semaphore_t g_machsem
;
99 semaphore_t g_leadersem
;
101 /* Global variables (chain) */
102 semaphore_t
*g_semarr
;
105 abs_to_nanos(uint64_t abstime
)
107 return (uint64_t)(abstime
* (((double)g_mti
.numer
) / ((double)g_mti
.denom
)));
111 nanos_to_abs(uint64_t ns
)
113 return (uint64_t)(ns
* (((double)g_mti
.denom
) / ((double)g_mti
.numer
)));
117 * Figure out what thread policy to use
120 parse_thread_policy(const char *str
)
122 if (strcmp(str
, "timeshare") == 0) {
123 return MY_POLICY_TIMESHARE
;
124 } else if (strcmp(str
, "realtime") == 0) {
125 return MY_POLICY_REALTIME
;
126 } else if (strcmp(str
, "fixed") == 0) {
127 return MY_POLICY_FIXEDPRI
;
129 printf("Invalid thread policy %s\n", str
);
135 * Figure out what wakeup pattern to use
138 parse_wakeup_pattern(const char *str
)
140 if (strcmp(str
, "chain") == 0) {
142 } else if (strcmp(str
, "broadcast-single-sem") == 0) {
143 return WAKE_BROADCAST_ONESEM
;
144 } else if (strcmp(str
, "broadcast-per-thread") == 0) {
145 return WAKE_BROADCAST_PERTHREAD
;
156 thread_setup(int my_id
)
161 case MY_POLICY_TIMESHARE
:
166 case MY_POLICY_REALTIME
:
168 thread_time_constraint_policy_data_t pol
;
170 /* Hard-coded realtime parameters (similar to what Digi uses) */
172 pol
.constraint
= nanos_to_abs(CONSTRAINT_NANOS
);
173 pol
.computation
= nanos_to_abs(COMPUTATION_NANOS
);
174 pol
.preemptible
= 0; /* Ignored by OS */
176 res
= thread_policy_set(mach_thread_self(), THREAD_TIME_CONSTRAINT_POLICY
, (thread_policy_t
) &pol
, THREAD_TIME_CONSTRAINT_POLICY_COUNT
);
177 assert(res
== 0, fail
);
180 case MY_POLICY_FIXEDPRI
:
182 thread_extended_policy_data_t pol
;
185 res
= thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY
, (thread_policy_t
) &pol
, THREAD_EXTENDED_POLICY_COUNT
);
186 assert(res
== 0, fail
);
191 printf("invalid policy type\n");
197 thread_affinity_policy_data_t affinity
;
199 affinity
.affinity_tag
= my_id
% 2;
201 res
= thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY
, (thread_policy_t
)&affinity
, THREAD_AFFINITY_POLICY_COUNT
);
202 assert(res
== 0, fail
);
211 * Wake up main thread if everyone's done
214 thread_finish_iteration(int id
)
218 volatile float x
= 0.0;
219 volatile float y
= 0.0;
221 debug_log("Thread %p finished iteration.\n", pthread_self());
223 #if MIMIC_DIGI_LEAD_TIME
225 * One randomly chosen thread determines when everybody gets to stop.
228 if (g_long_spinid
== id
) {
231 /* This thread took up fully half of his computation */
232 endspin
= g_starttime_abs
+ g_spinlength_abs
;
233 while (mach_absolute_time() < endspin
) {
239 #endif /* MIMIC_DIGI_LEAD_TIME */
241 new = OSAtomicIncrement32(&g_done_threads
);
243 debug_log("New value is %d\n", new);
246 * When the last thread finishes, everyone gets to go back to sleep.
248 if (new == g_numthreads
) {
249 debug_log("Thread %p signalling main thread.\n", pthread_self());
250 res
= semaphore_signal(g_main_sem
);
252 #ifndef MIMIC_DIGI_LEAD_TIME
254 while (g_done_threads
< g_numthreads
) {
266 * Wait for a wakeup, potentially wake up another of the "0-N" threads,
267 * and notify the main thread when done.
270 child_thread_func(void *arg
)
272 int my_id
= (int)(uintptr_t)arg
;
277 /* Set policy and so forth */
280 /* Tell main thread when everyone has set up */
281 new = OSAtomicIncrement32(&g_done_threads
);
282 semaphore_signal(g_main_sem
);
284 /* For each iteration */
285 for (i
= 0; i
< g_iterations
; i
++) {
287 * Leader thread either wakes everyone up or starts the chain going.
290 res
= semaphore_wait(g_leadersem
);
291 assert(res
== 0, fail
);
293 g_thread_endtimes_abs
[my_id
] = mach_absolute_time();
295 #if MIMIC_DIGI_LEAD_TIME
296 g_long_spinid
= rand() % g_numthreads
;
297 #endif /* MIMIC_DIGI_LEAD_TIME */
299 switch (g_waketype
) {
301 semaphore_signal(g_semarr
[my_id
+ 1]);
303 case WAKE_BROADCAST_ONESEM
:
304 semaphore_signal_all(g_machsem
);
306 case WAKE_BROADCAST_PERTHREAD
:
307 for (j
= 1; j
< g_numthreads
; j
++) {
308 semaphore_signal(g_semarr
[j
]);
312 printf("Invalid wakeup type?!\n");
317 * Everyone else waits to be woken up,
318 * records when she wake up, and possibly
322 case WAKE_BROADCAST_ONESEM
:
323 res
= semaphore_wait(g_machsem
);
324 assert(res
== KERN_SUCCESS
, fail
);
326 g_thread_endtimes_abs
[my_id
] = mach_absolute_time();
330 * For the chain wakeup case:
331 * wait, record time, signal next thread if appropriate
333 case WAKE_BROADCAST_PERTHREAD
:
334 res
= semaphore_wait(g_semarr
[my_id
]);
335 assert(res
== 0, fail
);
337 g_thread_endtimes_abs
[my_id
] = mach_absolute_time();
341 res
= semaphore_wait(g_semarr
[my_id
]);
342 assert(res
== 0, fail
);
344 g_thread_endtimes_abs
[my_id
] = mach_absolute_time();
346 if (my_id
< (g_numthreads
- 1)) {
347 res
= semaphore_signal(g_semarr
[my_id
+ 1]);
348 assert(res
== 0, fail
);
353 printf("Invalid wake type.\n");
358 res
= thread_finish_iteration(my_id
);
359 assert(res
== 0, fail
);
368 * Admittedly not very attractive.
373 printf("Usage: zn <num threads> <chain | broadcast-single-sem | broadcast-per-thread> <realtime | timeshare | fixed> <num iterations> [-trace <traceworthy latency in ns>] [-spin] [-affinity] [-verbose]\n");
377 * Given an array of uint64_t values, compute average, max, min, and standard deviation
380 compute_stats(uint64_t *values
, uint64_t count
, float *averagep
, uint64_t *maxp
, uint64_t *minp
, float *stddevp
)
385 uint64_t _min
= UINT64_MAX
;
389 for (i
= 0; i
< count
; i
++) {
391 _max
= values
[i
] > _max
? values
[i
] : _max
;
392 _min
= values
[i
] < _min
? values
[i
] : _min
;
395 _avg
= ((float)_sum
) / ((float)count
);
398 for (i
= 0; i
< count
; i
++) {
399 _dev
+= powf((((float)values
[i
]) - _avg
), 2);
412 main(int argc
, char **argv
)
417 uint64_t *worst_latencies_ns
;
418 uint64_t *worst_latencies_from_first_ns
;
421 uint64_t traceworthy_latency_ns
= TRACEWORTHY_NANOS
;
423 boolean_t seen_apptype
= FALSE
;
427 if (argc
< 5 || argc
> 10) {
432 /* How many threads? */
433 g_numthreads
= atoi(argv
[1]);
435 /* What wakeup pattern? */
436 g_waketype
= parse_wakeup_pattern(argv
[2]);
439 g_policy
= parse_thread_policy(argv
[3]);
442 g_iterations
= atoi(argv
[4]);
445 for (i
= 5; i
< argc
; i
++) {
446 if (strcmp(argv
[i
], "-spin") == 0) {
448 } else if (strcmp(argv
[i
], "-verbose") == 0) {
450 } else if ((strcmp(argv
[i
], "-trace") == 0) &&
452 traceworthy_latency_ns
= strtoull(argv
[++i
], NULL
, 10);
453 } else if (strcmp(argv
[i
], "-affinity") == 0) {
454 g_do_affinity
= TRUE
;
455 } else if (strcmp(argv
[i
], "-switched_apptype") == 0) {
464 selfexec_with_apptype(argc
, argv
);
467 mach_timebase_info(&g_mti
);
469 #if MIMIC_DIGI_LEAD_TIME
470 g_spinlength_abs
= nanos_to_abs(COMPUTATION_NANOS
) / 2;
471 #endif /* MIMIC_DIGI_LEAD_TIME */
473 /* Arrays for threads and their wakeup times */
474 threads
= (pthread_t
*) malloc(sizeof(pthread_t
) * g_numthreads
);
475 assert(threads
, fail
);
477 g_thread_endtimes_abs
= (uint64_t*) malloc(sizeof(uint64_t) * g_numthreads
);
478 assert(g_thread_endtimes_abs
, fail
);
480 worst_latencies_ns
= (uint64_t*) malloc(sizeof(uint64_t) * g_iterations
);
481 assert(worst_latencies_ns
, fail
);
483 worst_latencies_from_first_ns
= (uint64_t*) malloc(sizeof(uint64_t) * g_iterations
);
484 assert(worst_latencies_from_first_ns
, fail
);
485 res
= semaphore_create(mach_task_self(), &g_main_sem
, SYNC_POLICY_FIFO
, 0);
486 assert(res
== KERN_SUCCESS
, fail
);
488 /* Either one big semaphore or one per thread */
489 if (g_waketype
== WAKE_CHAIN
|| g_waketype
== WAKE_BROADCAST_PERTHREAD
) {
490 g_semarr
= malloc(sizeof(semaphore_t
) * g_numthreads
);
491 assert(g_semarr
!= NULL
, fail
);
493 for (i
= 0; i
< g_numthreads
; i
++) {
494 res
= semaphore_create(mach_task_self(), &g_semarr
[i
], SYNC_POLICY_FIFO
, 0);
495 assert(res
== KERN_SUCCESS
, fail
);
498 g_leadersem
= g_semarr
[0];
500 res
= semaphore_create(mach_task_self(), &g_machsem
, SYNC_POLICY_FIFO
, 0);
501 assert(res
== KERN_SUCCESS
, fail
);
502 res
= semaphore_create(mach_task_self(), &g_leadersem
, SYNC_POLICY_FIFO
, 0);
503 assert(res
== KERN_SUCCESS
, fail
);
506 /* Create the threads */
508 for (i
= 0; i
< g_numthreads
; i
++) {
509 res
= pthread_create(&threads
[i
], NULL
, child_thread_func
, (void*)(uintptr_t)i
);
510 assert(res
== 0, fail
);
513 res
= setpriority(PRIO_DARWIN_ROLE
, 0, PRIO_DARWIN_ROLE_UI_FOCAL
);
514 assert(res
== 0, fail
);
517 /* Switching to fixed pri may have stripped our main thread QoS and priority, so re-instate */
518 if (g_policy
== MY_POLICY_FIXEDPRI
) {
519 thread_precedence_policy_data_t prec
;
520 mach_msg_type_number_t count
;
521 boolean_t get_default
= FALSE
;
523 count
= THREAD_PRECEDENCE_POLICY_COUNT
;
524 res
= thread_policy_get(mach_thread_self(), THREAD_PRECEDENCE_POLICY
, (thread_policy_t
) &prec
, &count
, &get_default
);
525 assert(res
== 0, fail
);
527 prec
.importance
+= 16; /* 47 - 31 */
528 res
= thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY
, (thread_policy_t
) &prec
, THREAD_PRECEDENCE_POLICY_COUNT
);
529 assert(res
== 0, fail
);
532 /* Let everyone get settled */
533 for (i
= 0; i
< g_numthreads
; i
++) {
534 res
= semaphore_wait(g_main_sem
);
535 assert(res
== 0, fail
);
537 /* Let worker threads get back to sleep... */
538 usleep(g_numthreads
* 10);
541 for (i
= 0; i
< g_iterations
; i
++) {
543 uint64_t worst_abs
= 0, best_abs
= UINT64_MAX
;
548 g_starttime_abs
= mach_absolute_time();
551 semaphore_signal(g_leadersem
);
553 /* Wait for worker threads to finish */
554 semaphore_wait(g_main_sem
);
555 assert(res
== KERN_SUCCESS
, fail
);
558 * We report the worst latencies relative to start time
559 * and relative to the lead worker thread.
561 for (j
= 0; j
< g_numthreads
; j
++) {
562 uint64_t latency_abs
;
564 latency_abs
= g_thread_endtimes_abs
[j
] - g_starttime_abs
;
565 worst_abs
= worst_abs
< latency_abs
? latency_abs
: worst_abs
;
568 worst_latencies_ns
[i
] = abs_to_nanos(worst_abs
);
571 for (j
= 1; j
< g_numthreads
; j
++) {
572 uint64_t latency_abs
;
574 latency_abs
= g_thread_endtimes_abs
[j
] - g_thread_endtimes_abs
[0];
575 worst_abs
= worst_abs
< latency_abs
? latency_abs
: worst_abs
;
576 best_abs
= best_abs
> latency_abs
? latency_abs
: best_abs
;
579 worst_latencies_from_first_ns
[i
] = abs_to_nanos(worst_abs
);
582 * In the event of a bad run, cut a trace point.
584 if (worst_latencies_from_first_ns
[i
] > traceworthy_latency_ns
) {
588 printf("Worst on this round was %.2f us.\n", ((float)worst_latencies_from_first_ns
[i
]) / 1000.0);
591 _tmp
= syscall(SYS_kdebug_trace
, 0xEEEEEEEE, 0, 0, 0, 0);
594 /* Let worker threads get back to sleep... */
595 usleep(g_numthreads
* 10);
600 for (i
= 0; i
< g_numthreads
; i
++) {
601 res
= pthread_join(threads
[i
], NULL
);
602 assert(res
== 0, fail
);
605 compute_stats(worst_latencies_ns
, g_iterations
, &avg
, &max
, &min
, &stddev
);
606 printf("Results (from a stop):\n");
607 printf("Max:\t\t%.2f us\n", ((float)max
) / 1000.0);
608 printf("Min:\t\t%.2f us\n", ((float)min
) / 1000.0);
609 printf("Avg:\t\t%.2f us\n", avg
/ 1000.0);
610 printf("Stddev:\t\t%.2f us\n", stddev
/ 1000.0);
614 compute_stats(worst_latencies_from_first_ns
, g_iterations
, &avg
, &max
, &min
, &stddev
);
615 printf("Results (relative to first thread):\n");
616 printf("Max:\t\t%.2f us\n", ((float)max
) / 1000.0);
617 printf("Min:\t\t%.2f us\n", ((float)min
) / 1000.0);
618 printf("Avg:\t\t%.2f us\n", avg
/ 1000.0);
619 printf("Stddev:\t\t%.2f us\n", stddev
/ 1000.0);
622 for (i
= 0; i
< g_iterations
; i
++) {
623 printf("Iteration %d: %f us\n", i
, worst_latencies_ns
[i
] / 1000.0);
633 * WARNING: This is SPI specifically intended for use by launchd to start UI
634 * apps. We use it here for a test tool only to opt into QoS using the same
635 * policies. Do not use this outside xnu or libxpc/launchd.
638 selfexec_with_apptype(int argc
, char *argv
[])
641 posix_spawnattr_t attr
;
642 extern char **environ
;
643 char *new_argv
[argc
+ 1 + 1 /* NULL */];
646 int32_t prog_size
= PATH_MAX
;
648 ret
= _NSGetExecutablePath(prog
, &prog_size
);
649 if (ret
!= 0) err(1, "_NSGetExecutablePath");
651 for (i
=0; i
< argc
; i
++) {
652 new_argv
[i
] = argv
[i
];
655 new_argv
[i
] = "-switched_apptype";
656 new_argv
[i
+1] = NULL
;
658 ret
= posix_spawnattr_init(&attr
);
659 if (ret
!= 0) errc(1, ret
, "posix_spawnattr_init");
661 ret
= posix_spawnattr_setflags(&attr
, POSIX_SPAWN_SETEXEC
);
662 if (ret
!= 0) errc(1, ret
, "posix_spawnattr_setflags");
664 ret
= posix_spawnattr_setprocesstype_np(&attr
, POSIX_SPAWN_PROC_TYPE_APP_DEFAULT
);
665 if (ret
!= 0) errc(1, ret
, "posix_spawnattr_setprocesstype_np");
667 ret
= posix_spawn(NULL
, prog
, NULL
, &attr
, new_argv
, environ
);
668 if (ret
!= 0) errc(1, ret
, "posix_spawn");