/* -*- 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/. */

/***********************************************************************
**  1996 - Netscape Communications Corporation
**
** Name: alarmtst.c
**
** Description: Test alarms
**
** Modification History:
** 13-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
***********************************************************************/

#include "prlog.h"
#include "prinit.h"
#include "obsolete/pralarm.h"
#include "prlock.h"
#include "prlong.h"
#include "prcvar.h"
#include "prinrval.h"
#include "prtime.h"

/* Used to get the command line option */
#include "plgetopt.h"
#include <stdio.h>
#include <stdlib.h>

#if defined(XP_UNIX)
#  include <sys/time.h>
#endif

static PRIntn debug_mode;
static PRIntn failed_already = 0;
static PRThreadScope thread_scope = PR_LOCAL_THREAD;

typedef struct notifyData {
  PRLock* ml;
  PRCondVar* child;
  PRCondVar* parent;
  PRBool pending;
  PRUint32 counter;
} NotifyData;

static void Notifier(void* arg) {
  NotifyData* notifyData = (NotifyData*)arg;
  PR_Lock(notifyData->ml);
  while (notifyData->counter > 0) {
    while (!notifyData->pending) {
      PR_WaitCondVar(notifyData->child, PR_INTERVAL_NO_TIMEOUT);
    }
    notifyData->counter -= 1;
    notifyData->pending = PR_FALSE;
    PR_NotifyCondVar(notifyData->parent);
  }
  PR_Unlock(notifyData->ml);
} /* Notifier */
/***********************************************************************
** PRIVATE FUNCTION:    ConditionNotify
** DESCRIPTION:
**
** INPUTS:      loops
** OUTPUTS:     None
** RETURN:      overhead
** SIDE EFFECTS:
**
** RESTRICTIONS:
**      None
** MEMORY:      NA
** ALGORITHM:
**
***********************************************************************/

static PRIntervalTime ConditionNotify(PRUint32 loops) {
  PRThread* thread;
  NotifyData notifyData;
  PRIntervalTime timein, overhead;

  timein = PR_IntervalNow();

  notifyData.counter = loops;
  notifyData.ml = PR_NewLock();
  notifyData.child = PR_NewCondVar(notifyData.ml);
  notifyData.parent = PR_NewCondVar(notifyData.ml);
  thread = PR_CreateThread(PR_USER_THREAD, Notifier, &notifyData,
                           PR_GetThreadPriority(PR_GetCurrentThread()),
                           thread_scope, PR_JOINABLE_THREAD, 0);

  overhead = PR_IntervalNow() - timein; /* elapsed so far */

  PR_Lock(notifyData.ml);
  while (notifyData.counter > 0) {
    notifyData.pending = PR_TRUE;
    PR_NotifyCondVar(notifyData.child);
    while (notifyData.pending) {
      PR_WaitCondVar(notifyData.parent, PR_INTERVAL_NO_TIMEOUT);
    }
  }
  PR_Unlock(notifyData.ml);

  timein = PR_IntervalNow();

  (void)PR_JoinThread(thread);
  PR_DestroyCondVar(notifyData.child);
  PR_DestroyCondVar(notifyData.parent);
  PR_DestroyLock(notifyData.ml);

  overhead += (PR_IntervalNow() - timein); /* more overhead */

  return overhead;
} /* ConditionNotify */

static PRIntervalTime ConditionTimeout(PRUint32 loops) {
  PRUintn count;
  PRIntervalTime overhead, timein = PR_IntervalNow();

  PRLock* ml = PR_NewLock();
  PRCondVar* cv = PR_NewCondVar(ml);
  PRIntervalTime interval = PR_MillisecondsToInterval(50);

  overhead = PR_IntervalNow() - timein;

  PR_Lock(ml);
  for (count = 0; count < loops; ++count) {
    overhead += interval;
    PR_ASSERT(PR_WaitCondVar(cv, interval) == PR_SUCCESS);
  }
  PR_Unlock(ml);

  timein = PR_IntervalNow();
  PR_DestroyCondVar(cv);
  PR_DestroyLock(ml);
  overhead += (PR_IntervalNow() - timein);

  return overhead;
} /* ConditionTimeout */

typedef struct AlarmData {
  PRLock* ml;
  PRCondVar* cv;
  PRUint32 rate, late, times;
  PRIntervalTime duration, timein, period;
} AlarmData;

static PRBool AlarmFn1(PRAlarmID* id, void* clientData, PRUint32 late) {
  PRStatus rv = PR_SUCCESS;
  PRBool keepGoing, resetAlarm;
  PRIntervalTime interval, now = PR_IntervalNow();
  AlarmData* ad = (AlarmData*)clientData;

  PR_Lock(ad->ml);
  ad->late += late;
  ad->times += 1;
  keepGoing =
      ((PRIntervalTime)(now - ad->timein) < ad->duration) ? PR_TRUE : PR_FALSE;
  if (!keepGoing) {
    rv = PR_NotifyCondVar(ad->cv);
  }
  resetAlarm = ((ad->times % 31) == 0) ? PR_TRUE : PR_FALSE;

  interval = (ad->period + ad->rate - 1) / ad->rate;
  if (!late && (interval > 10)) {
    interval &= (now & 0x03) + 1;
    PR_WaitCondVar(ad->cv, interval);
  }

  PR_Unlock(ad->ml);

  if (rv != PR_SUCCESS) {
    if (!debug_mode) {
      failed_already = 1;
    } else {
      printf("AlarmFn: notify status: FAIL\n");
    }
  }

  if (resetAlarm) {
    ad->rate += 3;
    ad->late = ad->times = 0;
    if (PR_ResetAlarm(id, ad->period, ad->rate) != PR_SUCCESS) {
      if (!debug_mode) {
        failed_already = 1;
      } else {
        printf("AlarmFn: Resetting alarm status: FAIL\n");
      }

      keepGoing = PR_FALSE;
    }
  }

  return keepGoing;
} /* AlarmFn1 */

static PRIntervalTime Alarms1(PRUint32 loops) {
  PRAlarm* alarm;
  AlarmData ad;
  PRIntervalTime overhead, timein = PR_IntervalNow();
  PRIntervalTime duration = PR_SecondsToInterval(3);

  PRLock* ml = PR_NewLock();
  PRCondVar* cv = PR_NewCondVar(ml);

  ad.ml = ml;
  ad.cv = cv;
  ad.rate = 1;
  ad.times = loops;
  ad.late = ad.times = 0;
  ad.duration = duration;
  ad.timein = PR_IntervalNow();
  ad.period = PR_SecondsToInterval(1);

  alarm = PR_CreateAlarm();

  (void)PR_SetAlarm(alarm, ad.period, ad.rate, AlarmFn1, &ad);

  overhead = PR_IntervalNow() - timein;

  PR_Lock(ml);
  while ((PRIntervalTime)(PR_IntervalNow() - ad.timein) < duration) {
    PR_WaitCondVar(cv, PR_INTERVAL_NO_TIMEOUT);
  }
  PR_Unlock(ml);

  timein = PR_IntervalNow();
  (void)PR_DestroyAlarm(alarm);
  PR_DestroyCondVar(cv);
  PR_DestroyLock(ml);
  overhead += (PR_IntervalNow() - timein);

  return duration + overhead;
} /* Alarms1 */

static PRBool AlarmFn2(PRAlarmID* id, void* clientData, PRUint32 late) {
  PRBool keepGoing;
  PRStatus rv = PR_SUCCESS;
  AlarmData* ad = (AlarmData*)clientData;
  PRIntervalTime interval, now = PR_IntervalNow();

  PR_Lock(ad->ml);
  ad->times += 1;
  keepGoing =
      ((PRIntervalTime)(now - ad->timein) < ad->duration) ? PR_TRUE : PR_FALSE;
  interval = (ad->period + ad->rate - 1) / ad->rate;

  if (!late && (interval > 10)) {
    interval &= (now & 0x03) + 1;
    PR_WaitCondVar(ad->cv, interval);
  }

  if (!keepGoing) {
    rv = PR_NotifyCondVar(ad->cv);
  }

  PR_Unlock(ad->ml);

  if (rv != PR_SUCCESS) {
    failed_already = 1;
  };

  return keepGoing;
} /* AlarmFn2 */

static PRIntervalTime Alarms2(PRUint32 loops) {
  PRStatus rv;
  PRAlarm* alarm;
  PRIntervalTime overhead, timein = PR_IntervalNow();
  AlarmData ad;
  PRIntervalTime duration = PR_SecondsToInterval(30);

  PRLock* ml = PR_NewLock();
  PRCondVar* cv = PR_NewCondVar(ml);

  ad.ml = ml;
  ad.cv = cv;
  ad.rate = 1;
  ad.times = loops;
  ad.late = ad.times = 0;
  ad.duration = duration;
  ad.timein = PR_IntervalNow();
  ad.period = PR_SecondsToInterval(1);

  alarm = PR_CreateAlarm();

  (void)PR_SetAlarm(alarm, ad.period, ad.rate, AlarmFn2, &ad);

  overhead = PR_IntervalNow() - timein;

  PR_Lock(ml);
  while ((PRIntervalTime)(PR_IntervalNow() - ad.timein) < duration) {
    PR_WaitCondVar(cv, PR_INTERVAL_NO_TIMEOUT);
  }
  PR_Unlock(ml);

  timein = PR_IntervalNow();

  rv = PR_DestroyAlarm(alarm);
  if (rv != PR_SUCCESS) {
    if (!debug_mode) {
      failed_already = 1;
    } else {
      printf("***Destroying alarm status: FAIL\n");
    }
  }

  PR_DestroyCondVar(cv);
  PR_DestroyLock(ml);

  overhead += (PR_IntervalNow() - timein);

  return duration + overhead;
} /* Alarms2 */

static PRIntervalTime Alarms3(PRUint32 loops) {
  PRIntn i;
  PRStatus rv;
  PRAlarm* alarm;
  AlarmData ad[3];
  PRIntervalTime duration = PR_SecondsToInterval(30);
  PRIntervalTime overhead, timein = PR_IntervalNow();

  PRLock* ml = PR_NewLock();
  PRCondVar* cv = PR_NewCondVar(ml);

  for (i = 0; i < 3; ++i) {
    ad[i].ml = ml;
    ad[i].cv = cv;
    ad[i].rate = 1;
    ad[i].times = loops;
    ad[i].duration = duration;
    ad[i].late = ad[i].times = 0;
    ad[i].timein = PR_IntervalNow();
    ad[i].period = PR_SecondsToInterval(1);

    /* more loops, faster rate => same elapsed time */
    ad[i].times = (i + 1) * loops;
    ad[i].rate = (i + 1) * 10;
  }

  alarm = PR_CreateAlarm();

  for (i = 0; i < 3; ++i) {
    (void)PR_SetAlarm(alarm, ad[i].period, ad[i].rate, AlarmFn2, &ad[i]);
  }

  overhead = PR_IntervalNow() - timein;

  PR_Lock(ml);
  for (i = 0; i < 3; ++i) {
    while ((PRIntervalTime)(PR_IntervalNow() - ad[i].timein) < duration) {
      PR_WaitCondVar(cv, PR_INTERVAL_NO_TIMEOUT);
    }
  }
  PR_Unlock(ml);

  timein = PR_IntervalNow();

  if (debug_mode)
    printf("Alarms3 finished at %u, %u, %u\n", ad[0].timein, ad[1].timein,
           ad[2].timein);

  rv = PR_DestroyAlarm(alarm);
  if (rv != PR_SUCCESS) {
    if (!debug_mode) {
      failed_already = 1;
    } else {
      printf("***Destroying alarm status: FAIL\n");
    }
  }
  PR_DestroyCondVar(cv);
  PR_DestroyLock(ml);

  overhead += (duration / 3);
  overhead += (PR_IntervalNow() - timein);

  return overhead;
} /* Alarms3 */

static PRUint32 TimeThis(const char* msg, PRUint32 (*func)(PRUint32 loops),
                         PRUint32 loops) {
  PRUint32 overhead, usecs;
  PRIntervalTime predicted, timein, timeout, ticks;

  if (debug_mode) {
    printf("Testing %s ...", msg);
  }

  timein = PR_IntervalNow();
  predicted = func(loops);
  timeout = PR_IntervalNow();

  if (debug_mode) {
    printf(" done\n");
  }

  ticks = timeout - timein;
  usecs = PR_IntervalToMicroseconds(ticks);
  overhead = PR_IntervalToMicroseconds(predicted);

  if (ticks < predicted) {
    if (debug_mode) {
      printf("\tFinished in negative time\n");
      printf("\tpredicted overhead was %d usecs\n", overhead);
      printf("\ttest completed in %d usecs\n\n", usecs);
    }
  } else {
    if (debug_mode)
      printf("\ttotal: %d usecs\n\toverhead: %d usecs\n\tcost: %6.3f usecs\n\n",
             usecs, overhead, ((double)(usecs - overhead) / (double)loops));
  }

  return overhead;
} /* TimeThis */

int prmain(int argc, char** argv) {
  PRUint32 cpu, cpus = 0, loops = 0;

  /* 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, "Gdl:c:");
  while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
    if (PL_OPT_BAD == os) {
      continue;
    }
    switch (opt->option) {
      case 'G': /* GLOBAL threads */
        thread_scope = PR_GLOBAL_THREAD;
        break;
      case 'd': /* debug mode */
        debug_mode = 1;
        break;
      case 'l': /* loop count */
        loops = atoi(opt->value);
        break;
      case 'c': /* concurrency limit */
        cpus = atoi(opt->value);
        break;
      default:
        break;
    }
  }
  PL_DestroyOptState(opt);

  if (cpus == 0) {
    cpus = 1;
  }
  if (loops == 0) {
    loops = 4;
  }

  if (debug_mode) {
    printf("Alarm: Using %d loops\n", loops);
  }

  if (debug_mode) {
    printf("Alarm: Using %d cpu(s)\n", cpus);
  }

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

    PR_SetConcurrency(cpu);

    /* some basic time test */
    (void)TimeThis("ConditionNotify", ConditionNotify, loops);
    (void)TimeThis("ConditionTimeout", ConditionTimeout, loops);
    (void)TimeThis("Alarms1", Alarms1, loops);
    (void)TimeThis("Alarms2", Alarms2, loops);
    (void)TimeThis("Alarms3", Alarms3, loops);
  }
  return 0;
}

int main(int argc, char** argv) {
  PR_Initialize(prmain, argc, argv, 0);
  if (failed_already) {
    return 1;
  } else {
    return 0;
  }

} /* main */

/* alarmtst.c */
