/*
 *  schedcnt  by Davide Libenzi ( linux kernel scheduler latency tester )
 *  Version 0.19 - Copyright (C) 2001  Davide Libenzi
 *
 *  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
 *
 *  Davide Libenzi <davidel@xmailserver.org>
 *
 *
 *  The purpose of this tool is to measure the scheduler latency using
 *  the "latsched" kernel patch.
 *  Build:
 *
 *  gcc -o schedcnt schedcnt.c
 *
 *  Use:
 *
 *  schedcnt [--samples n] [--sttime u] [--klimit k] [-- cmdpath [arg] ...]
 *
 *  --samples   = Set the size of the sample buffer
 *  --ttime     = Set the sample time in seconds
 *  --sttime    = Set the sample time in microseconds
 *  --klimit    = Set the cut factor for samples. Samples > AVSC*klimit are not evalued in KAVS
 *  --          = Separate the optional command to be executed during the test time
 *  cmdpath     = Command to be executed
 *  arg         = Command argouments
 *
 *  The output is, for each cpu :
 *
 *  CPU NSAMP
 *  SAMP[0]
 *  ...
 *  SAMP[NSAMP-1]
 *  AVSC CHSQ KAVS
 *
 *
 *  CPU   = CPU number
 *  NSAMP = Number of readed samples
 *  AVSC  = Average schedule() cycles
 *  CHSQ  = ChiSquare of the CSCH distribution
 *  KAVS  = High cut average schedule() cycles
 *  SAMP[i] is in the form :
 *
 *  CENT CEXT CSCH PPID RTIM
 *
 *  CENT  = Cycle counter at schedule() entry
 *  CEXT  = Cycle counter at schedule() exit
 *  CSCH  = Cycle duration of schedule()
 *  PPID  = New scheduled PID
 *  RTIM  = Cycles run time ( this maybe incorrect some time due schedule() nesting )
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <asm/page.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/latsched.h>



#define TEST_NUM_SAMPLES	4000
#define SLEEP_SYNC_TIME		1500000
#define STD_SAMPLE_TIME		(5 * 1000000)
#define STD_KLIMIT			2.0



int main(int argc, char *argv[]) {
	int ii, res, lsfd, cpu, ncpus, icmd, valid;
	int samples = TEST_NUM_SAMPLES, sleeptime = STD_SAMPLE_TIME;
	pid_t expid = -1;
	unsigned long long schedtime, avgstime;
	double chisqr, davgstime, klimit = STD_KLIMIT, lmavg;
	char const * lsfile = "/dev/latsched";
	struct lsctl_getdata lsgd;

	for (ii = 1; ii < argc; ii++) {
		if (strcmp(argv[ii], "--samples") == 0) {
			if (++ii < argc)
				samples = atoi(argv[ii]);
			continue;
		}
		if (strcmp(argv[ii], "--sttime") == 0) {
			if (++ii < argc)
				sleeptime = atoi(argv[ii]);
			continue;
		}
		if (strcmp(argv[ii], "--ttime") == 0) {
			if (++ii < argc)
				sleeptime = atoi(argv[ii]) * 1000000;
			continue;
		}
		if (strcmp(argv[ii], "--klimit") == 0) {
			if (++ii < argc)
				klimit = atof(argv[ii]);
			continue;
		}
		if (strcmp(argv[ii], "--") == 0) {
			icmd = ++ii;
			break;
		}
	}

	ncpus = sysconf(_SC_NPROCESSORS_CONF);

	if ((lsfd = open(lsfile, O_RDWR)) == -1) {
		perror(lsfile);
		return 1;
	}

	if ((res = ioctl(lsfd, LS_SAMPLES, samples))) {
		if ((res = ioctl(lsfd, LS_STOP, 0)) || (res = ioctl(lsfd, LS_SAMPLES, samples))) {
			perror("LS_SAMPLES");
			close(lsfd);
			return 2;
		}
	}
	if ((res = ioctl(lsfd, LS_START, 0))) {
		perror("LS_START");
		close(lsfd);
		return 3;
	}

	if (icmd > 0 && icmd < argc) {
		expid = fork();
		if (expid == -1) {
			perror("fork");
			close(lsfd);
			return 5;
		} else if (expid == 0) {
			setpgid(0, getpid());
			execv(argv[icmd], &argv[icmd]);
			exit(0);
		}
	}

	usleep(sleeptime);

	if (expid > 0 && kill(-expid, SIGKILL))
		perror("SIGKILL");

	if ((res = ioctl(lsfd, LS_STOP, 0))) {
		perror("LS_STOP");
		close(lsfd);
		return 5;
	}

	usleep(SLEEP_SYNC_TIME);

	memset(&lsgd, 0, sizeof(lsgd));
	lsgd.size = samples;
	lsgd.data = (struct latsched_sample *) malloc(samples * sizeof(struct latsched_sample));

	for (cpu = 0; cpu < ncpus; cpu++) {
		lsgd.cpu = cpu;
		lsgd.size = samples;

		if ((res = ioctl(lsfd, LS_FETCH, &lsgd))) {
			perror("LS_FETCH");
			close(lsfd);
			return -1;
		}

		fprintf(stdout, "%d\t%d\n", cpu, lsgd.rsize);

		for (ii = 0, avgstime = 0; ii < lsgd.rsize; ii++) {
			schedtime = lsgd.data[ii].lss_out - lsgd.data[ii].lss_in;
			avgstime += schedtime;

			fprintf(stdout, "%llu\t%llu\t%llu\t%u",
					lsgd.data[ii].lss_in, lsgd.data[ii].lss_out, schedtime, lsgd.data[ii].lss_pid);

			if (ii < (lsgd.rsize - 1))
				fprintf(stdout, "\t%llu", lsgd.data[ii + 1].lss_in - lsgd.data[ii].lss_in);
			fprintf(stdout, "\n");
		}

		davgstime = (double) avgstime / (double) lsgd.rsize;

		for (ii = 0, chisqr = 0.0; ii < lsgd.rsize; ii++) {
			double difvv = (double) (lsgd.data[ii].lss_out - lsgd.data[ii].lss_in) - (double) davgstime;

			chisqr += (difvv * difvv) / davgstime;
		}

		for (ii = 0, avgstime = 0, valid = 0; ii < lsgd.rsize; ii++) {
			schedtime = lsgd.data[ii].lss_out - lsgd.data[ii].lss_in;

			if (schedtime < klimit * davgstime) {
				avgstime += schedtime;
				++valid;
			}
		}

		lmavg =  valid > 0 ? (double) avgstime / (double) valid: 0;

		fprintf(stdout, "%.0lf\t%.0lf\t%.0lf\n", davgstime, chisqr, lmavg);

	}
	close(lsfd);
	return 0;

}


