/*
 * CLOCK_REALTIME_FAST benchmark routine.
 * Written by Sean Chittenden <sean@chittenden.org>, June 2008.
 *
 * Compilation instructions:
 * % gcc -Wall -O3 -o bench_clock_realtime bench_clock_realtime.c
 *
 * Execution instructions:
 * % ./bench_clock_realtime 1000000 | sort -rnk1
 * % ./bench_clock_realtime | sort -rnk1
 */

#include <time.h>
#include <sys/time.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>

#ifndef MICROSEC
# define MICROSEC        1000000
#endif

void
tv_sub(struct timeval *ret, struct timeval *x, struct timeval *y) {
  if (x->tv_usec < y->tv_usec) {
    int nsec = (y->tv_usec - x->tv_usec) / MICROSEC + 1;
    y->tv_usec -= MICROSEC * nsec;
    y->tv_sec += nsec;
  }
  if (x->tv_usec - y->tv_usec > MICROSEC) {
    int nsec = (x->tv_usec - y->tv_usec) / MICROSEC;
    y->tv_usec += MICROSEC * nsec;
    y->tv_sec -= nsec;
  }

  ret->tv_sec = x->tv_sec - y->tv_sec;
  ret->tv_usec = x->tv_usec - y->tv_usec;
}


void
results(const char *test, int count, struct timeval *start, struct timeval *end) {
  struct timeval *tv_p, tv;
  tv_p = &tv;
  
  tv_sub(tv_p, end, start);
  printf("%0.6f\t%lu.%06lu\t%s\n", (float)(tv.tv_sec * MICROSEC + tv.tv_usec) / (float)count, tv.tv_sec, tv.tv_usec, test);
}


#define timeval_save_compare(name, iteration, new, old) do {     \
    if (new.tv_sec < old.tv_sec) { \
      printf("%s/%d: New  sec (%lu) less than old (%lu)\n", name, iteration, new.tv_sec, old.tv_sec); \
    } else if (new.tv_sec == old.tv_sec && new.tv_usec < old.tv_usec) { \
      printf("%s/%d: New usec (%lu) less than old (%lu)\n", name, iteration, new.tv_usec, old.tv_usec); \
    } else if (new.tv_sec == old.tv_sec && new.tv_usec == old.tv_usec) { \
      equal++; \
    } \
    old.tv_sec = new.tv_sec; \
    old.tv_usec = new.tv_usec; \
} while (0)


#define timespec_save_compare(name, iteration, new, old) do {    \
    if (new.tv_sec < old.tv_sec) { \
      printf("%s/%d: New  sec (%lu) less than old (%lu)\n", name, iteration, new.tv_sec, old.tv_sec); \
    } else if (new.tv_sec == old.tv_sec && new.tv_nsec < old.tv_nsec) { \
      printf("%s/%d: New nsec (%lu) less than old (%lu)\n", name, iteration, new.tv_nsec, old.tv_nsec); \
    } else if (new.tv_sec == old.tv_sec && new.tv_nsec == old.tv_nsec) { \
      equal++; \
    } \
    old.tv_sec = new.tv_sec; \
    old.tv_nsec = new.tv_nsec; \
} while (0)


#define bench_call(name, test, compare, post_print) do { \
  bzero(&tp_old, sizeof(struct timespec)); \
  bzero(&tp, sizeof(struct timespec)); \
  bzero(&tv_old, sizeof(struct timeval)); \
  bzero(&tv, sizeof(struct timeval)); \
  equal = 0; \
  gettimeofday(&start, NULL); \
  for (i = count; i; i--) { \
    test; \
    compare; \
  } \
  gettimeofday(&end, NULL); \
  post_print; \
  results(name, count, &start, &end); \
} while (0)

#define bench_call_timeval(name, test) do { \
    bench_call(name, test, timeval_save_compare(name, count - i, tv, tv_old), printf("Last value from %s: %lu.%06lu\tEqual: %d\n", name, tv.tv_sec, tv.tv_usec, equal)); \
} while (0)

#define bench_call_timespec(name, test) do { \
    bench_call(name, test, timespec_save_compare(name, count - i, tp, tp_old), printf("Last value from %s: %lu.%09lu\tEqual: %d\n", name, tp.tv_sec, tp.tv_nsec, equal)); \
} while (0)


int
main(int argc, char *argv[]) {
  struct timeval start, end, tv, tv_old, *tv_p;
  struct timespec tp, tp_old, *tp_p;
  int count, equal, i;
  time_t t = 0;

  if (argc == 2) {
    count = strtol(argv[1], NULL, 10);
  } else {
    srandomdev();
    count = (int)(random() / 10000);
  }

  tv_p = &tv;
  tp_p = &tp;

  fprintf(stderr, "clock realtime micro-benchmark.  %u syscall iterations.\n", count);
  fprintf(stderr, "Avg. us/call\tElapsed\t\tName\n");

  bench_call_timeval("gettimeofday(2)", gettimeofday(tv_p, NULL));
  bench_call("time(3)", t = time(NULL),, printf("Value from time(3): %lu\n", t));

#ifdef CLOCK_REALTIME
  bench_call_timespec("clock_gettime(2/CLOCK_REALTIME)", clock_gettime(CLOCK_REALTIME, tp_p));
#endif

#ifdef CLOCK_REALTIME_FAST
  bench_call_timespec("clock_gettime(2/CLOCK_REALTIME_FAST)", clock_gettime(CLOCK_REALTIME_FAST, tp_p));
#endif

#ifdef CLOCK_SECOND
  bench_call_timespec("clock_gettime(2/CLOCK_SECOND)", clock_gettime(CLOCK_SECOND, tp_p));
#endif

  return EX_OK;
}
