/******************************************************************************
 *
 *   Copyright © International Business Machines  Corp., 2010
 *
 *   This program is free software;  you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 *   the GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program;  if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * NAME
 *      condvar_perf.c
 *
 * DESCRIPTION
 *      This test is designed to test the efficiency of the pthread_cond_wait
 *      and pthread_cond_signal code paths within glibc.
 *
 * AUTHOR
 *      Darren Hart <dvhltc@us.ibm.com>
 *
 * HISTORY
 *      2010-Feb-23: Initial version by Darren Hart <dvhltc@us.ibm.com>
 *
 *****************************************************************************/

#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <stdio.h>
#include <pthread.h>
#include <getopt.h>

#define USEC_PER_SEC 1000000
#define NSEC_PER_USEC 1000
#define DEFAULT_ITERS 1000000

static int sched_fifo_prio = 0;
static int iterations;
static int synch = 0;

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void usage(char *prog)
{
	printf("Usage: %s\n", prog);
	printf("  -f #  Run as SCHED_FIFO prio # (default SCHED_OTHER)\n");
	printf("  -h	Display this help message\n");
	printf("  -i #	Number of iterations (default %d)\n", DEFAULT_ITERS);
	printf("  -s 	Synchronous signaling (lock mutex)\n");
}

void * waiter(void *arg)
{
	while (iterations) {
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond, &mutex);
		iterations--;
		pthread_mutex_unlock(&mutex);
	}
	return NULL;
}

int main(int argc, char *argv[])
{
	struct timespec start_ts, end_ts;
	int conf_iters = DEFAULT_ITERS;
	long long delta_us;
	pthread_t child_id;
	int ret = 0;
	int c;

	while ((c = getopt(argc, argv, "f:hi:s")) != -1) {
		switch(c) {
		case 'f':
			sched_fifo_prio = atoi(optarg);
			break;
		case 'h':
			usage(argv[0]);
			exit(0);
		case 'i':
			conf_iters = atoi(optarg);
			break;
		case 's':
			synch = 1;
			break;
		default:
			usage(argv[0]);
			exit(1);
		}
	}
	iterations = conf_iters;

	if (sched_fifo_prio) {
		struct sched_param sp;
		sp.sched_priority = sched_fifo_prio;
		if ((ret = sched_setscheduler(0, SCHED_FIFO, &sp))) {
			perror("sched_setscheduler");
			goto out;
		}
	}

	if ((ret = pthread_create(&child_id, NULL, waiter, NULL))) {
		perror("pthread_create failed");
		goto out;
	}

	clock_gettime(CLOCK_REALTIME, &start_ts);

	while (iterations) {
		if (synch) pthread_mutex_lock(&mutex);
		pthread_cond_signal(&cond);
		if (synch) pthread_mutex_unlock(&mutex);
	}

	clock_gettime(CLOCK_REALTIME, &end_ts);
	delta_us = (end_ts.tv_sec - start_ts.tv_sec) * USEC_PER_SEC;
	delta_us += (end_ts.tv_nsec - start_ts.tv_nsec) / NSEC_PER_USEC;

	printf("Scheduling: %s %d\n", sched_fifo_prio ? "SCHED_FIFO" : "SCHED_OTHER",
				      sched_fifo_prio);
	printf("Iterations: %d us\n", conf_iters);
	printf("Runtime: %lld us\n", delta_us);
	printf("Cycles/Second: %f\n", (float)conf_iters/((float)delta_us/USEC_PER_SEC));

 out:
	return ret;
}
