/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
** File:        lockfile.c
** Purpose:     test basic locking functions
**              Just because this times stuff, don't think its a perforamnce
**              test!!!
**
** Modification History:
** 19-May-97 AGarcia- Converted the test to accomodate the debug_mode flag.
**           The debug mode will print all of the printfs associated with this
*test.
**           The regress mode will be the default mode. Since the regress tool
*limits
**           the output to a one line status:PASS or FAIL,all of the printf
*statements
**           have been handled with an if (debug_mode) statement.
** 04-June-97 AGarcia removed the Test_Result function. Regress tool has been
*updated to
**          recognize the return code from tha main program.
***********************************************************************/
/***********************************************************************
** Includes
***********************************************************************/
/* Used to get the command line option */
#include "plgetopt.h"

#include "prcmon.h"
#include "prerror.h"
#include "prinit.h"
#include "prinrval.h"
#include "prlock.h"
#include "prlog.h"
#include "prmon.h"
#include "prthread.h"
#include "prtypes.h"

#include "private/pprio.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

PRIntn failed_already = 0;
PRIntn debug_mode;

const static PRIntervalTime contention_interval = 50;

typedef struct LockContentious_s {
  PRLock* ml;
  PRInt32 loops;
  PRIntervalTime overhead;
  PRIntervalTime interval;
} LockContentious_t;

#define LOCKFILE "prlock.fil"

static PRIntervalTime NonContentiousLock(PRInt32 loops) {
  PRFileDesc* _lockfile;
  while (loops-- > 0) {
    _lockfile = PR_Open(LOCKFILE, PR_CREATE_FILE | PR_RDWR, 0666);
    if (!_lockfile) {
      if (debug_mode)
        printf("could not create lockfile: %d [%d]\n", PR_GetError(),
               PR_GetOSError());
      return PR_INTERVAL_NO_TIMEOUT;
    }
    PR_LockFile(_lockfile);
    PR_UnlockFile(_lockfile);
    PR_Close(_lockfile);
  }
  return 0;
} /* NonContentiousLock */

static void PR_CALLBACK LockContender(void* arg) {
  LockContentious_t* contention = (LockContentious_t*)arg;
  PRFileDesc* _lockfile;
  while (contention->loops-- > 0) {
    _lockfile = PR_Open(LOCKFILE, PR_CREATE_FILE | PR_RDWR, 0666);
    if (!_lockfile) {
      if (debug_mode)
        printf("could not create lockfile: %d [%d]\n", PR_GetError(),
               PR_GetOSError());
      break;
    }
    PR_LockFile(_lockfile);
    PR_Sleep(contention->interval);
    PR_UnlockFile(_lockfile);
    PR_Close(_lockfile);
  }

} /* LockContender */

/*
** Win16 requires things passed to Threads not be on the stack
*/
static LockContentious_t contention;

static PRIntervalTime ContentiousLock(PRInt32 loops) {
  PRStatus status;
  PRThread* thread = NULL;
  PRIntervalTime overhead, timein = PR_IntervalNow();

  contention.loops = loops;
  contention.overhead = 0;
  contention.ml = PR_NewLock();
  contention.interval = contention_interval;
  thread =
      PR_CreateThread(PR_USER_THREAD, LockContender, &contention,
                      PR_PRIORITY_LOW, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
  PR_ASSERT(thread != NULL);

  overhead = PR_IntervalNow() - timein;

  while (contention.loops > 0) {
    PR_Lock(contention.ml);
    contention.overhead += contention.interval;
    PR_Sleep(contention.interval);
    PR_Unlock(contention.ml);
  }

  timein = PR_IntervalNow();
  status = PR_JoinThread(thread);
  PR_DestroyLock(contention.ml);
  overhead += (PR_IntervalNow() - timein);
  return overhead + contention.overhead;
} /* ContentiousLock */

static PRIntervalTime Test(const char* msg,
                           PRIntervalTime (*test)(PRInt32 loops), PRInt32 loops,
                           PRIntervalTime overhead) {
  /*
   * overhead - overhead not measured by the test.
   * duration - wall clock time it took to perform test.
   * predicted - extra time test says should not be counted
   *
   * Time accountable to the test is duration - overhead - predicted
   * All times are Intervals and accumulated for all iterations.
   */
  PRFloat64 elapsed;
  PRIntervalTime accountable, duration;
  PRUintn spaces = strlen(msg);
  PRIntervalTime timeout, timein = PR_IntervalNow();
  PRIntervalTime predicted = test(loops);
  timeout = PR_IntervalNow();
  duration = timeout - timein;
  accountable = duration - predicted;
  accountable -= overhead;
  elapsed = (PRFloat64)PR_IntervalToMicroseconds(accountable);
  if (debug_mode) {
    printf("%s:", msg);
  }
  while (spaces++ < 50)
    if (debug_mode) {
      printf(" ");
    }
  if ((PRInt32)accountable < 0) {
    if (debug_mode) {
      printf("*****.** usecs/iteration\n");
    }
  } else {
    if (debug_mode) {
      printf("%8.2f usecs/iteration\n", elapsed / loops);
    }
  }
  return duration;
} /* Test */

int main(int argc, char** argv) {
  PRIntervalTime duration;
  PRUint32 cpu, cpus = 2;
  PRInt32 loops = 100;

  /* The command line argument: -d is used to determine if the test is being run
  in debug mode. The regress tool requires only one line output:PASS or FAIL.
  All of the printfs associated with this test has been handled with a if
  (debug_mode) test. Usage: test_name -d
  */
  PLOptStatus os;
  PLOptState* opt = PL_CreateOptState(argc, argv, "d:");
  while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
    if (PL_OPT_BAD == os) {
      continue;
    }
    switch (opt->option) {
      case 'd': /* debug mode */
        debug_mode = 1;
        break;
      default:
        break;
    }
  }
  PL_DestroyOptState(opt);

  /* main test */

  PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);

  if (argc > 1) {
    loops = atoi(argv[1]);
  }
  if (loops == 0) {
    loops = 100;
  }
  if (debug_mode) {
    printf("Lock: Using %d loops\n", loops);
  }

  cpus = (argc < 3) ? 2 : atoi(argv[2]);
  if (cpus == 0) {
    cpus = 2;
  }
  if (debug_mode) {
    printf("Lock: Using %d cpu(s)\n", cpus);
  }

  for (cpu = 1; cpu <= cpus; ++cpu) {
    if (debug_mode) {
      printf("\nLockFile: Using %d CPU(s)\n", cpu);
    }
    PR_SetConcurrency(cpu);

    duration = Test("LockFile non-contentious locking/unlocking",
                    NonContentiousLock, loops, 0);
    (void)Test("LockFile contentious locking/unlocking", ContentiousLock, loops,
               duration);
  }

  PR_Delete(LOCKFILE); /* try to get rid of evidence */

  if (debug_mode) {
    printf("%s: test %s\n", "Lock(mutex) test",
           ((failed_already) ? "failed" : "passed"));
  }
  if (failed_already) {
    return 1;
  } else {
    return 0;
  }
} /* main */

/* testlock.c */
