/*
 *   Copyright (c) International Business Machines Corp., 2001-2004
 *
 *   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
 */
#include <pthread.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include <assert.h>

#include "config.h"

#include "ffsb.h"
#include "util.h"
#include "parser.h"

/* State information for the polling function below */
struct ffsb_time_poll {
	struct timeval starttime;
	int wait_time;
};

/* This is the polling function used by the threadgroups to check
 * elapsed time, when it returns 1 they know it is time to stop
 */
static int ffsb_poll_fn(void *ptr)
{
	struct ffsb_time_poll *data = (struct ffsb_time_poll *)ptr;
	struct timeval curtime, difftime;
	gettimeofday(&curtime, NULL);

	timersub(&curtime, &data->starttime, &difftime);
	if (difftime.tv_sec >= data->wait_time)
		return 1;
	return 0;
}

int main(int argc, char *argv[])
{
	int i;
	ffsb_config_t fc;
	ffsb_barrier_t thread_barrier, tg_barrier;
	tg_run_params_t *params;
	struct ffsb_time_poll pdata;
	struct timeval starttime, endtime, difftime;
	pthread_attr_t attr;
	ffsb_op_results_t total_results;
	double totaltime = 0.0f, usertime = 0.0f, systime = 0.0f;
	struct rusage before_self, before_children, after_self, after_children;
	pthread_t *fs_pts;	/* threads to do filesystem creates in parallel */
	char *callout = NULL;

	char ctime_start_buf[32];
	char ctime_end_buf[32];

	memset(&before_self, 0, sizeof(before_self));
	memset(&before_children, 0, sizeof(before_children));
	memset(&after_self, 0, sizeof(after_self));
	memset(&after_children, 0, sizeof(after_children));

	ffsb_unbuffer_stdout();

	if (argc < 2) {
		fprintf(stderr, "usage: %s <config file>\n", argv[0]);
		exit(1);
	}

	/* VERSION comes from config.h (which is autogenerated by autoconf) */
	printf("FFSB version %s started\n\n", VERSION);

	ffsb_parse_newconfig(&fc, argv[1]);
	pdata.wait_time = fc.time;

	if (fc.time)
		printf("benchmark time = %u\n", fc.time);
	else
		printf("Only creating the fileset, not running benchmark.\n");

	pthread_attr_init(&attr);
	pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

	for (i = 0; i < fc.num_threadgroups; i++)
		tg_print_config(&fc.groups[i]);

	fs_pts = ffsb_malloc(sizeof(pthread_t) * fc.num_filesys);

	gettimeofday(&starttime, NULL);
	for (i = 0; i < fc.num_filesys; i++) {
		fs_print_config(&fc.filesystems[i]);
		pthread_create(fs_pts + i, &attr, construct_ffsb_fs,
			       &fc.filesystems[i]);
	}

	fflush(stdout);
	for (i = 0; i < fc.num_filesys; i++)
		pthread_join(fs_pts[i], NULL);

	gettimeofday(&endtime, NULL);
	timersub(&endtime, &starttime, &difftime);
	printf("fs setup took %ld secs\n", difftime.tv_sec);
	free(fs_pts);

	if (fc.time == 0) {
		printf("Setup complete, exiting\n");
		return 0;
	}

	params = ffsb_malloc(sizeof(tg_run_params_t) * fc.num_threadgroups);

	init_ffsb_op_results(&total_results);
	ffsb_barrier_init(&thread_barrier, fc.num_totalthreads);
	ffsb_barrier_init(&tg_barrier, fc.num_threadgroups + 1);

	ffsb_sync();

	/* Execute the callout if any and wait for it to return */
	callout = fc_get_callout(&fc);
	if (callout) {
		printf("executing callout: \n %s\n", callout);
		if (ffsb_system(callout) < 0) {
			perror("system");
			exit(1);
		}
	}

	/* Spawn all of the threadgroup master threads */
	for (i = 0; i < fc.num_threadgroups; i++) {
		params[i].tg = &fc.groups[i];
		params[i].fc = &fc;
		params[i].poll_fn = ffsb_poll_fn;
		params[i].poll_data = &pdata;
		params[i].wait_time = FFSB_TG_WAIT_TIME;
		params[i].tg_barrier = &tg_barrier;
		params[i].thread_barrier = &thread_barrier;

		pthread_create(&params[i].pt, &attr, tg_run, &params[i]);
	}

	ffsb_getrusage(&before_self, &before_children);
	gettimeofday(&pdata.starttime, NULL);

	ffsb_barrier_wait(&tg_barrier);	/* sync with tg's to start */
	printf("Starting Actual Benchmark At: %s\n",
	       ctime_r(&pdata.starttime.tv_sec, ctime_start_buf));
	fflush(stdout);

	/* Wait for all of the threadgroup master threads to finish */
	for (i = 0; i < fc.num_threadgroups; i++)
		pthread_join(params[i].pt, NULL);

	ffsb_sync();
	gettimeofday(&endtime, NULL);
	ffsb_getrusage(&after_self, &after_children);

	printf("FFSB benchmark finished   at: %s\n",
	       ctime_r(&endtime.tv_sec, ctime_end_buf));
	printf("Results:\n");
	fflush(stdout);

	timersub(&endtime, &pdata.starttime, &difftime);

	totaltime = tvtodouble(&difftime);

	printf("Benchmark took %.2lf sec\n", totaltime);
	printf("\n");

	for (i = 0; i < fc.num_threadgroups; i++) {
		struct ffsb_op_results tg_results;
		ffsb_tg_t *tg = fc.groups + i;

		init_ffsb_op_results(&tg_results);

		/* Grab the individual tg results */
		tg_collect_results(tg, &tg_results);

		if (fc.num_threadgroups == 1)
			printf("Total Results\n");
		else
			printf("ThreadGroup %d\n", i);

		printf("===============\n");
		print_results(&tg_results, totaltime);

		if (tg_needs_stats(tg)) {
			ffsb_statsd_t fsd;
			tg_collect_stats(tg, &fsd);
			ffsb_statsd_print(&fsd);
		}
		printf("\n");

		/* Add the tg results to the total */
		tg_collect_results(&fc.groups[i], &total_results);
	}

	if (fc.num_threadgroups > 1) {
		printf("Total Results\n");
		printf("===============\n");
		print_results(&total_results, totaltime);
	}
#define USEC_PER_SEC ((double)(1000000.0f))

	/* sum up self and children after */
	usertime = (after_self.ru_utime.tv_sec +
		    ((after_self.ru_utime.tv_usec) / USEC_PER_SEC)) +
	    ((after_children.ru_utime.tv_sec +
	      ((after_children.ru_utime.tv_usec) / USEC_PER_SEC)));

	/* subtract away the before */
	usertime -= (before_self.ru_utime.tv_sec +
		     ((before_self.ru_utime.tv_usec) / USEC_PER_SEC)) +
	    ((before_children.ru_utime.tv_sec +
	      ((before_children.ru_utime.tv_usec) / USEC_PER_SEC)));

	/* sum up self and children after */
	systime = (after_self.ru_stime.tv_sec +
		   ((after_self.ru_stime.tv_usec) / USEC_PER_SEC)) +
	    ((after_children.ru_stime.tv_sec +
	      ((after_children.ru_stime.tv_usec) / USEC_PER_SEC)));

	/* subtract away the before */
	systime -= (before_self.ru_stime.tv_sec +
		    ((before_self.ru_stime.tv_usec) / USEC_PER_SEC)) +
	    ((before_children.ru_stime.tv_sec +
	      ((before_children.ru_stime.tv_usec) / USEC_PER_SEC)));

	printf("\n\n");
	printf("%.1lf%% User   Time\n", 100 * usertime / totaltime);
	printf("%.1lf%% System Time\n", 100 * systime / totaltime);
	printf("%.1f%% CPU Utilization\n", 100 * (usertime + systime) /
	       totaltime);
	free(params);
	destroy_ffsb_config(&fc);

	return 0;
}
