/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS Base is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
 *
 *  CA performance test
 *
 *  History
 *  joh 09-12-89 Initial release
 *  joh 12-20-94 portability
 *
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <limits.h>

#define epicsAssertAuthor "Jeff Hill johill@lanl.gov"

#include "epicsAssert.h"
#include "epicsTime.h"
#include "cadef.h"
#include "caProto.h"

#include "caDiagnostics.h"

#ifndef NULL
#define NULL 0
#endif

#define WAIT_FOR_ACK

typedef struct testItem {
    chid                chix;
    char                name[128];
    int                 type;
    int                 count;
    void *              pValue;
} ti;

typedef void tf ( ti *pItems, unsigned iterations, unsigned *pInlineIter );

/*
 * test_pend()
 */
static void test_pend(
ti          *pItems,
unsigned    iterations,
unsigned    *pInlineIter
)
{
    unsigned    i;
    int         status;

    for (i=0; i<iterations; i++) {
        status = ca_pend_event(1e-9);
        if (status != ECA_TIMEOUT && status != ECA_NORMAL) {
            SEVCHK(status, NULL);
        }
        status = ca_pend_event(1e-9);
        if (status != ECA_TIMEOUT && status != ECA_NORMAL) {
            SEVCHK(status, NULL);
        }
        status = ca_pend_event(1e-9);
        if (status != ECA_TIMEOUT && status != ECA_NORMAL) {
            SEVCHK(status, NULL);
        }
        status = ca_pend_event(1e-9);
        if (status != ECA_TIMEOUT && status != ECA_NORMAL) {
            SEVCHK(status, NULL);
        }
        status = ca_pend_event(1e-9);
        if (status != ECA_TIMEOUT && status != ECA_NORMAL) {
            SEVCHK(status, NULL);
        }
    }
    *pInlineIter = 5;
}

/*
 * test_search ()
 */
static void test_search (
ti      *pItems,
unsigned    iterations,
unsigned    *pInlineIter
)
{
    unsigned i;
    int status;

    for ( i = 0u; i < iterations; i++ ) {
        status = ca_search ( pItems[i].name, &pItems[i].chix );
        SEVCHK (status, NULL);
    }
    status = ca_pend_io ( 0.0 );
        SEVCHK ( status, NULL );

    *pInlineIter = 1;
}

/*
 * test_sync_search()
 */
#if 0
static void test_sync_search(
ti      *pItems,
unsigned    iterations,
unsigned    *pInlineIter
)
{
    unsigned i;
    int status;

    for (i=0u; i<iterations; i++) {
        status = ca_search (pItems[i].name, &pItems[i].chix);
        SEVCHK (status, NULL);
        status = ca_pend_io(0.0);
        SEVCHK (status, NULL);
    }

    *pInlineIter = 1;
}
#endif

/*
 * test_free ()
 */
static void test_free(
ti      *pItems,
unsigned    iterations,
unsigned    *pInlineIter
)
{
    int status;
    unsigned i;

    for (i=0u; i<iterations; i++) {
        status = ca_clear_channel (pItems[i].chix);
        SEVCHK (status, NULL);
    }
    status = ca_flush_io();
    SEVCHK (status, NULL);
    *pInlineIter = 1;
}

/*
 * test_put ()
 */
static void test_put(
ti      *pItems,
unsigned    iterations,
unsigned    *pInlineIter
)
{
    ti      *pi;
    int     status;
    dbr_int_t   val;

    for (pi=pItems; pi < &pItems[iterations]; pi++) {
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_put(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
    }
#ifdef WAIT_FOR_ACK
    status = ca_array_get (DBR_INT, 1, pItems[0].chix, &val);
    SEVCHK (status, NULL);
    ca_pend_io(100.0);
#endif
    status = ca_array_put(
            pItems[0].type,
            pItems[0].count,
            pItems[0].chix,
            pItems[0].pValue);
        SEVCHK (status, NULL);
    status = ca_flush_io();
        SEVCHK (status, NULL);

    *pInlineIter = 10;
}

/*
 * test_get ()
 */
static void test_get(
ti          *pItems,
unsigned    iterations,
unsigned    *pInlineIter
)
{
    ti  *pi;
    int status;

    for (pi=pItems; pi<&pItems[iterations]; pi++) {
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
        SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
    }
    status = ca_pend_io(1e20);
        SEVCHK (status, NULL);

    *pInlineIter = 10;
}

/*
 * test_wait ()
 */
static void test_wait (
ti      *pItems,
unsigned    iterations,
unsigned    *pInlineIter
)
{
    ti  *pi;
    int status;

    for (pi=pItems; pi<&pItems[iterations]; pi++) {
        status = ca_array_get(
                pi->type,
                pi->count,
                pi->chix,
                pi->pValue);
            SEVCHK (status, NULL);
        status = ca_pend_io(100.0);
        SEVCHK (status, NULL);
    }

    *pInlineIter = 1;
}

/*
 * measure_get_latency
 */
static void measure_get_latency (ti *pItems, unsigned iterations)
{
    epicsTimeStamp end_time;
    epicsTimeStamp start_time;
    double delay;
    double X = 0u;
    double XX = 0u;
    double max = DBL_MIN;
    double min = DBL_MAX;
    double mean;
    double stdDev;
    ti *pi;
    int status;

    for ( pi = pItems; pi < &pItems[iterations]; pi++ ) {
        epicsTimeGetCurrent ( &start_time );
        status = ca_array_get ( pi->type, pi->count,
                        pi->chix, pi->pValue );
        SEVCHK ( status, NULL );
        status = ca_pend_io ( 100.0 );
        SEVCHK ( status, NULL );

        epicsTimeGetCurrent ( &end_time );

        delay = epicsTimeDiffInSeconds ( &end_time,&start_time );

        X += delay;
        XX += delay*delay;

        if ( delay > max ) {
            max = delay;
        }

        if ( delay < min ) {
            min = delay;
        }
    }

    mean = X/iterations;
    stdDev = sqrt ( XX/iterations - mean*mean );
    printf (
        "Get Latency - "
        "mean = %3.1f uS, "
        "std dev = %3.1f uS, "
        "min = %3.1f uS "
        "max = %3.1f uS\n",
        mean * 1e6, stdDev * 1e6,
        min * 1e6, max * 1e6 );
}

/*
 * printSearchStat()
 */
static void printSearchStat ( const ti * pi, unsigned iterations )
{
    unsigned i;
    double  X = 0u;
    double  XX = 0u;
    double  max = DBL_MIN;
    double  min = DBL_MAX;
    double  mean;
    double  stdDev;

    for ( i = 0; i < iterations; i++ ) {
        double retry = ca_search_attempts ( pi[i].chix );
        X += retry;
        XX += retry * retry;
        if ( retry > max ) {
            max = retry;
        }
        if ( retry < min ) {
            min = retry;
        }
    }

    mean = X / iterations;
    stdDev = sqrt( XX / iterations - mean * mean );
    printf (
        "Search tries per chan - "
        "mean = %3.1f "
        "std dev = %3.1f "
        "min = %3.1f "
        "max = %3.1f\n",
        mean, stdDev, min, max);
}

/*
 * timeIt ()
 */
void timeIt ( tf *pfunc, ti *pItems, unsigned iterations,
    unsigned nBytesSent, unsigned nBytesRecv )
{
    epicsTimeStamp      end_time;
    epicsTimeStamp      start_time;
    double              delay;
    unsigned            inlineIter = 0;

    epicsTimeGetCurrent ( &start_time );
    (*pfunc) ( pItems, iterations, &inlineIter );
    epicsTimeGetCurrent ( &end_time );
    delay = epicsTimeDiffInSeconds ( &end_time, &start_time );
    if ( delay > 0.0 ) {
        double freq = ( iterations * inlineIter ) / delay;
        printf ( "Per Op, %8.4f uS ( %8.4f MHz )",
            1e6 / freq, freq / 1e6 );
        if ( pItems != NULL ) {
            printf(", %8.4f snd Mbps, %8.4f rcv Mbps\n",
                (inlineIter*nBytesSent*CHAR_BIT)/(delay*1e6),
                (inlineIter*nBytesRecv*CHAR_BIT)/(delay*1e6) );
        }
        else {
            printf ("\n");
        }
    }
}

/*
 * test ()
 */
static void test ( ti *pItems, unsigned iterations )
{
    unsigned payloadSize, dblPayloadSize;
    unsigned nBytesSent, nBytesRecv;

    payloadSize =
        dbr_size_n ( pItems[0].type, pItems[0].count );
    payloadSize = CA_MESSAGE_ALIGN ( payloadSize );

    dblPayloadSize = dbr_size [ DBR_DOUBLE ];
    dblPayloadSize = CA_MESSAGE_ALIGN ( dblPayloadSize );

    if ( payloadSize > dblPayloadSize ) {
        unsigned factor = payloadSize / dblPayloadSize;
        while ( factor ) {
            if ( iterations > 10 * factor ) {
                iterations /= factor;
                break;
            }
            factor /= 2;
        }
    }

    printf ( "\t### async put test ###\n");
    nBytesSent = sizeof ( caHdr ) + CA_MESSAGE_ALIGN( payloadSize );
    nBytesRecv = 0u;
    timeIt ( test_put, pItems, iterations,
        nBytesSent * iterations,
        nBytesRecv * iterations );

    printf ( "\t### async get test ###\n");
    nBytesSent = sizeof ( caHdr );
    nBytesRecv = sizeof ( caHdr ) + CA_MESSAGE_ALIGN ( payloadSize );
    timeIt ( test_get, pItems, iterations,
        nBytesSent * ( iterations ),
        nBytesRecv * ( iterations ) );

    printf ("\t### synch get test ###\n");
    nBytesSent = sizeof ( caHdr );
    nBytesRecv = sizeof ( caHdr ) + CA_MESSAGE_ALIGN ( payloadSize );
    if ( iterations > 100 ) {
        iterations /= 100;
    }
    else if ( iterations > 10 ) {
        iterations /= 10;
    }
    timeIt ( test_wait, pItems, iterations,
        nBytesSent * iterations,
        nBytesRecv * iterations );
}

/*
 * catime ()
 */
int catime ( const char * channelName,
    unsigned channelCount, enum appendNumberFlag appNF )
{
    unsigned i;
    int j;
    unsigned strsize;
    unsigned nBytesSent, nBytesRecv;
    ti *pItemList;

    if ( channelCount == 0 ) {
        printf ( "channel count was zero\n" );
        return 0;
    }

    pItemList = calloc ( channelCount, sizeof (ti) );
    if ( ! pItemList ) {
        return -1;
    }

    SEVCHK ( ca_context_create ( ca_disable_preemptive_callback ),
        "Unable to initialize" );

    if ( appNF == appendNumber ) {
        printf ( "Testing with %u channels named %snnn\n",
            channelCount, channelName );
    }
    else {
        printf ( "Testing with %u channels named %s\n",
             channelCount, channelName );
    }

    strsize = sizeof ( pItemList[0].name ) - 1;
    nBytesSent = 0;
    nBytesRecv = 0;
    for ( i=0; i < channelCount; i++ ) {
        if ( appNF == appendNumber ) {
            sprintf ( pItemList[i].name,"%.*s%.6u",
                (int) (strsize - 15u), channelName, i );
        }
        else {
            strncpy ( pItemList[i].name, channelName, strsize);
        }
        pItemList[i].name[strsize]= '\0';
        pItemList[i].count = 0;
        pItemList[i].pValue = 0;
        nBytesSent += 2 * ( CA_MESSAGE_ALIGN ( strlen ( pItemList[i].name ) )
                            + sizeof (caHdr) );
        nBytesRecv += 2 * sizeof (caHdr);
    }

    printf ( "Channel Connect Test\n" );
    printf ( "--------------------\n" );
    timeIt ( test_search, pItemList, channelCount, nBytesSent, nBytesRecv );
    printSearchStat ( pItemList, channelCount );

    for ( i = 0; i < channelCount; i++ ) {
        size_t count = ca_element_count ( pItemList[i].chix );
        size_t size = sizeof ( dbr_string_t ) * count;
        pItemList[i].count = count;
        pItemList[i].pValue = malloc ( size );
        assert ( pItemList[i].pValue );
    }

    printf (
        "channel name=%s, native type=%d, native count=%u\n",
        ca_name (pItemList[0].chix),
        ca_field_type (pItemList[0].chix),
        pItemList[0].count );

    printf ("Pend Event Test\n");
    printf ( "----------------\n" );
    timeIt ( test_pend, NULL, 100, 0, 0 );

    for ( i = 0; i < channelCount; i++ ) {
        dbr_float_t * pFltVal = ( dbr_float_t * ) pItemList[i].pValue;
        double val = i;
        val /= channelCount;
        for ( j = 0; j < pItemList[i].count; j++ ) {
            pFltVal[j] = (dbr_float_t) val;
        }
        pItemList[i].type = DBR_FLOAT;
    }
    printf ( "DBR_FLOAT Test\n" );
    printf ( "--------------\n" );
    test ( pItemList, channelCount );

    for ( i = 0; i < channelCount; i++ ) {
        dbr_double_t * pDblVal = ( dbr_double_t * ) pItemList[i].pValue;
        double val = i;
        val /= channelCount;
        for ( j = 0; j < pItemList[i].count; j++ ) {
            pDblVal[j] = (dbr_double_t) val;
        }
        pItemList[i].type = DBR_DOUBLE;
    }
    printf ( "DBR_DOUBLE Test\n" );
    printf ( "---------------\n" );
    test ( pItemList, channelCount );


    for ( i = 0; i < channelCount; i++ ) {
        dbr_string_t * pStrVal = ( dbr_string_t * ) pItemList[i].pValue;
        double val = i;
        val /= channelCount;
        for ( j = 0; j < pItemList[i].count; j++ ) {
            sprintf ( pStrVal[j], "%f", val );
        }
        pItemList[i].type = DBR_STRING;
    }
    printf ( "DBR_STRING Test\n" );
    printf ( "---------------\n" );
    test ( pItemList, channelCount );

    for ( i = 0; i < channelCount; i++ ) {
        dbr_int_t * pIntVal = ( dbr_int_t * ) pItemList[i].pValue;
        double val = i;
        val /= channelCount;
        for ( j = 0; j < pItemList[i].count; j++ ) {
            pIntVal[j] = (dbr_int_t) val;
        }
        pItemList[i].type = DBR_INT;
    }
    printf ( "DBR_INT Test\n" );
    printf ( "------------\n" );
    test ( pItemList, channelCount );

    printf ( "Get Latency Test\n" );
    printf ( "----------------\n" );
    for ( i = 0; i < channelCount; i++ ) {
        dbr_double_t * pDblVal = ( dbr_double_t * ) pItemList[i].pValue;
        for ( j = 0; j < pItemList[i].count; j++ ) {
            pDblVal[j] = 0;
        }
        pItemList[i].type = DBR_DOUBLE;
    }
    measure_get_latency ( pItemList, channelCount );

    printf ( "Free Channel Test\n" );
    printf ( "-----------------\n" );
    timeIt ( test_free, pItemList, channelCount, 0, 0 );

    SEVCHK ( ca_task_exit (), "Unable to free resources at exit" );

    for ( i = 0; i < channelCount; i++ ) {
        free ( pItemList[i].pValue );
    }

    free ( pItemList );

    return CATIME_OK;
}




