/******************************************************************************
 *
 *   Copyright © International Business Machines  Corp., 2007, 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
 *     hrtimer-prio.c
 *
 * DESCRIPTION
 *     Test the latency of hrtimers under rt load.
 *     The busy_threads should run at a priority higher than the system
 *     softirq_hrtimer, but lower than the timer_thread.  The timer_thread
 *     measure the time it takes to return from a nanosleep call.  If the
 *     lower priority threads can increase the latency of the higher
 *     priority thread, it is considered a failure.
 *
 * USAGE:
 *     Use run_auto.sh script in current directory to build and run test.
 *
 * AUTHOR
 *      Darren Hart <dvhltc@us.ibm.com>
 *
 * HISTORY
 *      2007-Aug-08:      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>

#define DEF_MED_PRIO 60		// (softirqd-hrtimer,98)
#define DEF_ITERATIONS 10000
#define HIST_BUCKETS 100
#define DEF_BUSY_TIME 10	// Duration of busy work in milliseconds
#define DEF_SLEEP_TIME 10000	// Duration of nanosleep in nanoseconds
#define DEF_CRITERIA 10		// maximum timer latency in microseconds

static int med_prio = DEF_MED_PRIO;
static int high_prio;
static int busy_time = DEF_BUSY_TIME;
static int iterations = DEF_ITERATIONS;
static int busy_threads;

static stats_container_t dat;
static stats_record_t rec;
static atomic_t busy_threads_started;
static unsigned long min_delta;
static unsigned long max_delta;

void usage(void)
{
	rt_help();
	printf("hrtimer-prio specific options:\n");
	printf("  -t#	   #:busy work time in ms, defaults to %d ms\n",
	       DEF_BUSY_TIME);
	printf("  -i#	   #:number of iterations, defaults to %d\n",
	       DEF_ITERATIONS);
	printf("  -n#	   #:number of busy threads, defaults to NR_CPUS*2\n");
	printf
	    ("  -f#	   #:rt fifo priority of busy threads (1,98), defaults to %d\n",
	     DEF_MED_PRIO);
}

int parse_args(int c, char *v)
{

	int handled = 1;
	switch (c) {
	case 'h':
		usage();
		exit(0);
	case 't':
		busy_time = atoi(v);
		break;
	case 'n':
		busy_threads = atoi(v);
		break;
	case 'f':
		med_prio = MIN(atoi(v), 98);
		break;
	case 'i':
		iterations = atoi(v);
		if (iterations < 100) {
			fprintf(stderr,
				"Number of iterations cannot be less than 100.\n");
			exit(1);
		}
		break;
	default:
		handled = 0;
		break;
	}
	return handled;
}

void *busy_thread(void *thread)
{
	atomic_inc(&busy_threads_started);
	while (1) {
		busy_work_ms(busy_time);
		sched_yield();
	}
	return NULL;
}

void *timer_thread(void *thread)
{
	int i;
	nsec_t start, end;
	unsigned long delta_us;
	while (atomic_get(&busy_threads_started) < busy_threads) {
		rt_nanosleep(10000);
	}
	printf("All Busy Threads started, commencing test\n");	// FIXME: use debug infrastructure
	max_delta = 0;
	for (i = 0; i < iterations; i++) {
		start = rt_gettime();
		rt_nanosleep(DEF_SLEEP_TIME);
		end = rt_gettime();
		delta_us =
		    ((unsigned long)(end - start) - DEF_SLEEP_TIME) / NS_PER_US;
		rec.x = i;
		rec.y = delta_us;
		stats_container_append(&dat, rec);
		max_delta = MAX(max_delta, delta_us);
		min_delta = (i == 0) ? delta_us : MIN(min_delta, delta_us);
	}
	return NULL;
}

int main(int argc, char *argv[])
{
	int ret = 1;
	int b;
	float avg_delta;
	int t_id;
	setup();
	busy_threads = 2 * sysconf(_SC_NPROCESSORS_ONLN);	// default busy_threads
	pass_criteria = DEF_CRITERIA;
	rt_init("f:i:jhn:t:", parse_args, argc, argv);
	high_prio = med_prio + 1;

	// Set main()'s prio to one above the timer_thread so it is sure to not
	// be starved
	if (set_priority(high_prio + 1) < 0) {
		printf("Failed to set main()'s priority to %d\n",
		       high_prio + 1);
		exit(1);
	}

	printf("\n-------------------------------------------\n");
	printf("High Resolution Timer Priority (Starvation)\n");
	printf("-------------------------------------------\n\n");
	printf("Running %d iterations\n", iterations);
	printf("Running with %d busy threads\n", busy_threads);
	printf("Busy thread work time: %d\n", busy_time);
	printf("Busy thread priority: %d\n", med_prio);
	printf("Timer thread priority: %d\n", high_prio);

	stats_container_t hist;
	stats_quantiles_t quantiles;
	if (stats_container_init(&dat, iterations)) {
		printf("Cannot init stat containers for dat\n");
		exit(1);
	}
	if (stats_container_init(&hist, HIST_BUCKETS)) {
		printf("Cannot init stat containers for hist\n");
		exit(1);
	}
	if (stats_quantiles_init(&quantiles, (int)log10(iterations))) {
		printf("Cannot init stat quantiles\n");
		exit(1);
	}

	t_id = create_fifo_thread(timer_thread, NULL, high_prio);
	if (t_id == -1) {
		printf("Failed to create timer thread\n");
		exit(1);
	}
	for (b = 0; b < busy_threads; b++) {
		if (create_fifo_thread(busy_thread, NULL, med_prio) < 0) {
			printf("Failed to create a busy thread\n");
			exit(1);
		}
	}
	join_thread(t_id);

	avg_delta = stats_avg(&dat);
	stats_hist(&hist, &dat);
	stats_container_save("samples",
			     "High Resolution Timer Latency Scatter Plot",
			     "Iteration", "Latency (us)", &dat, "points");
	stats_container_save("hist", "High Resolution Timer Latency Histogram",
			     "Latency (us)", "Samples", &hist, "steps");

	if (max_delta <= pass_criteria)
		ret = 0;

	printf("Minimum: %ld us\n", min_delta);
	printf("Maximum: %ld us\n", max_delta);
	printf("Average: %f us\n", avg_delta);
	printf("Standard Deviation: %f\n", stats_stddev(&dat));
	printf("Quantiles:\n");
	stats_quantiles_calc(&dat, &quantiles);
	stats_quantiles_print(&quantiles);
	printf("\nCriteria: Maximum wakeup latency < %lu us\n",
	       (unsigned long)pass_criteria);
	printf("Result: %s\n", ret ? "FAIL" : "PASS");

	return ret;
}
