/*   $Source: bitbucket.org:berkeleylab/gasnet.git/tests/testlockcontend.c $
 * Description: GASNet lock performance test
 *   Measures the overhead associated with contended locks
 * Copyright 2002, Dan Bonachea <bonachea@cs.berkeley.edu>
 * Terms of use are as specified in license.txt
 */

#include <test.h>

#ifndef GASNET_PAR
#error This test can only be built for GASNet PAR configuration
#endif

static gex_Client_t      myclient;
static gex_EP_t    myep;
static gex_TM_t myteam;
static gex_Segment_t     mysegment;

int mynode = 0;
int iters=0;
void *myseg = NULL;
int accuracy = 0;
int maxthreads = 2;
int threads = 1;

void header(const char *desc) {
      printf("\n---- %s ----\n"
             "   Threads    Total time    Avg. time\n"
             "   -------    ----------    ---------\n", desc);
}
void report(int threads, int64_t totaltime, int iters) {
      char format[80];
      snprintf(format, sizeof(format), "%c:  %%6i     %%%i.%if s  %%%i.%if us\n", 
              TEST_SECTION_NAME(), (4+accuracy), accuracy, (4+accuracy), accuracy);
      printf(format, threads, totaltime/1.0E9, (totaltime/1000.0)/iters);
      fflush(stdout);
}

/* placed in a function to avoid excessive inlining */
gasnett_tick_t ticktime(void) { return gasnett_ticks_now(); }
uint64_t tickcvt(gasnett_tick_t ticks) { return gasnett_ticks_to_ns(ticks); }

void* thread_fn1(void*);
void* thread_fn2(void*);
void* thread_fn3(void*);
void* thread_fn4(void*);

/* ------------------------------------------------------------------------------------ */
/* This tester measures the performance of contended HSLs and pthread mutexes.
 */
int main(int argc, char **argv) {
  

  GASNET_Safe(gex_Client_Init(&myclient, &myep, &myteam, "testlockcontend", &argc, &argv, 0));
  GASNET_Safe(gex_Segment_Attach(&mysegment, myteam, TEST_SEGSZ_REQUEST));
  test_init("testlockcontend",1,"(maxthreads) (iters) (accuracy) (test sections)");

  if (argc > 1) maxthreads = atoi(argv[1]);
  maxthreads = test_thread_limit(maxthreads);
  if (maxthreads < 1) {
    printf("Threads must be between 1 and %i\n", TEST_MAXTHREADS);
    gasnet_exit(-1);
  }

  if (argc > 2) iters = atoi(argv[2]);
  if (!iters) iters = 1000000;

  if (argc > 3) accuracy = atoi(argv[3]);
  if (!accuracy) accuracy = 3;

  if (argc > 4) TEST_SECTION_PARSE(argv[4]);

  if (argc > 5) test_usage();

  mynode = gex_TM_QueryRank(myteam);
  myseg = TEST_MYSEG();

  if (mynode == 0) {
    printf("Running locks performance test with 1..%i threads and %i iterations...\n",maxthreads,iters);
    fflush(stdout);
    MSG0("Spawning pthreads...");
    if (TEST_SECTION_BEGIN_ENABLED()) {
      header("lock/unlock contended pthread mutex (others in thread barrier)");
      test_createandjoin_pthreads(threads = maxthreads, &thread_fn1, NULL, 0);
    }
    if (TEST_SECTION_BEGIN_ENABLED()) {
      header("lock/unlock contended HSL (others in thread barrier)");
      test_createandjoin_pthreads(threads = maxthreads, &thread_fn2, NULL, 0);
    }
    if (TEST_SECTION_BEGIN_ENABLED()) {
      header("lock/unlock contended pthread mutex (no other threads)");
      for (threads=1; threads<=maxthreads; ++threads) {
	test_createandjoin_pthreads(threads, &thread_fn3, NULL, 0);
      }
    }
    if (TEST_SECTION_BEGIN_ENABLED()) {
      header("lock/unlock contended HSL (no other threads)");
      for (threads=1; threads<=maxthreads; ++threads) {
	test_createandjoin_pthreads(threads, &thread_fn4, NULL, 0);
      }
    }
  }

  BARRIER();
  MSG("done.");

  gasnet_exit(0);
  return 0;
}

/* ------------------------------------------------------------------------------------ */
#undef MSG0
#undef ERR
#define MSG0 THREAD_MSG0(id)
#define ERR  THREAD_ERR(id)

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
char _pad[GASNETT_CACHE_LINE_BYTES] = { 0 }; /* bug 2231 workaround */
gex_HSL_t hsl = GEX_HSL_INITIALIZER;

/* ------------------------------------------------------------------------------------ */
#define TIME_OPERATION_SOME(id, op)                             \
do {                                                            \
  PTHREAD_LOCALBARRIER(threads);                                \
  { int i, _thr, _iters = iters, _warmupiters = MAX(1,iters/10);\
    gasnett_tick_t start,end;  /* use ticks interface */        \
    for (i=0; i < _warmupiters; i++) { op; } /* warm-up */      \
    for (_thr = 1; _thr <= threads; ++_thr) {                   \
      PTHREAD_LOCALBARRIER(threads);                            \
      start = ticktime();                                       \
      if (id < _thr) for (i=0; i < _iters; i++) { op; }         \
      PTHREAD_LOCALBARRIER(threads);                            \
      end = ticktime();                                         \
      if (!id)                                                  \
        report(_thr, tickcvt(end - start), iters);              \
    }                                                           \
  }                                                             \
} while (0)

void * thread_fn1(void *arg) { GASNET_BEGIN_FUNCTION();
  int id = (int)(uintptr_t)arg;
  TIME_OPERATION_SOME(id, { pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex); });
  return NULL;
}

void * thread_fn2(void *arg) { GASNET_BEGIN_FUNCTION();
  int id = (int)(uintptr_t)arg;
  TIME_OPERATION_SOME(id, { gex_HSL_Lock(&hsl); gex_HSL_Unlock(&hsl); });
  return NULL;
}

/* ------------------------------------------------------------------------------------ */
#define TIME_OPERATION_ALL(id, op)                              \
do {                                                            \
  PTHREAD_LOCALBARRIER(threads);                                \
  { int i, _iters = iters, _warmupiters = MAX(1,iters/10);      \
    gasnett_tick_t start,end;  /* use ticks interface */        \
    for (i=0; i < _warmupiters; i++) { op; } /* warm-up */      \
    PTHREAD_LOCALBARRIER(threads);                              \
    start = ticktime();                                         \
    for (i=0; i < _iters; i++) { op; }                          \
    PTHREAD_LOCALBARRIER(threads);                              \
    end = ticktime();                                           \
    if (!id)                                                    \
      report(threads, tickcvt(end - start), iters);             \
  }                                                             \
} while (0)

void * thread_fn3(void *arg) { GASNET_BEGIN_FUNCTION();
  int id = (int)(uintptr_t)arg;
  TIME_OPERATION_ALL(id, { pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex); });
  return NULL;
}

void * thread_fn4(void *arg) { GASNET_BEGIN_FUNCTION();
  int id = (int)(uintptr_t)arg;
  TIME_OPERATION_ALL(id, { gex_HSL_Lock(&hsl); gex_HSL_Unlock(&hsl); });
  return NULL;
}
/* ------------------------------------------------------------------------------------ */

