#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>

static double get_hires_time(void)
{
  struct timeval tv;

  gettimeofday(&tv, NULL);
  return tv.tv_sec + tv.tv_usec / 1000000.0;
}

sig_atomic_t signal_counter;

static void my_signal_handler(int signo)
{
  ++signal_counter;
}

int writer_main(int loops, int size)
{
  int rc;
  int i;
  int fd;
  char *buffer;
  double start_time;
  double elapsed_time;

  start_time = get_hires_time();

  /* Set up a chunk of data to write. */
  buffer = malloc(size);
  assert(buffer != NULL);
  memset(buffer, 42, size);

  /* Write and fsync as fast as we can. */
  fd = open("output", O_RDWR | O_CREAT | O_TRUNC, 0664);
  assert(fd > 0);
  for (i = 0; i < loops; ++i) {
    rc = lseek(fd, 0, SEEK_SET);
    assert(rc == 0);
    rc = write(fd, buffer, size);
    assert(rc == size);
    rc = fsync(fd);
    assert(rc == 0);
  }
  rc = close(fd);
  assert(rc == 0);

  /* Compute the rates. */
  elapsed_time = get_hires_time() - start_time;
  printf("%d bytes, %d fsyncs/sec, %d signals/sec received\n",
	 size,
	 (int) (loops / elapsed_time),
	 (int) (signal_counter / elapsed_time));
  return 0;
}

int interruptor_main(pid_t writer_pid, int delay)
{
  double start_time;
  double elapsed_time;
  uint64_t counter = 0;

  start_time = get_hires_time();
  
  while (kill(writer_pid, SIGUSR2) == 0) {
    ++counter;
    if (delay > 0)
      usleep(delay);
  }

  elapsed_time = get_hires_time() - start_time;
  printf("%d signals/sec sent\n", (int) (counter / elapsed_time));
  return 0;
}

int main(int argc, char *argv[])
{
  int loops;
  int size;
  int delay;
  pid_t writer_pid;
  pid_t interruptor_pid;
  struct sigaction act;
  int status;
  int rc;

  /* Read command line. */
  if (argc != 4) {
    printf("Usage: %s <loops> <bytes> <usleep>\n", argv[0]);
    exit(1);
  }
  loops = atoi(argv[1]);
  size = atoi(argv[2]);
  delay = atoi(argv[3]);

  /* Set up the signal handler so it can be inherited by the writer. */
  memset(&act, 0, sizeof(struct sigaction));
  act.sa_handler = my_signal_handler;
  act.sa_flags = SA_RESTART;
  rc = sigaction(SIGUSR2, &act, 0);
  assert(rc == 0);
  
  /* Fork the writer. */
  writer_pid = fork();
  if (writer_pid == 0)
    return writer_main(loops, size);

  /* Fork the interruptor. */
  interruptor_pid = fork();
  if (interruptor_pid == 0)
    return interruptor_main(writer_pid, delay);

  /* Wait for them both to finish. */
  waitpid(writer_pid, &status, 0);
  assert(status == 0);
  waitpid(interruptor_pid, &status, 0);
  assert(status == 0);

  return 0;
}
