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/syscall.h>
33 #include <sys/types.h>
34 #include <sys/ptrace.h>
35 #include <semaphore.h>
42 #include <libkern/OSAtomic.h>
44 #include <mach/mach_time.h>
45 #include <mach/mach.h>
46 #include <mach/task.h>
47 #include <mach/semaphore.h>
49 typedef enum wake_type
{ WAKE_BROADCAST_ONESEM
, WAKE_BROADCAST_PERTHREAD
, WAKE_CHAIN
} wake_type_t
;
50 typedef enum my_policy_type
{ MY_POLICY_REALTIME
, MY_POLICY_TIMESHARE
, MY_POLICY_FIXEDPRI
} my_policy_type_t
;
52 #define assert(truth, label) do { if(!(truth)) { printf("Thread %p: failure on line %d\n", pthread_self(), __LINE__); goto label; } } while (0)
54 #define CONSTRAINT_NANOS (20000000ll) /* 20 ms */
55 #define COMPUTATION_NANOS (10000000ll) /* 10 ms */
56 #define TRACEWORTHY_NANOS (10000000ll) /* 10 ms */
59 #define debug_log(args...) printf(args)
61 #define debug_log(args...) do { } while(0)
65 void* child_thread_func(void *arg
);
68 my_policy_type_t
parse_thread_policy(const char *str
);
69 int thread_finish_iteration();
71 /* Global variables (general) */
73 wake_type_t g_waketype
;
76 struct mach_timebase_info g_mti
;
77 semaphore_t g_main_sem
;
78 uint64_t *g_thread_endtimes_abs
;
79 volatile int32_t g_done_threads
;
80 boolean_t g_do_spin
= FALSE
;
81 boolean_t g_verbose
= FALSE
;
82 uint64_t g_starttime_abs
;
83 #if MIMIC_DIGI_LEAD_TIME
85 uint64_t g_spinlength_abs
;
86 #endif /* MIMIC_DIGI_LEAD_TIME */
88 /* Global variables (broadcast) */
89 semaphore_t g_machsem
;
90 semaphore_t g_leadersem
;
92 /* Global variables (chain) */
93 semaphore_t
*g_semarr
;
96 abs_to_nanos(uint64_t abstime
)
98 return (uint64_t)(abstime
* (((double)g_mti
.numer
) / ((double)g_mti
.denom
)));
102 nanos_to_abs(uint64_t ns
)
104 return (uint64_t)(ns
* (((double)g_mti
.denom
) / ((double)g_mti
.numer
)));
108 * Figure out what thread policy to use
111 parse_thread_policy(const char *str
)
113 if (strcmp(str
, "timeshare") == 0) {
114 return MY_POLICY_TIMESHARE
;
115 } else if (strcmp(str
, "realtime") == 0) {
116 return MY_POLICY_REALTIME
;
117 } else if (strcmp(str
, "fixed") == 0) {
118 return MY_POLICY_FIXEDPRI
;
120 printf("Invalid thread policy %s\n", str
);
126 * Figure out what wakeup pattern to use
129 parse_wakeup_pattern(const char *str
)
131 if (strcmp(str
, "chain") == 0) {
133 } else if (strcmp(str
, "broadcast-single-sem") == 0) {
134 return WAKE_BROADCAST_ONESEM
;
135 } else if (strcmp(str
, "broadcast-per-thread") == 0) {
136 return WAKE_BROADCAST_PERTHREAD
;
152 case MY_POLICY_TIMESHARE
:
156 case MY_POLICY_REALTIME
:
158 thread_time_constraint_policy_data_t pol
;
160 /* Hard-coded realtime parameters (similar to what Digi uses) */
162 pol
.constraint
= nanos_to_abs(CONSTRAINT_NANOS
);
163 pol
.computation
= nanos_to_abs(COMPUTATION_NANOS
);
164 pol
.preemptible
= 0; /* Ignored by OS */
166 res
= thread_policy_set(mach_thread_self(), THREAD_TIME_CONSTRAINT_POLICY
, (thread_policy_t
) &pol
, THREAD_TIME_CONSTRAINT_POLICY_COUNT
);
167 assert(res
== 0, fail
);
170 case MY_POLICY_FIXEDPRI
:
172 thread_extended_policy_data_t pol
;
175 res
= thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY
, (thread_policy_t
) &pol
, THREAD_EXTENDED_POLICY_COUNT
);
176 assert(res
== 0, fail
);
181 printf("invalid policy type\n");
192 * Wake up main thread if everyone's done
195 thread_finish_iteration(int id
)
199 volatile float x
= 0.0;
200 volatile float y
= 0.0;
202 debug_log("Thread %p finished iteration.\n", pthread_self());
204 #if MIMIC_DIGI_LEAD_TIME
206 * One randomly chosen thread determines when everybody gets to stop.
209 if (g_long_spinid
== id
) {
212 /* This thread took up fully half of his computation */
213 endspin
= g_starttime_abs
+ g_spinlength_abs
;
214 while (mach_absolute_time() < endspin
) {
220 #endif /* MIMIC_DIGI_LEAD_TIME */
222 new = OSAtomicIncrement32(&g_done_threads
);
224 debug_log("New value is %d\n", new);
227 * When the last thread finishes, everyone gets to go back to sleep.
229 if (new == g_numthreads
) {
230 debug_log("Thread %p signalling main thread.\n", pthread_self());
231 res
= semaphore_signal(g_main_sem
);
234 while (g_done_threads
< g_numthreads
) {
245 * Wait for a wakeup, potentially wake up another of the "0-N" threads,
246 * and notify the main thread when done.
249 child_thread_func(void *arg
)
251 int my_id
= (int)(uintptr_t)arg
;
256 /* Set policy and so forth */
259 /* Tell main thread when everyone has set up */
260 new = OSAtomicIncrement32(&g_done_threads
);
261 if (new == g_numthreads
) {
262 semaphore_signal(g_main_sem
);
265 /* For each iteration */
266 for (i
= 0; i
< g_iterations
; i
++) {
268 * Leader thread either wakes everyone up or starts the chain going.
271 res
= semaphore_wait(g_leadersem
);
272 assert(res
== 0, fail
);
274 g_thread_endtimes_abs
[my_id
] = mach_absolute_time();
276 #if MIMIC_DIGI_LEAD_TIME
277 g_long_spinid
= rand() % g_numthreads
;
278 #endif /* MIMIC_DIGI_LEAD_TIME */
280 switch (g_waketype
) {
282 semaphore_signal(g_semarr
[my_id
+ 1]);
284 case WAKE_BROADCAST_ONESEM
:
285 semaphore_signal_all(g_machsem
);
287 case WAKE_BROADCAST_PERTHREAD
:
288 for (j
= 1; j
< g_numthreads
; j
++) {
289 semaphore_signal(g_semarr
[j
]);
293 printf("Invalid wakeup type?!\n");
298 * Everyone else waits to be woken up,
299 * records when she wake up, and possibly
303 case WAKE_BROADCAST_ONESEM
:
304 res
= semaphore_wait(g_machsem
);
305 assert(res
== KERN_SUCCESS
, fail
);
307 g_thread_endtimes_abs
[my_id
] = mach_absolute_time();
311 * For the chain wakeup case:
312 * wait, record time, signal next thread if appropriate
314 case WAKE_BROADCAST_PERTHREAD
:
315 res
= semaphore_wait(g_semarr
[my_id
]);
316 assert(res
== 0, fail
);
318 g_thread_endtimes_abs
[my_id
] = mach_absolute_time();
322 res
= semaphore_wait(g_semarr
[my_id
]);
323 assert(res
== 0, fail
);
325 g_thread_endtimes_abs
[my_id
] = mach_absolute_time();
327 if (my_id
< (g_numthreads
- 1)) {
328 res
= semaphore_signal(g_semarr
[my_id
+ 1]);
329 assert(res
== 0, fail
);
334 printf("Invalid wake type.\n");
339 res
= thread_finish_iteration(my_id
);
340 assert(res
== 0, fail
);
349 * Admittedly not very attractive.
354 printf("Usage: zn <num threads> <chain | broadcast-single-sem | broadcast-per-thread> <realtime | timeshare | fixed> <num iterations> [-trace <traceworthy latency in ns>] [-spin] [-verbose]\n");
358 * Given an array of uint64_t values, compute average, max, min, and standard deviation
361 compute_stats(uint64_t *values
, uint64_t count
, float *averagep
, uint64_t *maxp
, uint64_t *minp
, float *stddevp
)
366 uint64_t _min
= UINT64_MAX
;
370 for (i
= 0; i
< count
; i
++) {
372 _max
= values
[i
] > _max
? values
[i
] : _max
;
373 _min
= values
[i
] < _min
? values
[i
] : _min
;
376 _avg
= ((float)_sum
) / ((float)count
);
379 for (i
= 0; i
< count
; i
++) {
380 _dev
+= powf((((float)values
[i
]) - _avg
), 2);
393 main(int argc
, char **argv
)
398 uint64_t *worst_latencies_ns
;
399 uint64_t *worst_latencies_from_first_ns
;
402 uint64_t traceworthy_latency_ns
= TRACEWORTHY_NANOS
;
407 if (argc
< 5 || argc
> 9) {
412 /* How many threads? */
413 g_numthreads
= atoi(argv
[1]);
415 /* What wakeup pattern? */
416 g_waketype
= parse_wakeup_pattern(argv
[2]);
419 g_policy
= parse_thread_policy(argv
[3]);
422 g_iterations
= atoi(argv
[4]);
425 for (i
= 5; i
< argc
; i
++) {
426 if (strcmp(argv
[i
], "-spin") == 0) {
428 } else if (strcmp(argv
[i
], "-verbose") == 0) {
430 } else if ((strcmp(argv
[i
], "-trace") == 0) &&
432 traceworthy_latency_ns
= strtoull(argv
[++i
], NULL
, 10);
439 mach_timebase_info(&g_mti
);
441 #if MIMIC_DIGI_LEAD_TIME
442 g_spinlength_abs
= nanos_to_abs(COMPUTATION_NANOS
) / 2;
443 #endif /* MIMIC_DIGI_LEAD_TIME */
445 /* Arrays for threads and their wakeup times */
446 threads
= (pthread_t
*) malloc(sizeof(pthread_t
) * g_numthreads
);
447 assert(threads
, fail
);
449 g_thread_endtimes_abs
= (uint64_t*) malloc(sizeof(uint64_t) * g_numthreads
);
450 assert(g_thread_endtimes_abs
, fail
);
452 worst_latencies_ns
= (uint64_t*) malloc(sizeof(uint64_t) * g_iterations
);
453 assert(worst_latencies_ns
, fail
);
455 worst_latencies_from_first_ns
= (uint64_t*) malloc(sizeof(uint64_t) * g_iterations
);
456 assert(worst_latencies_from_first_ns
, fail
);
457 res
= semaphore_create(mach_task_self(), &g_main_sem
, SYNC_POLICY_FIFO
, 0);
458 assert(res
== KERN_SUCCESS
, fail
);
460 /* Either one big semaphore or one per thread */
461 if (g_waketype
== WAKE_CHAIN
|| g_waketype
== WAKE_BROADCAST_PERTHREAD
) {
462 g_semarr
= malloc(sizeof(semaphore_t
) * g_numthreads
);
463 assert(g_semarr
!= NULL
, fail
);
465 for (i
= 0; i
< g_numthreads
; i
++) {
466 res
= semaphore_create(mach_task_self(), &g_semarr
[i
], SYNC_POLICY_FIFO
, 0);
467 assert(res
== KERN_SUCCESS
, fail
);
470 g_leadersem
= g_semarr
[0];
472 res
= semaphore_create(mach_task_self(), &g_machsem
, SYNC_POLICY_FIFO
, 0);
473 assert(res
== KERN_SUCCESS
, fail
);
474 res
= semaphore_create(mach_task_self(), &g_leadersem
, SYNC_POLICY_FIFO
, 0);
475 assert(res
== KERN_SUCCESS
, fail
);
478 /* Create the threads */
480 for (i
= 0; i
< g_numthreads
; i
++) {
481 res
= pthread_create(&threads
[i
], NULL
, child_thread_func
, (void*)(uintptr_t)i
);
482 assert(res
== 0, fail
);
485 /* Let everyone get settled */
486 semaphore_wait(g_main_sem
);
490 for (i
= 0; i
< g_iterations
; i
++) {
492 uint64_t worst_abs
= 0, best_abs
= UINT64_MAX
;
497 g_starttime_abs
= mach_absolute_time();
500 semaphore_signal(g_leadersem
);
502 /* Wait for worker threads to finish */
503 semaphore_wait(g_main_sem
);
504 assert(res
== KERN_SUCCESS
, fail
);
507 * We report the worst latencies relative to start time
508 * and relative to the lead worker thread.
510 for (j
= 0; j
< g_numthreads
; j
++) {
511 uint64_t latency_abs
;
513 latency_abs
= g_thread_endtimes_abs
[j
] - g_starttime_abs
;
514 worst_abs
= worst_abs
< latency_abs
? latency_abs
: worst_abs
;
517 worst_latencies_ns
[i
] = abs_to_nanos(worst_abs
);
520 for (j
= 1; j
< g_numthreads
; j
++) {
521 uint64_t latency_abs
;
523 latency_abs
= g_thread_endtimes_abs
[j
] - g_thread_endtimes_abs
[0];
524 worst_abs
= worst_abs
< latency_abs
? latency_abs
: worst_abs
;
525 best_abs
= best_abs
> latency_abs
? latency_abs
: best_abs
;
528 worst_latencies_from_first_ns
[i
] = abs_to_nanos(worst_abs
);
531 * In the event of a bad run, cut a trace point.
533 if (worst_latencies_from_first_ns
[i
] > traceworthy_latency_ns
) {
537 printf("Worst on this round was %.2f us.\n", ((float)worst_latencies_from_first_ns
[i
]) / 1000.0);
540 _tmp
= syscall(SYS_kdebug_trace
, 0xEEEEEEEE, 0, 0, 0, 0);
543 /* Let worker threads get back to sleep... */
544 usleep(g_numthreads
* 10);
549 for (i
= 0; i
< g_numthreads
; i
++) {
550 res
= pthread_join(threads
[i
], NULL
);
551 assert(res
== 0, fail
);
554 compute_stats(worst_latencies_ns
, g_iterations
, &avg
, &max
, &min
, &stddev
);
555 printf("Results (from a stop):\n");
556 printf("Max:\t\t%.2f us\n", ((float)max
) / 1000.0);
557 printf("Min:\t\t%.2f us\n", ((float)min
) / 1000.0);
558 printf("Avg:\t\t%.2f us\n", avg
/ 1000.0);
559 printf("Stddev:\t\t%.2f us\n", stddev
/ 1000.0);
563 compute_stats(worst_latencies_from_first_ns
, g_iterations
, &avg
, &max
, &min
, &stddev
);
564 printf("Results (relative to first thread):\n");
565 printf("Max:\t\t%.2f us\n", ((float)max
) / 1000.0);
566 printf("Min:\t\t%.2f us\n", ((float)min
) / 1000.0);
567 printf("Avg:\t\t%.2f us\n", avg
/ 1000.0);
568 printf("Stddev:\t\t%.2f us\n", stddev
/ 1000.0);
571 for (i
= 0; i
< g_iterations
; i
++) {
572 printf("Iteration %d: %f us\n", i
, worst_latencies_ns
[i
] / 1000.0);