/*
 Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights
 reserved.
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; version 2 of
 the License.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 02110-1301  USA
 */

#include <my_config.h>

#include <stdlib.h>
#ifdef __GNUC__
/* Required for useconds_t and C99 */
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>

#include "workqueue.h"
#include <ndbmemcache_config.h>

#ifdef HAVE_SRANDOMDEV
#define seed_randomizer(X) srandomdev()
#else 
#define seed_randomizer(X) srandom(X)
#endif

#define TEST_ITERATIONS 1000000
#define DO_SLEEP 1


void * producer_thread(void *arg);
void * consumer_thread(void *arg);

struct threadinfo { 
  struct workqueue *q;
  int queue_size;
  int nconsumers;
  int producer_batch_size;
  int producer_median_sleep;
  int consumer_median_sleep;
  int iterations;
  int report_interval;
};  

struct threadreturn {
  unsigned int nrecv;
};

int run_test(struct threadinfo *);
void express_nanosec(Uint64 nsec);

int sleep_microseconds(int);

int main() {
  struct workqueue *queue=
      (struct workqueue *) calloc(1, sizeof(struct workqueue));

  struct threadinfo test0 = { queue, 32768, 1, 0, 0, 0, 10000000, 1000000 };
  struct threadinfo test1 = { queue, 32768, 2, 0, 0, 0, 10000000, 1000000 };
  struct threadinfo test2 = { queue, 8192, 2, 10, 800, 200, 200000, 25000 };
  struct threadinfo test3 = { queue, 8192, 2, 1, 850, 50, 125000, 25000 };
  struct threadinfo test4 = { queue, 8192, 2, 20, 50, 500, 200000, 25000 };
  struct threadinfo test5 = { queue, 8192, 2, 1, 50, 0, 150000, 50000 };
  struct threadinfo test6 = { queue, 16384, 8, 1, 20, 160, 200000, 50000 };
  
  seed_randomizer(1);

  /* Test 0: no-sleep with 1 consumer */
  printf("No-sleep test with 1 consumer\n");
  if(run_test(& test0)) printf("Test OK \n\n");
  else exit(1);

  /* Test 1: the no-sleep test */
  printf("No-sleep wham!bam! test with %d iterations: \n", test1.iterations);
  if(run_test(& test1)) printf("Test OK \n\n");
  else exit(1);
  
  /* Test 2: fast producer, slow consumer */
  printf("Fast producer / slow consumer test: \n");
  if(run_test(& test2)) printf("Test OK \n\n");
  else exit(1);

  /* Test 3: slow producer, fast consumer */
  printf("Slow producer / fast consumer test : \n");  
  if(run_test(& test3)) printf("Test OK \n\n");
  else exit(1);

  /* Test 4: very slow consumer */  
  printf("very slow consumer test: \n");  
  if(run_test(& test4)) printf("Test OK \n\n");
  else exit(1);

  /* Test 5: whambam! consumer */  
  printf("Sluggish producer, whambam! consumer test: \n");  
  if(run_test(& test5)) printf("Test OK \n\n");
  else exit(1);

  /* Test 6: simulation */
  printf("Memcached simluation test: \n");  
  if(run_test(& test6)) printf("Test OK \n\n");
  else exit(1);

  printf("Passed.\n");
  exit(0);
}

int run_test(struct threadinfo *params) {
  pthread_t producer_thd_id;
  pthread_t *consumer_thd_ids;
  int i;
  int total_consumed = 0;
  
  consumer_thd_ids = calloc(sizeof(pthread_t), params->nconsumers);

  if(workqueue_init(params->q, params->queue_size, params->nconsumers) ) {
    printf("Workqueue init failed.\n");
    exit(1);
  }
  
  pthread_create(&producer_thd_id, NULL, producer_thread, (void *) params);  
  for(i = 0; i < params->nconsumers; i++) 
    pthread_create(& consumer_thd_ids[i], NULL, consumer_thread, (void *) params);
  
  pthread_join(producer_thd_id, NULL);
  for(i = 0; i < params->nconsumers; i++) {
    void *ret;
    pthread_join(consumer_thd_ids[i], &ret);
    total_consumed += ((struct threadreturn *) ret)->nrecv;
  }

  workqueue_destroy(params->q);  
  free(consumer_thd_ids);  
  return (total_consumed == params->iterations);
}


void * producer_thread(void *arg) {
  long long total_sleep = 0;
  int slp = 0;
  size_t i = 1;
  int n_ints;
  int sample_interval = 1000;
  int nsamples = 0, total_depth = 0;
  int do_sample = random() % sample_interval;

  struct threadinfo *testinfo = (struct threadinfo *) arg;  
  struct workqueue *queue = testinfo->q;
  int batchsize = testinfo->producer_batch_size;
  int sleeptime = testinfo->producer_median_sleep;
  unsigned int iterations = testinfo->iterations + 1;
  
  /* Generate consecutive integers, in random batches.  And sleep for random
     amounts of time between them.  Put them on the queue. 
  */
  while(i < iterations) {  /* we count from e.g. "1" to "100000" */
    /* sleep time: non-uniform ("two dice") distribution */
    if(sleeptime)
      slp =  ( (random() % sleeptime) + (random() % sleeptime) );   

    /* how many numbers to generate: */
    if(batchsize)
      n_ints = (random() % batchsize) + 1;
    else 
      n_ints = 1;
  
    while(n_ints-- && i < iterations) {
      workqueue_add(queue, (void *) i);
      i++;
    }
    if(sleeptime) total_sleep += sleep_microseconds(slp);

    if(do_sample-- == 0) {
      nsamples++;
      total_depth += queue->depth;
      do_sample = random() % sample_interval;
    }
  }

  printf("Producer thread sent %d. Slept for %f sec.  Average depth: %d\n", 
         (int) i-1, (double) total_sleep / 1000000, total_depth / nsamples);

  workqueue_abort(queue);
  return 0;
}


void * consumer_thread(void *arg) {
  int slp = 0;
  long long total_sleep = 0;
  size_t i;
  size_t last_i = 0;
  
  struct threadinfo *testinfo = (struct threadinfo *) arg;  
  struct workqueue *queue = testinfo->q;
  int sleeptime = testinfo->consumer_median_sleep;
  struct threadreturn *ret = malloc(sizeof(struct threadreturn));
  ret->nrecv = 0;
  
  /* fetch items from the queue, 1 at a time, and sleep for some time to 
     simulate processing them */
  
  while(1) {
    /* sleep time: non-uniform ("two dice") distribution */
    if(sleeptime) 
      slp =  ( (random() % sleeptime) + (random() % sleeptime) );

    i = (size_t) workqueue_consumer_wait(queue);
    if(i) {
      ret->nrecv++;
      if(i == 10) printf("read 10.\n");
      if(i % testinfo->report_interval == 0) printf("read %d.\n", (int) i);
      assert(i > last_i);
      last_i = i;
      if(sleeptime) total_sleep += sleep_microseconds(slp);
    }
    else {    
      printf("Consumer thread read %d; slept for %f sec. \n", ret->nrecv,
             (double) total_sleep / 1000000);
      return (void *) ret;
    }
  }
}


/* sleep for some number of microseconds, less than a full second.
   returns number of microseconds slept.
*/
int sleep_microseconds(int usec) {
  struct timespec time_to_sleep;
  struct timespec time_slept;
  
  time_to_sleep.tv_nsec = (usec * 1000);
  time_to_sleep.tv_sec = 0;
  if(nanosleep(&time_to_sleep, &time_slept))  /* interrupted */
    return (( time_to_sleep.tv_nsec - time_slept.tv_nsec) / 1000);
  else return usec;
}


void express_nanosec(Uint64 ns) {
  const char *units[4] = { "ns", "us", "ms", "s" };
  int p;
  for(p = 0; ns > 1000 && p < 4; ns /= 1000, p++);
  
  printf("%llu %s\n", (unsigned long long) ns, units[p]);  
}
