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