]> git.saurik.com Git - apple/xnu.git/blame - tools/tests/jitter/timer_jitter.c
xnu-2422.1.72.tar.gz
[apple/xnu.git] / tools / tests / jitter / timer_jitter.c
CommitLineData
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>
31#include <sys/wait.h>
32#include <sys/syscall.h>
33#include <sys/types.h>
34#include <sys/ptrace.h>
35#include <semaphore.h>
36#include <stdlib.h>
37#include <pthread.h>
38#include <fcntl.h>
39#include <errno.h>
40#include <err.h>
41#include <string.h>
42
43#include <libkern/OSAtomic.h>
44
45#include <mach/mach_time.h>
46#include <mach/mach.h>
47#include <mach/task.h>
48#include <mach/semaphore.h>
49
50typedef enum my_policy_type { MY_POLICY_REALTIME, MY_POLICY_TIMESHARE, MY_POLICY_FIXEDPRI } my_policy_type_t;
51
52#define DEFAULT_MAX_SLEEP_NS 2000000000ll /* Two seconds */
53#define CONSTRAINT_NANOS (20000000ll) /* 20 ms */
54#define COMPUTATION_NANOS (10000000ll) /* 10 ms */
55
56struct mach_timebase_info g_mti;
57
58#define assert(truth, label) do { if(!(truth)) { printf("Thread %p: failure on line %d\n", pthread_self(), __LINE__); goto label; } } while (0)
59
60struct second_thread_args {
61 semaphore_t wakeup_semaphore;
62 semaphore_t return_semaphore;
63 uint64_t iterations;
64 my_policy_type_t pol;
65 double *wakeup_second_jitter_arr;
66 uint64_t woke_on_same_cpu;
67 uint64_t too_much;
68 volatile uint64_t last_poke_time;
69 volatile int cpuno;
70};
71
72extern int cpu_number(void);
73
74void *
75second_thread(void *args);
76
77void
78print_usage()
79{
80 printf("Usage: jitter [-w] [-s <random seed>] [-n <min sleep, ns>] [-m <max sleep, ns>] <realtime | timeshare | fixed> <num iterations> <traceworthy jitter, ns>\n");
81}
82
83my_policy_type_t
84parse_thread_policy(const char *str)
85{
86 if (strcmp(str, "timeshare") == 0) {
87 return MY_POLICY_TIMESHARE;
88 } else if (strcmp(str, "realtime") == 0) {
89 return MY_POLICY_REALTIME;
90 } else if (strcmp(str, "fixed") == 0) {
91 return MY_POLICY_FIXEDPRI;
92 } else {
93 printf("Invalid thread policy %s\n", str);
94 exit(1);
95 }
96}
97
98int
99thread_setup(my_policy_type_t pol)
100{
101 int res;
102
103 switch (pol) {
104 case MY_POLICY_TIMESHARE:
105 {
106 return 0;
107 }
108 case MY_POLICY_REALTIME:
109 {
110 thread_time_constraint_policy_data_t pol;
111
112 /* Hard-coded realtime parameters (similar to what Digi uses) */
113 pol.period = 100000;
114 pol.constraint = CONSTRAINT_NANOS * g_mti.denom / g_mti.numer;
115 pol.computation = COMPUTATION_NANOS * g_mti.denom / g_mti.numer;
116 pol.preemptible = 0; /* Ignored by OS */
117
118 res = thread_policy_set(mach_thread_self(), THREAD_TIME_CONSTRAINT_POLICY, (thread_policy_t) &pol, THREAD_TIME_CONSTRAINT_POLICY_COUNT);
119 assert(res == 0, fail);
120 break;
121 }
122 case MY_POLICY_FIXEDPRI:
123 {
124 thread_extended_policy_data_t pol;
125 pol.timeshare = 0;
126
127 res = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, (thread_policy_t) &pol, THREAD_EXTENDED_POLICY_COUNT);
128 assert(res == 0, fail);
129 break;
130 }
131 default:
132 {
133 printf("invalid policy type\n");
134 return 1;
135 }
136 }
137
138 return 0;
139fail:
140 return 1;
141}
142
143uint64_t
144get_random_sleep_length_abs_ns(uint64_t min_sleep_ns, uint64_t max_sleep_ns)
145{
146 uint64_t tmp;
147
148 tmp = (uint32_t)random();
149 tmp <<= 32;
150 tmp |= (uint32_t)random();
151
152 /* Now use the random number to sleep amount within the window */
153 tmp %= (max_sleep_ns - min_sleep_ns);
154
155 return min_sleep_ns + tmp;
156}
157
158void
159compute_stats(double *values, uint64_t count, double *average_magnitudep, double *maxp, double *minp, double *stddevp)
160{
161 uint64_t i;
162 double _sum = 0;
163 double _max = 0;
164 double _min = (double)INT64_MAX;
165 double _avg = 0;
166 double _dev = 0;
167
168 for (i = 0; i < count; i++) {
169 _sum += fabs(values[i]);
170 _max = values[i] > _max ? values[i] : _max;
171 _min = values[i] < _min ? values[i] : _min;
172 }
173
174 _avg = _sum / (double)count;
175
176 _dev = 0;
177 for (i = 0; i < count; i++) {
178 _dev += pow((values[i] - _avg), 2);
179 }
180
181 _dev /= count;
182 _dev = sqrt(_dev);
183
184 *average_magnitudep = _avg;
185 *maxp = _max;
186 *minp = _min;
187 *stddevp = _dev;
188}
189
190void
191print_stats_us(const char *label, double avg, double max, double min, double stddev)
192{
193 printf("Max %s: %.1lfus\n", label, max / 1000.0 * (((double)g_mti.numer) / ((double)g_mti.denom)));
194 printf("Min %s: %.1lfus\n", label, min / 1000.0 * (((double)g_mti.numer) / ((double)g_mti.denom)));
195 printf("Avg magnitude of %s: %.1lfus\n", label, avg / 1000.0 * (((double)g_mti.numer) / ((double)g_mti.denom)));
196 printf("Stddev: %.1lfus\n", stddev / 1000.0 * (((double)g_mti.numer) / ((double)g_mti.denom)));
197 putchar('\n');
198}
199
200void
201print_stats_fract(const char *label, double avg, double max, double min, double stddev)
202{
203 printf("Max %s jitter: %.1lf%%\n", label, max * 100);
204 printf("Min %s jitter: %.1lf%%\n", label, min * 100);
205 printf("Avg %s jitter: %.1lf%%\n", label, avg * 100);
206 printf("Stddev: %.1lf%%\n", stddev * 100);
207 putchar('\n');
208}
209
210int
211main(int argc, char **argv)
212{
213 uint64_t iterations, i;
214 double *jitter_arr, *fraction_arr;
215 double *wakeup_second_jitter_arr;
216 uint64_t target_time;
217 uint64_t sleep_length_abs;
218 uint64_t min_sleep_ns = 0;
219 uint64_t max_sleep_ns = DEFAULT_MAX_SLEEP_NS;
220 uint64_t wake_time;
221 unsigned random_seed;
222 boolean_t need_seed = TRUE;
223 char ch;
224 int res;
225 kern_return_t kret;
226 my_policy_type_t pol;
227 boolean_t wakeup_second_thread = FALSE;
228 semaphore_t wakeup_semaphore, return_semaphore;
229
230 double avg, stddev, max, min;
231 double avg_fract, stddev_fract, max_fract, min_fract;
232 uint64_t too_much;
233
234 struct second_thread_args secargs;
235 pthread_t secthread;
236
237 mach_timebase_info(&g_mti);
238
239 /* Seed random */
240 opterr = 0;
241 while ((ch = getopt(argc, argv, "m:n:hs:w")) != -1 && ch != '?') {
242 switch (ch) {
243 case 's':
244 /* Specified seed for random)() */
245 random_seed = (unsigned)atoi(optarg);
246 srandom(random_seed);
247 need_seed = FALSE;
248 break;
249 case 'm':
250 /* How long per timer? */
251 max_sleep_ns = strtoull(optarg, NULL, 10);
252 break;
253 case 'n':
254 /* How long per timer? */
255 min_sleep_ns = strtoull(optarg, NULL, 10);
256 break;
257 case 'w':
258 /* After each timed wait, wakeup another thread */
259 wakeup_second_thread = TRUE;
260 break;
261 case 'h':
262 print_usage();
263 exit(0);
264 break;
265 default:
266 fprintf(stderr, "Got unexpected result from getopt().\n");
267 exit(1);
268 break;
269 }
270 }
271
272 argc -= optind;
273 argv += optind;
274
275 if (argc != 3) {
276 print_usage();
277 exit(1);
278 }
279
280 if (min_sleep_ns >= max_sleep_ns) {
281 print_usage();
282 exit(1);
283 }
284
285 if (need_seed) {
286 srandom(time(NULL));
287 }
288
289 /* What scheduling policy? */
290 pol = parse_thread_policy(argv[0]);
291
292 /* How many timers? */
293 iterations = strtoull(argv[1], NULL, 10);
294
295 /* How much jitter is so extreme that we should cut a trace point */
296 too_much = strtoull(argv[2], NULL, 10);
297
298 /* Array for data */
299 jitter_arr = (double*)malloc(sizeof(*jitter_arr) * iterations);
300 if (jitter_arr == NULL) {
301 printf("Couldn't allocate array to store results.\n");
302 exit(1);
303 }
304
305 fraction_arr = (double*)malloc(sizeof(*fraction_arr) * iterations);
306 if (fraction_arr == NULL) {
307 printf("Couldn't allocate array to store results.\n");
308 exit(1);
309 }
310
311 if (wakeup_second_thread) {
312 /* Array for data */
313 wakeup_second_jitter_arr = (double*)malloc(sizeof(*jitter_arr) * iterations);
314 if (wakeup_second_jitter_arr == NULL) {
315 printf("Couldn't allocate array to store results.\n");
316 exit(1);
317 }
318
319 kret = semaphore_create(mach_task_self(), &wakeup_semaphore, SYNC_POLICY_FIFO, 0);
320 if (kret != KERN_SUCCESS) {
321 printf("Couldn't allocate semaphore %d\n", kret);
322 exit(1);
323 }
324
325 kret = semaphore_create(mach_task_self(), &return_semaphore, SYNC_POLICY_FIFO, 0);
326 if (kret != KERN_SUCCESS) {
327 printf("Couldn't allocate semaphore %d\n", kret);
328 exit(1);
329 }
330
331
332 secargs.wakeup_semaphore = wakeup_semaphore;
333 secargs.return_semaphore = return_semaphore;
334 secargs.iterations = iterations;
335 secargs.pol = pol;
336 secargs.wakeup_second_jitter_arr = wakeup_second_jitter_arr;
337 secargs.woke_on_same_cpu = 0;
338 secargs.too_much = too_much;
339 secargs.last_poke_time = 0ULL;
340 secargs.cpuno = 0;
341
342 res = pthread_create(&secthread, NULL, second_thread, &secargs);
343 if (res) {
344 err(1, "pthread_create");
345 }
346
347 sleep(1); /* Time for other thread to start up */
348 }
349
350 /* Set scheduling policy */
351 res = thread_setup(pol);
352 if (res != 0) {
353 printf("Couldn't set thread policy.\n");
354 exit(1);
355 }
356
357 /*
358 * Repeatedly pick a random timer length and
359 * try to sleep exactly that long
360 */
361 for (i = 0; i < iterations; i++) {
362 sleep_length_abs = (uint64_t) (get_random_sleep_length_abs_ns(min_sleep_ns, max_sleep_ns) * (((double)g_mti.denom) / ((double)g_mti.numer)));
363 target_time = mach_absolute_time() + sleep_length_abs;
364
365 /* Sleep */
366 kret = mach_wait_until(target_time);
367 wake_time = mach_absolute_time();
368
369 jitter_arr[i] = (double)(wake_time - target_time);
370 fraction_arr[i] = jitter_arr[i] / ((double)sleep_length_abs);
371
372 /* Too much: cut a tracepoint for a debugger */
373 if (jitter_arr[i] >= too_much) {
374 syscall(SYS_kdebug_trace, 0xeeeeeeee, 0, 0, 0, 0);
375 }
376
377 if (wakeup_second_thread) {
378 secargs.last_poke_time = mach_absolute_time();
379 secargs.cpuno = cpu_number();
380 OSMemoryBarrier();
381 kret = semaphore_signal(wakeup_semaphore);
382 if (kret != KERN_SUCCESS) {
383 errx(1, "semaphore_signal");
384 }
385
386 kret = semaphore_wait(return_semaphore);
387 if (kret != KERN_SUCCESS) {
388 errx(1, "semaphore_wait");
389 }
390
391 }
392 }
393
394 /*
395 * Compute statistics and output results.
396 */
397 compute_stats(jitter_arr, iterations, &avg, &max, &min, &stddev);
398 compute_stats(fraction_arr, iterations, &avg_fract, &max_fract, &min_fract, &stddev_fract);
399
400 putchar('\n');
401 print_stats_us("jitter", avg, max, min, stddev);
402 print_stats_fract("%", avg_fract, max_fract, min_fract, stddev_fract);
403
404 if (wakeup_second_thread) {
405
406 res = pthread_join(secthread, NULL);
407 if (res) {
408 err(1, "pthread_join");
409 }
410
411 compute_stats(wakeup_second_jitter_arr, iterations, &avg, &max, &min, &stddev);
412
413 putchar('\n');
414 print_stats_us("second jitter", avg, max, min, stddev);
415
416 putchar('\n');
417 printf("%llu/%llu (%.1f%%) wakeups on same CPU\n", secargs.woke_on_same_cpu, iterations,
418 100.0*((double)secargs.woke_on_same_cpu)/iterations);
419 }
420
421 return 0;
422}
423
424void *
425second_thread(void *args)
426{
427 struct second_thread_args *secargs = (struct second_thread_args *)args;
428 int res;
429 uint64_t i;
430 kern_return_t kret;
431 uint64_t wake_time;
432 int cpuno;
433
434 /* Set scheduling policy */
435 res = thread_setup(secargs->pol);
436 if (res != 0) {
437 printf("Couldn't set thread policy.\n");
438 exit(1);
439 }
440
441 /*
442 * Repeatedly pick a random timer length and
443 * try to sleep exactly that long
444 */
445 for (i = 0; i < secargs->iterations; i++) {
446
447 /* Wake up when poked by main thread */
448 kret = semaphore_wait(secargs->wakeup_semaphore);
449 if (kret != KERN_SUCCESS) {
450 errx(1, "semaphore_wait %d", kret);
451 }
452
453 wake_time = mach_absolute_time();
454 cpuno = cpu_number();
455 if (wake_time < secargs->last_poke_time) {
456 /* Woke in past, unsynchronized mach_absolute_time()? */
457
458 errx(1, "woke in past %llu (%d) < %llu (%d)", wake_time, cpuno, secargs->last_poke_time, secargs->cpuno);
459 }
460
461 if (cpuno == secargs->cpuno) {
462 secargs->woke_on_same_cpu++;
463 }
464
465 secargs->wakeup_second_jitter_arr[i] = (double)(wake_time - secargs->last_poke_time);
466
467 /* Too much: cut a tracepoint for a debugger */
468 if (secargs->wakeup_second_jitter_arr[i] >= secargs->too_much) {
469 syscall(SYS_kdebug_trace, 0xeeeeeeef, 0, 0, 0, 0);
470 }
471
472 kret = semaphore_signal(secargs->return_semaphore);
473 if (kret != KERN_SUCCESS) {
474 errx(1, "semaphore_signal %d", kret);
475 }
476
477 }
478
479 return NULL;
480}