/*
 * wr-nexgen : write data from RAM to target file(s) or device, 
 *             used for e.g. file system write throughput tests
 */

#define _GNU_SOURCE 1
#define _LARGEFILE64_SOURCE 1  /* Large File Support (LFS) '*64()' functions. */
#define _FILE_OFFSET_BITS 64   /* Automatic '*()' --> '*64()' replacement. */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <sys/time.h>  /* gettimeofday() */
#include <unistd.h>    /* gettimeofday(), usleep() */

#include <sys/mman.h>  /* mmap() */
#include <errno.h>

#include <string.h>    /* strstr() */

#define USE_MMAP 0

typedef struct sDir {
  char *nameTemplate;
  int nameNumber;
  int blocksPerFile;
  int fid;
  int nowBlocks;
  double prevOperTime;
  double maxOperTime;
} tDir, *pDir;


/* Buffer occupancy counters. */
long byteRate;  /* bytes per second */
long bytesInBuffer;  /* how much unread data in buffer */
long maxBytesInBuffer;  /* the max of previous */

double
tim(void) {
  struct timeval tv;
  double t;
  static double prevT = 0.0;

  assert( gettimeofday(&tv, NULL) == 0 );
  t = (double)tv.tv_sec + (double)tv.tv_usec/1000000.0;
  if ((prevT > 0.0) && (byteRate > 0)) {  /* has been called once; call #2.. */
    /* Update the amount of data "flown" in & its max. */
    bytesInBuffer += (int)(byteRate*(t - prevT));
    if (bytesInBuffer > maxBytesInBuffer) {
      maxBytesInBuffer = bytesInBuffer;
    }
  }
  prevT = t;
  return t;
}  /* tim */


void
randbuf(char *buf, ssize_t nbytes)
{
  int i;

  for (i=0; i<nbytes; i++) {
    buf[i] = (char)rand();
  }  /* for */
}  /* randbuf */


int
main(int argc, char *argv[]) {
  #if USE_MMAP
  int fd_mmap;
  #endif
  int readMode;
  int blksize;
  off64_t totalblks;
  off64_t memblks;
  char *memblk;
  char *p;
  int ndirs;
  pDir dirs;
  int i;
  double recordStart;
  double totalDur;
  int usleeps;
  int blk;
  double mbits;
  /* 1% incr progress output. */
  int onePercentIncr;
  int onePercentBlocks;
  double onePercentTime, startTime;

  if (argc < 6) {
    fprintf(stderr, "%s: needs at least byterate blocksize totalblocks memblocks ndirs { path%%d blks }\n", argv[0]);
    exit(EXIT_FAILURE);
  }

#define TIMESTART(i) \
      { dirs[i].prevOperTime = tim(); }
#define TIMESTOP(i) \
      { double dur = tim() - dirs[i].prevOperTime; \
        if (dur > dirs[i].maxOperTime) dirs[i].maxOperTime = dur; }

  /* Init variables according to command line, allocate buffers. */
  readMode = (strstr(argv[0], "rd") != NULL);
  /* later, after rand() time measurement: byteRate = atol(argv[1]); */
  blksize = atoi(argv[2]);
  totalblks = atoll(argv[3]);
  memblks = atoll(argv[4]);
  #if _LARGEFILE64_SOURCE
  fprintf(stderr, "wr-nexgen compiled for LFS 64-bit file support, sizeof(off_t)=%u\n", sizeof(off_t));
  #endif
  fprintf(stderr, "RAM buffer size=%5.2f MB\n"
                  "Final file size=%6.2f MB\n",
     (memblks*blksize)/(1024.0*1024.0),
     (totalblks*blksize)/(1024.0*1024.0) );

  #if USE_MMAP
  fd_mmap = open("/tmp/wr-mmap", O_RDWR | O_CREAT);
  if (fd_mmap == -1) {
     fprintf(stderr, "/tmp/wr-mmap open failed, errno=%d\n", errno);
     exit(EXIT_FAILURE);
  }
  memblk  = mmap((void*)0, memblks*blksize, PROT_WRITE | PROT_READ, MAP_SHARED | MAP_LOCKED, fd_mmap, 0 );
  if (memblk == MAP_FAILED) {
     fprintf(stderr, "mmap failed, errno=%d\n", errno);
     exit(EXIT_FAILURE);
  }
  #else
  assert( (memblk = malloc((size_t)(memblks*blksize))) != NULL );
  #endif

  /* Init memblk with random values. */
  byteRate = 0L;
  {
    double st = tim();

    printf("Initializing memory buffer with rand()...");
    fflush(stdout);
    randbuf(memblk, (memblks*blksize));
    printf("took %f seconds.\n", tim()-st);
  }
  byteRate = atol(argv[1]);
  ndirs = atoi(argv[5]);
  assert( (argc >= (6 + ndirs*2)) );
  /* Allocate one extra struct for call-to-call time statistics. */
  assert( (dirs = malloc((ndirs+1)*sizeof(tDir))) != NULL );
  for (i=0; i<ndirs; i++) {
    dirs[i].nameTemplate = argv[6+i*2];
    dirs[i].nameNumber = 0;
    dirs[i].blocksPerFile = atoi(argv[7+i*2]);
    dirs[i].nowBlocks = dirs[i].blocksPerFile;  /* init to end */
    dirs[i].maxOperTime = 0.0;  /* init to smallest value */
  }  /* for each directory */
  dirs[ndirs].nameTemplate =   "call-to-call  ";
  dirs[ndirs].maxOperTime = 0.0;

  /* Start looping for total number of blocks. */
  bytesInBuffer = maxBytesInBuffer = usleeps = 0;
  recordStart = tim();
  TIMESTART(ndirs);  /* call-to-call (not per file) */
  onePercentIncr = 0;
  onePercentBlocks = totalblks / 100;
  onePercentTime = tim();
  startTime = tim();
  for (blk=0; blk<totalblks; blk++) {
    i = blk % ndirs;
    if (dirs[i].nowBlocks >= dirs[i].blocksPerFile) {
      /* Open a new file in this directory / template. */
      char pn[255];
      int n = blk / dirs[i].blocksPerFile;

      snprintf(pn, sizeof(pn), dirs[i].nameTemplate, n);
      if (n==0) TIMESTART(i);  /* only for first file */
      if (readMode) {
        assert( (dirs[i].fid = open(pn, O_RDONLY /*| O_DIRECT*/)) != -1 );
      } else {
        dirs[i].fid = creat(pn, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH /*| O_DIRECT*/);
        if (dirs[i].fid == -1) {
           fprintf(stderr, "creat() error: %d '%s'\n", errno, strerror(errno));
           exit(EXIT_FAILURE);
        }
//        assert( (dirs[i].fid = open(pn, O_WRONLY | O_CREAT /*| O_DIRECT*/)) != -1 );
      }
      dirs[i].nowBlocks = 0;
    }

    /* If we are doing rate-limited testing, check for available new data. */
    if (byteRate > 0L) {
      while (bytesInBuffer < blksize) {
        /* Not one full block in buffer, wait. */
        usleep(1000);  /* a small amount, probably ends up to be 10--20msec */
        usleeps++;
        (void)tim();  /* update 'bytesInBuffer' */
      }  /* while not at least one full block in buffer */
    }

    /* Write (or read) one block. */
    p = &(memblk[(blk % memblks)*blksize]);
    *(p+2) = 127; // write to make page 'dirty'
    if (readMode) {
      assert( (read(dirs[i].fid,
                     p,
                     blksize)) == blksize );
    } else {
      assert( (write(dirs[i].fid,
                     p,
                     blksize)) == blksize );
    }
    TIMESTOP(i);  /* accumulates close--open-write in worst case */
    TIMESTOP(ndirs);  /* call-to-call */
    /* Once the previous have updated 'bytesInBuffer' increase, */
    /* only then decrement for the amount written. */
    bytesInBuffer -= blksize;
    TIMESTART(i);
    TIMESTART(ndirs);
    dirs[i].nowBlocks++;
    /* 1% progress indication. */
    if(--onePercentBlocks <= 0) {
      double t = tim();

      onePercentIncr++;
      onePercentBlocks = totalblks / 100;
      /* No rate limitation used, so reporting 10% speeds makes sense. */
      if (1 /*byteRate <= 0*/) {
        double mbits;

        mbits = 8.0*onePercentBlocks*blksize
          /(t - onePercentTime)
          /1000.0/1000.0;
        printf("%3d%% \t %f Mbits(dec)/s \t %f sec\n",
               onePercentIncr,
               mbits, (t - startTime)
               );
      }
      onePercentTime = t;
    }  /* if 1% block counter expired */

    /* Time to switch to another file? */
    if (dirs[i].nowBlocks >= dirs[i].blocksPerFile) {
      /* Close the ready file before opening a new. */
      assert( (close(dirs[i].fid) != -1) );
    }
  }  /* for each block */
  TIMESTOP(ndirs);
  totalDur = tim() - recordStart;

  /* Final report. */
  mbits = 8.0*totalblks*blksize/totalDur/1000.0/1000.0;
  printf("Took %f seconds, %f Mbits(dec)/s (%.1f%% of PCI33).\n",
         totalDur,
         mbits,
         mbits/(33.0*4.0*8.0)*100.0
         );
  if (byteRate > 0) {
    printf("Max. unwritten bytes in buffer %Lu  (%d usleeps()).\n",
           (unsigned long long)maxBytesInBuffer,
           usleeps
           );
  }
  for (i=0; i<=ndirs; i++) {
    printf("%s  max. %f seconds.\n",
           dirs[i].nameTemplate,
           dirs[i].maxOperTime
           );
  }  /* for each directory */

  #if USE_MMAP
  munmap(memblk, memblks*blksize);
  #endif

  return(EXIT_SUCCESS);
}  /* main */

