/*  time-schedule.c

    Programme to test how long a context switch takes.

    Copyright (C) 1998  Richard Gooch

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    Richard Gooch may be reached by email at  rgo...@atnf.csiro.au
    The postal address is:
      Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
*/

/*
    This programme will determine the context switch (scheduling) overhead on
    a system. It takes into account SMP machines. True context switches are
    measured.


    Written by      Richard Gooch   15-SEP-1998

    Last updated by Richard Gooch   16-SEP-1998


*/
#include <unistd.h>
#ifndef _REENTRANT
#  define _REENTRANT
#endif
#ifndef _POSIX_THREAD_SAFE_FUNCTIONS
#  define _POSIX_THREAD_SAFE_FUNCTIONS
#endif
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sched.h>
#include <sys/time.h>
#include <sys/mman.h>

#if 0                             /*  Set to 1 if you don't have Karma  */
#  define mt_num_processors() 1   /*  Set to the number of processors   */
#  define ERRSTRING sys_errlist[errno]
#  define FALSE 0
#  define TRUE  1
#else
#  include <karma.h>
#  include <karma_mt.h>
#endif


#define MAX_ITERATIONS  10000

static void hog_other_cpus ();
static void run_yielder (int use_threads);
static void *yielder_main (void *arg);
static void s_term_handler ();
static void run_low_priority (unsigned int num);


static volatile unsigned int sched_count = 0;


int main (int argc, char **argv)
{
    int use_threads = FALSE;
    unsigned int count;
    signed long total_diffs;
    /*signed long diffs[MAX_ITERATIONS];*/
    static char *usage = "time-schedule [-h] [-thread] [num_running]";

    for (count = 1; count < argc; ++count)
    {
	if (strcmp (argv[count], "-h") == 0)
	{
	    fprintf (stderr, "Usage:\t%s\n", usage);
	    exit (0);
	}
	else if (strcmp (argv[count], "-thread") == 0) use_threads = TRUE;
	else run_low_priority ( atoi (argv[count]) );
    }
    if (geteuid () == 0)
    {
	struct sched_param sp;

	memset (&sp, 0, sizeof sp);
	sp.sched_priority = 10;
	if (sched_setscheduler (0, SCHED_FIFO, &sp) != 0)
	{
	    fprintf (stderr, "Error changing to RT class\t%s\n", ERRSTRING);
	    exit (1);
	}
	if (mlockall (MCL_CURRENT | MCL_FUTURE) != 0)
	{
	    fprintf (stderr, "Error locking pages\t%s\n", ERRSTRING);
	    exit (1);
	}
    }
    else fprintf (stderr, "Not running with RT priority\n");
    hog_other_cpus ();
    run_yielder (use_threads);
    /*memset (diffs, 0, sizeof diffs);*/
    total_diffs = 0;
    for (count = 0; count < MAX_ITERATIONS; ++count)
    {
	int i;
	signed long diff;
	struct timeval before, after;

	gettimeofday (&before, NULL);
	for (i = 0; i < 10; ++i) sched_yield ();
	gettimeofday (&after, NULL);
	diff = 1000000 * (after.tv_sec - before.tv_sec);
	diff += after.tv_usec - before.tv_usec;
	diff = diff / 20;
	/*diffs[count] = diff;*/
	total_diffs += diff;
    }
#if 0
    for (count = 0; count < MAX_ITERATIONS; count += 500)
    {
	printf ("%-8ld us\n", diffs[count]);
    }
#endif
    printf ("Average scheduling latency: %ld us\n",
	    total_diffs / MAX_ITERATIONS);
    fflush (stdout);
    if (use_threads) fprintf (stderr, "Number of yields: %u\n", sched_count);
    /*  Finish up  */
    kill (0, SIGTERM);
    return (0);
}   /*  End Function main  */


static void hog_other_cpus ()
/*  [SUMMARY] Hog other CPUs with a high-priority job.
    [RETURNS] Nothing.
*/
{
    unsigned int count;

    for (count = mt_num_processors (); count > 1; --count)
    {
	switch ( fork () )
	{
	  case 0:
	    /*  Child  */
	    while (TRUE);
	    break;
	  case -1:
	    /*  Error  */
	    fprintf (stderr, "Error forking\t%s\n", ERRSTRING);
	    kill (0, SIGTERM);
	    break;
	  default:
	    /*  Parent  */
	    break;
	}
    }
    fprintf (stderr, "Started %u hog processes\n", mt_num_processors () - 1);
}   /*  End Function hog_other_cpus  */

static void run_yielder (int use_threads)
/*  [SUMMARY] Run other process which will continuously yield.
    <use_threads> If TRUE, the yielding process is just a thread.
    [RETURNS] Nothing.
*/
{
    struct sigaction new_action;
    pthread_t thread;

    if (use_threads)
    {
	if (pthread_create (&thread, NULL, yielder_main, NULL) != 0)
	{
	    fprintf (stderr, "Error creating thread\t%s\n", ERRSTRING);
	    kill (0, SIGTERM);
	}
	fprintf (stderr, "Started yielder thread\n");
	return;
    }
    switch ( fork () )
    {
      case 0:
	/*  Child  */
	break;
      case -1:
	/*  Error  */
	fprintf (stderr, "Error forking\t%s\n", ERRSTRING);
	kill (0, SIGTERM);
	break;
      default:
	/*  Parent  */
	fprintf (stderr, "Started yielder process\n");
	return;
	/*break;*/
    }
    memset (&new_action, 0, sizeof new_action);
    sigemptyset (&new_action.sa_mask);
    new_action.sa_handler = s_term_handler;
    if (sigaction (SIGTERM, &new_action, NULL) != 0)
    {
	fprintf (stderr, "Error setting SIGTERM handler\t%s\n", ERRSTRING);
	exit (1);
    }
    yielder_main (NULL);
}   /*  End Function run_yielder  */

static void *yielder_main (void *arg)
/*  [SUMMARY] Yielder function.
    <arg> An arbitrary argument. Ignored.
    [RETURNS] NULL.
*/
{
    while (TRUE)
    {
	sched_yield ();
	++sched_count;
    }
}   /*  End Function yielder_main  */

static void s_term_handler ()
{
    fprintf (stderr, "Number of yields: %u\n", sched_count);
    exit (0);
}   /*  End Function s_term_handler  */

static void run_low_priority (unsigned int num)
/*  [SUMMARY] Run low priority processes.
    <num> Number of processes.
    [RETURNS] Nothing.
*/
{
    fprintf (stderr, "Starting %u low priority processes\n", num);
    for (; num > 0; --num)
    {
	switch ( fork () )
	{
	  case 0:
	    /*  Child  */
	    if (nice (10) != 0)
	    {
		fprintf (stderr, "Error nicing\t%s\n", ERRSTRING);
		kill (0, SIGTERM);
	    }
	    while (TRUE) sched_yield ();
	    break;
	  case -1:
	    /*  Error  */
	    fprintf (stderr, "Error forking\t%s\n", ERRSTRING);
	    kill (0, SIGTERM);
	    break;
	  default:
	    /*  Parent  */
	    break;
	}
    }
}   /*  End Function run_low_priority  */