/* -*- 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:    instrumt.c
** Description: This test is for the NSPR debug aids defined in
** prcountr.h, prtrace.h, prolock.h
**
** The test case tests the three debug aids in NSPR:
**
** Diagnostic messages can be enabled using "instrumt -v 6"
** This sets the msgLevel to something that PR_LOG() likes.
** Also define in the environment "NSPR_LOG_MODULES=Test:6"
**
** CounterTest() tests the counter facility. This test
** creates 4 threads. Each thread either increments, decrements,
** adds to or subtracts from a counter, depending on an argument
** passed to the thread at thread-create time. Each of these threads
** does COUNT_LIMIT iterations doing its thing. When all 4 threads
** are done, the result of the counter is evaluated. If all was atomic,
** the the value of the counter should be zero.
**
** TraceTest():
** This test mingles with the counter test. Counters trace.
** A thread to extract trace entries on the fly is started.
** A thread to dump trace entries to a file is started.
**
** OrderedLockTest():
**
**
**
**
**
*/

#include <stdio.h>
#include <plstr.h>
#include <prclist.h>
#include <prmem.h>
#include <plgetopt.h>
#include <prlog.h>
#include <prmon.h>
#include <pratom.h>
#include <prtrace.h>
#include <prcountr.h>
#include <prolock.h>

#define COUNT_LIMIT  (10 * ( 1024))

#define SMALL_TRACE_BUFSIZE  ( 60 * 1024 )

typedef enum
{
    CountLoop = 1,
    TraceLoop = 2,
    TraceFlow = 3
} TraceTypes;


PRLogModuleLevel msgLevel = PR_LOG_ALWAYS;

PRBool  help = PR_FALSE;
PRBool  failed = PR_FALSE;


PRLogModuleInfo *lm;
PRMonitor   *mon;
PRInt32     activeThreads = 0;
PR_DEFINE_COUNTER( hCounter );
PR_DEFINE_TRACE( hTrace );

static void Help(void)
{
    printf("Help? ... Ha!\n");
}

static void ListCounters(void)
{
    PR_DEFINE_COUNTER( qh );
    PR_DEFINE_COUNTER( rh );
    const char *qn, *rn, *dn;
    const char **qname = &qn, **rname = &rn, **desc = &dn;
    PRUint32    tCtr;

    PR_INIT_COUNTER_HANDLE( qh, NULL );
    PR_FIND_NEXT_COUNTER_QNAME(qh, qh );
    while ( qh != NULL )
    {
        PR_INIT_COUNTER_HANDLE( rh, NULL );
        PR_FIND_NEXT_COUNTER_RNAME(rh, rh, qh );
        while ( rh != NULL )
        {
            PR_GET_COUNTER_NAME_FROM_HANDLE( rh, qname, rname, desc );
            PR_GET_COUNTER(tCtr, rh);
            PR_LOG( lm, msgLevel,
                    ( "QName: %s  RName: %s  Desc: %s  Value: %ld\n",
                      qn, rn, dn, tCtr ));
            PR_FIND_NEXT_COUNTER_RNAME(rh, rh, qh );
        }
        PR_FIND_NEXT_COUNTER_QNAME(qh, qh);
    }
    return;
} /* end ListCounters() */

static void ListTraces(void)
{
    PR_DEFINE_TRACE( qh );
    PR_DEFINE_TRACE( rh );
    const char *qn, *rn, *dn;
    const char **qname = &qn, **rname = &rn, **desc = &dn;

    PR_INIT_TRACE_HANDLE( qh, NULL );
    PR_FIND_NEXT_TRACE_QNAME(qh, qh );
    while ( qh != NULL )
    {
        PR_INIT_TRACE_HANDLE( rh, NULL );
        PR_FIND_NEXT_TRACE_RNAME(rh, rh, qh );
        while ( rh != NULL )
        {
            PR_GET_TRACE_NAME_FROM_HANDLE( rh, qname, rname, desc );
            PR_LOG( lm, msgLevel,
                    ( "QName: %s  RName: %s  Desc: %s",
                      qn, rn, dn ));
            PR_FIND_NEXT_TRACE_RNAME(rh, rh, qh );
        }
        PR_FIND_NEXT_TRACE_QNAME(qh, qh);
    }
    return;
} /* end ListCounters() */


static PRInt32 one = 1;
static PRInt32 two = 2;
static PRInt32 three = 3;
static PRInt32 four = 4;

/*
** Thread to iteratively count something.
*/
static void PR_CALLBACK CountSomething( void *arg )
{
    PRInt32 switchVar = *((PRInt32 *)arg);
    PRInt32 i;

    PR_LOG( lm, msgLevel,
            ("CountSomething: begin thread %ld", switchVar ));

    for ( i = 0; i < COUNT_LIMIT ; i++)
    {
        switch ( switchVar )
        {
            case 1 :
                PR_INCREMENT_COUNTER( hCounter );
                break;
            case 2 :
                PR_DECREMENT_COUNTER( hCounter );
                break;
            case 3 :
                PR_ADD_TO_COUNTER( hCounter, 1 );
                break;
            case 4 :
                PR_SUBTRACT_FROM_COUNTER( hCounter, 1 );
                break;
            default :
                PR_ASSERT( 0 );
                break;
        }
        PR_TRACE( hTrace, CountLoop, switchVar, i, 0, 0, 0, 0, 0 );
    } /* end for() */

    PR_LOG( lm, msgLevel,
            ("CounterSomething: end thread %ld", switchVar ));

    PR_EnterMonitor(mon);
    --activeThreads;
    PR_Notify( mon );
    PR_ExitMonitor(mon);

    return;
} /* end CountSomething() */

/*
** Create the counter threads.
*/
static void CounterTest( void )
{
    PRThread *t1, *t2, *t3, *t4;
    PRIntn i = 0;
    PR_DEFINE_COUNTER( tc );
    PR_DEFINE_COUNTER( zCounter );

    PR_LOG( lm, msgLevel,
            ("Begin CounterTest"));

    /*
    ** Test Get and Set of a counter.
    **
    */
    PR_CREATE_COUNTER( zCounter, "Atomic", "get/set test", "test get and set of counter" );
    PR_SET_COUNTER( zCounter, 9 );
    PR_GET_COUNTER( i, zCounter );
    if ( i != 9 )
    {
        failed = PR_TRUE;
        PR_LOG( lm, msgLevel,
                ("Counter set/get failed"));
    }

    activeThreads += 4;
    PR_CREATE_COUNTER( hCounter, "Atomic", "SMP Tests", "test atomic nature of counter" );

    PR_GET_COUNTER_HANDLE_FROM_NAME( tc, "Atomic", "SMP Tests" );
    PR_ASSERT( tc == hCounter );

    t1 = PR_CreateThread(PR_USER_THREAD,
                         CountSomething, &one,
                         PR_PRIORITY_NORMAL,
                         PR_GLOBAL_THREAD,
                         PR_UNJOINABLE_THREAD,
                         0);
    PR_ASSERT(t1);

    t2 = PR_CreateThread(PR_USER_THREAD,
                         CountSomething, &two,
                         PR_PRIORITY_NORMAL,
                         PR_GLOBAL_THREAD,
                         PR_UNJOINABLE_THREAD,
                         0);
    PR_ASSERT(t2);

    t3 = PR_CreateThread(PR_USER_THREAD,
                         CountSomething, &three,
                         PR_PRIORITY_NORMAL,
                         PR_GLOBAL_THREAD,
                         PR_UNJOINABLE_THREAD,
                         0);
    PR_ASSERT(t3);

    t4 = PR_CreateThread(PR_USER_THREAD,
                         CountSomething, &four,
                         PR_PRIORITY_NORMAL,
                         PR_GLOBAL_THREAD,
                         PR_UNJOINABLE_THREAD,
                         0);
    PR_ASSERT(t4);

    PR_LOG( lm, msgLevel,
            ("Counter Threads started"));

    ListCounters();
    return;
} /* end CounterTest() */

/*
** Thread to dump trace buffer to a file.
*/
static void PR_CALLBACK RecordTrace(void *arg )
{
    PR_RECORD_TRACE_ENTRIES();

    PR_EnterMonitor(mon);
    --activeThreads;
    PR_Notify( mon );
    PR_ExitMonitor(mon);

    return;
} /* end RecordTrace() */



#define NUM_TRACE_RECORDS ( 10000 )
/*
** Thread to extract and print trace entries from the buffer.
*/
static void PR_CALLBACK SampleTrace( void *arg )
{
#if defined(DEBUG) || defined(FORCE_NSPR_TRACE)
    PRInt32 found, rc;
    PRTraceEntry    *foundEntries;
    PRInt32 i;

    foundEntries = (PRTraceEntry *)PR_Malloc( NUM_TRACE_RECORDS * sizeof(PRTraceEntry));
    PR_ASSERT(foundEntries != NULL );

    do
    {
        rc = PR_GetTraceEntries( foundEntries, NUM_TRACE_RECORDS, &found);
        PR_LOG( lm, msgLevel,
                ("SampleTrace: Lost Data: %ld found: %ld", rc, found ));

        if ( found != 0)
        {
            for ( i = 0 ; i < found; i++ )
            {
                PR_LOG( lm, msgLevel,
                        ("SampleTrace, detail: Thread: %p, Time: %llX, UD0: %ld, UD1: %ld, UD2: %8.8ld",
                         (foundEntries +i)->thread,
                         (foundEntries +i)->time,
                         (foundEntries +i)->userData[0],
                         (foundEntries +i)->userData[1],
                         (foundEntries +i)->userData[2] ));
            }
        }
        PR_Sleep(PR_MillisecondsToInterval(50));
    }
    while( found != 0 && activeThreads >= 1 );

    PR_Free( foundEntries );

    PR_EnterMonitor(mon);
    --activeThreads;
    PR_Notify( mon );
    PR_ExitMonitor(mon);

    PR_LOG( lm, msgLevel,
            ("SampleTrace(): exiting"));

#endif
    return;
} /* end RecordTrace() */

/*
** Basic trace test.
*/
static void TraceTest( void )
{
    PRInt32 i;
    PRInt32 size;
    PR_DEFINE_TRACE( th );
    PRThread *t1, *t2;

    PR_LOG( lm, msgLevel,
            ("Begin TraceTest"));

    size = SMALL_TRACE_BUFSIZE;
    PR_SET_TRACE_OPTION( PRTraceBufSize, &size );
    PR_GET_TRACE_OPTION( PRTraceBufSize, &i );

    PR_CREATE_TRACE( th, "TraceTest", "tt2", "A description for the trace test" );
    PR_CREATE_TRACE( th, "TraceTest", "tt3", "A description for the trace test" );
    PR_CREATE_TRACE( th, "TraceTest", "tt4", "A description for the trace test" );
    PR_CREATE_TRACE( th, "TraceTest", "tt5", "A description for the trace test" );
    PR_CREATE_TRACE( th, "TraceTest", "tt6", "A description for the trace test" );
    PR_CREATE_TRACE( th, "TraceTest", "tt7", "A description for the trace test" );
    PR_CREATE_TRACE( th, "TraceTest", "tt8", "A description for the trace test" );

    PR_CREATE_TRACE( th, "Trace Test", "tt0", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt1", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt2", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt3", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt4", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt5", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt6", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt7", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt8", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt9", "QName is Trace Test, not TraceTest" );
    PR_CREATE_TRACE( th, "Trace Test", "tt10", "QName is Trace Test, not TraceTest" );



    activeThreads += 2;
    t1 = PR_CreateThread(PR_USER_THREAD,
                         RecordTrace, NULL,
                         PR_PRIORITY_NORMAL,
                         PR_GLOBAL_THREAD,
                         PR_UNJOINABLE_THREAD,
                         0);
    PR_ASSERT(t1);

    t2 = PR_CreateThread(PR_USER_THREAD,
                         SampleTrace, 0,
                         PR_PRIORITY_NORMAL,
                         PR_GLOBAL_THREAD,
                         PR_UNJOINABLE_THREAD,
                         0);
    PR_ASSERT(t2);

    ListTraces();

    PR_GET_TRACE_HANDLE_FROM_NAME( th, "TraceTest","tt1" );
    PR_ASSERT( th == hTrace );

    PR_LOG( lm, msgLevel,
            ("End TraceTest"));
    return;
} /* end TraceTest() */


/*
** Ordered lock test.
*/
static void OrderedLockTest( void )
{
    PR_LOG( lm, msgLevel,
            ("Begin OrderedLockTest"));


} /* end OrderedLockTest() */


int main(int argc, char **argv)
{
#if defined(DEBUG) || defined(FORCE_NSPR_TRACE)
    PRUint32    counter;
    PLOptStatus os;
    PLOptState *opt = PL_CreateOptState(argc, argv, "hdv:");
    lm = PR_NewLogModule("Test");

    while (PL_OPT_EOL != (os = PL_GetNextOpt(opt)))
    {
        if (PL_OPT_BAD == os) {
            continue;
        }
        switch (opt->option)
        {
            case 'v':  /* verbose mode */
                msgLevel = (PRLogModuleLevel)atol( opt->value);
                break;
            case 'h':  /* help message */
                Help();
                help = PR_TRUE;
                break;
            default:
                break;
        }
    }
    PL_DestroyOptState(opt);

    PR_CREATE_TRACE( hTrace, "TraceTest", "tt1", "A description for the trace test" );
    mon = PR_NewMonitor();
    PR_EnterMonitor( mon );

    TraceTest();
    CounterTest();
    OrderedLockTest();

    /* Wait for all threads to exit */
    while ( activeThreads > 0 ) {
        if ( activeThreads == 1 ) {
            PR_SET_TRACE_OPTION( PRTraceStopRecording, NULL );
        }
        PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
        PR_GET_COUNTER( counter, hCounter );
    }
    PR_ExitMonitor( mon );

    /*
    ** Evaluate results
    */
    PR_GET_COUNTER( counter, hCounter );
    if ( counter != 0 )
    {
        failed = PR_TRUE;
        PR_LOG( lm, msgLevel,
                ("Expected counter == 0, found: %ld", counter));
        printf("FAIL\n");
    }
    else
    {
        printf("PASS\n");
    }


    PR_DESTROY_COUNTER( hCounter );

    PR_DestroyMonitor( mon );

    PR_TRACE( hTrace, TraceFlow, 0xfff,0,0,0,0,0,0);
    PR_DESTROY_TRACE( hTrace );
#else
    printf("Test not defined\n");
#endif
    return 0;
}  /* main() */
/* end instrumt.c */

