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