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