Home | History | Annotate | Download | only in src
      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 	&gtod_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