/******************************************************************************
 *
 *   Copyright © International Business Machines  Corp., 2006, 2008
 *
 *   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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * NAME
 *     async_handler.c
 *
 * DESCRIPTION
 *     Measure the latency involved in asynchronous event handlers.
 *     Specifically it measures the latency of the pthread_cond_signal
 *     call until the signalled thread is scheduled.
 *
 * USAGE:
 *     Use run_auto.sh script in current directory to build and run test.
 *
 * AUTHOR
 *     Darren Hart <dvhltc@us.ibm.com>
 *
 * HISTORY
 *     2006-Oct-20:   Initial version by Darren Hart <dvhltc@us.ibm.com>
 *
 *	This line has to be added to avoid a stupid CVS problem
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <librttest.h>
#include <libstats.h>
#include <getopt.h>

#define SIGNAL_PRIO 89
#define HANDLER_PRIO 89
#define DEFAULT_ITERATIONS 1000000	/* about 1 minute @ 2GHz */
#define HIST_BUCKETS 100
#define PASS_US 100

static nsec_t start;
static nsec_t end;
static int iterations = 0;

#define CHILD_START   0
#define CHILD_WAIT    1
#define CHILD_HANDLED 2
#define CHILD_QUIT    3
atomic_t step;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex;

static int ret = 0;

void usage(void)
{
	rt_help();
	printf("async_handler specific options:\n");
	printf
	    ("  -iITERATIONS  number of iterations to calculate the average over\n");
}

int parse_args(int c, char *v)
{

	int handled = 1;
	switch (c) {
	case 'h':
		usage();
		exit(0);
	case 'i':
		iterations = atoi(v);
		break;
	default:
		handled = 0;
		break;
	}
	return handled;
}

void *handler_thread(void *arg)
{

	while (atomic_get(&step) != CHILD_QUIT) {
		pthread_mutex_lock(&mutex);
		atomic_set(CHILD_WAIT, &step);
		if (pthread_cond_wait(&cond, &mutex) != 0) {
			perror("pthead_cond_wait");
			break;
		}
		end = rt_gettime();
		atomic_set(CHILD_HANDLED, &step);
		pthread_mutex_unlock(&mutex);
		while (atomic_get(&step) == CHILD_HANDLED)
			usleep(10);
	}
	printf("handler thread exiting\n");
	return 0;
}

void *signal_thread(void *arg)
{

	int i;
	long delta, max, min;
	stats_container_t dat;
	stats_container_t hist;
	stats_record_t rec;

	stats_container_init(&dat, iterations);
	stats_container_init(&hist, HIST_BUCKETS);

	min = max = 0;
	for (i = 0; i < iterations; i++) {
		/* wait for child to wait on cond, then signal the event */
		while (atomic_get(&step) != CHILD_WAIT)
			usleep(10);
		pthread_mutex_lock(&mutex);
		start = rt_gettime();
		if (pthread_cond_signal(&cond) != 0) {
			perror("pthread_cond_signal");
			atomic_set(CHILD_QUIT, &step);
			break;
		}
		pthread_mutex_unlock(&mutex);

		/* wait for the event handler to schedule */
		while (atomic_get(&step) != CHILD_HANDLED)
			usleep(10);
		delta = (long)((end - start) / NS_PER_US);
		if (delta > pass_criteria)
			ret = 1;
		rec.x = i;
		rec.y = delta;
		stats_container_append(&dat, rec);
		if (i == 0)
			min = max = delta;
		else {
			min = MIN(min, delta);
			max = MAX(max, delta);
		}
		atomic_set((i == iterations - 1) ? CHILD_QUIT : CHILD_START,
			   &step);
	}
	printf("recording statistics...\n");
	printf("Min: %ld us\n", min);
	printf("Max: %ld us\n", max);
	printf("Avg: %.4f us\n", stats_avg(&dat));
	printf("StdDev: %.4f us\n", stats_stddev(&dat));
	stats_hist(&hist, &dat);
	stats_container_save("samples",
			     "Asynchronous Event Handling Latency Scatter Plot",
			     "Iteration", "Latency (us)", &dat, "points");
	stats_container_save("hist",
			     "Asynchronous Event Handling Latency Histogram",
			     "Latency (us)", "Samples", &hist, "steps");
	printf("signal thread exiting\n");

	return NULL;
}

int main(int argc, char *argv[])
{
	int signal_id, handler_id;
	setup();

	printf("\n-----------------------------------\n");
	printf("Asynchronous Event Handling Latency\n");
	printf("-----------------------------------\n\n");

	pass_criteria = PASS_US;
	rt_init("i:h", parse_args, argc, argv);

	init_pi_mutex(&mutex);

	atomic_set(CHILD_START, &step);

	if (iterations == 0)
		iterations = DEFAULT_ITERATIONS;
	printf("Running %d iterations\n", iterations);

	handler_id =
	    create_fifo_thread(handler_thread, (void *)0, HANDLER_PRIO);
	signal_id = create_fifo_thread(signal_thread, (void *)0, SIGNAL_PRIO);

	join_threads();

	printf("\nCriteria: latencies < %d\n", (int)pass_criteria);
	printf("Result: %s\n", ret ? "FAIL" : "PASS");

	return ret;
}
