1 /* 2 * Copyright 2008 Google Inc. All Rights Reserved. 3 * Author: md (at) 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 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 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__) 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__) 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 94 static inline uint64_t rdtsc_mfence(void) 95 { 96 __asm__ __volatile__("mfence" ::: "memory"); 97 return rdtsc(); 98 } 99 100 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 */ 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 */ 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 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 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 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 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 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