/**
* Copyright (C) Mellanox Technologies Ltd. 2001-2011.  ALL RIGHTS RESERVED.
* This software product is a proprietary product of Mellanox Technologies Ltd.
* (the "Company") and all right, title, and interest and to the software product,
* including all associated intellectual property rights, are and shall
* remain exclusively with the Company.
*
* This software product is governed by the End User License Agreement
* provided with the software product.
* $COPYRIGHT$
* $HEADER$
*/


#ifndef MXM_PERF_H_
#define MXM_PERF_H_

#include <mxm/api/mxm_api.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>

BEGIN_C_DECLS

#define MXM_USEC_PER_SEC   1000000ul     /* Microsec */

#define mxm_max(a, b) \
    ({ \
         typeof(a) _a = (a);  \
         typeof(b) _b = (b);  \
         _a > _b ? _a : _b;   \
     })

typedef unsigned long long   mxm_time_t;

typedef enum {
    MXM_PERF_CMD_SEND, /* Send  */
    MXM_PERF_CMD_RECV, /* Receive */
    MXM_PERF_CMD_SEND_RECV, /* Both send and receive */
    MXM_PERF_CMD_SEND_PINGPONG, /* Send, then receive reply */
    MXM_PERF_CMD_PUT, /* Put */
    MXM_PERF_CMD_PUT_PINGPONG, /* Put and wait for remote side to put */
    MXM_PERF_CMD_GET, /* Get */
    MXM_PERF_CMD_AM, /* Send AM */
    MXM_PERF_CMD_AM_PINGPONG, /* Send AM and wait for remote side */
    MXM_PERF_CMD_PROGRESS,
} mxm_perf_cmd_t;


enum {
    MXM_PERF_TEST_FLAG_REG_MEM    = MXM_BIT(0), /* Pre-register memory */
    MXM_PERF_TEST_FLAG_REQ_WAIT   = MXM_BIT(1), /* Set MXM_REQ_WAIT flag */
    MXM_PERF_TEST_FLAG_REQ_SYNC   = MXM_BIT(2), /* Set MXM_REQ_SYNC flag */
    MXM_PERF_TEST_FLAG_REQ_LAZY   = MXM_BIT(3), /* Set MXM_REQ_LAZY flag and call fence if needed */
    MXM_PERF_TEST_FLAG_RECV_CONN  = MXM_BIT(4), /* Receive from specific connection */
    MXM_PERF_TEST_FLAG_WARMUP     = MXM_BIT(5), /* Run some warm-up iterations */
    MXM_PERF_TEST_FLAG_VALIDATE   = MXM_BIT(7), /* Validate data. Affects performance. */
};

typedef enum {
    MXM_PERF_TEST_CYCLE_RR,
    MXM_PERF_TEST_CYCLE_UNIFORM,
    MXM_PERF_TEST_CYCLE_NONUNIFORM,
} mxm_perf_cycle_t;

/**
 * Performance counter type.
 */
typedef uint64_t mxm_perf_counter_t;


/**
 * Describes a performance test.
 */
typedef struct mxm_perf_test {
    mxm_perf_cmd_t       command;         /* Command to perform */
    unsigned             index;           /* Test participant index */
    uint32_t             flags;           /* Test flags */
    size_t               message_size;    /* How much data in the command */
    unsigned             window;          /* Send window size (number of requests) */
    mxm_perf_cycle_t     cycle;           /* How to cycle through connections */

    mxm_perf_counter_t   max_iters;       /* Iterations limit, 0 - unlimited */
    double               max_time;        /* Time limit (seconds), 0 - unlimited */

    mxm_req_data_type_t  data_type;       /* Data type to send/recv */
    unsigned             iov_count;       /* Number of entries in IOV list, if used */

    void                 *local_addr;     /* Local data for memory operations, for GET/PUT */
    mxm_vaddr_t          remote_addr;     /* Remote data for memory operations */
    mxm_mem_key_t        remote_mkey;     /* Remote memory access key */
} mxm_perf_test_t;


/*
 * Performance test result.
 *
 * Time values are in seconds.
 * Size values are in bytes.
 */
typedef struct mxm_perf_result {
    mxm_perf_counter_t      iters;
    double                  elapsed_time;
    mxm_perf_counter_t      bytes;
    struct {
        double              typical;
        double              moment_average; /* Average since last report */
        double              total_average;  /* Average of the whole test */
    }
    latency, bandwidth, msgrate;
} mxm_perf_result_t;


/*
 * Performance test result callback.
 */
typedef void (*mxm_perf_report_cb_t)(mxm_perf_result_t *result, void *user_data);


/**
 * Run a performance test.
 *
 * @param conn             Connection to send / receive on.
 * @param mq               Send / receive context.
 * @param test             Test description.
 * @param report_cb        Callback to call periodically with current results.
 * @param user_data        Passed to the callback.
 * @param report_interval  Interval to call the report callback, in seconds.
 * @param result           Filled with test results.
 *
 * @return
 */
mxm_error_t
mxm_perf_test_run(mxm_conn_h *conns, unsigned conn_cnt, mxm_mq_h mq,
                  mxm_ep_h ep, mxm_h mxmh, mxm_perf_test_t *test,
                  mxm_perf_report_cb_t report_cb, void *user_data,
                  double report_interval, mxm_perf_result_t *result);


/**
 * @return CPU clock frequency.
 */
double mxm_perf_get_cpu_freq();


#if defined(__x86_64__)

static inline mxm_time_t mxm_get_time()
{
    uint32_t low, high;
    asm volatile ("rdtsc" : "=a" (low), "=d" (high));
    return ((mxm_time_t)high << 32) | (mxm_time_t)low;
}

#define __mxm_get_clocks_per_sec()   mxm_perf_get_cpu_freq()

#elif defined(__powerpc__)

#ifdef HAVE_SYS_PLATFORM_PPC_H
#include <sys/platform/ppc.h>
#else
/* Read the Time Base Register.   */
static inline uint64_t
__ppc_get_timebase (void)
{
#ifdef __powerpc64__
  uint64_t __tb;
  /* "volatile" is necessary here, because the user expects this assembly
     isn't moved after an optimization.  */
  __asm__ volatile ("mfspr %0, 268" : "=r" (__tb));
  return __tb;
#else  /* not __powerpc64__ */
  uint32_t __tbu, __tbl, __tmp; \
  __asm__ volatile ("0:\n\t"
            "mftbu %0\n\t"
            "mftbl %1\n\t"
            "mftbu %2\n\t"
            "cmpw %0, %2\n\t"
            "bne- 0b"
            : "=r" (__tbu), "=r" (__tbl), "=r" (__tmp));
  return (((uint64_t) __tbu << 32) | __tbl);
#endif  /* not __powerpc64__ */
}
#endif
static inline mxm_time_t mxm_get_time()
{
    return __ppc_get_timebase();
}

#define __mxm_get_clocks_per_sec()   mxm_perf_get_cpu_freq()

#elif defined(__aarch64__)

#include <sys/times.h>
static inline mxm_time_t mxm_get_time(void)
{
    mxm_time_t ret;
    struct tms accurate_clock;

    times(&accurate_clock);
    ret = accurate_clock.tms_utime + accurate_clock.tms_stime;

    return ret;
}

#define __mxm_get_clocks_per_sec() CLOCKS_PER_SEC

#else
#error Unsupported arch
#endif

static inline double mxm_perf_get_cpu_clocks_per_sec()
{
    static double clocks_per_sec = 0.0;
    static int initialized = 0;

    if (!initialized) {
        clocks_per_sec = mxm_perf_get_cpu_freq();
        initialized = 1;
    }
    return clocks_per_sec;
}

/**
 *  * @return The clock value of a single second.
 *   */
static inline double mxm_time_sec_value()
{
   return mxm_perf_get_cpu_clocks_per_sec();
}

/**
 *  * Convert seconds to MXM time units.
 *   */
static inline mxm_time_t mxm_time_from_sec(double sec)
{
   return sec * mxm_time_sec_value();
}

/**
 * Convert MXM time units to seconds.
 */
static inline double mxm_time_to_sec(mxm_time_t time)
{
    return time / mxm_time_sec_value();
}

/**
 * Convert MXM time units to microseconds.
 */
static inline double mxm_time_to_usec(mxm_time_t time)
{
    return mxm_time_to_sec(time) * MXM_USEC_PER_SEC;
}

END_C_DECLS

#endif /* MXM_PERF_H_ */
