C++程序  |  389行  |  7.65 KB

/*
 * Copyright 2008 Google Inc. All Rights Reserved.
 * Author: md@google.com (Michael Davidson)
 *
 * Based on time-warp-test.c, which is:
 * Copyright (C) 2005, Ingo Molnar
 */
#define _GNU_SOURCE

#include <errno.h>
#include <pthread.h>
#include <getopt.h>
#include <sched.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

#include "cpuset.h"
#include "spinlock.h"
#include "threads.h"
#include "logging.h"


char	*program	= "";
long	duration	= 0;
long	threshold	= 0;
int	verbose		= 0;

const char optstring[] = "c:d:ht:v";

struct option options[] = {
	{ "cpus",	required_argument,	0, 	'c'	},
	{ "duration",	required_argument,	0,	'd'	},
	{ "help",	no_argument,		0, 	'h'	},
	{ "threshold",	required_argument,	0, 	't'	},
	{ "verbose",	no_argument,		0, 	'v'	},
	{ 0,	0,	0,	0 }
};


void usage(void)
{
	printf("usage: %s [-hv] [-c <cpu_set>] [-d duration] [-t threshold] "
		"tsc|gtod|clock", program);
}


const char help_text[] =
"check time sources for monotonicity across multiple CPUs\n"
"  -c,--cpus        set of cpus to test (default: all)\n"
"  -d,--duration    test duration in seconds (default: infinite)\n"
"  -t,--threshold   error threshold (default: 0)\n"
"  -v,--verbose     verbose output\n"
"  tsc              test the TSC\n"
"  gtod             test gettimeofday()\n"
"  clock            test CLOCK_MONOTONIC\n";


void help(void)
{
	usage();
	printf("%s", help_text);
}


/*
 * get the TSC as 64 bit value with CPU clock frequency resolution
 */
#if defined(__x86_64__)
static inline uint64_t rdtsc(void)
{
	uint32_t	tsc_lo, tsc_hi;
	__asm__ __volatile__("rdtsc" : "=a" (tsc_lo), "=d" (tsc_hi));
	return ((uint64_t)tsc_hi << 32) | tsc_lo;
}
#elif defined(__i386__)
static inline uint64_t rdtsc(void)
{
	uint64_t	tsc;
	__asm__ __volatile__("rdtsc" : "=A" (tsc));
	return tsc;
}
#else
#error "rdtsc() not implemented for this architecture"
#endif


static inline uint64_t rdtsc_mfence(void)
{
	__asm__ __volatile__("mfence" ::: "memory");
	return rdtsc();
}


static inline uint64_t rdtsc_lfence(void)
{
	__asm__ __volatile__("lfence" ::: "memory");
	return rdtsc();
}


/*
 * get result from gettimeofday() as a 64 bit value
 * with microsecond resolution
 */
static inline uint64_t rdgtod(void)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	return (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec;
}


/*
 * get result from clock_gettime(CLOCK_MONOTONIC) as a 64 bit value
 * with nanosecond resolution
 */
static inline uint64_t rdclock(void)
{
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
}


/*
 * test data
 */
typedef struct test_info {
	const char	*name;		/* test name			*/
	void		(*func)(struct test_info *);	/* the test	*/
	spinlock_t	lock;
	uint64_t	last;		/* last time value		*/
	long		loops;		/* # of test loop iterations	*/
	long		warps;		/* # of backward time jumps	*/
	int64_t		worst;		/* worst backward time jump	*/
	uint64_t	start;		/* test start time		*/
	int		done;		/* flag to stop test		*/
} test_info_t;


void show_warps(struct test_info *test)
{
	INFO("new %s-warp maximum: %9"PRId64, test->name, test->worst);
}


#define	DEFINE_TEST(_name)				\
							\
void _name##_test(struct test_info *test)		\
{							\
	uint64_t t0, t1;				\
	int64_t delta;					\
							\
	spin_lock(&test->lock);				\
	t1 = rd##_name();				\
	t0 = test->last;				\
	test->last = rd##_name();			\
	test->loops++;					\
	spin_unlock(&test->lock);			\
							\
	delta = t1 - t0;				\
	if (delta < 0 && delta < -threshold) {		\
		spin_lock(&test->lock);			\
		++test->warps;				\
		if (delta < test->worst) {		\
			test->worst = delta;		\
			show_warps(test);		\
		}					\
		spin_unlock(&test->lock);		\
	}						\
	if (!((unsigned long)t0 & 31))			\
		asm volatile ("rep; nop");		\
}							\
							\
struct test_info _name##_test_info = {			\
	.name = #_name,					\
	.func = _name##_test,				\
}

DEFINE_TEST(tsc);
DEFINE_TEST(tsc_lfence);
DEFINE_TEST(tsc_mfence);
DEFINE_TEST(gtod);
DEFINE_TEST(clock);

struct test_info *tests[] = {
	&tsc_test_info,
	&tsc_lfence_test_info,
	&tsc_mfence_test_info,
	&gtod_test_info,
	&clock_test_info,
	NULL
};


void show_progress(struct test_info *test)
{
	static int	count;
	const char	progress[] = "\\|/-";
	uint64_t	elapsed = rdgtod() - test->start;

        printf(" | %.2f us, %s-warps:%ld %c\r",
                        (double)elapsed/(double)test->loops,
			test->name,
                        test->warps,
			progress[++count & 3]);
	fflush(stdout);
}


void *test_loop(void *arg)
{
	struct test_info *test = arg;
	
	while (! test->done)
		(*test->func)(test);

	return NULL;
}


int run_test(cpu_set_t *cpus, long duration, struct test_info *test)
{
	int		errs;
	int		ncpus;
	int		nthreads;
	struct timespec ts		= { .tv_sec = 0, .tv_nsec = 200000000 };
	struct timespec	*timeout	= (verbose || duration) ? &ts : NULL;
	sigset_t	signals;

	/*
	 * Make sure that SIG_INT is blocked so we can
	 * wait for it in the main test loop below.
	 */
	sigemptyset(&signals);
	sigaddset(&signals, SIGINT);
	sigprocmask(SIG_BLOCK, &signals, NULL);

	/*
	 * test start time
	 */
	test->start = rdgtod();

	/*
 	 * create the threads
 	 */
	ncpus = count_cpus(cpus);
	nthreads = create_per_cpu_threads(cpus, test_loop, test);
	if (nthreads != ncpus) {
		ERROR(0, "failed to create threads: expected %d, got %d",
			ncpus, nthreads);
		if (nthreads) {
			test->done = 1;
			join_threads();
		}
		return 1;
	}

	if (duration) {
		INFO("running %s test on %d cpus for %ld seconds",
			 test->name, ncpus, duration);
	} else {
		INFO("running %s test on %d cpus", test->name, ncpus);
	}

	/*
 	 * wait for a signal
 	 */
	while (sigtimedwait(&signals, NULL, timeout) < 0) {
		if (duration  && rdgtod() > test->start + duration * 1000000)
			break;

		if (verbose)
			show_progress(test);
	}

	/*
	 * tell the test threads that we are done and wait for them to exit
	 */
	test->done = 1;

	join_threads();

	errs = (test->warps != 0);

	if (!errs)
		printf("PASS:\n");
	else
		printf("FAIL: %s-worst-warp=%"PRId64"\n",
			test->name, test->worst);
	
	return errs;
}


int
main(int argc, char *argv[])
{
	int		c;
	cpu_set_t	cpus;
	int		errs;
	int		i;
	test_info_t	*test;
	const char	*testname;
	extern int	opterr;
	extern int	optind;
	extern char	*optarg;

	if ((program = strrchr(argv[0], '/')) != NULL)
		++program;
	else
		program = argv[0];
	set_program_name(program);

	/*
	 * default to checking all cpus
	 */
	for (c = 0; c < CPU_SETSIZE; c++) {
		CPU_SET(c, &cpus);
	}

	opterr = 0;
	errs = 0;
	while ((c = getopt_long(argc, argv, optstring, options, NULL)) != EOF) {
		switch (c) {
			case 'c':
				if (parse_cpu_set(optarg, &cpus) != 0)
					++errs;
				break;
			case 'd':
				duration = strtol(optarg, NULL, 0);
				break;
			case 'h':
				help();
				exit(0);
			case 't':
				threshold = strtol(optarg, NULL, 0);
				break;
			case 'v':
				++verbose;
				break;
			default:
				ERROR(0, "unknown option '%c'", c);
				++errs;
				break;
		}
	}

	if (errs || optind != argc-1) {
		usage();
		exit(1);
	}

	testname = argv[optind];
	for (i = 0; (test = tests[i]) != NULL; i++) {
		if (strcmp(testname, test->name) == 0)
			break;
	}

	if (!test) {
		ERROR(0, "unknown test '%s'\n", testname);
		usage();
		exit(1);
	}

	/*
	 * limit the set of CPUs to the ones that are currently available
	 * (Note that on some kernel versions sched_setaffinity() will fail
	 * if you specify CPUs that are not currently online so we ignore
	 * the return value and hope for the best)
	 */
	sched_setaffinity(0, sizeof cpus, &cpus);
	if (sched_getaffinity(0, sizeof cpus, &cpus) < 0) {
		ERROR(errno, "sched_getaffinity() failed");
		exit(1);
	}

	return run_test(&cpus, duration, test);
}