#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/times.h>
#include "clock.h"

/* 
 * Routines for using the cycle counter 
 */

/* Detect whether running on Alpha */
#ifdef __alpha
#define IS_ALPHA 1
#else
#define IS_ALPHA 0
#endif

/* Detect whether running on x86 */
#ifdef __i386__
#define IS_x86 1
#else
#define IS_x86 0
#endif

#if IS_ALPHA
/* Initialize the cycle counter */
static unsigned cyc_hi = 0;
static unsigned cyc_lo = 0;


/* Use Alpha cycle timer to compute cycles.  Then use
   measured clock speed to compute seconds 
*/

/*
 * counterRoutine is an array of Alpha instructions to access 
 * the Alpha's processor cycle counter. It uses the rpcc 
 * instruction to access the counter. This 64 bit register is 
 * divided into two parts. The lower 32 bits are the cycles 
 * used by the current process. The upper 32 bits are wall 
 * clock cycles. These instructions read the counter, and 
 * convert the lower 32 bits into an unsigned int - this is the 
 * user space counter value.
 * NOTE: The counter has a very limited time span. With a 
 * 450MhZ clock the counter can time things for about 9 
 * seconds. */
static unsigned int counterRoutine[] =
{
    0x601fc000u,
    0x401f0000u,
    0x6bfa8001u
};

/* Cast the above instructions into a function. */
static unsigned int (*counter)(void)= (void *)counterRoutine;


void start_counter()
{
    /* Get cycle counter */
    cyc_hi = 0;
    cyc_lo = counter();
}

double get_counter()
{
    unsigned ncyc_hi, ncyc_lo;
    unsigned hi, lo, borrow;
    double result;
    ncyc_lo = counter();
    ncyc_hi = 0;
    lo = ncyc_lo - cyc_lo;
    borrow = lo > ncyc_lo;
    hi = ncyc_hi - cyc_hi - borrow;
    result = (double) hi * (1 << 30) * 4 + lo;
    if (result < 0) {
        fprintf(stderr, "Error: Cycle counter returning negative value: %.0f\n", result);
    }
    return result;
}
#endif /* Alpha */

#if IS_x86
/* $begin x86cyclecounter */
/* Initialize the cycle counter */
static unsigned cyc_hi = 0;
static unsigned cyc_lo = 0;

static int affinity = -1;

/* Set *hi and *lo to the high and low order bits  of the cycle counter.  
   Implementation requires assembly code to use the rdtsc instruction. */
void access_counter(unsigned *hi, unsigned *lo)
{
    asm("rdtsc; movl %%edx,%0; movl %%eax,%1"   /* Read cycle counter */
        : "=r" (*hi), "=r" (*lo)                /* and move results to */
        : /* No input */                        /* the two outputs */
        : "%edx", "%eax");
}


/* Randomly choose a CPU affinity.  Setting affinity ensures that we're
   always reading the same timestamp counter.  Doing it randomly helps
   avoid having multiple students interfere with one another. */
void choose_affinity()
{
    cpu_set_t mask;
    int ncpus;
    struct timeval now;

    /* If affinity hasn't been set, choose a CPU */
    if (affinity == -1) {

        CPU_ZERO(&mask);

        /*
         * Finding out how many CPUs there are is a pain.  Why isn't there
         * a system call?  We just have to try setting affinity to a
         * high-numbered CPU until we succeed.
         *
         * (This could be improved to use a binary search, BTW.)
         */
        for (ncpus = CPU_SETSIZE - 1; ncpus > 0;  ncpus--) {
            CPU_SET(ncpus, &mask);
            if (sched_setaffinity(0, sizeof mask, &mask) == 0)
                break;                          /* Success on last CPU */
            CPU_CLR(ncpus, &mask);
        }
        ncpus++;

        /*
         * Pick a CPU "randomly".  For a random number, we'll use the
         * current time, mashed a bit to deal with the possiblity that
         * it might not really be microsecond-accurate.
         */
        gettimeofday(&now, (struct timezone *)NULL);
        affinity = (now.tv_sec ^ now.tv_usec) % ncpus;

        CPU_ZERO(&mask);
        CPU_SET(affinity, &mask);
        sched_setaffinity(0, sizeof mask, &mask);
    }
}

/* Record the current value of the cycle counter. */
void start_counter()
{
    if (affinity == -1)
        choose_affinity();
    access_counter(&cyc_hi, &cyc_lo);
}

/* Return the number of cycles since the last call to start_counter. */
double get_counter()
{
    unsigned ncyc_hi, ncyc_lo;
    unsigned hi, lo, borrow;
    double result;

    /* Get cycle counter */
    access_counter(&ncyc_hi, &ncyc_lo);

    /* Do double precision subtraction */
    lo = ncyc_lo - cyc_lo;
    borrow = lo > ncyc_lo;
    hi = ncyc_hi - cyc_hi - borrow;
    result = (double) hi * (1 << 30) * 4 + lo;
    if (result < 0) {
        fprintf(stderr, "Error: counter returns neg value: %.0f\n", result);
    }
    return result;
}
/* $end x86cyclecounter */
#endif /* x86 */

double ovhd()
{
    /* Do it twice to eliminate cache effects */
    int i;
    double result;

    for (i = 0; i < 2; i++) {
        start_counter();
        result = get_counter();
    }
    return result;
}

/* $begin mhz */
/* Estimate the clock rate by measuring the cycles that elapse */ 
/* while sleeping for sleeptime seconds */
double mhz_full(int verbose, int sleeptime)
{
    double rate;

    start_counter();
    sleep(sleeptime);
    rate = get_counter() / (1e6*sleeptime);
    if (verbose) 
        printf("Processor clock rate ~= %.1f MHz\n", rate);
    return rate;
}
/* $end mhz */

/* Version using a default sleeptime */
double mhz(int verbose)
{
    return mhz_full(verbose, 2);
}

/** Special counters that compensate for timer interrupt overhead */

static double cyc_per_tick = 0.0;

#define NEVENT 100
#define THRESHOLD 1000
#define RECORDTHRESH 3000

/* Attempt to see how much time is used by timer interrupt */
static void calibrate(int verbose)
{
    double oldt;
    struct tms t;
    clock_t oldc;
    int e = 0;

    times(&t);
    oldc = t.tms_utime;
    start_counter();
    oldt = get_counter();
    while (e <NEVENT) {
        double newt = get_counter();

        if (newt-oldt >= THRESHOLD) {
            clock_t newc;
            times(&t);
            newc = t.tms_utime;
            if (newc > oldc) {
                double cpt = (newt-oldt)/(newc-oldc);
                if ((cyc_per_tick == 0.0 || cyc_per_tick > cpt) && cpt > RECORDTHRESH)
                    cyc_per_tick = cpt;
                /*
                  if (verbose)
                  printf("Saw event lasting %.0f cycles and %d ticks.  Ratio = %f\n",
                  newt-oldt, (int) (newc-oldc), cpt);
                */
                e++;
                oldc = newc;
            }
            oldt = newt;
        }
    }
      /* ifdef added by Sanjit - 10/2001 */
#ifdef DEBUG
    if (verbose)
        printf("Setting cyc_per_tick to %f\n", cyc_per_tick);
#endif
}

static clock_t start_tick = 0;

void start_comp_counter() 
{
    struct tms t;

    if (cyc_per_tick == 0.0)
        calibrate(1);
    times(&t);
    start_tick = t.tms_utime;
    start_counter();
}

double get_comp_counter() 
{
    double time = get_counter();
    double ctime;
    struct tms t;
    clock_t ticks;

    times(&t);
    ticks = t.tms_utime - start_tick;
    ctime = time - ticks*cyc_per_tick;
    /*
      printf("Measured %.0f cycles.  Ticks = %d.  Corrected %.0f cycles\n",
      time, (int) ticks, ctime);
    */
    return ctime;
}

