#include <sys/time.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>

static int lt_iteration_num;
static pthread_mutex_t lt_stat_lock;

static pthread_mutex_t lt_global_lock;
static sem_t lt_global_sem;

struct lt_test
{
	char		name[16];
	void		(* lock)(struct lt_test *t);
	int		counter;
	double		speed;
};

static void lt_usage(const char *p)
{
	fprintf(stderr, "Usage: %s <options>\n"
			" -N num       - number of locked iterations\n"
			" -n num       - number of threads racing for the lock\n"
			, p);
	exit(-1);
}

static void lt_process_posix(struct lt_test *t)
{
	int i;

	for (i=0; i<lt_iteration_num; ++i) {
		pthread_mutex_lock(&lt_global_lock);
		t->counter++;
		pthread_mutex_unlock(&lt_global_lock);
	}
}

static void lt_process_sem(struct lt_test *t)
{
	int i;

	for (i=0; i<lt_iteration_num; ++i) {
		sem_wait(&lt_global_sem);
		t->counter++;
		sem_post(&lt_global_sem);
	}
}

static struct lt_test lt_tests[] = {
	{ .lock = lt_process_posix, .name = "POSIX", },
	{ .lock = lt_process_sem, .name = "Semaphore", },
};

static double lt_process_lock(struct lt_test *t)
{
	struct timeval tv1, tv2;
	long usec;
	double speed;

	gettimeofday(&tv1, NULL);
	t->lock(t);
	gettimeofday(&tv2, NULL);

	usec = tv2.tv_usec - tv1.tv_usec;
	usec += (tv2.tv_sec - tv1.tv_sec) * 1000000;

	/*
	 * Rather subtle overflow detection though.
	 */
	if (!usec) {
		fprintf(stderr, "Usec overflow, results are incorrect: %ld vs %ld\n",
				usec, tv1.tv_usec + tv1.tv_sec * 1000000);
		return 0;
	}

	speed = (double)lt_iteration_num * 1000000 / (double)usec;

	pthread_mutex_lock(&lt_stat_lock);
	t->speed += speed;
	pthread_mutex_unlock(&lt_stat_lock);

	return speed;
}

static void *lt_process(void *data __attribute__ ((unused)))
{
	unsigned int i;

	for (i=0; i<sizeof(lt_tests)/sizeof(lt_tests[0]); ++i)
		lt_process_lock(&lt_tests[i]);

	return NULL;
}

static int lt_init_locks(void)
{
	int err;

	err = pthread_mutex_init(&lt_global_lock, NULL);
	if (err) {
		fprintf(stderr, "Failed to initialize POSIX test mutex: err: %d.\n", err);
		return err;
	}
	
	err = pthread_mutex_init(&lt_stat_lock, NULL);
	if (err) {
		fprintf(stderr, "Failed to initialize POSIX stat mutex: err: %d.\n", err);
		return err;
	}

	err = sem_init(&lt_global_sem, 0, 1);
	if (err) {
		err = -errno;
		fprintf(stderr, "Failed to initialize semaphore: err: %d.\n", err);
		return err;
	}

	return 0;
}

int main(int argc, char *argv[])
{
	int ch, num, i, err;
	pthread_t *tids;

	num = 1;
	lt_iteration_num = 10000;

	while ((ch = getopt(argc, argv, "N:n:h")) != -1) {
		switch (ch) {
			case 'N':
				lt_iteration_num = atoi(optarg);
				break;
			case 'n':
				num = atoi(optarg);
				break;
			default:
				lt_usage(argv[0]);
				/* not reached */
		}
	}

	tids = malloc(sizeof(pthread_t) * num);
	if (!tids)
		return -1;
	memset(tids, 0, sizeof(pthread_t) * num);

	err = lt_init_locks();
	if (err)
		return err;

	for (i=0; i<num; ++i) {
		err = pthread_create(&tids[i], NULL, lt_process, NULL);
		if (err) {
			fprintf(stderr, "Failed to create %d/%d POSIX thread: err: %d.\n",
					i, num, err);
			return err;
		}
	}

	for (i=0; i<num; ++i)
		pthread_join(tids[i], NULL);

	for (i=0; (unsigned)i<sizeof(lt_tests)/sizeof(lt_tests[0]); ++i) {
		struct lt_test *t = &lt_tests[i];

		t->speed /= (double)(2 * num);

		if (t->counter != num * lt_iteration_num)
			printf("%s lock test failed: counter: %d, must be %d.\n",
					t->name, t->counter, num * lt_iteration_num);
		else
			printf("%10s lock performance: %.3f locks/sec\n", t->name, t->speed);
	}
	return 0;
}

