/* 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:        arena.c
** Description: Testing arenas
**
*/

#include <string.h>
#include <time.h>
#include <stdlib.h>
#include "nspr.h"
#include "plarena.h"
#include "plgetopt.h"

PRLogModuleInfo* tLM;
PRIntn threadCount = 0;
PRMonitor* tMon;
PRBool failed_already = PR_FALSE;

/* Arguments from the command line with default values */
PRIntn debug_mode = 0;
PRIntn poolMin = 4096;
PRIntn poolMax = (100 * 4096);
PRIntn arenaMin = 40;
PRIntn arenaMax = (100 * 40);
PRIntn stressIterations = 15;
PRIntn maxAlloc = (1024 * 1024);
PRIntn stressThreads = 4;

void DumpAll(void) { return; }

/*
** Test Arena allocation.
*/
static void ArenaAllocate(void) {
  PLArenaPool ap;
  void* ptr;
  PRInt32 i;

  PL_InitArenaPool(&ap, "AllocArena", 2048, sizeof(double));
  PR_LOG(tLM, PR_LOG_DEBUG,
         ("AA, InitPool -- Pool: %p. first: %p, current: %p, size: %d", &ap,
          ap.first, ap.current, ap.arenasize));

  for (i = 0; i < 150; i++) {
    PL_ARENA_ALLOCATE(ptr, &ap, 512);
    PR_LOG(tLM, PR_LOG_DEBUG,
           ("AA, after alloc -- Pool: %p. first: %p, current: %p, size: %d",
            &ap, ap.first, ap.current, ap.arenasize));
    PR_LOG(tLM, PR_LOG_DEBUG, ("AA -- Pool: %p. alloc: %p ", &ap, ptr));
  }

  PL_FreeArenaPool(&ap);

  for (i = 0; i < 221; i++) {
    PL_ARENA_ALLOCATE(ptr, &ap, 512);
    PR_LOG(tLM, PR_LOG_DEBUG,
           ("AA, after alloc -- Pool: %p. first: %p, current: %p, size: %d",
            &ap, ap.first, ap.current, ap.arenasize));
    PR_LOG(tLM, PR_LOG_DEBUG, ("AA -- Pool: %p. alloc: %p ", &ap, ptr));
  }

  PL_FreeArenaPool(&ap);

  return;
} /* end ArenaGrow() */
/*
** Test Arena grow.
*/
static void ArenaGrow(void) {
  PLArenaPool ap;
  void* ptr;
  PRInt32 i;

  PL_InitArenaPool(&ap, "TheArena", 4096, sizeof(double));
  PL_ARENA_ALLOCATE(ptr, &ap, 512);

  PR_LOG(tLM, PR_LOG_DEBUG,
         ("Before growth -- Pool: %p. alloc: %p ", &ap, ptr));

  for (i = 0; i < 10; i++) {
    PL_ARENA_GROW(ptr, &ap, 512, 7000);
    PR_LOG(tLM, PR_LOG_DEBUG,
           ("After growth -- Pool: %p. alloc: %p ", &ap, ptr));
  }

  return;
} /* end ArenaGrow() */

/*
** Test arena Mark and Release.
*/
static void MarkAndRelease(void) {
  PLArenaPool ap;
  void* ptr = NULL;
  void *mark0, *mark1;
  PRIntn i;

  PL_InitArenaPool(&ap, "TheArena", 4096, sizeof(double));
  mark0 = PL_ARENA_MARK(&ap);
  PR_LOG(tLM, PR_LOG_DEBUG,
         ("mark0. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p, m0: %p",
          &ap, ap.first.next, ap.current, ap.arenasize, ptr, mark0));

  for (i = 0; i < 201; i++) {
    PL_ARENA_ALLOCATE(ptr, &ap, 512);
    PR_LOG(tLM, PR_LOG_DEBUG,
           ("mr. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p", &ap,
            ap.first.next, ap.current, ap.arenasize, ptr));
  }

  mark1 = PL_ARENA_MARK(&ap);
  PR_LOG(tLM, PR_LOG_DEBUG,
         ("mark1. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p, m1: %p",
          &ap, ap.first.next, ap.current, ap.arenasize, ptr, mark1));

  for (i = 0; i < 225; i++) {
    PL_ARENA_ALLOCATE(ptr, &ap, 512);
    PR_LOG(tLM, PR_LOG_DEBUG,
           ("mr. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p", &ap,
            ap.first.next, ap.current, ap.arenasize, ptr));
  }

  PL_ARENA_RELEASE(&ap, mark1);
  PR_LOG(tLM, PR_LOG_DEBUG,
         ("Release-1: %p -- Pool: %p. first: %p, current: %p, size: %d", mark1,
          &ap, ap.first, ap.current, ap.arenasize));

  for (i = 0; i < 20; i++) {
    PL_ARENA_ALLOCATE(ptr, &ap, 512);
    PR_LOG(tLM, PR_LOG_DEBUG,
           ("mr. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p", &ap,
            ap.first.next, ap.current, ap.arenasize, ptr));
  }

  PL_ARENA_RELEASE(&ap, mark1);
  PR_LOG(tLM, PR_LOG_DEBUG,
         ("Release-1. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p", &ap,
          ap.first.next, ap.current, ap.arenasize, ptr));

  PL_ARENA_RELEASE(&ap, mark0);
  PR_LOG(tLM, PR_LOG_DEBUG,
         ("Release-0. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p", &ap,
          ap.first.next, ap.current, ap.arenasize, ptr));

  PL_FreeArenaPool(&ap);
  PR_LOG(tLM, PR_LOG_DEBUG,
         ("Free. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p", &ap,
          ap.first.next, ap.current, ap.arenasize, ptr));

  PL_FinishArenaPool(&ap);
  PR_LOG(tLM, PR_LOG_DEBUG,
         ("Finish. ap: %p, ap.f: %p, ap.c: %p, ap.siz: %d, alloc: %p", &ap,
          ap.first.next, ap.current, ap.arenasize, ptr));

  return;
} /* end MarkAndRelease() */

/*
** RandSize() returns a random number in the range
** min..max, rounded to the next doubleword
**
*/
static PRIntn RandSize(PRIntn min, PRIntn max) {
  PRIntn sz = (rand() % (max - min)) + min + sizeof(double);

  sz &= ~sizeof(double) - 1;

  return (sz);
}

/*
** StressThread()
** A bunch of these beat on individual arenas
** This tests the free_list protection.
**
*/
static void PR_CALLBACK StressThread(void* arg) {
  PLArenaPool ap;
  PRIntn i;
  PRIntn sz;
  void* ptr;
  PRThread* tp = PR_GetCurrentThread();

  PR_LOG(tLM, PR_LOG_DEBUG,
         ("Stress Thread %p started\n", PR_GetCurrentThread()));
  PL_InitArenaPool(&ap, "TheArena", RandSize(poolMin, poolMax), sizeof(double));

  for (i = 0; i < stressIterations; i++) {
    PRIntn allocated = 0;

    while (allocated < maxAlloc) {
      sz = RandSize(arenaMin, arenaMax);
      PL_ARENA_ALLOCATE(ptr, &ap, sz);
      if (ptr == NULL) {
        PR_LOG(
            tLM, PR_LOG_ERROR,
            ("ARENA_ALLOCATE() returned NULL\n\tAllocated: %d\n", allocated));
        break;
      }
      allocated += sz;
    }
    PR_LOG(tLM, PR_LOG_DEBUG,
           ("Stress thread %p finished one iteration\n", tp));
    PL_FreeArenaPool(&ap);
  }
  PR_LOG(tLM, PR_LOG_DEBUG, ("Stress thread %p finished all iteration\n", tp));
  PL_FinishArenaPool(&ap);
  PR_LOG(tLM, PR_LOG_DEBUG, ("Stress thread %p after FinishArenaPool()\n", tp));

  /* That's all folks! let's quit */
  PR_EnterMonitor(tMon);
  threadCount--;
  PR_Notify(tMon);
  PR_ExitMonitor(tMon);
  return;
}

/*
** Stress()
** Flog the hell out of arenas multi-threaded.
** Do NOT pass an individual arena to another thread.
**
*/
static void Stress(void) {
  PRThread* tt;
  PRIntn i;

  tMon = PR_NewMonitor();

  for (i = 0; i < stressThreads; i++) {
    PR_EnterMonitor(tMon);
    tt = PR_CreateThread(PR_USER_THREAD, StressThread, NULL, PR_PRIORITY_NORMAL,
                         PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0);
    threadCount++;
    PR_ExitMonitor(tMon);
  }

  /* Wait for all threads to exit */
  PR_EnterMonitor(tMon);
  while (threadCount != 0) {
    PR_Wait(tMon, PR_INTERVAL_NO_TIMEOUT);
  }
  PR_ExitMonitor(tMon);
  PR_DestroyMonitor(tMon);

  return;
} /* end Stress() */

/*
** EvaluateResults()
** uses failed_already to display results and set program
** exit code.
*/
static PRIntn EvaluateResults(void) {
  PRIntn rc = 0;

  if (failed_already == PR_TRUE) {
    PR_LOG(tLM, PR_LOG_DEBUG, ("FAIL\n"));
    rc = 1;
  } else {
    PR_LOG(tLM, PR_LOG_DEBUG, ("PASS\n"));
  }
  return (rc);
} /* EvaluateResults() */

void Help(void) {
  printf("arena [options]\n");
  printf("where options are:\n");
  printf("-p <n>   minimum size of an arena pool. Default(%d)\n", poolMin);
  printf("-P <n>   maximum size of an arena pool. Default(%d)\n", poolMax);
  printf("-a <n>   minimum size of an arena allocation. Default(%d)\n",
         arenaMin);
  printf("-A <n>   maximum size of an arena allocation. Default(%d)\n",
         arenaMax);
  printf("-i <n>   number of iterations in a stress thread. Default(%d)\n",
         stressIterations);
  printf(
      "-s <n>   maximum allocation for a single stress thread. Default(%d)\n",
      maxAlloc);
  printf("-t <n>   number of stress threads. Default(%d)\n", stressThreads);
  printf("-d       enable debug mode\n");
  printf("\n");
  exit(1);
}

PRIntn main(PRIntn argc, char* argv[]) {
  PLOptStatus os;
  PLOptState* opt = PL_CreateOptState(argc, argv, "dhp:P:a:A:i:s:t:");
  while (PL_OPT_EOL != (os = PL_GetNextOpt(opt))) {
    if (PL_OPT_BAD == os) {
      continue;
    }
    switch (opt->option) {
      case 'a': /* arena Min size */
        arenaMin = atol(opt->value);
        break;
      case 'A': /* arena Max size  */
        arenaMax = atol(opt->value);
        break;
      case 'p': /* pool Min size */
        poolMin = atol(opt->value);
        break;
      case 'P': /* pool Max size */
        poolMax = atol(opt->value);
        break;
      case 'i': /* Iterations in stress tests */
        stressIterations = atol(opt->value);
        break;
      case 's': /* storage to get per iteration */
        maxAlloc = atol(opt->value);
        break;
      case 't': /* Number of stress threads to create */
        stressThreads = atol(opt->value);
        break;
      case 'd': /* debug mode */
        debug_mode = 1;
        break;
      case 'h': /* help */
      default:
        Help();
    } /* end switch() */
  } /* end while() */
  PL_DestroyOptState(opt);

  srand((unsigned)time(NULL)); /* seed random number generator */
  tLM = PR_NewLogModule("testcase");

#if 0
    ArenaAllocate();
    ArenaGrow();
#endif

  MarkAndRelease();

  Stress();

  return (EvaluateResults());
} /* end main() */

/* arena.c */
