]>
Commit | Line | Data |
---|---|---|
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 | typedef enum wake_type { WAKE_BROADCAST_ONESEM, WAKE_BROADCAST_PERTHREAD, WAKE_CHAIN, WAKE_HOP } wake_type_t; | |
57 | typedef enum my_policy_type { MY_POLICY_REALTIME, MY_POLICY_TIMESHARE, MY_POLICY_FIXEDPRI } my_policy_type_t; | |
58 | ||
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) | |
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 */ | |
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[]); | |
80 | ||
81 | /* Global variables (general) */ | |
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; | |
118 | ||
119 | /* Global variables (broadcast) */ | |
120 | static semaphore_t g_broadcastsem; | |
121 | static semaphore_t g_leadersem; | |
122 | static semaphore_t g_readysem; | |
123 | static semaphore_t g_donesem; | |
124 | ||
125 | /* Global variables (chain) */ | |
126 | static semaphore_t *g_semarr; | |
127 | ||
128 | static uint64_t | |
129 | abs_to_nanos(uint64_t abstime) | |
130 | { | |
131 | return (uint64_t)(abstime * (((double)g_mti.numer) / ((double)g_mti.denom))); | |
132 | } | |
133 | ||
134 | static uint64_t | |
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 | */ | |
143 | static my_policy_type_t | |
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 { | |
153 | errx(EX_USAGE, "Invalid thread policy \"%s\"", str); | |
154 | } | |
155 | } | |
156 | ||
157 | /* | |
158 | * Figure out what wakeup pattern to use | |
159 | */ | |
160 | static wake_type_t | |
161 | parse_wakeup_pattern(const char *str) | |
162 | { | |
163 | if (strcmp(str, "chain") == 0) { | |
164 | return WAKE_CHAIN; | |
165 | } else if (strcmp(str, "hop") == 0) { | |
166 | return WAKE_HOP; | |
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 { | |
172 | errx(EX_USAGE, "Invalid wakeup pattern \"%s\"", str); | |
173 | } | |
174 | } | |
175 | ||
176 | /* | |
177 | * Set policy | |
178 | */ | |
179 | static int | |
180 | thread_setup(uint32_t my_id) | |
181 | { | |
182 | kern_return_t kr; | |
183 | errno_t ret; | |
184 | thread_time_constraint_policy_data_t pol; | |
185 | ||
186 | switch (g_policy) { | |
187 | case MY_POLICY_TIMESHARE: | |
188 | break; | |
189 | case MY_POLICY_REALTIME: | |
190 | /* Hard-coded realtime parameters (similar to what Digi uses) */ | |
191 | pol.period = 100000; | |
192 | pol.constraint = (uint32_t) nanos_to_abs(CONSTRAINT_NANOS); | |
193 | pol.computation = (uint32_t) nanos_to_abs(COMPUTATION_NANOS); | |
194 | pol.preemptible = 0; /* Ignored by OS */ | |
195 | ||
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); | |
199 | break; | |
200 | case MY_POLICY_FIXEDPRI: | |
201 | ret = pthread_set_fixedpriority_self(); | |
202 | if (ret) errc(EX_OSERR, ret, "pthread_set_fixedpriority_self"); | |
203 | break; | |
204 | default: | |
205 | errx(EX_USAGE, "invalid policy type %d", g_policy); | |
206 | } | |
207 | ||
208 | if (g_do_affinity) { | |
209 | thread_affinity_policy_data_t affinity; | |
210 | ||
211 | affinity.affinity_tag = my_id % 2; | |
212 | ||
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); | |
216 | } | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
221 | /* | |
222 | * Wait for a wakeup, potentially wake up another of the "0-N" threads, | |
223 | * and notify the main thread when done. | |
224 | */ | |
225 | static void* | |
226 | worker_thread(void *arg) | |
227 | { | |
228 | uint32_t my_id = (uint32_t)(uintptr_t)arg; | |
229 | kern_return_t kr; | |
230 | ||
231 | volatile double x = 0.0; | |
232 | volatile double y = 0.0; | |
233 | ||
234 | /* Set policy and so forth */ | |
235 | thread_setup(my_id); | |
236 | ||
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 | */ | |
242 | ||
243 | /* Give the worker threads undisturbed time to finish before waiting on them */ | |
244 | if (g_do_sleep) | |
245 | usleep(g_iteration_sleeptime_us); | |
246 | ||
247 | debug_log("%d Leader thread wait for ready\n", i); | |
248 | ||
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 | */ | |
254 | ||
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 | } | |
259 | ||
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); | |
266 | ||
267 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); | |
268 | ||
269 | debug_log("%d Leader thread go\n", i); | |
270 | ||
271 | assert_zero_t(my_id, g_done_threads); | |
272 | ||
273 | switch (g_waketype) { | |
274 | case WAKE_BROADCAST_ONESEM: | |
275 | kr = semaphore_signal_all(g_broadcastsem); | |
276 | mach_assert_zero_t(my_id, kr); | |
277 | break; | |
278 | case WAKE_BROADCAST_PERTHREAD: | |
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); | |
282 | } | |
283 | break; | |
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; | |
292 | } | |
293 | } else { | |
294 | /* | |
295 | * Everyone else waits to be woken up, | |
296 | * records when she wakes up, and possibly | |
297 | * wakes up a friend. | |
298 | */ | |
299 | switch(g_waketype) { | |
300 | case WAKE_BROADCAST_ONESEM: | |
301 | kr = semaphore_wait_signal(g_broadcastsem, g_readysem); | |
302 | mach_assert_zero_t(my_id, kr); | |
303 | ||
304 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); | |
305 | break; | |
306 | ||
307 | case WAKE_BROADCAST_PERTHREAD: | |
308 | kr = semaphore_wait_signal(g_semarr[my_id], g_readysem); | |
309 | mach_assert_zero_t(my_id, kr); | |
310 | ||
311 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); | |
312 | break; | |
313 | ||
314 | case WAKE_CHAIN: | |
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 */ | |
319 | ||
320 | g_thread_endtimes_abs[my_id] = mach_absolute_time(); | |
321 | ||
322 | if (my_id < (g_numthreads - 1)) { | |
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); | |
343 | } | |
344 | ||
345 | break; | |
346 | } | |
347 | } | |
348 | ||
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); | |
386 | } | |
387 | ||
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); | |
392 | ||
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; | |
411 | } | |
412 | ||
413 | /* | |
414 | * Given an array of uint64_t values, compute average, max, min, and standard deviation | |
415 | */ | |
416 | static void | |
417 | compute_stats(uint64_t *values, uint64_t count, float *averagep, uint64_t *maxp, uint64_t *minp, float *stddevp) | |
418 | { | |
419 | uint32_t i; | |
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 | { | |
451 | errno_t ret; | |
452 | kern_return_t kr; | |
453 | ||
454 | pthread_t *threads; | |
455 | uint64_t *worst_latencies_ns; | |
456 | uint64_t *worst_latencies_from_first_ns; | |
457 | uint64_t max, min; | |
458 | float avg, stddev; | |
459 | ||
460 | for (int i = 0; i < argc; i++) | |
461 | if (strcmp(argv[i], "--switched_apptype") == 0) | |
462 | g_seen_apptype = TRUE; | |
463 | ||
464 | if (!g_seen_apptype) | |
465 | selfexec_with_apptype(argc, argv); | |
466 | ||
467 | parse_args(argc, argv); | |
468 | ||
469 | srand((unsigned int)time(NULL)); | |
470 | ||
471 | mach_timebase_info(&g_mti); | |
472 | ||
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)"); | |
476 | ||
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); | |
484 | } | |
485 | ||
486 | /* Estimate the amount of time the cleanup phase needs to back off */ | |
487 | g_iteration_sleeptime_us = g_numthreads * 20; | |
488 | ||
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; | |
494 | ||
495 | /* Arrays for threads and their wakeup times */ | |
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; | |
509 | ||
510 | worst_latencies_ns = (uint64_t*) valloc(latencies_size); | |
511 | assert(worst_latencies_ns); | |
512 | ||
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"); | |
516 | ||
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); | |
526 | ||
527 | /* Either one big semaphore or one per thread */ | |
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); | |
534 | ||
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); | |
538 | } | |
539 | ||
540 | g_leadersem = g_semarr[0]; | |
541 | } else { | |
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); | |
546 | } | |
547 | ||
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 | ||
556 | /* Create the threads */ | |
557 | g_done_threads = 0; | |
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); | |
561 | } | |
562 | ||
563 | ret = setpriority(PRIO_DARWIN_ROLE, 0, PRIO_DARWIN_ROLE_UI_FOCAL); | |
564 | if (ret) errc(EX_OSERR, ret, "setpriority"); | |
565 | ||
566 | thread_setup(0); | |
567 | ||
568 | /* Let everyone get settled */ | |
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); | |
575 | ||
576 | /* Go! */ | |
577 | for (uint32_t i = 0; i < g_iterations; i++) { | |
578 | uint32_t j; | |
579 | uint64_t worst_abs = 0, best_abs = UINT64_MAX; | |
580 | ||
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 | ||
586 | g_done_threads = 0; | |
587 | OSMemoryBarrier(); | |
588 | ||
589 | g_starttime_abs = mach_absolute_time(); | |
590 | ||
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); | |
594 | ||
595 | debug_log("%d Main thread return\n", i); | |
596 | ||
597 | /* | |
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; | |
603 | ||
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 | */ | |
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); | |
627 | ||
628 | if (g_verbose) | |
629 | printf("Worst on this round was %.2f us.\n", ((float)worst_latencies_from_first_ns[i]) / 1000.0); | |
630 | } | |
631 | ||
632 | /* Give the system a bit more time to settle */ | |
633 | if (g_do_sleep) | |
634 | usleep(g_iteration_sleeptime_us); | |
635 | } | |
636 | ||
637 | /* Rejoin threads */ | |
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); | |
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 | |
660 | for (uint32_t i = 0; i < g_iterations; i++) { | |
661 | printf("Iteration %d: %f us\n", i, worst_latencies_ns[i] / 1000.0); | |
662 | } | |
663 | #endif | |
664 | ||
665 | free(threads); | |
666 | free(g_thread_endtimes_abs); | |
667 | free(worst_latencies_ns); | |
668 | free(worst_latencies_from_first_ns); | |
669 | ||
670 | return 0; | |
671 | } | |
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 | */ | |
678 | static void | |
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]; | |
687 | uint32_t prog_size = PATH_MAX; | |
688 | ||
689 | ret = _NSGetExecutablePath(prog, &prog_size); | |
690 | if (ret) err(EX_OSERR, "_NSGetExecutablePath"); | |
691 | ||
692 | for (i=0; i < argc; i++) { | |
693 | new_argv[i] = argv[i]; | |
694 | } | |
695 | ||
696 | new_argv[i] = "--switched_apptype"; | |
697 | new_argv[i+1] = NULL; | |
698 | ||
699 | ret = posix_spawnattr_init(&attr); | |
700 | if (ret) errc(EX_OSERR, ret, "posix_spawnattr_init"); | |
701 | ||
702 | ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETEXEC); | |
703 | if (ret) errc(EX_OSERR, ret, "posix_spawnattr_setflags"); | |
704 | ||
705 | ret = posix_spawnattr_setprocesstype_np(&attr, POSIX_SPAWN_PROC_TYPE_APP_DEFAULT); | |
706 | if (ret) errc(EX_OSERR, ret, "posix_spawnattr_setprocesstype_np"); | |
707 | ||
708 | ret = posix_spawn(NULL, prog, NULL, &attr, new_argv, environ); | |
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]"); | |
721 | } | |
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 |