1 /*
2 * Copyright 2008 Google Inc. All Rights Reserved.
3 * Author: md@google.com (Michael Davidson)
4 *
5 * Based on time-warp-test.c, which is:
6 * Copyright (C) 2005, Ingo Molnar
7 */
8 #define _GNU_SOURCE
9
10 #include <errno.h>
11 #include <pthread.h>
12 #include <getopt.h>
13 #include <sched.h>
14 #include <signal.h>
15 #include <stdarg.h>
16 #include <stdint.h>
17 #include <inttypes.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/time.h>
22 #include <time.h>
23
24 #include "cpuset.h"
25 #include "spinlock.h"
26 #include "threads.h"
27 #include "logging.h"
28
29
30 char *program = "";
31 long duration = 0;
32 long threshold = 0;
33 int verbose = 0;
34
35 const char optstring[] = "c:d:ht:v";
36
37 struct option options[] = {
38 { "cpus", required_argument, 0, 'c' },
39 { "duration", required_argument, 0, 'd' },
40 { "help", no_argument, 0, 'h' },
41 { "threshold", required_argument, 0, 't' },
42 { "verbose", no_argument, 0, 'v' },
43 { 0, 0, 0, 0 }
44 };
45
46
usage(void)47 void usage(void)
48 {
49 printf("usage: %s [-hv] [-c <cpu_set>] [-d duration] [-t threshold] "
50 "tsc|gtod|clock", program);
51 }
52
53
54 const char help_text[] =
55 "check time sources for monotonicity across multiple CPUs\n"
56 " -c,--cpus set of cpus to test (default: all)\n"
57 " -d,--duration test duration in seconds (default: infinite)\n"
58 " -t,--threshold error threshold (default: 0)\n"
59 " -v,--verbose verbose output\n"
60 " tsc test the TSC\n"
61 " gtod test gettimeofday()\n"
62 " clock test CLOCK_MONOTONIC\n";
63
64
help(void)65 void help(void)
66 {
67 usage();
68 printf("%s", help_text);
69 }
70
71
72 /*
73 * get the TSC as 64 bit value with CPU clock frequency resolution
74 */
75 #if defined(__x86_64__)
rdtsc(void)76 static inline uint64_t rdtsc(void)
77 {
78 uint32_t tsc_lo, tsc_hi;
79 __asm__ __volatile__("rdtsc" : "=a" (tsc_lo), "=d" (tsc_hi));
80 return ((uint64_t)tsc_hi << 32) | tsc_lo;
81 }
82 #elif defined(__i386__)
rdtsc(void)83 static inline uint64_t rdtsc(void)
84 {
85 uint64_t tsc;
86 __asm__ __volatile__("rdtsc" : "=A" (tsc));
87 return tsc;
88 }
89 #else
90 #error "rdtsc() not implemented for this architecture"
91 #endif
92
93
rdtsc_mfence(void)94 static inline uint64_t rdtsc_mfence(void)
95 {
96 __asm__ __volatile__("mfence" ::: "memory");
97 return rdtsc();
98 }
99
100
rdtsc_lfence(void)101 static inline uint64_t rdtsc_lfence(void)
102 {
103 __asm__ __volatile__("lfence" ::: "memory");
104 return rdtsc();
105 }
106
107
108 /*
109 * get result from gettimeofday() as a 64 bit value
110 * with microsecond resolution
111 */
rdgtod(void)112 static inline uint64_t rdgtod(void)
113 {
114 struct timeval tv;
115
116 gettimeofday(&tv, NULL);
117 return (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec;
118 }
119
120
121 /*
122 * get result from clock_gettime(CLOCK_MONOTONIC) as a 64 bit value
123 * with nanosecond resolution
124 */
rdclock(void)125 static inline uint64_t rdclock(void)
126 {
127 struct timespec ts;
128
129 clock_gettime(CLOCK_MONOTONIC, &ts);
130 return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
131 }
132
133
134 /*
135 * test data
136 */
137 typedef struct test_info {
138 const char *name; /* test name */
139 void (*func)(struct test_info *); /* the test */
140 spinlock_t lock;
141 uint64_t last; /* last time value */
142 long loops; /* # of test loop iterations */
143 long warps; /* # of backward time jumps */
144 int64_t worst; /* worst backward time jump */
145 uint64_t start; /* test start time */
146 int done; /* flag to stop test */
147 } test_info_t;
148
149
show_warps(struct test_info * test)150 void show_warps(struct test_info *test)
151 {
152 INFO("new %s-warp maximum: %9"PRId64, test->name, test->worst);
153 }
154
155
156 #define DEFINE_TEST(_name) \
157 \
158 void _name##_test(struct test_info *test) \
159 { \
160 uint64_t t0, t1; \
161 int64_t delta; \
162 \
163 spin_lock(&test->lock); \
164 t1 = rd##_name(); \
165 t0 = test->last; \
166 test->last = rd##_name(); \
167 test->loops++; \
168 spin_unlock(&test->lock); \
169 \
170 delta = t1 - t0; \
171 if (delta < 0 && delta < -threshold) { \
172 spin_lock(&test->lock); \
173 ++test->warps; \
174 if (delta < test->worst) { \
175 test->worst = delta; \
176 show_warps(test); \
177 } \
178 spin_unlock(&test->lock); \
179 } \
180 if (!((unsigned long)t0 & 31)) \
181 asm volatile ("rep; nop"); \
182 } \
183 \
184 struct test_info _name##_test_info = { \
185 .name = #_name, \
186 .func = _name##_test, \
187 }
188
189 DEFINE_TEST(tsc);
190 DEFINE_TEST(tsc_lfence);
191 DEFINE_TEST(tsc_mfence);
192 DEFINE_TEST(gtod);
193 DEFINE_TEST(clock);
194
195 struct test_info *tests[] = {
196 &tsc_test_info,
197 &tsc_lfence_test_info,
198 &tsc_mfence_test_info,
199 >od_test_info,
200 &clock_test_info,
201 NULL
202 };
203
204
show_progress(struct test_info * test)205 void show_progress(struct test_info *test)
206 {
207 static int count;
208 const char progress[] = "\\|/-";
209 uint64_t elapsed = rdgtod() - test->start;
210
211 printf(" | %.2f us, %s-warps:%ld %c\r",
212 (double)elapsed/(double)test->loops,
213 test->name,
214 test->warps,
215 progress[++count & 3]);
216 fflush(stdout);
217 }
218
219
test_loop(void * arg)220 void *test_loop(void *arg)
221 {
222 struct test_info *test = arg;
223
224 while (! test->done)
225 (*test->func)(test);
226
227 return NULL;
228 }
229
230
run_test(cpu_set_t * cpus,long duration,struct test_info * test)231 int run_test(cpu_set_t *cpus, long duration, struct test_info *test)
232 {
233 int errs;
234 int ncpus;
235 int nthreads;
236 struct timespec ts = { .tv_sec = 0, .tv_nsec = 200000000 };
237 struct timespec *timeout = (verbose || duration) ? &ts : NULL;
238 sigset_t signals;
239
240 /*
241 * Make sure that SIG_INT is blocked so we can
242 * wait for it in the main test loop below.
243 */
244 sigemptyset(&signals);
245 sigaddset(&signals, SIGINT);
246 sigprocmask(SIG_BLOCK, &signals, NULL);
247
248 /*
249 * test start time
250 */
251 test->start = rdgtod();
252
253 /*
254 * create the threads
255 */
256 ncpus = count_cpus(cpus);
257 nthreads = create_per_cpu_threads(cpus, test_loop, test);
258 if (nthreads != ncpus) {
259 ERROR(0, "failed to create threads: expected %d, got %d",
260 ncpus, nthreads);
261 if (nthreads) {
262 test->done = 1;
263 join_threads();
264 }
265 return 1;
266 }
267
268 if (duration) {
269 INFO("running %s test on %d cpus for %ld seconds",
270 test->name, ncpus, duration);
271 } else {
272 INFO("running %s test on %d cpus", test->name, ncpus);
273 }
274
275 /*
276 * wait for a signal
277 */
278 while (sigtimedwait(&signals, NULL, timeout) < 0) {
279 if (duration && rdgtod() > test->start + duration * 1000000)
280 break;
281
282 if (verbose)
283 show_progress(test);
284 }
285
286 /*
287 * tell the test threads that we are done and wait for them to exit
288 */
289 test->done = 1;
290
291 join_threads();
292
293 errs = (test->warps != 0);
294
295 if (!errs)
296 printf("PASS:\n");
297 else
298 printf("FAIL: %s-worst-warp=%"PRId64"\n",
299 test->name, test->worst);
300
301 return errs;
302 }
303
304
305 int
main(int argc,char * argv[])306 main(int argc, char *argv[])
307 {
308 int c;
309 cpu_set_t cpus;
310 int errs;
311 int i;
312 test_info_t *test;
313 const char *testname;
314 extern int opterr;
315 extern int optind;
316 extern char *optarg;
317
318 if ((program = strrchr(argv[0], '/')) != NULL)
319 ++program;
320 else
321 program = argv[0];
322 set_program_name(program);
323
324 /*
325 * default to checking all cpus
326 */
327 for (c = 0; c < CPU_SETSIZE; c++) {
328 CPU_SET(c, &cpus);
329 }
330
331 opterr = 0;
332 errs = 0;
333 while ((c = getopt_long(argc, argv, optstring, options, NULL)) != EOF) {
334 switch (c) {
335 case 'c':
336 if (parse_cpu_set(optarg, &cpus) != 0)
337 ++errs;
338 break;
339 case 'd':
340 duration = strtol(optarg, NULL, 0);
341 break;
342 case 'h':
343 help();
344 exit(0);
345 case 't':
346 threshold = strtol(optarg, NULL, 0);
347 break;
348 case 'v':
349 ++verbose;
350 break;
351 default:
352 ERROR(0, "unknown option '%c'", c);
353 ++errs;
354 break;
355 }
356 }
357
358 if (errs || optind != argc-1) {
359 usage();
360 exit(1);
361 }
362
363 testname = argv[optind];
364 for (i = 0; (test = tests[i]) != NULL; i++) {
365 if (strcmp(testname, test->name) == 0)
366 break;
367 }
368
369 if (!test) {
370 ERROR(0, "unknown test '%s'\n", testname);
371 usage();
372 exit(1);
373 }
374
375 /*
376 * limit the set of CPUs to the ones that are currently available
377 * (Note that on some kernel versions sched_setaffinity() will fail
378 * if you specify CPUs that are not currently online so we ignore
379 * the return value and hope for the best)
380 */
381 sched_setaffinity(0, sizeof cpus, &cpus);
382 if (sched_getaffinity(0, sizeof cpus, &cpus) < 0) {
383 ERROR(errno, "sched_getaffinity() failed");
384 exit(1);
385 }
386
387 return run_test(&cpus, duration, test);
388 }
389