/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2009 Oracle.  All rights reserved.
 *
 * $Id$
 */

#include "bench.h"

#ifdef _POSIX_THREADS
typedef struct {
	pthread_t	id;
	DB_ENV		*dbenv;
	int		iterations;
	db_mutex_t	mutex;
	int		contentions;
} threadinfo_t;

static void *latch_threadmain __P((void *));
#endif

static int   time_latches __P((DB_ENV *, db_mutex_t, int));

#define	LATCH_THREADS_MAX	100

/* Return the environment needed for __mutex_lock(), depending on release.
 */
#if DB_VERSION_MAJOR <4 || DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 7
#define	ENV_ARG(dbenv)	(dbenv)
#else
#define	ENV_ARG(dbenv)	((dbenv)->env)
#endif

/*
 * In the mulithreaded latch test each thread locks and updates this variable.
 * It detects contention when the value of this counter changes during the
 * mutex lock call.
 */
static int CurrentCounter = 0;
static int   latch_usage __P((void));

static int
latch_usage()
{
	(void)fprintf(stderr, "usage: b_latch [-c number of %s",
	    "lock+unlock pairs] [-n number of threads]\n");
	return (EXIT_FAILURE);
}

/*
 * time_latches --
 *	Repeat acquire and release of an exclusive latch, counting the
 *	number of times that 'someone else' got it just as we tried to.
 */
static int time_latches(dbenv, mutex, iterations)
	DB_ENV *dbenv;
	db_mutex_t mutex;
	int iterations;
{
	int contended, i, previous;

	contended = 0;
	for (i = 0; i < iterations; ++i) {
		previous = CurrentCounter;
		DB_BENCH_ASSERT(__mutex_lock(ENV_ARG(dbenv), mutex) == 0);
		if (previous != CurrentCounter)
			contended++;
		CurrentCounter++;
		DB_BENCH_ASSERT(__mutex_unlock(ENV_ARG(dbenv), mutex) == 0);
	}
	return (contended);
}

#ifdef _POSIX_THREADS
/*
 * latch_threadmain --
 *	Entry point for multithreaded latching test.
 *
 *	Currently only supported for POSIX threads.
 */
static void *
latch_threadmain(arg)
	void *arg;
{
	threadinfo_t	*info = arg;

	info->contentions = time_latches(info->dbenv,
	    info->mutex, info->iterations);

	return ((void *) 0);
}
#endif

/*
 * b_latch --
 *	Measure the speed of latching and mutex operations.
 *
 *
 */
int
b_latch(argc, argv)
	int argc;
	char *argv[];
{
	extern char *optarg;
	extern int optind;
	DB_ENV *dbenv;
	int ch, count, nthreads;
#ifdef _POSIX_THREADS
	threadinfo_t threads[LATCH_THREADS_MAX];
	int i, ret;
	void *status;
#endif
	db_mutex_t mutex;
	int contended;

	contended = 0;
	count = 1000000;
	nthreads = 0;	/* Default to running the test without extra threads */
	while ((ch = getopt(argc, argv, "c:n:")) != EOF)
		switch (ch) {
		case 'c':
			count = atoi(optarg);
			break;
		case 'n':
			nthreads = atoi(optarg);
			break;
		case '?':
		default:
			return (latch_usage());
		}
	argc -= optind;
	argv += optind;
	if (argc != 0 || count < 1 || nthreads < 0 ||
	    nthreads > LATCH_THREADS_MAX)
		return (latch_usage());
#ifndef _POSIX_THREADS
	if (nthreads > 1) {
		(void)fprintf(stderr,
		    "Sorry, support for -n %d: threads not yet available\n",
		    nthreads);
		exit(EXIT_FAILURE);
	}
#endif

	/* Create the environment. */
	DB_BENCH_ASSERT(db_env_create(&dbenv, 0) == 0);
	dbenv->set_errfile(dbenv, stderr);
#if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR < 1
	DB_BENCH_ASSERT(dbenv->open(dbenv, TESTDIR,
	    NULL, DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG |
	    DB_INIT_MPOOL | DB_INIT_TXN | DB_PRIVATE, 0666) == 0);
#else
	DB_BENCH_ASSERT(dbenv->open(dbenv, TESTDIR,
	    DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG |
	    DB_INIT_MPOOL | DB_INIT_TXN | DB_PRIVATE, 0666) == 0);
#endif
	DB_BENCH_ASSERT(dbenv->mutex_alloc(dbenv, DB_MUTEX_SELF_BLOCK,
	    &mutex) == 0);
#ifdef _POSIX_THREADS
	for (i = 0; i < nthreads; i++)  {
		threads[i].dbenv = dbenv;
		threads[i].mutex = mutex;
		threads[i].iterations =
		    nthreads <= 1 ? count : count / nthreads;
	}
#endif

	/* Start and acquire and release a mutex count times. If there's
	 * posix support and a non-zero number of threads start them.
	 */
	TIMER_START;
#ifdef _POSIX_THREADS
	if (nthreads > 0) {
		for (i = 0; i < nthreads; i++)
			DB_BENCH_ASSERT(pthread_create(&threads[i].id,
			    NULL, latch_threadmain, &threads[i]) == 0);
		for (i = 0; i < nthreads; i++) {
			ret = pthread_join(threads[i].id, &status);
			DB_BENCH_ASSERT(ret == 0);
			contended += threads[i].contentions;
		}

	} else
#endif
		contended = time_latches(dbenv, mutex, count);
	TIMER_STOP;

	printf("# %d mutex lock-unlock pairs of %d thread%s\n", count,
	    nthreads, nthreads == 1 ? "" : "s");
	TIMER_DISPLAY(count);

	DB_BENCH_ASSERT(dbenv->mutex_free(dbenv, mutex) == 0);
	DB_BENCH_ASSERT(dbenv->close(dbenv, 0) == 0);
	COMPQUIET(contended, 0);

	return (0);
}