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