/****************************************************
 *     This software released to the public domain  *
 *     without restrictions of any kind.            *
 ****************************************************/

/*
 *  NAME
 *	cpu - CPU activity sensor
 *
 *  SYNOPSIS
 *	cpu [ -cdr ] [-s delay ] [ -t time ]
 *
 *  DESCRIPTION
 *	CPU is a program to detect the overall processor
 *	activity present in a system as part of performance
 *	testing.  CPU must be calibrated on an idle
 *	system, where it times a test loop and writes
 *	the results to the file /usr/tmp/.cpu.   Thereafter
 *	CPU may be run at any time when the system is
 *	under load.   Using CPU statistics provided by
 *	the system, and by measuring the test loop slowdown,
 *	the program can approximate general system CPU loading.
 *
 *	-c	    Calibrate.  Should be run when the system
 *		    is as idle as possible; single user mode
 *		    is best.
 *
 *	-d	    Turn on debugging flags.
 *
 *	-r	    Test repeatedly until interrupted.
 *
 *	-s delay    Sleep for the given delay time before
 *		    beginning the test.
 *
 *	-t time	    Run the test for time seconds.
 *
 *	On completion, CPU outputs the following 3 time values.
 *
 *	process	    The amount of time the system reported
 *		    that other processes were running.
 *
 *	interrupt   The slowdown of the test loop not reflected
 *		    by the system statistics.  Most likely this
 *		    time was consumed by interrupt routines, by
 *		    task switching overhead, or because activity
 *		    was synchronized to the system clock.
 *
 *	total	    The total slowdown of the test loop relative
 *		    to the speed measured at calibration time.
 *
 *	The total time is the most interesting, since it reflects
 *	the real processor utilization abosorbed by other processes.
 *	Proper use of the other two numbers is left as a problem
 *	for the reader.
 *
 *  WARNINGS
 *	CPU is astoundingly inaccurate in systems with different
 *	speed memories.  In some systems (eg i386 with 16 bit
 *	memory on the AT bus) it is not uncommon for calibration
 *	runs to vary by a factor of 2 or more.  There is no
 *	solution but to change the memory or find another machine.
 *
 *	The order of parameters 2 & 3 on setvbuf() vary from
 *	system, and are even inconsistent between the manuals
 *	and the libraries on stock system V.2.  You are wise
 *	to check the order of the parameters on your system and
 *	set the SETVBUF #define accordingly.
 */

#if !defined(lint)
char whatstring[] = "@(#)cpu.c 6.2 9/12/93" ;
#endif

#if sun
#define BSD 1
#endif

#if BSD
#   include <sys/types.h>
#   include <sys/time.h>
#   include <sys/resource.h>
#else
#   include <sys/types.h>
#   include <sys/times.h>
#   include <sys/param.h>
#endif

#include <signal.h>
#include <ctype.h>
#include <stdio.h>

#define uchar unsigned char
#define ushort unsigned short
#define ulong unsigned long

extern char *optarg ;
extern int optind ;

extern char *ttyname() ;
extern long atol() ;
extern unsigned sleep() ;
extern time_t time() ;

#if BSD
#else
extern unsigned alarm() ;
extern void exit() ;
extern void perror() ;
extern time_t times() ;
#endif


#if BSD
#   define TICS 1000	    /* Fractional seconds */
#   define MS(tv) (1000000L / TICS * (tv).tv_sec + (tv).tv_usec / TICS)
#endif

#define CALFILE "/usr/tmp/.cpu"

double speed ;		    /* Speed of the processor running */
double process ;	    /* Amount of the machine allocated to us */

int debug ;		    /* Debug flag */
int delay ;		    /* Delay before start */
int timeout ;		    /* Alarm occurred flag */
int interrupt ;		    /* Got an interrupt */

ulong rep ;		    /* Repeat count */



/*******
 *  Catch interrupts.
 */

void
sigalrm()
{
    timeout = 1 ;
}

void
sigint()
{
    interrupt = 1 ;
}


/*******
 *  delay - Routine to chew time.
 */

hog()
{
    static int a = 1 ;
    static int b = 2 ;
    static int c = 3 ;

    a += b ;
    b += c ;
    c += a ;
}


/*******
 *  measure - Procedure to measure our access to the CPU.
 */

measure(runtime)
int runtime ;
{
    ulong real ;
    ulong sys ;
    ulong user ;
    ulong count ;

#if BSD
    struct timeval tv ;
    struct rusage ru ;
#else
    struct tms tms ;
#endif

    /*
     *	If the runtime is negative, report all CPU used
     *	by another task.
     */

    if (runtime <= 0)
    {
	process = 0.0 ;
	speed = 0.0 ;
	return ;
    }

    /*
     *	Get time before.
     */

    if (debug) (void) fprintf(stderr,"Cpu started\n") ;

#if BSD
    (void) gettimeofday(&tv, (struct timezone *)0) ;
    real = MS(tv) ;

    (void) getrusage(RUSAGE_SELF, &ru) ;
    user = MS(ru.ru_utime) ;
    sys = MS(ru.ru_stime) ;
#else
    real = times(&tms) ;
    user = tms.tms_utime ;
    sys = tms.tms_stime ;
#endif

    /*
     *	Chew CPU for the allotted period.
     */

    count = 0 ;
    timeout = 0 ;

    (void) signal(SIGALRM, sigalrm) ;
    (void) alarm((unsigned) runtime) ;

    while (!timeout && !interrupt)
    {
	hog() ;
	count++ ;
    }

    /*
     *	Get time after.
     */

#if BSD
    (void) gettimeofday(&tv, (struct timezone *)0) ;
    real = MS(tv) - real ;

    (void) getrusage(RUSAGE_SELF, &ru) ;
    user = MS(ru.ru_utime) - user ;
    sys = MS(ru.ru_stime) - sys ;
#else
    real = times(&tms) - real ;
    user = tms.tms_utime - user ;
    sys = tms.tms_stime - sys ;
#endif

    /*
     *	Figure results.
     */

    process = real > 0 ? (double) user / (double) real : 0.0 ;
    speed = user ? (double) count / (double) user : 0.0 ;

    if (debug)
    {
	(void) fprintf(stderr,
	    "real=%ld, sys=%ld, user=%ld, count=%ld\n",
	    real, sys, user, count) ;
	(void) fprintf(stderr, "raw process=%.5f, speed=%.5f\n",
	    process, speed) ;
    }
}



main(argc, argv)
int argc ;
char **argv ;
{
    double cprocess ;
    double cspeed ;
    time_t clock ;
    int calibrate ;
    int runtime = 60 ;
    int err ;
    int c ;
    FILE *cfile ;
    char iobuf[1024] ;
    char buf[200] ;

    /*
     *	Unix V.2 (original release) had parameters 2 & 3 of
     *	the SETVBUF call reversed.   This has been corrected
     *	SVR3.   Set SETVBUF if you get a lint error.
     */

#if SETVBUF
    (void) setvbuf(stderr, _IOLBF, iobuf, sizeof(iobuf)) ;
#else
    (void) setvbuf(stderr, iobuf, _IOLBF, sizeof(iobuf)) ;
#endif

    calibrate = 0 ;
    err = 0 ;

    while ((c = getopt(argc, argv, "cdrs:t:")) != -1)
    {
	switch (c)
	{
	case 'c':
	    calibrate++ ;
	    break ;

	case 'd':
	    debug++ ;
	    break ;

	case 'r':
	    rep = -1 ;
	    break ;

	case 's':
	    delay = atoi(optarg) ;
	    break ;

	case 't':
	    runtime = atoi(optarg) ;
	    break ;

	case '?':
	    err++ ;
	}
    }

    if (err || optind < argc)
    {
	(void) fprintf(stderr,
	    "usage: %s [ -cdr ] [ -s delay ] [ -t time ]\n", argv[0]) ;
	exit(2) ;
    }

    (void) nice(40) ;

    if (calibrate)
    {
	measure(runtime) ;

	if ((cfile = fopen(CALFILE, "w")) == 0)
	{
	    perror(CALFILE) ;
	    exit(1) ;
	}

	(void) fprintf(cfile, "%f %f\n", process, speed) ;

	(void) fprintf(stderr,
	    "Calibrated process=%f, speed=%f\n", process, speed) ;
    }
    else
    {
	if ((cfile = fopen(CALFILE, "r")) == 0)
	{
	    perror(CALFILE) ;
	    (void) fprintf(stderr,
		"Perhaps you should run \"%s -c\" first.\n", argv[0]) ;
	    exit(1) ;
	}

	if  (	fscanf(cfile, "%lf %lf", &cprocess, &cspeed) != 2
	    ||	cprocess == 0
	    ||	cspeed == 0
	    )
	{
	    (void) fprintf(stderr,
		"%s file does not contain calibrated speed.\n", CALFILE) ;
	}

	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
	    (void) signal(SIGINT, sigint) ;

	clock = time((time_t *)0) ;

	if (delay)
	{
	    clock += delay ;

	    (void) sleep((unsigned) delay) ;
	}

	for (;;)
	{
	    clock += runtime ;

	    measure((int)(clock - (int)time((time_t *) 0))) ;

	    process /= cprocess ;
	    speed /= cspeed ;

	    if (debug)
	    {
		(void) fprintf(stderr, "normalized process=%.5f, speed=%.5f\n",
		    process, speed) ;
	    }

	    (void) sprintf(buf,
		"TIME process=%.1f, interrupt=%.1f, total=%.1f\n",
		100 * (1 - process) * speed,
		100 * (1 - speed),
		100 * (1 - process * speed)) ;

	    (void) write(2, buf, (unsigned) strlen(buf)) ;

	    if (rep == 0 || interrupt) break ;

	    rep-- ;
	}
    }

    return(0) ;
}
