]>
Commit | Line | Data |
---|---|---|
6d2010ae A |
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> | |
a1c7dba1 | 31 | #include <sys/kdebug.h> |
6d2010ae A |
32 | #include <stdlib.h> |
33 | #include <pthread.h> | |
6d2010ae | 34 | #include <errno.h> |
fe8ab488 | 35 | #include <err.h> |
6d2010ae | 36 | #include <string.h> |
3e170ce0 A |
37 | #include <assert.h> |
38 | #include <sysexits.h> | |
39 | #include <sys/sysctl.h> | |
40 | #include <getopt.h> | |
6d2010ae | 41 | |
fe8ab488 A |
42 | #include <spawn.h> |
43 | #include <spawn_private.h> | |
44 | #include <sys/spawn_internal.h> | |
45 | #include <mach-o/dyld.h> | |
46 | ||
6d2010ae A |
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 | ||
3e170ce0 A |
54 | #include <pthread/qos_private.h> |
55 | ||
56 | typedef enum wake_type { WAKE_BROADCAST_ONESEM, WAKE_BROADCAST_PERTHREAD, WAKE_CHAIN, WAKE_HOP } wake_type_t; | |
6d2010ae A |
57 | typedef enum my_policy_type { MY_POLICY_REALTIME, MY_POLICY_TIMESHARE, MY_POLICY_FIXEDPRI } my_policy_type_t; |
58 | ||
3e170ce0 A |
59 | #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) |
60 | #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) | |
61 | #define assert_zero_t(tid, error) do { if ((error) != 0) { fprintf(stderr, "[FAIL] Thread %d error %d ", (tid), (error)); assert(error == 0); } } while (0) | |
6d2010ae A |
62 | |
63 | #define CONSTRAINT_NANOS (20000000ll) /* 20 ms */ | |
64 | #define COMPUTATION_NANOS (10000000ll) /* 10 ms */ | |
65 | #define TRACEWORTHY_NANOS (10000000ll) /* 10 ms */ | |
66 | ||
67 | #if DEBUG | |
68 | #define debug_log(args...) printf(args) | |
69 | #else | |
70 | #define debug_log(args...) do { } while(0) | |
71 | #endif | |
72 | ||
73 | /* Declarations */ | |
3e170ce0 A |
74 | static void* worker_thread(void *arg); |
75 | static void usage(); | |
76 | static int thread_setup(uint32_t my_id); | |
77 | static my_policy_type_t parse_thread_policy(const char *str); | |
78 | static void selfexec_with_apptype(int argc, char *argv[]); | |
79 | static void parse_args(int argc, char *argv[]); | |
6d2010ae A |
80 | |
81 | /* Global variables (general) */ | |
3e170ce0 A |
82 | static uint32_t g_numcpus; |
83 | static uint32_t g_numthreads; | |
84 | static wake_type_t g_waketype; | |
85 | static policy_t g_policy; | |
86 | static uint32_t g_iterations; | |
87 | static struct mach_timebase_info g_mti; | |
88 | static semaphore_t g_main_sem; | |
89 | static uint64_t *g_thread_endtimes_abs; | |
90 | static volatile uint32_t g_done_threads; | |
91 | static boolean_t g_verbose = FALSE; | |
92 | static boolean_t g_do_affinity = FALSE; | |
93 | static uint64_t g_starttime_abs; | |
94 | static uint32_t g_iteration_sleeptime_us = 0; | |
95 | ||
96 | /* Threshold for dropping a 'bad run' tracepoint */ | |
97 | static uint64_t g_traceworthy_latency_ns = TRACEWORTHY_NANOS; | |
98 | ||
99 | /* Have we re-execed to set apptype? */ | |
100 | static boolean_t g_seen_apptype = FALSE; | |
101 | ||
102 | /* usleep in betweeen iterations */ | |
103 | static boolean_t g_do_sleep = TRUE; | |
104 | ||
105 | /* Every thread spins until all threads have checked in */ | |
106 | static boolean_t g_do_all_spin = FALSE; | |
107 | ||
108 | /* One randomly chosen thread holds up the train for a certain duration. */ | |
109 | static boolean_t g_do_one_long_spin = FALSE; | |
110 | static uint32_t g_one_long_spin_id = 0; | |
111 | static uint64_t g_one_long_spin_length_abs = 0; | |
112 | static uint64_t g_one_long_spin_length_ns = 0; | |
113 | ||
114 | /* Each thread spins for a certain duration after waking up before blocking again. */ | |
115 | static boolean_t g_do_each_spin = FALSE; | |
116 | static uint64_t g_each_spin_duration_abs = 0; | |
117 | static uint64_t g_each_spin_duration_ns = 0; | |
6d2010ae A |
118 | |
119 | /* Global variables (broadcast) */ | |
3e170ce0 A |
120 | static semaphore_t g_broadcastsem; |
121 | static semaphore_t g_leadersem; | |
122 | static semaphore_t g_readysem; | |
123 | static semaphore_t g_donesem; | |
6d2010ae A |
124 | |
125 | /* Global variables (chain) */ | |
3e170ce0 | 126 | static semaphore_t *g_semarr; |
6d2010ae | 127 | |
3e170ce0 | 128 | static uint64_t |
6d2010ae A |
129 | abs_to_nanos(uint64_t abstime) |
130 | { | |
131 | return (uint64_t)(abstime * (((double)g_mti.numer) / ((double)g_mti.denom))); | |
132 | } | |
133 | ||
3e170ce0 | 134 | static uint64_t |
6d2010ae A |
135 | nanos_to_abs(uint64_t ns) |
136 | { | |
137 | return (uint64_t)(ns * (((double)g_mti.denom) / ((double)g_mti.numer))); | |
138 | } | |
139 | ||
140 | /* | |
141 | * Figure out what thread policy to use | |
142 | */ | |
3e170ce0 | 143 | static my_policy_type_t |
6d2010ae A |
144 | parse_thread_policy(const char *str) |
145 | { | |
146 | if (strcmp(str, "timeshare") == 0) { | |
147 | return MY_POLICY_TIMESHARE; | |
148 | } else if (strcmp(str, "realtime") == 0) { | |
149 | return MY_POLICY_REALTIME; | |
150 | } else if (strcmp(str, "fixed") == 0) { | |
151 | return MY_POLICY_FIXEDPRI; | |
152 | } else { | |
3e170ce0 | 153 | errx(EX_USAGE, "Invalid thread policy \"%s\"", str); |
6d2010ae A |
154 | } |
155 | } | |
156 | ||
157 | /* | |
158 | * Figure out what wakeup pattern to use | |
159 | */ | |
3e170ce0 | 160 | static wake_type_t |
6d2010ae A |
161 | parse_wakeup_pattern(const char *str) |
162 | { | |
163 | if (strcmp(str, "chain") == 0) { | |
164 | return WAKE_CHAIN; | |
3e170ce0 A |
165 | } else if (strcmp(str, "hop") == 0) { |
166 | return WAKE_HOP; | |
6d2010ae A |
167 | } else if (strcmp(str, "broadcast-single-sem") == 0) { |
168 | return WAKE_BROADCAST_ONESEM; | |
169 | } else if (strcmp(str, "broadcast-per-thread") == 0) { | |
170 | return WAKE_BROADCAST_PERTHREAD; | |
171 | } else { | |
3e170ce0 | 172 | errx(EX_USAGE, "Invalid wakeup pattern \"%s\"", str); |
6d2010ae A |
173 | } |
174 | } | |
175 | ||
176 | /* | |
177 | * Set policy | |
178 | */ | |
3e170ce0 A |
179 | static int |
180 | thread_setup(uint32_t my_id) | |
6d2010ae | 181 | { |
3e170ce0 A |
182 | kern_return_t kr; |
183 | errno_t ret; | |
184 | thread_time_constraint_policy_data_t pol; | |
6d2010ae A |
185 | |
186 | switch (g_policy) { | |
187 | case MY_POLICY_TIMESHARE: | |
fe8ab488 | 188 | break; |
3e170ce0 | 189 | case MY_POLICY_REALTIME: |
6d2010ae | 190 | /* Hard-coded realtime parameters (similar to what Digi uses) */ |
3e170ce0 A |
191 | pol.period = 100000; |
192 | pol.constraint = (uint32_t) nanos_to_abs(CONSTRAINT_NANOS); | |
193 | pol.computation = (uint32_t) nanos_to_abs(COMPUTATION_NANOS); | |
6d2010ae A |
194 | pol.preemptible = 0; /* Ignored by OS */ |
195 | ||
3e170ce0 A |
196 | kr = thread_policy_set(mach_thread_self(), THREAD_TIME_CONSTRAINT_POLICY, |
197 | (thread_policy_t) &pol, THREAD_TIME_CONSTRAINT_POLICY_COUNT); | |
198 | mach_assert_zero_t(my_id, kr); | |
6d2010ae | 199 | break; |
3e170ce0 A |
200 | case MY_POLICY_FIXEDPRI: |
201 | ret = pthread_set_fixedpriority_self(); | |
202 | if (ret) errc(EX_OSERR, ret, "pthread_set_fixedpriority_self"); | |
6d2010ae | 203 | break; |
6d2010ae | 204 | default: |
3e170ce0 | 205 | errx(EX_USAGE, "invalid policy type %d", g_policy); |
6d2010ae A |
206 | } |
207 | ||
39236c6e A |
208 | if (g_do_affinity) { |
209 | thread_affinity_policy_data_t affinity; | |
210 | ||
211 | affinity.affinity_tag = my_id % 2; | |
212 | ||
3e170ce0 A |
213 | kr = thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY, |
214 | (thread_policy_t)&affinity, THREAD_AFFINITY_POLICY_COUNT); | |
215 | mach_assert_zero_t(my_id, kr); | |
39236c6e A |
216 | } |
217 | ||
6d2010ae | 218 | return 0; |
6d2010ae A |
219 | } |
220 | ||
221 | /* | |
3e170ce0 A |
222 | * Wait for a wakeup, potentially wake up another of the "0-N" threads, |
223 | * and notify the main thread when done. | |
6d2010ae | 224 | */ |
3e170ce0 A |
225 | static void* |
226 | worker_thread(void *arg) | |
6d2010ae | 227 | { |
3e170ce0 A |
228 | uint32_t my_id = (uint32_t)(uintptr_t)arg; |
229 | kern_return_t kr; | |
6d2010ae | 230 | |
3e170ce0 A |
231 | volatile double x = 0.0; |
232 | volatile double y = 0.0; | |
6d2010ae | 233 | |
3e170ce0 A |
234 | /* Set policy and so forth */ |
235 | thread_setup(my_id); | |
6d2010ae | 236 | |
3e170ce0 A |
237 | for (uint32_t i = 0; i < g_iterations; i++) { |
238 | if (my_id == 0) { | |
239 | /* | |
240 | * Leader thread either wakes everyone up or starts the chain going. | |
241 | */ | |
6d2010ae | 242 | |
3e170ce0 A |
243 | /* Give the worker threads undisturbed time to finish before waiting on them */ |
244 | if (g_do_sleep) | |
245 | usleep(g_iteration_sleeptime_us); | |
6d2010ae | 246 | |
3e170ce0 | 247 | debug_log("%d Leader thread wait for ready\n", i); |
6d2010ae | 248 | |
3e170ce0 A |
249 | /* |
250 | * Wait for everyone else to declare ready | |
251 | * Is there a better way to do this that won't interfere with the rest of the chain? | |
252 | * TODO: Invent 'semaphore wait for N signals' | |
253 | */ | |
6d2010ae | 254 | |
3e170ce0 A |
255 | for (uint32_t j = 0 ; j < g_numthreads - 1; j++) { |
256 | kr = semaphore_wait(g_readysem); | |
257 | mach_assert_zero_t(my_id, kr); | |
258 | } | |
6d2010ae | 259 | |
3e170ce0 A |
260 | debug_log("%d Leader thread wait\n", i); |
261 | ||
262 | /* Signal main thread and wait for start of iteration */ | |
263 | ||
264 | kr = semaphore_wait_signal(g_leadersem, g_main_sem); | |
265 | mach_assert_zero_t(my_id, kr); | |
6d2010ae | 266 | |
6d2010ae A |
267 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); |
268 | ||
3e170ce0 A |
269 | debug_log("%d Leader thread go\n", i); |
270 | ||
271 | assert_zero_t(my_id, g_done_threads); | |
6d2010ae A |
272 | |
273 | switch (g_waketype) { | |
3e170ce0 A |
274 | case WAKE_BROADCAST_ONESEM: |
275 | kr = semaphore_signal_all(g_broadcastsem); | |
276 | mach_assert_zero_t(my_id, kr); | |
6d2010ae A |
277 | break; |
278 | case WAKE_BROADCAST_PERTHREAD: | |
3e170ce0 A |
279 | for (uint32_t j = 1; j < g_numthreads; j++) { |
280 | kr = semaphore_signal(g_semarr[j]); | |
281 | mach_assert_zero_t(my_id, kr); | |
6d2010ae A |
282 | } |
283 | break; | |
3e170ce0 A |
284 | case WAKE_CHAIN: |
285 | kr = semaphore_signal(g_semarr[my_id + 1]); | |
286 | mach_assert_zero_t(my_id, kr); | |
287 | break; | |
288 | case WAKE_HOP: | |
289 | kr = semaphore_wait_signal(g_donesem, g_semarr[my_id + 1]); | |
290 | mach_assert_zero_t(my_id, kr); | |
291 | break; | |
6d2010ae A |
292 | } |
293 | } else { | |
294 | /* | |
295 | * Everyone else waits to be woken up, | |
3e170ce0 | 296 | * records when she wakes up, and possibly |
6d2010ae A |
297 | * wakes up a friend. |
298 | */ | |
299 | switch(g_waketype) { | |
300 | case WAKE_BROADCAST_ONESEM: | |
3e170ce0 A |
301 | kr = semaphore_wait_signal(g_broadcastsem, g_readysem); |
302 | mach_assert_zero_t(my_id, kr); | |
6d2010ae A |
303 | |
304 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); | |
6d2010ae | 305 | break; |
3e170ce0 | 306 | |
6d2010ae | 307 | case WAKE_BROADCAST_PERTHREAD: |
3e170ce0 A |
308 | kr = semaphore_wait_signal(g_semarr[my_id], g_readysem); |
309 | mach_assert_zero_t(my_id, kr); | |
6d2010ae A |
310 | |
311 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); | |
312 | break; | |
313 | ||
314 | case WAKE_CHAIN: | |
3e170ce0 A |
315 | kr = semaphore_wait_signal(g_semarr[my_id], g_readysem); |
316 | mach_assert_zero_t(my_id, kr); | |
317 | ||
318 | /* Signal the next thread *after* recording wake time */ | |
6d2010ae A |
319 | |
320 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); | |
321 | ||
322 | if (my_id < (g_numthreads - 1)) { | |
3e170ce0 A |
323 | kr = semaphore_signal(g_semarr[my_id + 1]); |
324 | mach_assert_zero_t(my_id, kr); | |
325 | } | |
326 | ||
327 | break; | |
328 | ||
329 | case WAKE_HOP: | |
330 | kr = semaphore_wait_signal(g_semarr[my_id], g_readysem); | |
331 | mach_assert_zero_t(my_id, kr); | |
332 | ||
333 | /* Signal the next thread *after* recording wake time */ | |
334 | ||
335 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); | |
336 | ||
337 | if (my_id < (g_numthreads - 1)) { | |
338 | kr = semaphore_wait_signal(g_donesem, g_semarr[my_id + 1]); | |
339 | mach_assert_zero_t(my_id, kr); | |
340 | } else { | |
341 | kr = semaphore_signal_all(g_donesem); | |
342 | mach_assert_zero_t(my_id, kr); | |
6d2010ae A |
343 | } |
344 | ||
345 | break; | |
6d2010ae A |
346 | } |
347 | } | |
348 | ||
3e170ce0 A |
349 | debug_log("Thread %p woke up for iteration %d.\n", pthread_self(), i); |
350 | ||
351 | if (g_do_one_long_spin && g_one_long_spin_id == my_id) { | |
352 | /* One randomly chosen thread holds up the train for a while. */ | |
353 | ||
354 | uint64_t endspin = g_starttime_abs + g_one_long_spin_length_abs; | |
355 | while (mach_absolute_time() < endspin) { | |
356 | y = y + 1.5 + x; | |
357 | x = sqrt(y); | |
358 | } | |
359 | } | |
360 | ||
361 | if (g_do_each_spin) { | |
362 | /* Each thread spins for a certain duration after waking up before blocking again. */ | |
363 | ||
364 | uint64_t endspin = mach_absolute_time() + g_each_spin_duration_abs; | |
365 | while (mach_absolute_time() < endspin) { | |
366 | y = y + 1.5 + x; | |
367 | x = sqrt(y); | |
368 | } | |
369 | } | |
370 | ||
371 | int32_t new = OSAtomicIncrement32((volatile int32_t *)&g_done_threads); | |
372 | (void)new; | |
373 | ||
374 | debug_log("Thread %p new value is %d, iteration %d\n", pthread_self(), new, i); | |
375 | ||
376 | if (g_do_all_spin) { | |
377 | /* Everyone spins until the last thread checks in. */ | |
378 | ||
379 | while (g_done_threads < g_numthreads) { | |
380 | y = y + 1.5 + x; | |
381 | x = sqrt(y); | |
382 | } | |
383 | } | |
384 | ||
385 | debug_log("Thread %p done spinning, iteration %d\n", pthread_self(), i); | |
6d2010ae A |
386 | } |
387 | ||
3e170ce0 A |
388 | if (my_id == 0) { |
389 | /* Give the worker threads undisturbed time to finish before waiting on them */ | |
390 | if (g_do_sleep) | |
391 | usleep(g_iteration_sleeptime_us); | |
6d2010ae | 392 | |
3e170ce0 A |
393 | /* Wait for the worker threads to finish */ |
394 | for (uint32_t i = 0 ; i < g_numthreads - 1; i++) { | |
395 | kr = semaphore_wait(g_readysem); | |
396 | mach_assert_zero_t(my_id, kr); | |
397 | } | |
398 | ||
399 | /* Tell everyone and the main thread that the last iteration is done */ | |
400 | debug_log("%d Leader thread done\n", i); | |
401 | ||
402 | kr = semaphore_signal_all(g_main_sem); | |
403 | mach_assert_zero_t(my_id, kr); | |
404 | } else { | |
405 | /* Hold up thread teardown so it doesn't affect the last iteration */ | |
406 | kr = semaphore_wait_signal(g_main_sem, g_readysem); | |
407 | mach_assert_zero_t(my_id, kr); | |
408 | } | |
409 | ||
410 | return 0; | |
6d2010ae A |
411 | } |
412 | ||
413 | /* | |
414 | * Given an array of uint64_t values, compute average, max, min, and standard deviation | |
415 | */ | |
3e170ce0 | 416 | static void |
6d2010ae A |
417 | compute_stats(uint64_t *values, uint64_t count, float *averagep, uint64_t *maxp, uint64_t *minp, float *stddevp) |
418 | { | |
3e170ce0 | 419 | uint32_t i; |
6d2010ae A |
420 | uint64_t _sum = 0; |
421 | uint64_t _max = 0; | |
422 | uint64_t _min = UINT64_MAX; | |
423 | float _avg = 0; | |
424 | float _dev = 0; | |
425 | ||
426 | for (i = 0; i < count; i++) { | |
427 | _sum += values[i]; | |
428 | _max = values[i] > _max ? values[i] : _max; | |
429 | _min = values[i] < _min ? values[i] : _min; | |
430 | } | |
431 | ||
432 | _avg = ((float)_sum) / ((float)count); | |
433 | ||
434 | _dev = 0; | |
435 | for (i = 0; i < count; i++) { | |
436 | _dev += powf((((float)values[i]) - _avg), 2); | |
437 | } | |
438 | ||
439 | _dev /= count; | |
440 | _dev = sqrtf(_dev); | |
441 | ||
442 | *averagep = _avg; | |
443 | *maxp = _max; | |
444 | *minp = _min; | |
445 | *stddevp = _dev; | |
446 | } | |
447 | ||
448 | int | |
449 | main(int argc, char **argv) | |
450 | { | |
3e170ce0 A |
451 | errno_t ret; |
452 | kern_return_t kr; | |
453 | ||
6d2010ae A |
454 | pthread_t *threads; |
455 | uint64_t *worst_latencies_ns; | |
456 | uint64_t *worst_latencies_from_first_ns; | |
6d2010ae | 457 | uint64_t max, min; |
6d2010ae A |
458 | float avg, stddev; |
459 | ||
3e170ce0 A |
460 | for (int i = 0; i < argc; i++) |
461 | if (strcmp(argv[i], "--switched_apptype") == 0) | |
462 | g_seen_apptype = TRUE; | |
6d2010ae | 463 | |
3e170ce0 A |
464 | if (!g_seen_apptype) |
465 | selfexec_with_apptype(argc, argv); | |
6d2010ae | 466 | |
3e170ce0 | 467 | parse_args(argc, argv); |
6d2010ae | 468 | |
3e170ce0 | 469 | srand((unsigned int)time(NULL)); |
6d2010ae | 470 | |
3e170ce0 | 471 | mach_timebase_info(&g_mti); |
6d2010ae | 472 | |
3e170ce0 A |
473 | size_t ncpu_size = sizeof(g_numcpus); |
474 | ret = sysctlbyname("hw.ncpu", &g_numcpus, &ncpu_size, NULL, 0); | |
475 | if (ret) err(EX_OSERR, "Failed sysctlbyname(hw.ncpu)"); | |
6d2010ae | 476 | |
3e170ce0 A |
477 | if (g_do_each_spin) |
478 | g_each_spin_duration_abs = nanos_to_abs(g_each_spin_duration_ns); | |
479 | ||
480 | /* Configure the long-spin thread to take up half of its computation */ | |
481 | if (g_do_one_long_spin) { | |
482 | g_one_long_spin_length_ns = COMPUTATION_NANOS / 2; | |
483 | g_one_long_spin_length_abs = nanos_to_abs(g_one_long_spin_length_ns); | |
fe8ab488 A |
484 | } |
485 | ||
3e170ce0 A |
486 | /* Estimate the amount of time the cleanup phase needs to back off */ |
487 | g_iteration_sleeptime_us = g_numthreads * 20; | |
6d2010ae | 488 | |
3e170ce0 A |
489 | uint32_t threads_per_core = (g_numthreads / g_numcpus) + 1; |
490 | if (g_do_each_spin) | |
491 | g_iteration_sleeptime_us += threads_per_core * (g_each_spin_duration_ns / NSEC_PER_USEC); | |
492 | if (g_do_one_long_spin) | |
493 | g_iteration_sleeptime_us += g_one_long_spin_length_ns / NSEC_PER_USEC; | |
6d2010ae A |
494 | |
495 | /* Arrays for threads and their wakeup times */ | |
3e170ce0 A |
496 | threads = (pthread_t*) valloc(sizeof(pthread_t) * g_numthreads); |
497 | assert(threads); | |
498 | ||
499 | size_t endtimes_size = sizeof(uint64_t) * g_numthreads; | |
500 | ||
501 | g_thread_endtimes_abs = (uint64_t*) valloc(endtimes_size); | |
502 | assert(g_thread_endtimes_abs); | |
503 | ||
504 | /* Ensure the allocation is pre-faulted */ | |
505 | ret = memset_s(g_thread_endtimes_abs, endtimes_size, 0, endtimes_size); | |
506 | if (ret) errc(EX_OSERR, ret, "memset_s endtimes"); | |
507 | ||
508 | size_t latencies_size = sizeof(uint64_t) * g_iterations; | |
6d2010ae | 509 | |
3e170ce0 A |
510 | worst_latencies_ns = (uint64_t*) valloc(latencies_size); |
511 | assert(worst_latencies_ns); | |
6d2010ae | 512 | |
3e170ce0 A |
513 | /* Ensure the allocation is pre-faulted */ |
514 | ret = memset_s(worst_latencies_ns, latencies_size, 0, latencies_size); | |
515 | if (ret) errc(EX_OSERR, ret, "memset_s latencies"); | |
6d2010ae | 516 | |
3e170ce0 A |
517 | worst_latencies_from_first_ns = (uint64_t*) valloc(latencies_size); |
518 | assert(worst_latencies_from_first_ns); | |
519 | ||
520 | /* Ensure the allocation is pre-faulted */ | |
521 | ret = memset_s(worst_latencies_from_first_ns, latencies_size, 0, latencies_size); | |
522 | if (ret) errc(EX_OSERR, ret, "memset_s latencies_from_first"); | |
523 | ||
524 | kr = semaphore_create(mach_task_self(), &g_main_sem, SYNC_POLICY_FIFO, 0); | |
525 | mach_assert_zero(kr); | |
6d2010ae A |
526 | |
527 | /* Either one big semaphore or one per thread */ | |
3e170ce0 A |
528 | if (g_waketype == WAKE_CHAIN || |
529 | g_waketype == WAKE_BROADCAST_PERTHREAD || | |
530 | g_waketype == WAKE_HOP) { | |
531 | ||
532 | g_semarr = valloc(sizeof(semaphore_t) * g_numthreads); | |
533 | assert(g_semarr); | |
6d2010ae | 534 | |
3e170ce0 A |
535 | for (uint32_t i = 0; i < g_numthreads; i++) { |
536 | kr = semaphore_create(mach_task_self(), &g_semarr[i], SYNC_POLICY_FIFO, 0); | |
537 | mach_assert_zero(kr); | |
6d2010ae | 538 | } |
3e170ce0 | 539 | |
6d2010ae A |
540 | g_leadersem = g_semarr[0]; |
541 | } else { | |
3e170ce0 A |
542 | kr = semaphore_create(mach_task_self(), &g_broadcastsem, SYNC_POLICY_FIFO, 0); |
543 | mach_assert_zero(kr); | |
544 | kr = semaphore_create(mach_task_self(), &g_leadersem, SYNC_POLICY_FIFO, 0); | |
545 | mach_assert_zero(kr); | |
6d2010ae A |
546 | } |
547 | ||
3e170ce0 A |
548 | if (g_waketype == WAKE_HOP) { |
549 | kr = semaphore_create(mach_task_self(), &g_donesem, SYNC_POLICY_FIFO, 0); | |
550 | mach_assert_zero(kr); | |
551 | } | |
552 | ||
553 | kr = semaphore_create(mach_task_self(), &g_readysem, SYNC_POLICY_FIFO, 0); | |
554 | mach_assert_zero(kr); | |
555 | ||
6d2010ae A |
556 | /* Create the threads */ |
557 | g_done_threads = 0; | |
3e170ce0 A |
558 | for (uint32_t i = 0; i < g_numthreads; i++) { |
559 | ret = pthread_create(&threads[i], NULL, worker_thread, (void*)(uintptr_t)i); | |
560 | if (ret) errc(EX_OSERR, ret, "pthread_create %d", i); | |
6d2010ae A |
561 | } |
562 | ||
3e170ce0 A |
563 | ret = setpriority(PRIO_DARWIN_ROLE, 0, PRIO_DARWIN_ROLE_UI_FOCAL); |
564 | if (ret) errc(EX_OSERR, ret, "setpriority"); | |
fe8ab488 | 565 | |
3e170ce0 | 566 | thread_setup(0); |
fe8ab488 | 567 | |
6d2010ae | 568 | /* Let everyone get settled */ |
3e170ce0 A |
569 | kr = semaphore_wait(g_main_sem); |
570 | mach_assert_zero(kr); | |
571 | ||
572 | /* Give the system a bit more time to settle */ | |
573 | if (g_do_sleep) | |
574 | usleep(g_iteration_sleeptime_us); | |
6d2010ae A |
575 | |
576 | /* Go! */ | |
3e170ce0 A |
577 | for (uint32_t i = 0; i < g_iterations; i++) { |
578 | uint32_t j; | |
6d2010ae A |
579 | uint64_t worst_abs = 0, best_abs = UINT64_MAX; |
580 | ||
3e170ce0 A |
581 | if (g_do_one_long_spin) |
582 | g_one_long_spin_id = (uint32_t)rand() % g_numthreads; | |
583 | ||
584 | debug_log("%d Main thread reset\n", i); | |
585 | ||
6d2010ae A |
586 | g_done_threads = 0; |
587 | OSMemoryBarrier(); | |
588 | ||
589 | g_starttime_abs = mach_absolute_time(); | |
590 | ||
3e170ce0 A |
591 | /* Fire them off and wait for worker threads to finish */ |
592 | kr = semaphore_wait_signal(g_main_sem, g_leadersem); | |
593 | mach_assert_zero(kr); | |
6d2010ae | 594 | |
3e170ce0 | 595 | debug_log("%d Main thread return\n", i); |
6d2010ae | 596 | |
3e170ce0 | 597 | /* |
6d2010ae A |
598 | * We report the worst latencies relative to start time |
599 | * and relative to the lead worker thread. | |
600 | */ | |
601 | for (j = 0; j < g_numthreads; j++) { | |
602 | uint64_t latency_abs; | |
3e170ce0 | 603 | |
6d2010ae A |
604 | latency_abs = g_thread_endtimes_abs[j] - g_starttime_abs; |
605 | worst_abs = worst_abs < latency_abs ? latency_abs : worst_abs; | |
606 | } | |
607 | ||
608 | worst_latencies_ns[i] = abs_to_nanos(worst_abs); | |
609 | ||
610 | worst_abs = 0; | |
611 | for (j = 1; j < g_numthreads; j++) { | |
612 | uint64_t latency_abs; | |
613 | ||
614 | latency_abs = g_thread_endtimes_abs[j] - g_thread_endtimes_abs[0]; | |
615 | worst_abs = worst_abs < latency_abs ? latency_abs : worst_abs; | |
616 | best_abs = best_abs > latency_abs ? latency_abs : best_abs; | |
617 | } | |
618 | ||
619 | worst_latencies_from_first_ns[i] = abs_to_nanos(worst_abs); | |
620 | ||
621 | /* | |
622 | * In the event of a bad run, cut a trace point. | |
623 | */ | |
3e170ce0 A |
624 | if (worst_latencies_from_first_ns[i] > g_traceworthy_latency_ns) { |
625 | /* Ariadne's ad-hoc test signpost */ | |
626 | kdebug_trace(ARIADNEDBG_CODE(0, 0), worst_latencies_from_first_ns[i], g_traceworthy_latency_ns, 0, 0); | |
6d2010ae | 627 | |
3e170ce0 | 628 | if (g_verbose) |
6d2010ae | 629 | printf("Worst on this round was %.2f us.\n", ((float)worst_latencies_from_first_ns[i]) / 1000.0); |
6d2010ae A |
630 | } |
631 | ||
3e170ce0 A |
632 | /* Give the system a bit more time to settle */ |
633 | if (g_do_sleep) | |
634 | usleep(g_iteration_sleeptime_us); | |
6d2010ae A |
635 | } |
636 | ||
637 | /* Rejoin threads */ | |
3e170ce0 A |
638 | for (uint32_t i = 0; i < g_numthreads; i++) { |
639 | ret = pthread_join(threads[i], NULL); | |
640 | if (ret) errc(EX_OSERR, ret, "pthread_join %d", i); | |
6d2010ae A |
641 | } |
642 | ||
643 | compute_stats(worst_latencies_ns, g_iterations, &avg, &max, &min, &stddev); | |
644 | printf("Results (from a stop):\n"); | |
645 | printf("Max:\t\t%.2f us\n", ((float)max) / 1000.0); | |
646 | printf("Min:\t\t%.2f us\n", ((float)min) / 1000.0); | |
647 | printf("Avg:\t\t%.2f us\n", avg / 1000.0); | |
648 | printf("Stddev:\t\t%.2f us\n", stddev / 1000.0); | |
649 | ||
650 | putchar('\n'); | |
651 | ||
652 | compute_stats(worst_latencies_from_first_ns, g_iterations, &avg, &max, &min, &stddev); | |
653 | printf("Results (relative to first thread):\n"); | |
654 | printf("Max:\t\t%.2f us\n", ((float)max) / 1000.0); | |
655 | printf("Min:\t\t%.2f us\n", ((float)min) / 1000.0); | |
656 | printf("Avg:\t\t%.2f us\n", avg / 1000.0); | |
657 | printf("Stddev:\t\t%.2f us\n", stddev / 1000.0); | |
658 | ||
659 | #if 0 | |
3e170ce0 | 660 | for (uint32_t i = 0; i < g_iterations; i++) { |
6d2010ae A |
661 | printf("Iteration %d: %f us\n", i, worst_latencies_ns[i] / 1000.0); |
662 | } | |
3e170ce0 A |
663 | #endif |
664 | ||
665 | free(threads); | |
666 | free(g_thread_endtimes_abs); | |
667 | free(worst_latencies_ns); | |
668 | free(worst_latencies_from_first_ns); | |
6d2010ae A |
669 | |
670 | return 0; | |
6d2010ae | 671 | } |
fe8ab488 A |
672 | |
673 | /* | |
674 | * WARNING: This is SPI specifically intended for use by launchd to start UI | |
675 | * apps. We use it here for a test tool only to opt into QoS using the same | |
676 | * policies. Do not use this outside xnu or libxpc/launchd. | |
677 | */ | |
3e170ce0 | 678 | static void |
fe8ab488 A |
679 | selfexec_with_apptype(int argc, char *argv[]) |
680 | { | |
681 | int ret; | |
682 | posix_spawnattr_t attr; | |
683 | extern char **environ; | |
684 | char *new_argv[argc + 1 + 1 /* NULL */]; | |
685 | int i; | |
686 | char prog[PATH_MAX]; | |
a1c7dba1 | 687 | uint32_t prog_size = PATH_MAX; |
fe8ab488 A |
688 | |
689 | ret = _NSGetExecutablePath(prog, &prog_size); | |
3e170ce0 | 690 | if (ret) err(EX_OSERR, "_NSGetExecutablePath"); |
fe8ab488 A |
691 | |
692 | for (i=0; i < argc; i++) { | |
693 | new_argv[i] = argv[i]; | |
694 | } | |
695 | ||
3e170ce0 | 696 | new_argv[i] = "--switched_apptype"; |
fe8ab488 A |
697 | new_argv[i+1] = NULL; |
698 | ||
699 | ret = posix_spawnattr_init(&attr); | |
3e170ce0 | 700 | if (ret) errc(EX_OSERR, ret, "posix_spawnattr_init"); |
fe8ab488 A |
701 | |
702 | ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETEXEC); | |
3e170ce0 | 703 | if (ret) errc(EX_OSERR, ret, "posix_spawnattr_setflags"); |
fe8ab488 A |
704 | |
705 | ret = posix_spawnattr_setprocesstype_np(&attr, POSIX_SPAWN_PROC_TYPE_APP_DEFAULT); | |
3e170ce0 | 706 | if (ret) errc(EX_OSERR, ret, "posix_spawnattr_setprocesstype_np"); |
fe8ab488 A |
707 | |
708 | ret = posix_spawn(NULL, prog, NULL, &attr, new_argv, environ); | |
3e170ce0 A |
709 | if (ret) errc(EX_OSERR, ret, "posix_spawn"); |
710 | } | |
711 | ||
712 | /* | |
713 | * Admittedly not very attractive. | |
714 | */ | |
715 | static void __attribute__((noreturn)) | |
716 | usage() | |
717 | { | |
718 | errx(EX_USAGE, "Usage: zn <threads> <chain | hop | broadcast-single-sem | broadcast-per-thread> " | |
719 | "<realtime | timeshare | fixed> <iterations> [--trace <traceworthy latency in ns>] " | |
720 | "[--spin-one] [--spin-all] [--spin-time <nanos>] [--affinity] [--no-sleep] [--verbose]"); | |
fe8ab488 | 721 | } |
3e170ce0 A |
722 | |
723 | static void | |
724 | parse_args(int argc, char *argv[]) | |
725 | { | |
726 | int ch, option_index = 0; | |
727 | char *cp; | |
728 | ||
729 | static struct option longopts[] = { | |
730 | { "spin-time", required_argument, NULL, 2 }, | |
731 | { "trace", required_argument, NULL, 3 }, | |
732 | { "switched_apptype", no_argument, (int*)&g_seen_apptype, TRUE }, | |
733 | { "spin-one", no_argument, (int*)&g_do_one_long_spin, TRUE }, | |
734 | { "spin-all", no_argument, (int*)&g_do_all_spin, TRUE }, | |
735 | { "affinity", no_argument, (int*)&g_do_affinity, TRUE }, | |
736 | { "no-sleep", no_argument, (int*)&g_do_sleep, FALSE }, | |
737 | { "verbose", no_argument, (int*)&g_verbose, TRUE }, | |
738 | { "help", no_argument, NULL, 'h' }, | |
739 | { NULL, 0, NULL, 0 } | |
740 | }; | |
741 | ||
742 | while ((ch = getopt_long(argc, argv, "h", longopts, &option_index)) != -1) { | |
743 | switch (ch) { | |
744 | case 0: | |
745 | /* getopt_long set a variable */ | |
746 | break; | |
747 | case 2: | |
748 | /* spin-time */ | |
749 | g_do_each_spin = TRUE; | |
750 | g_each_spin_duration_ns = strtoull(optarg, &cp, 10); | |
751 | ||
752 | if (cp == optarg || *cp) | |
753 | errx(EX_USAGE, "arg --%s requires a decimal number, found \"%s\"", | |
754 | longopts[option_index].name, optarg); | |
755 | break; | |
756 | case 3: | |
757 | /* trace */ | |
758 | g_traceworthy_latency_ns = strtoull(optarg, &cp, 10); | |
759 | ||
760 | if (cp == optarg || *cp) | |
761 | errx(EX_USAGE, "arg --%s requires a decimal number, found \"%s\"", | |
762 | longopts[option_index].name, optarg); | |
763 | break; | |
764 | case '?': | |
765 | case 'h': | |
766 | default: | |
767 | usage(); | |
768 | /* NORETURN */ | |
769 | } | |
770 | } | |
771 | ||
772 | /* | |
773 | * getopt_long reorders all the options to the beginning of the argv array. | |
774 | * Jump past them to the non-option arguments. | |
775 | */ | |
776 | ||
777 | argc -= optind; | |
778 | argv += optind; | |
779 | ||
780 | if (argc > 4) { | |
781 | warnx("Too many non-option arguments passed"); | |
782 | usage(); | |
783 | } | |
784 | ||
785 | if (argc != 4) { | |
786 | warnx("Missing required <threads> <waketype> <policy> <iterations> arguments"); | |
787 | usage(); | |
788 | } | |
789 | ||
790 | /* How many threads? */ | |
791 | g_numthreads = (uint32_t)strtoull(argv[0], &cp, 10); | |
792 | ||
793 | if (cp == argv[0] || *cp) | |
794 | errx(EX_USAGE, "numthreads requires a decimal number, found \"%s\"", argv[0]); | |
795 | ||
796 | if (g_numthreads < 1) | |
797 | errx(EX_USAGE, "Must use at least one thread"); | |
798 | ||
799 | /* What wakeup pattern? */ | |
800 | g_waketype = parse_wakeup_pattern(argv[1]); | |
801 | ||
802 | /* Policy */ | |
803 | g_policy = parse_thread_policy(argv[2]); | |
804 | ||
805 | /* Iterations */ | |
806 | g_iterations = (uint32_t)strtoull(argv[3], &cp, 10); | |
807 | ||
808 | if (cp == argv[3] || *cp) | |
809 | errx(EX_USAGE, "numthreads requires a decimal number, found \"%s\"", argv[3]); | |
810 | ||
811 | if (g_iterations < 1) | |
812 | errx(EX_USAGE, "Must have at least one iteration"); | |
813 | ||
814 | if (g_numthreads == 1 && g_waketype == WAKE_CHAIN) | |
815 | errx(EX_USAGE, "chain mode requires more than one thread"); | |
816 | ||
817 | if (g_numthreads == 1 && g_waketype == WAKE_HOP) | |
818 | errx(EX_USAGE, "hop mode requires more than one thread"); | |
819 | } | |
820 | ||
821 |