/**
* Copyright (c) NVIDIA CORPORATION & AFFILIATES, 2021. ALL RIGHTS RESERVED.
*
* See file LICENSE for terms.
*/

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "perftest.h"

#include <ucs/sys/string.h>
#include <ucs/sys/sys.h>
#include <ucs/sys/sock.h>
#include <ucs/debug/log.h>

const struct option TEST_PARAMS_ARGS_LONG[] =
{
    {"daemon-local",  required_argument, 0, 'g'},
    {"daemon-remote", required_argument, 0, 'G'},
    {0, 0, 0, 0}
};

static void print_memory_type_usage(void)
{
    ucs_memory_type_t it;

    ucs_memory_type_for_each(it) {
        printf("                        %s - %s\n", ucs_memory_type_names[it],
               ucs_memory_type_descs[it]);
    }
}

static void usage(const struct perftest_context *ctx, const char *program)
{
    static const char* api_names[] = {
        [UCX_PERF_API_UCT] = "UCT",
        [UCX_PERF_API_UCP] = "UCP"
    };
    test_type_t *test;
    int UCS_V_UNUSED rank;

#ifdef HAVE_MPI
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    if (ctx->mpi && (rank != 0)) {
        return;
    }
#endif

#if defined (HAVE_MPI)
    printf("  Note: test can be also launched as an MPI application\n");
    printf("\n");
#endif
    printf("  Usage: %s [ server-address ] [ options ]\n", program);
    printf("\n");
    printf("  Supported server-address:\n");
    printf("     <hostname>         with default socket-based test setup\n");
    printf("     <ip-address>       with default socket-based test setup\n");
    printf("     lid:<dec|hex>      with MAD-based test setup (-K)\n");
    printf("     guid:<hex>         with MAD-based test setup (-K)\n");
    printf("\n");
    printf("  Common options:\n");
    printf("     -t <test>      test to run:\n");
    for (test = tests; test->name; ++test) {
        printf("    %13s - %s %s\n", test->name,
               api_names[test->api], test->desc);
    }
    printf("\n");
    printf("     -a <send-device-type[:dev-id]>[,<recv-device-type[:dev-id]>]\n");
    printf("                    Accelerator device type and device id to use for running the test.\n");
    printf("                    device id is optional, it corresponds to the index of\n");
    printf("                    the device in the list of available devices\n");
    printf("     -L <level>     device cooperation level for gdaki (thread)\n");
    printf("                    thread - thread level\n");
    printf("                    warp   - warp level\n");
    printf("                    block  - block level\n");
    printf("                    grid   - grid level\n");
    printf("     -F <size>      flow control window size for device tests (%u).\n",
                                ctx->params.super.device_fc_window);
    printf("                    This option defines the number of iterations per which a single flow control\n");
    printf("                    request is sent.\n");
    printf("     -Y <mode>      channel selection mode for device tests (single)\n");
    printf("                    single          - use a single fixed channel (channel 0, default)\n");
    printf("                    random[:<seed>] - use random channel per operation with optional random seed\n");
    printf("                    per-thread      - use global thread ID\n");
    printf("     -s <size>      list of scatter-gather sizes for single message (%zu)\n",
                                ctx->params.super.msg_size_list[0]);
    printf("                    for example: \"-s 16,48,8192,8192,14\"\n");
    printf("                    compact form example: \"-s 1024:16 expands to [1024, ..., 1024] with 16 elements\n");
    printf("     -m <send mem type>[,<recv mem type>]\n");
    printf("                    memory type of message for sender and receiver (host)\n");
    print_memory_type_usage();
    printf("     -n <iters>     number of iterations to run (%"PRIu64")\n", ctx->params.super.max_iter);
    printf("     -w <iters>     number of warm-up iterations (%"PRIu64")\n",
                                ctx->params.super.warmup_iter);
    printf("     -c <cpulist>   set affinity to this CPU list (separated by comma) (off)\n");
    printf("     -O <count>     maximal number of uncompleted outstanding sends (%u)\n",
                                ctx->params.super.max_outstanding);
    printf("     -i <offset>    distance between consecutive scatter-gather entries (%zu)\n",
                                ctx->params.super.iov_stride);
    printf("     -l             use loopback connection\n");
    printf("                    in this case, the process will communicate with itself,\n");
    printf("                    so passing server hostname is not allowed\n");
    printf("     -o             do not progress the responder in one-sided tests\n");
    printf("     -B             register memory with NONBLOCK flag\n");
    printf("     -b <file>      read and execute tests from a batch file: every line in the\n");
    printf("                    file is a test to run. The first word is a user-defined\n");
    printf("                    test name, followed by command-line arguments, for example:\n");
    printf("\n");
    printf("                        test_tag_bandwidth_64k -t tag_bw -s 65536 -n 10000\n");
    printf("\n");
    printf("     -R <rank>      percentile rank of the percentile data in latency tests (%.1f)\n",
                                ctx->params.super.percentile_rank);
    printf("     -p <port>      TCP port to use for data exchange (%d)\n", ctx->port);
    printf("     -6             Use IPv6 address for in data exchange\n");
#ifdef HAVE_MPI
    printf("     -P <0|1>       disable/enable MPI mode (%d)\n", ctx->mpi);
#endif
    printf("     -K <ca:port>   use MAD for test setup and synchronization\n");
    printf("     -h             show this help message\n");
    printf("\n");
    printf("  Output format:\n");
    printf("     -N             use numeric formatting (thousands separator)\n");
    printf("     -f             print only final numbers\n");
    printf("     -v             print CSV-formatted output\n");
    printf("     -X             print extra information about the operation\n");
    printf("     -q             do not print error messages\n");
    printf("\n");
    printf("  UCT only:\n");
    printf("     -d <device>    device to use for testing\n");
    printf("     -x <tl>        transport to use for testing\n");
    printf("     -D <layout>    data layout for sender side:\n");
    printf("                        short    - short messages (default, cannot be used for get)\n");
    printf("                        shortiov - short io-vector messages (only for active messages)\n");
    printf("                        bcopy    - copy-out (cannot be used for atomics)\n");
    printf("                        zcopy    - zero-copy (cannot be used for atomics)\n");
    printf("     -W <count>     flow control window size, for active messages (%u)\n",
                                ctx->params.super.uct.fc_window);
    printf("     -H <size>      active message header size (%zu), included in message size\n",
                                ctx->params.super.uct.am_hdr_size);
    printf("     -A <mode>      asynchronous progress mode (thread_spinlock)\n");
    printf("                        thread_spinlock - separate progress thread with spin locking\n");
    printf("                        thread_mutex - separate progress thread with mutex locking\n");
    printf("                        signal - signal-based timer\n");
    printf("\n");
    printf("  UCP only:\n");
    printf("     -T <threads>[:<blocks>]\n");
    printf("                    number of threads in the test (%d).\n",
           ctx->params.super.thread_count);
    printf("                    blocks is optional, it corresponds to the number of device blocks\n");
    printf("                    if blocks is specified, then threads value corresponds to the number\n");
    printf("                    of device threads in each block\n");
    printf("     -M <thread>    thread support level for progress engine (single)\n");
    printf("                        single     - only the master thread can access\n");
    printf("                        serialized - one thread can access at a time\n");
    printf("                        multi      - multiple threads can access\n");
    printf("     -D <layout>[,<layout>]\n");
    printf("                    data layout for sender and receiver side (contig)\n");
    printf("                        contig - Continuous datatype\n");
    printf("                        iov    - Scatter-gather list\n");
    printf("     -C             use wild-card tag for tag tests\n");
    printf("     -U             force unexpected flow by using tag probe\n");
    printf("     -r <mode>      receive mode for stream tests (recv)\n");
    printf("                        recv       : Use ucp_stream_recv_nb\n");
    printf("                        recv_data  : Use ucp_stream_recv_data_nb\n");
    printf("     -I             create context with wakeup feature enabled\n");
    printf("     -e             create endpoints with error handling support\n");
    printf("     -E <mode>      wait mode for tests\n");
    printf("                        poll       : repeatedly call worker_progress\n");
    printf("                        sleep      : go to sleep after posting requests\n");
    printf("     -H <size>      active message header size (%zu), not included in message size\n",
                                ctx->params.super.ucp.am_hdr_size);
    printf("     -y             do additional memcopy to the user memory in active message receive handler\n");
    printf("     -z             pass pre-registered memory handle\n");
    printf("     -g <IP>[:<port>], --daemon-local <IP>[:<port>]\n");
    printf("                    IP address and port of the local daemon to offload UCP operations to\n");
    printf("                    Port is optional, by default daemon port is (%d)\n",
                                DEFAULT_DAEMON_PORT);
    printf("     -G <IP>[:<port>], --daemon-remote <IP>[:<port>]\n");
    printf("                    IP address and port of the remote daemon to offload UCP operations to\n");
    printf("                    Port is optional, by default daemon port is (%d)\n",
                                DEFAULT_DAEMON_PORT);
    printf("\n");
    printf("   NOTE: When running UCP tests, transport and device should be specified by\n");
    printf("         environment variables: UCX_TLS and UCX_[SELF|SHM|NET]_DEVICES.\n");
    printf("\n");
}

static ucs_status_t parse_int(const char *opt_arg, int *value, const char *desc,
                              int min_value, int max_value)
{
    char *endptr;
    int parsed_value;

    if (opt_arg == NULL) {
        ucs_error("%s string is NULL", desc);
        return UCS_ERR_INVALID_PARAM;
    }

    parsed_value = strtol(opt_arg, &endptr, 10);
    if ((endptr == opt_arg) || (*endptr != '\0')) {
        ucs_error("failed to parse %s: %s", desc, opt_arg);
        return UCS_ERR_INVALID_PARAM;
    }

    if ((parsed_value < min_value) || (parsed_value > max_value)) {
        ucs_error("value for %s (%s) is out of range: [%d, %d]", desc, opt_arg,
                  min_value, max_value);
        return UCS_ERR_INVALID_PARAM;
    }

    *value = parsed_value;
    return UCS_OK;
}

static ucs_status_t parse_mem_type(const char *opt_arg,
                                   ucs_memory_type_t *mem_type)
{
    ucs_memory_type_t it;

    if (opt_arg == NULL) {
        ucs_error("memory type string is NULL");
        return UCS_ERR_INVALID_PARAM;
    }

    ucs_memory_type_for_each(it) {
        if (!strcmp(opt_arg, ucs_memory_type_names[it])) {
            *mem_type = it;
            return UCS_OK;
        }
    }

    ucs_error("unsupported memory type: \"%s\"", opt_arg);
    return UCS_ERR_INVALID_PARAM;
}

static ucs_status_t
parse_accel_device(char *opt_arg, ucx_perf_accel_dev_t *dev)
{
    const char *delim = ":";
    char *saveptr = NULL;
    char *token;
    ucs_status_t status;
    ucs_memory_type_t mem_type;
    int device_id;

    if (opt_arg == NULL) {
        ucs_error("mem type param is NULL");
        return UCS_ERR_INVALID_PARAM;
    }

    token  = strtok_r(opt_arg, delim, &saveptr);
    status = parse_mem_type(token, &mem_type);
    if (status != UCS_OK) {
        return status;
    }

    token = strtok_r(NULL, delim, &saveptr);
    if (NULL == token) {
        device_id = UCX_PERF_MEM_DEV_DEFAULT;
    } else {
        status = parse_int(token, &device_id, "device id", 0, INT_MAX);
        if (status != UCS_OK) {
            return status;
        }
    }

    dev->mem_type  = mem_type;
    dev->device_id = device_id;
    return UCS_OK;
}

static ucs_status_t parse_mem_type_params(const char *opt_arg,
                                          ucs_memory_type_t *send_mem_type,
                                          ucs_memory_type_t *recv_mem_type)
{
    const char *delim   = ",";
    char *token         = strtok((char*)opt_arg, delim);
    ucs_status_t status;

    status = parse_mem_type(token, send_mem_type);
    if (status != UCS_OK) {
        return status;
    }

    token = strtok(NULL, delim);
    if (NULL == token) {
        *recv_mem_type = *send_mem_type;
        return UCS_OK;
    } else {
        return parse_mem_type(token, recv_mem_type);
    }
}

static ucs_status_t parse_accel_device_params(const char *opt_arg,
                                              ucx_perf_accel_dev_t *send_device,
                                              ucx_perf_accel_dev_t *recv_device)
{
    const char *delim = ",";
    char *saveptr = NULL;
    char *token, *arg;
    ucs_status_t status;

    arg = ucs_alloca(strlen(opt_arg) + 1);
    strcpy(arg, opt_arg);
    token  = strtok_r(arg, delim, &saveptr);
    status = parse_accel_device(token, send_device);
    if (status != UCS_OK) {
        return status;
    }

    token = strtok_r(NULL, delim, &saveptr);
    if (NULL == token) {
        *recv_device = *send_device;
        return UCS_OK;
    }

    status = parse_accel_device(token, recv_device);
    if (status != UCS_OK) {
        return status;
    }

    if (send_device->mem_type == recv_device->mem_type) {
        if (send_device->device_id == UCX_PERF_MEM_DEV_DEFAULT) {
            send_device->device_id = recv_device->device_id;
        } else if (recv_device->device_id == UCX_PERF_MEM_DEV_DEFAULT) {
            recv_device->device_id = send_device->device_id;
        }
    }

    return UCS_OK;
}

static ucs_status_t parse_thread_params(const char *opt_arg,
                                        unsigned *thread_count,
                                        unsigned *block_count)
{
    const char *delim = ":";
    char *saveptr = NULL;
    char *token, *arg;
    ucs_status_t status;

    arg = ucs_alloca(strlen(opt_arg) + 1);
    strcpy(arg, opt_arg);
    token = strtok_r(arg, delim, &saveptr);
    status = parse_int(token, thread_count, "thread count", 1, INT_MAX);
    if (status != UCS_OK) {
        return status;
    }

    token = strtok_r(NULL, delim, &saveptr);
    if (token != NULL) {
        status = parse_int(token, block_count, "block count", 1, INT_MAX);
        if (status != UCS_OK) {
            return status;
        }
    } else {
        *block_count = 1;
    }

    return UCS_OK;
}

static ucs_status_t
parse_message_sizes_list(const char *opt_arg, ucx_perf_params_t *params)
{
    const char delim = ',';
    size_t *msg_size_list, token_num, token_it;
    char *optarg_ptr, *optarg_ptr2;

    optarg_ptr = (char *)opt_arg;
    token_num  = 0;
    /* count the number of given message sizes */
    while ((optarg_ptr = strchr(optarg_ptr, delim)) != NULL) {
        ++optarg_ptr;
        ++token_num;
    }
    ++token_num;

    msg_size_list = realloc(params->msg_size_list,
                            sizeof(*params->msg_size_list) * token_num);
    if (NULL == msg_size_list) {
        return UCS_ERR_NO_MEMORY;
    }

    params->msg_size_list = msg_size_list;

    optarg_ptr = (char *)opt_arg;
    errno = 0;
    for (token_it = 0; token_it < token_num; ++token_it) {
        params->msg_size_list[token_it] = strtoul(optarg_ptr, &optarg_ptr2, 10);
        if (((ERANGE == errno) && (ULONG_MAX == params->msg_size_list[token_it])) ||
            ((errno != 0) && (params->msg_size_list[token_it] == 0)) ||
            (optarg_ptr == optarg_ptr2)) {
            free(params->msg_size_list);
            params->msg_size_list = NULL; /* prevent double free */
            ucs_error("Invalid option substring argument at position %lu", token_it);
            return UCS_ERR_INVALID_PARAM;
        }
        optarg_ptr = optarg_ptr2 + 1;
    }

    params->msg_size_cnt = token_num;
    return UCS_OK;
}

static ucs_status_t
parse_message_sizes_compact(const char *opt_arg, ucx_perf_params_t *params)
{
    const char *delim = ":";
    char *saveptr = NULL;
    char *token, *arg;
    int msg_size, element_count, i;
    size_t *msg_size_list;
    ucs_status_t status;


    arg = ucs_alloca(strlen(opt_arg) + 1);
    strcpy(arg, opt_arg);
    token = strtok_r(arg, delim, &saveptr);
    status = parse_int(token, &msg_size, "message size", 1, INT_MAX);
    if (status != UCS_OK) {
        return status;
    }

    token = strtok_r(NULL, delim, &saveptr);
    status = parse_int(token, &element_count, "elements", 1, INT_MAX);
    if (status != UCS_OK) {
        return status;
    }

    msg_size_list = realloc(params->msg_size_list,
                            sizeof(*params->msg_size_list) * element_count);
    if (NULL == msg_size_list) {
        return UCS_ERR_NO_MEMORY;
    }

    params->msg_size_list = msg_size_list;
    for (i = 0; i < element_count; ++i) {
        params->msg_size_list[i] = msg_size;
    }

    params->msg_size_cnt = element_count;
    return UCS_OK;
}

static int is_compact_form(const char *input)
{
    return strchr(input, ':') != NULL;
}

static ucs_status_t parse_message_sizes_params(const char *opt_arg,
                                               ucx_perf_params_t *params)
{
    if (is_compact_form(opt_arg)) {
        return parse_message_sizes_compact(opt_arg, params);
    }

    return parse_message_sizes_list(opt_arg, params);
}

static ucs_status_t parse_device_level(const char *opt_arg,
                                       ucs_device_level_t *device_level)
{
    ucs_device_level_t level;
    for (level = UCS_DEVICE_LEVEL_THREAD; level <= UCS_DEVICE_LEVEL_GRID; ++level) {
        if (!strcmp(opt_arg, ucs_device_level_name(level))) {
            *device_level = level;
            return UCS_OK;
        }
    }

    ucs_error("Invalid option argument for device level: %s", opt_arg);
    return UCS_ERR_INVALID_PARAM;
}

static ucs_status_t parse_ucp_datatype_params(const char *opt_arg,
                                              ucp_perf_datatype_t *datatype)
{
    const char  *iov_type         = "iov";
    const size_t iov_type_size    = strlen("iov");
    const char  *contig_type      = "contig";
    const size_t contig_type_size = strlen("contig");

    if (0 == strncmp(opt_arg, iov_type, iov_type_size)) {
        *datatype = UCP_PERF_DATATYPE_IOV;
    } else if (0 == strncmp(opt_arg, contig_type, contig_type_size)) {
        *datatype = UCP_PERF_DATATYPE_CONTIG;
    } else {
        return UCS_ERR_INVALID_PARAM;
    }

    return UCS_OK;
}

static ucs_status_t parse_channel_mode(const char *opt_arg,
                                       ucx_perf_channel_mode_t *channel_mode,
                                       unsigned long long *channel_rand_seed)
{
    if (!strcmp(opt_arg, "single")) {
        *channel_mode = UCX_PERF_CHANNEL_MODE_SINGLE;
    } else if (!strncmp(opt_arg, "random", 6)) {
        if (opt_arg[6] != '\0' && opt_arg[6] != ':') {
            return UCS_ERR_INVALID_PARAM;
        }

#ifdef HAVE_CURAND
        *channel_mode = UCX_PERF_CHANNEL_MODE_RANDOM;

        if (opt_arg[6] == ':') {
            *channel_rand_seed = strtoull(opt_arg + 7, NULL, 10);
        }
#else
        ucs_warn("random channel mode is not supported (UCX was built without cuRAND). Falling back to per-thread mode.");
        *channel_mode = UCX_PERF_CHANNEL_MODE_PER_THREAD;
#endif
    } else if (!strcmp(opt_arg, "per-thread")) {
        *channel_mode = UCX_PERF_CHANNEL_MODE_PER_THREAD;
    } else {
        return UCS_ERR_INVALID_PARAM;
    }

    return UCS_OK;
}

/**
 * Verifies that the daemon parameters are valid before each test run in the
 * batch. Parameters verified by this function are configured per test case
 * basis, therefore need to be checked before each test run.
 */
static ucs_status_t check_daemon_params(const ucx_perf_params_t *params)
{
    if (!params->ucp.is_daemon_mode) {
        return UCS_OK;
    }

    if ((params->ucp.send_datatype != UCP_PERF_DATATYPE_CONTIG) ||
        (params->ucp.recv_datatype != UCP_PERF_DATATYPE_CONTIG)) {
        ucs_error("only contiguous datatype is supported in offloaded mode");
        return UCS_ERR_UNSUPPORTED;
    }

    if ((params->send_mem_type != UCS_MEMORY_TYPE_HOST) ||
        (params->recv_mem_type != UCS_MEMORY_TYPE_HOST)) {
        ucs_error("only HOST memory type is supported in offloaded mode");
        return UCS_ERR_UNSUPPORTED;
    }

    if (params->command != UCX_PERF_CMD_AM) {
        ucs_error("only UCP AM API is supported in offloaded mode");
        return UCS_ERR_UNSUPPORTED;
    }

    if (params->ucp.am_hdr_size != 0) {
        ucs_error("sending UCP AM with non-zero header size is not supported"
                  " in offloaded mode");
        return UCS_ERR_UNSUPPORTED;
    }

    if (params->thread_count > 1) {
        ucs_error("only 1 thread is supported in offloaded mode");
        return UCS_ERR_UNSUPPORTED;
    }

    return UCS_OK;
}

/**
 * This function initializes the daemon parameters once, before the test starts.
 * All the initialized variables remain immutable during the test execution.
 */
static ucs_status_t init_daemon_params(ucx_perf_params_t *params)
{
    struct sockaddr_storage *local_addr  = &params->ucp.dmn_local_addr;
    struct sockaddr_storage *remote_addr = &params->ucp.dmn_remote_addr;

    /* Return if both daemon addresses are not configured */
    if ((0 == local_addr->ss_family) && (0 == remote_addr->ss_family)) {
        return UCS_OK;
    }

    /* If only one address is specified, use it in both cases */
    if (0 == local_addr->ss_family) {
        memcpy(local_addr, remote_addr, sizeof(*local_addr));
    } else if (0 == remote_addr->ss_family) {
        memcpy(remote_addr, local_addr, sizeof(*remote_addr));
    }

    params->ucp.is_daemon_mode = 1;
    return UCS_OK;
}

ucs_status_t parse_test_params(perftest_params_t *params, char opt,
                               const char *opt_arg)
{
    const char *optarg2 = NULL;
    test_type_t *test;
    unsigned i;

    switch (opt) {
    case 'd':
        ucs_snprintf_zero(params->super.uct.dev_name,
                          sizeof(params->super.uct.dev_name), "%s", opt_arg);
        return UCS_OK;
    case 'x':
        ucs_snprintf_zero(params->super.uct.tl_name,
                          sizeof(params->super.uct.tl_name), "%s", opt_arg);
        return UCS_OK;
    case 't':
        for (i = 0; tests[i].name != NULL; ++i) {
            test = &tests[i];
            if (!strcmp(opt_arg, test->name)) {
                params->super.api       = test->api;
                params->super.command   = test->command;
                params->super.test_type = test->test_type;
                params->test_id         = i;
                break;
            }
        }
        if (params->test_id == TEST_ID_UNDEFINED) {
            ucs_error("Invalid option argument for -t");
            return UCS_ERR_INVALID_PARAM;
        }
        return UCS_OK;
    case 'D':
        if (!strcmp(opt_arg, "short")) {
            params->super.uct.data_layout   = UCT_PERF_DATA_LAYOUT_SHORT;
        } else if (!strcmp(opt_arg, "shortiov")) {
            params->super.uct.data_layout   = UCT_PERF_DATA_LAYOUT_SHORT_IOV;
        } else if (!strcmp(opt_arg, "bcopy")) {
            params->super.uct.data_layout   = UCT_PERF_DATA_LAYOUT_BCOPY;
        } else if (!strcmp(opt_arg, "zcopy")) {
            params->super.uct.data_layout   = UCT_PERF_DATA_LAYOUT_ZCOPY;
        } else if (UCS_OK == parse_ucp_datatype_params(opt_arg,
                                                       &params->super.ucp.send_datatype)) {
            optarg2 = strchr(opt_arg, ',');
            if (optarg2) {
                if (UCS_OK != parse_ucp_datatype_params(optarg2 + 1,
                                                       &params->super.ucp.recv_datatype)) {
                    return UCS_ERR_INVALID_PARAM;
                }
            }
        } else {
            ucs_error("Invalid option argument for -D");
            return UCS_ERR_INVALID_PARAM;
        }
        return UCS_OK;
    case 'E':
        if (!strcmp(opt_arg, "poll")) {
            params->super.wait_mode = UCX_PERF_WAIT_MODE_POLL;
            return UCS_OK;
        } else if (!strcmp(opt_arg, "sleep")) {
            params->super.wait_mode = UCX_PERF_WAIT_MODE_SLEEP;
            return UCS_OK;
        } else {
            ucs_error("Invalid option argument for -E");
            return UCS_ERR_INVALID_PARAM;
        }
        return UCS_OK;
    case 'i':
        params->super.iov_stride = atol(opt_arg);
        return UCS_OK;
    case 'l':
        params->super.flags |= UCX_PERF_TEST_FLAG_LOOPBACK;
        return UCS_OK;
    case 'n':
        params->super.max_iter = atol(opt_arg);
        return UCS_OK;
    case 's':
        return parse_message_sizes_params(opt_arg, &params->super);
    case 'H':
        params->super.uct.am_hdr_size = atol(opt_arg);
        params->super.ucp.am_hdr_size = atol(opt_arg);
        return UCS_OK;
    case 'W':
        params->super.uct.fc_window = atoi(opt_arg);
        return UCS_OK;
    case 'O':
        params->super.max_outstanding = atoi(opt_arg);
        return UCS_OK;
    case 'w':
        params->super.warmup_iter = atol(opt_arg);
        return UCS_OK;
    case 'o':
        params->super.flags |= UCX_PERF_TEST_FLAG_ONE_SIDED;
        return UCS_OK;
    case 'B':
        params->super.flags |= UCX_PERF_TEST_FLAG_MAP_NONBLOCK;
        return UCS_OK;
    case 'q':
        params->super.flags &= ~UCX_PERF_TEST_FLAG_VERBOSE;
        return UCS_OK;
    case 'C':
        params->super.flags |= UCX_PERF_TEST_FLAG_TAG_WILDCARD;
        return UCS_OK;
    case 'U':
        params->super.flags |= UCX_PERF_TEST_FLAG_TAG_UNEXP_PROBE;
        return UCS_OK;
    case 'I':
        params->super.flags |= UCX_PERF_TEST_FLAG_WAKEUP;
        return UCS_OK;
    case 'e':
        params->super.flags |= UCX_PERF_TEST_FLAG_ERR_HANDLING;
        return UCS_OK;
    case 'M':
        if (!strcmp(opt_arg, "single")) {
            params->super.thread_mode = UCS_THREAD_MODE_SINGLE;
            return UCS_OK;
        } else if (!strcmp(opt_arg, "serialized")) {
            params->super.thread_mode = UCS_THREAD_MODE_SERIALIZED;
            return UCS_OK;
        } else if (!strcmp(opt_arg, "multi")) {
            params->super.thread_mode = UCS_THREAD_MODE_MULTI;
            return UCS_OK;
        } else {
            ucs_error("Invalid option argument for -M");
            return UCS_ERR_INVALID_PARAM;
        }
    case 'T':
        return parse_thread_params(opt_arg, &params->super.thread_count,
                                   &params->super.device_block_count);
    case 'A':
        if (!strcmp(opt_arg, "thread") || !strcmp(opt_arg, "thread_spinlock")) {
            params->super.async_mode = UCS_ASYNC_MODE_THREAD_SPINLOCK;
            return UCS_OK;
        } else if (!strcmp(opt_arg, "thread_mutex")) {
            params->super.async_mode = UCS_ASYNC_MODE_THREAD_MUTEX;
            return UCS_OK;
        } else if (!strcmp(opt_arg, "signal")) {
            params->super.async_mode = UCS_ASYNC_MODE_SIGNAL;
            return UCS_OK;
        } else {
            ucs_error("Invalid option argument for -A");
            return UCS_ERR_INVALID_PARAM;
        }
    case 'r':
        if (!strcmp(opt_arg, "recv_data")) {
            params->super.flags |= UCX_PERF_TEST_FLAG_STREAM_RECV_DATA;
            return UCS_OK;
        } else if (!strcmp(opt_arg, "recv")) {
            params->super.flags &= ~UCX_PERF_TEST_FLAG_STREAM_RECV_DATA;
            return UCS_OK;
        }
        return UCS_ERR_INVALID_PARAM;
    case 'R':
        params->super.percentile_rank = atof(opt_arg);
        if ((0.0 <= params->super.percentile_rank) && (params->super.percentile_rank <= 100.0)) {
            return UCS_OK;
        } else {
            ucs_error("Invalid option argument for -R");
            return UCS_ERR_INVALID_PARAM;
        }
    case 'm':
        return parse_mem_type_params(opt_arg, &params->super.send_mem_type,
                                     &params->super.recv_mem_type);
    case 'a':
        return parse_accel_device_params(opt_arg, &params->super.send_device,
                                         &params->super.recv_device);
    case 'L':
        return parse_device_level(opt_arg, &params->super.device_level);
    case 'F':
        return parse_int(opt_arg, &params->super.device_fc_window,
                         "device flow control window size", 1, INT_MAX);
    case 'Y':
        return parse_channel_mode(opt_arg, &params->super.device_channel_mode,
                                  &params->super.channel_rand_seed);
    case 'y':
        params->super.flags |= UCX_PERF_TEST_FLAG_AM_RECV_COPY;
        return UCS_OK;
    case 'z':
        params->super.flags |= UCX_PERF_TEST_FLAG_PREREG;
        return UCS_OK;
    default:
       return UCS_ERR_INVALID_PARAM;
    }
}

ucs_status_t adjust_test_params(perftest_params_t *params,
                                const char *error_prefix)
{
    test_type_t *test;

    if (params->test_id == TEST_ID_UNDEFINED) {
        ucs_error("%smissing test name", error_prefix);
        return UCS_ERR_INVALID_PARAM;
    }

    test = &tests[params->test_id];

    if (params->super.max_outstanding == 0) {
        params->super.max_outstanding = test->window_size;
    }

    if (params->super.send_device.mem_type != UCS_MEMORY_TYPE_LAST) {
        /* TODO: Add getter function for thread count */
        params->super.device_thread_count = params->super.thread_count;
        params->super.thread_count        = 1;
    }

    return UCS_OK;
}

ucs_status_t clone_params(perftest_params_t *dest,
                          const perftest_params_t *src)
{
    size_t msg_size_list_size;

    *dest                     = *src;
    msg_size_list_size        = dest->super.msg_size_cnt *
                                sizeof(*dest->super.msg_size_list);
    dest->super.msg_size_list = malloc(msg_size_list_size);
    if (dest->super.msg_size_list == NULL) {
        return ((msg_size_list_size != 0) ? UCS_ERR_NO_MEMORY : UCS_OK);
    }

    memcpy(dest->super.msg_size_list, src->super.msg_size_list,
           msg_size_list_size);
    return UCS_OK;
}

ucs_status_t check_params(const perftest_params_t *params)
{
    ucs_status_t status;

    status = check_daemon_params(&params->super);
    if (status != UCS_OK) {
        return status;
    }

    switch (params->super.api) {
    case UCX_PERF_API_UCT:
        if (!strcmp(params->super.uct.dev_name, TL_RESOURCE_NAME_NONE)) {
            ucs_error("A device must be specified with -d flag for UCT test");
            return UCS_ERR_INVALID_PARAM;
        }
        if (!strcmp(params->super.uct.tl_name, TL_RESOURCE_NAME_NONE)) {
            ucs_error(
                    "A transport must be specified with -x flag for UCT test");
            return UCS_ERR_INVALID_PARAM;
        }
        return UCS_OK;
    case UCX_PERF_API_UCP:
        if (strcmp(params->super.uct.dev_name, TL_RESOURCE_NAME_NONE)) {
            ucs_warn("-d '%s' ignored for UCP test; see NOTES section in help "
                     "message",
                     params->super.uct.dev_name);
        }
        if (strcmp(params->super.uct.tl_name, TL_RESOURCE_NAME_NONE)) {
            ucs_warn("-x '%s' ignored for UCP test; see NOTES section in help "
                     "message",
                     params->super.uct.tl_name);
        }
        return UCS_OK;
    default:
        ucs_error("Invalid test case");
        return UCS_ERR_INVALID_PARAM;
    }
}

static ucs_status_t parse_cpus(char *opt_arg, struct perftest_context *ctx)
{
    char *endptr, *cpu_list = opt_arg;
    int cpu;

    ctx->num_cpus = 0;
    cpu           = strtol(cpu_list, &endptr, 10);

    while (((*endptr == ',') || (*endptr == '\0')) && (ctx->num_cpus < MAX_CPUS)) {
        if (cpu < 0) {
            ucs_error("invalid cpu number detected: (%d)", cpu);
            return UCS_ERR_INVALID_PARAM;
        }

        ctx->cpus[ctx->num_cpus++] = cpu;

        if (*endptr == '\0') {
            break;
        }

        cpu_list = endptr + 1; /* skip the comma */
        cpu      = strtol(cpu_list, &endptr, 10);
    }

    if (*endptr == ',') {
        ucs_error("number of listed cpus exceeds the maximum supported value (%d)",
                  MAX_CPUS);
        return UCS_ERR_INVALID_PARAM;
    }

    return UCS_OK;
}

ucs_status_t parse_opts(struct perftest_context *ctx, int mpi_initialized,
                        int argc, char **argv)
{
    ucs_status_t status;
    int c;

    ucs_trace_func("");

    ucx_perf_global_init(); /* initialize memory types */

    status = init_test_params(&ctx->params);
    if (status != UCS_OK) {
        return status;
    }

    ctx->server_addr     = NULL;
    ctx->num_batch_files = 0;
    ctx->port            = 13337;
    ctx->af              = AF_INET;
    ctx->flags           = 0;
    ctx->mpi             = mpi_initialized;
    ctx->mad_port        = NULL;

    optind = 1;
    while ((c = getopt_long(argc, argv,
                            "p:b:6NfvXc:P:hK:g:G:k" TEST_PARAMS_ARGS,
                            TEST_PARAMS_ARGS_LONG, NULL)) != -1) {
        switch (c) {
        case 'p':
            status = ucs_sock_port_from_string(optarg, &ctx->port);
            if (status != UCS_OK) {
                goto err;
            }
            break;
        case '6':
            ctx->af = AF_INET6;
            break;
        case 'b':
            if (ctx->num_batch_files < MAX_BATCH_FILES) {
                ctx->batch_files[ctx->num_batch_files++] = optarg;
            }
            break;
        case 'N':
            ctx->flags |= TEST_FLAG_NUMERIC_FMT;
            break;
        case 'f':
            ctx->flags |= TEST_FLAG_PRINT_FINAL;
            break;
        case 'v':
            ctx->flags |= TEST_FLAG_PRINT_CSV;
            break;
        case 'X':
            ctx->flags |= TEST_FLAG_PRINT_EXTRA_INFO;
            break;
        case 'c':
            ctx->flags |= TEST_FLAG_SET_AFFINITY;
            status = parse_cpus(optarg, ctx);
            if (status != UCS_OK) {
                goto err;
            }
            break;
        case 'K':
            ctx->mad_port = optarg;
            break;
        case 'P':
#ifdef HAVE_MPI
            ctx->mpi = atoi(optarg) && mpi_initialized;
            break;
#endif
        case 'g': /* handles daemon-local long option as well */
            status = ucs_sock_ipportstr_to_sockaddr(
                        optarg, DEFAULT_DAEMON_PORT,
                        &ctx->params.super.ucp.dmn_local_addr);
            if (status != UCS_OK) {
                goto err;
            }
            break;
        case 'G': /* handles daemon-remote long option as well */
            status = ucs_sock_ipportstr_to_sockaddr(
                        optarg, DEFAULT_DAEMON_PORT,
                        &ctx->params.super.ucp.dmn_remote_addr);
            if (status != UCS_OK) {
                goto err;
            }
            break;
        case 'h':
            usage(ctx, ucs_basename(argv[0]));
            status = UCS_ERR_CANCELED;
            goto err;
        default:
            status = parse_test_params(&ctx->params, c, optarg);
            if (status != UCS_OK) {
                usage(ctx, ucs_basename(argv[0]));
                goto err;
            }
            break;
        }
    }

    if ((ctx->mpi != 0) && (ctx->mad_port != NULL)) {
        ucs_error("conflicting arguments: cannot use MPI and IB RTE at the "
                  "same time (mpirun and -K)");
        status = UCS_ERR_INVALID_PARAM;
        goto err;
    }

    if (optind < argc) {
        ctx->server_addr = argv[optind];

        if (ctx->params.super.flags & UCX_PERF_TEST_FLAG_LOOPBACK) {
            ucs_error("conflicting arguments: server hostname argument is not "
                      "allowed in loopback (-l) mode");
            status = UCS_ERR_INVALID_PARAM;
            goto err;
        }
    }

    return init_daemon_params(&ctx->params.super);

err:
    perftest_params_release_msg_size_list(&ctx->params);
    return status;
}
