/*
 * qperf - main.
 * Measure socket and RDMA performance.
 *
 * Copyright (c) 2002-2009 Johann George.  All rights reserved.
 * Copyright (c) 2006-2009 QLogic Corporation.  All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#define _GNU_SOURCE
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <sched.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/times.h>
#include <sys/select.h>
#include <sys/utsname.h>
#include "qperf.h"


/*
 * Configurable parameters.  If your change makes this version of qperf
 * incompatible with previous versions (usually a change to the Req structure),
 * increment VER_MIN and set VER_INC to 0.  Otherwise, just increment VER_INC.
 * VER_MAJ is reserved for major changes.
 */
#define VER_MAJ 0                       /* Major version */
#define VER_MIN 4                       /* Minor version */
#define VER_INC 11                       /* Incremental version */
#define LISTENQ 5                       /* Size of listen queue */
#define BUFSIZE 1024                    /* Size of buffers */


/*
 * Default parameter values.
 */
#define DEF_TIME        2               /* Test duration */
#define DEF_TIMEOUT     5               /* Timeout */
#define DEF_PRECISION   3               /* Precision displayed */
#define DEF_LISTEN_PORT 19765           /* Listen port */


/*
 * Option list.
 */
typedef struct OPTION {
    char       *name;                   /* Name of option */
    char       *type;                   /* Type */
    int         arg1;                   /* First argument */
    int         arg2;                   /* Second argument */
} OPTION;


/*
 * Used to loop through a range of values.
 */
typedef struct LOOP {
    struct LOOP  *next;                 /* Pointer to next loop */
    OPTION       *option;               /* Loop variable */
    long          init;                 /* Initial value */
    long          last;                 /* Last value */
    long          incr;                 /* Increment */
    int           mult;                 /* If set, multiply, otherwise add */
} LOOP;


/*
 * Parameter information.
 */
typedef struct PAR_INFO {
    PAR_INDEX   index;                  /* Index into parameter table */
    int         type;                   /* Type */
    void       *ptr;                    /* Pointer to value */
    char       *name;                   /* Option name */
    int         set;                    /* Parameter has been set */
    int         used;                   /* Parameter has been used */
    int         inuse;                  /* Parameter is in use */
} PAR_INFO;


/*
 * Parameter name association.
 */
typedef struct PAR_NAME {
    char       *name;                   /* Name */
    PAR_INDEX   loc_i;                  /* Local index */
    PAR_INDEX   rem_i;                  /* Remote index */
} PAR_NAME;


/*
 * A simple mapping between two strings.
 */
typedef struct DICT {
    char       *str1;                   /* String 1 */
    char       *str2;                   /* String 2 */
} DICT;


/*
 * Test prototype.
 */
typedef struct TEST {
    char       *name;                   /* Test name */
    void      (*client)(void);          /* Client function */
    void      (*server)(void);          /* Server function */
} TEST;


/*
 * Used to save output data for formatting.
 */
typedef struct SHOW {
    char    *pref;                      /* Name prefix */
    char    *name;                      /* Name */
    char    *data;                      /* Data */
    char    *unit;                      /* Unit */
    char    *altn;                      /* Alternative value */
} SHOW;


/*
 * Configuration information.
 */
typedef struct CONF {
    char        node[STRSIZE];          /* Node */
    char        cpu[STRSIZE];           /* CPU */
    char        os[STRSIZE];            /* Operating System */
    char        qperf[STRSIZE];         /* Qperf version */
} CONF;


/*
 * Function prototypes.
 */
static void      add_ustat(USTAT *l, USTAT *r);
static long      arg_long(char ***argvp);
static long      arg_size(char ***argvp);
static char     *arg_strn(char ***argvp);
static long      arg_time(char ***argvp);
static void      calc_node(RESN *resn, STAT *stat);
static void      calc_results(void);
static void      client(TEST *test);
static int       cmpsub(char *s2, char *s1);
static char     *commify(char *data);
static void      dec_req_data(REQ *host);
static void      dec_req_version(REQ *host);
static void      dec_stat(STAT *host);
static void      dec_ustat(USTAT *host);
static void      do_args(char *args[]);
static void      do_loop(LOOP *loop, TEST *test);
static void      do_option(OPTION *option, char ***argvp);
static void      enc_req(REQ *host);
static void      enc_stat(STAT *host);
static void      enc_ustat(USTAT *host);
static TEST     *find_test(char *name);
static OPTION   *find_option(char *name);
static void      get_conf(CONF *conf);
static void      get_cpu(CONF *conf);
static void      get_times(CLOCK timex[T_N]);
static void      initialize(void);
static void      init_lstat(void);
static char     *loop_arg(char **pp);
static int       nice_1024(char *pref, char *name, long long value);
static PAR_INFO *par_info(PAR_INDEX index);
static PAR_INFO *par_set(char *name, PAR_INDEX index);
static int       par_isset(PAR_INDEX index);
static void      parse_loop(char ***argvp);
static void      place_any(char *pref, char *name, char *unit, char *data,
                           char *altn);
static void      place_show(void);
static void      place_val(char *pref, char *name, char *unit, double value);
static void      remotefd_close(void);
static void      remotefd_setup(void);
static void      run_client_conf(void);
static void      run_client_quit(void);
static void      run_server_conf(void);
static void      run_server_quit(void);
static void      server(void);
static void      server_listen(void);
static int       server_recv_request(void);
static void      set_affinity(void);
static void      set_signals(void);
static void      show_debug(void);
static void      show_info(MEASURE measure);
static void      show_rest(void);
static void      show_used(void);
static void      sig_alrm(int signo, siginfo_t *siginfo, void *ucontext);
static void      sig_quit(int signo, siginfo_t *siginfo, void *ucontext);
static void      sig_urg(int signo, siginfo_t *siginfo, void *ucontext);
static char     *skip_colon(char *s);
static void      start_test_timer(int seconds);
static long      str_size(char *arg, char *str);
static void      strncopy(char *d, char *s, int n);
static char     *two_args(char ***argvp);
static int       verbose(int type, double value);
static void      version_error(void);
static void      view_band(int type, char *pref, char *name, double value);
static void      view_cost(int type, char *pref, char *name, double value);
static void      view_cpus(int type, char *pref, char *name, double value);
static void      view_rate(int type, char *pref, char *name, double value);
static void      view_long(int type, char *pref, char *name, long long value);
static void      view_size(int type, char *pref, char *name, long long value);
static void      view_strn(int type, char *pref, char *name, char *value);
static void      view_time(int type, char *pref, char *name, double value);


/*
 * Configurable variables.
 */
static int  ListenPort      = DEF_LISTEN_PORT;
static int  Precision       = DEF_PRECISION;
static int  ServerWait      = DEF_TIMEOUT;
static int  UseBitsPerSec   = 0;


/*
 * Static variables.
 */
static REQ      RReq;
static STAT     IStat;
static int      ListenFD;
static LOOP    *Loops;
static int      ProcStatFD;
static STAT     RStat;
static int      ShowIndex;
static SHOW     ShowTable[256];
static int      UnifyUnits;
static int      UnifyNodes;
static int      VerboseConf;
static int      VerboseStat;
static int      VerboseTime;
static int      VerboseUsed;


/*
 * Global variables.
 */
RES          Res;
REQ          Req;
STAT         LStat;
char        *TestName;
char        *ServerName;
SS           ServerAddr;
int          ServerAddrLen;
int          RemoteFD;
int          Debug;
volatile int Finished;


/*
 * Parameter names.  This is used to print out the names of the parameters that
 * have been set.
 */
PAR_NAME ParName[] ={
    { "access_recv",    L_ACCESS_RECV,    R_ACCESS_RECV   },
    { "affinity",       L_AFFINITY,       R_AFFINITY      },
    { "alt_port",       L_ALT_PORT,       R_ALT_PORT      },
    { "flip",           L_FLIP,           R_FLIP          },
    { "id",             L_ID,             R_ID            },
    { "msg_size",       L_MSG_SIZE,       R_MSG_SIZE      },
    { "mtu_size",       L_MTU_SIZE,       R_MTU_SIZE      },
    { "no_msgs",        L_NO_MSGS,        R_NO_MSGS       },
    { "poll_mode",      L_POLL_MODE,      R_POLL_MODE     },
    { "port",           L_PORT,           R_PORT          },
    { "rd_atomic",      L_RD_ATOMIC,      R_RD_ATOMIC     },
    { "service_level",  L_SL,             R_SL            },
    { "sock_buf_size",  L_SOCK_BUF_SIZE,  R_SOCK_BUF_SIZE },
    { "src_path_bits",  L_SRC_PATH_BITS,  R_SRC_PATH_BITS },
    { "time",           L_TIME,           R_TIME          },
    { "timeout",        L_TIMEOUT,        R_TIMEOUT       },
    { "use_cm",         L_USE_CM,         R_USE_CM        },
};


/*
 * Parameters.  These must be listed in the same order as the indices are
 * defined.
 */
PAR_INFO ParInfo[P_N] ={
    { P_NULL,                                       },
    { L_ACCESS_RECV,    'l',  &Req.access_recv      },
    { R_ACCESS_RECV,    'l',  &RReq.access_recv     },
    { L_AFFINITY,       'l',  &Req.affinity         },
    { R_AFFINITY,       'l',  &RReq.affinity        },
    { L_ALT_PORT,       'l',  &Req.alt_port         },
    { R_ALT_PORT,       'l',  &RReq.alt_port        },
    { L_FLIP,           'l',  &Req.flip             },
    { R_FLIP,           'l',  &RReq.flip            },
    { L_ID,             'p',  &Req.id               },
    { R_ID,             'p',  &RReq.id              },
    { L_MSG_SIZE,       's',  &Req.msg_size         },
    { R_MSG_SIZE,       's',  &RReq.msg_size        },
    { L_MTU_SIZE,       's',  &Req.mtu_size         },
    { R_MTU_SIZE,       's',  &RReq.mtu_size        },
    { L_NO_MSGS,        'l',  &Req.no_msgs          },
    { R_NO_MSGS,        'l',  &RReq.no_msgs         },
    { L_POLL_MODE,      'l',  &Req.poll_mode        },
    { R_POLL_MODE,      'l',  &RReq.poll_mode       },
    { L_PORT,           'l',  &Req.port             },
    { R_PORT,           'l',  &RReq.port            },
    { L_RD_ATOMIC,      'l',  &Req.rd_atomic        },
    { R_RD_ATOMIC,      'l',  &RReq.rd_atomic       },
    { L_SL,             'l',  &Req.sl               },
    { R_SL,             'l',  &RReq.sl              },
    { L_SOCK_BUF_SIZE,  's',  &Req.sock_buf_size    },
    { R_SOCK_BUF_SIZE,  's',  &RReq.sock_buf_size   },
    { L_SRC_PATH_BITS,  's',  &Req.src_path_bits    },
    { R_SRC_PATH_BITS,  's',  &RReq.src_path_bits   },
    { L_STATIC_RATE,    'p',  &Req.static_rate      },
    { R_STATIC_RATE,    'p',  &RReq.static_rate     },
    { L_TIME,           't',  &Req.time             },
    { R_TIME,           't',  &RReq.time            },
    { L_TIMEOUT,        't',  &Req.timeout          },
    { R_TIMEOUT,        't',  &RReq.timeout         },
    { L_USE_CM,         'l',  &Req.use_cm           },
    { R_USE_CM,         'l',  &RReq.use_cm          },
};


/*
 * Renamed options.  First is old, second is new.
 */
DICT Renamed[] = {
    /* -a becomes -ca (--cpu_affinity) */
    { "--affinity",         "--cpu_affinity"        },
    {   "-a",               "-ca"                   },
    {  "--loc_affinity",    "--loc_cpu_affinity"    },
    {   "-la",              "-lca"                  },
    {  "--rem_affinity",    "--rem_cpu_affinity"    },
    {   "-ra",              "-rca"                  },
    /* -r becomes -sr (--static_rate) */
    { "--rate",             "--static_rate"         },
    {   "-r",               "-sr"                   },
    {  "--loc_rate",        "--loc_static_rate"     },
    {   "-lr",              "-lsr"                  },
    {  "--rem_rate",        "--rem_static_rate"     },
    {   "-rr",              "-rsr"                  },
    /* -p becomes -ip (--ip_port) */
    { "--port",             "--ip_port"             },
    {   "-p",               "-ip"                   },
    /* -P becomes -cp (--cq_poll) */
    { "--poll",             "--cq_poll"             },
    {   "-P",               "-cp"                   },
    {  "--loc_poll",        "--loc_cq_poll"         },
    {   "-lP",              "-lcp"                  },
    {  "--rem_poll",        "--rem_cq_poll"         },
    {   "-rP",              "-rcp"                  },
    /* -R becomes -nr (--rd_atomic) */
    {   "-R",               "-nr"                   },
    {   "-lR",              "-lnr"                  },
    {   "-rR",              "-rnr"                  },
    /* -T becomes -to (--timeout) */
    {   "-T",               "-to"                   },
    {   "-lT",              "-lto"                  },
    {   "-rT",              "-rto"                  },
    /* -S becomes -sb (--sock_buf_size) */
    {   "-S",               "-sb"                   },
    {   "-lS",              "-lsb"                  },
    {   "-rS",              "-rsb"                  },
    /* -W becomes -ws (--wait_server) */
    { "--wait",             "--wait_server"         },
    {   "-W",               "-ws"                   },
    /* verbose options */
    {   "-vC",              "-vvc",                 },
    {   "-vS",              "-vvs",                 },
    {   "-vT",              "-vvt",                 },
    {   "-vU",              "-vvu",                 },
    /* options that are on */
    {   "-aro",             "-ar1"                  },
    {   "-cmo",             "-cm1"                  },
    {   "-fo",              "-f1"                   },
    {   "-cpo",             "-cp1"                  },
    {   "-lcpo",            "-lcp1"                 },
    {   "-rcpo",            "-rcp1"                 },
    /* miscellaneous */
    {   "-Ar",              "-ar"                   },
    {   "-M",               "-mt"                   },
    {   "-u",               "-uu",                  },
};


/*
 * Options.  The type field (2nd column) is used by do_option.  If it begins
 * with a S, it is a valid server option.  If it begins with a X, it is
 * obsolete and will eventually go away.
 */
OPTION Options[] ={
    { "--access_recv",        "int",   L_ACCESS_RECV,   R_ACCESS_RECV   },
    {   "-ar",                "int",   L_ACCESS_RECV,   R_ACCESS_RECV   },
    {   "-ar1",               "set1",  L_ACCESS_RECV,   R_ACCESS_RECV   },
    { "--alt_port",           "int",   L_ALT_PORT,      R_ALT_PORT      },
    {   "-ap",                "int",   L_ALT_PORT,      R_ALT_PORT      },
    {  "--loc_alt_port",      "int",   L_ALT_PORT,                      },
    {   "-lap",               "int",   L_ALT_PORT,                      },
    {  "--rem_alt_port",      "int",   R_ALT_PORT                       },
    {   "-rap",               "int",   R_ALT_PORT                       },
    { "--cpu_affinity",       "int",   L_AFFINITY,      R_AFFINITY      },
    {   "-ca",                "int",   L_AFFINITY,      R_AFFINITY      },
    {  "--loc_cpu_affinity",  "int",   L_AFFINITY,                      },
    {   "-lca",               "int",   L_AFFINITY,                      },
    {  "--rem_cpu_affinity",  "int",   R_AFFINITY                       },
    {   "-rca",               "int",   R_AFFINITY                       },
    { "--debug",              "Sdebug",                                 },
    {   "-D",                 "Sdebug",                                 },
    { "--flip",               "int",   L_FLIP,          R_FLIP          },
    {   "-f",                 "int",   L_FLIP,          R_FLIP          },
    {   "-f1",                "set1",  L_FLIP,          R_FLIP          },
    { "--help",               "help"                                    }, 
    {   "-h",                 "help"                                    }, 
    { "--host",               "host",                                   },
    {   "-H",                 "host",                                   },
    { "--id",                 "str",   L_ID,            R_ID            },
    {   "-i",                 "str",   L_ID,            R_ID            },
    {  "--loc_id",            "str",   L_ID,                            },
    {   "-li",                "str",   L_ID,                            },
    {  "--rem_id",            "str",   R_ID                             },
    {   "-ri",                "str",   R_ID                             },
    { "--listen_port",        "Slp",                                    },
    {   "-lp",                "Slp",                                    },
    { "--loop",               "loop",                                   },
    {   "-oo",                "loop",                                   },
    { "--msg_size",           "size",  L_MSG_SIZE,      R_MSG_SIZE      },
    {   "-m",                 "size",  L_MSG_SIZE,      R_MSG_SIZE      },
    { "--mtu_size",           "size",  L_MTU_SIZE,      R_MTU_SIZE      },
    {   "-mt",                "size",  L_MTU_SIZE,      R_MTU_SIZE      },
    { "--no_msgs",            "int",   L_NO_MSGS,       R_NO_MSGS       },
    {   "-n",                 "int",   L_NO_MSGS,       R_NO_MSGS       },
    { "--cq_poll",            "int",   L_POLL_MODE,     R_POLL_MODE     },
    {  "-cp",                 "int",   L_POLL_MODE,     R_POLL_MODE     },
    {   "-cp1",               "set1",  L_POLL_MODE,     R_POLL_MODE     },
    {  "--loc_cq_poll",       "int",   L_POLL_MODE,                     },
    {   "-lcp",               "int",   L_POLL_MODE,                     },
    {   "-lcp1",              "set1",  L_POLL_MODE                      },
    {  "--rem_cq_poll",       "int",   R_POLL_MODE                      },
    {   "-rcp",               "int",   R_POLL_MODE                      },
    {   "-rcp1",              "set1",  R_POLL_MODE                      },
    { "--ip_port",            "int",   L_PORT,          R_PORT          },
    {   "-ip",                "int",   L_PORT,          R_PORT          },
    { "--precision",          "precision",                              },
    {   "-e",                 "precision",                              },
    { "--rd_atomic",          "int",   L_RD_ATOMIC,     R_RD_ATOMIC     },
    {   "-nr",                "int",   L_RD_ATOMIC,     R_RD_ATOMIC     },
    {  "--loc_rd_atomic",     "int",   L_RD_ATOMIC,                     },
    {   "-lnr",               "int",   L_RD_ATOMIC,                     },
    {  "--rem_rd_atomic",     "int",   R_RD_ATOMIC                      },
    {   "-rnr",               "int",   R_RD_ATOMIC                      },
    { "--service_level",      "sl",    L_SL,            R_SL            },
    {   "-sl",                "sl",    L_SL,            R_SL            },
    {  "--loc_service_level", "sl",    L_SL                             },
    {   "-lsl",               "sl",    L_SL                             },
    {  "--rem_service_level", "sl",    R_SL                             },
    {   "-rsl",               "sl",    R_SL                             },
    { "--sock_buf_size",      "size",  L_SOCK_BUF_SIZE, R_SOCK_BUF_SIZE },
    {   "-sb",                "size",  L_SOCK_BUF_SIZE, R_SOCK_BUF_SIZE },
    {  "--loc_sock_buf_size", "size",  L_SOCK_BUF_SIZE                  },
    {   "-lsb",               "size",  L_SOCK_BUF_SIZE                  },
    {  "--rem_sock_buf_size", "size",  R_SOCK_BUF_SIZE                  },
    {   "-rsb",               "size",  R_SOCK_BUF_SIZE                  },
    { "--src_path_bits",      "size",  L_SRC_PATH_BITS, R_SRC_PATH_BITS },
    {   "-sp",                "size",  L_SRC_PATH_BITS, R_SRC_PATH_BITS },
    {  "--loc_src_path_bits", "size",  L_SRC_PATH_BITS                  },
    {   "-lsp",               "size",  L_SRC_PATH_BITS                  },
    {  "--rem_src_path_bits", "size",  R_SRC_PATH_BITS                  },
    {   "-rsp",               "size",  R_SRC_PATH_BITS                  },
    { "--static_rate",        "str",   L_STATIC_RATE,   R_STATIC_RATE   },
    {   "-sr",                "str",   L_STATIC_RATE,   R_STATIC_RATE   },
    {  "--loc_static_rate",   "str",   L_STATIC_RATE                    },
    {   "-lsr",               "str",   L_STATIC_RATE                    },
    {  "--rem_static_rate",   "str",   R_STATIC_RATE                    },
    {   "-rsr",               "str",   R_STATIC_RATE                    },
    { "--time",               "time",  L_TIME,          R_TIME          },
    {   "-t",                 "time",  L_TIME,          R_TIME          },
    { "--timeout",            "time",  L_TIMEOUT,       R_TIMEOUT       },
    {   "-to",                "time",  L_TIMEOUT,       R_TIMEOUT       },
    {  "--loc_timeout",       "Stime", L_TIMEOUT                        },
    {   "-lto",               "Stime", L_TIMEOUT                        },
    {  "--rem_timeout",       "time",  R_TIMEOUT                        },
    {   "-rto",               "time",  R_TIMEOUT                        },
    { "--unify_nodes",        "un",                                     },
    {   "-un",                "un",                                     },
    { "--unify_units",        "uu",                                     },
    {   "-uu",                "uu",                                     },
    { "--use_bits_per_sec",   "ub",                                     },
    {   "-ub",                "ub",                                     },
    { "--use_cm",             "int",   L_USE_CM,        R_USE_CM        },
    {   "-cm",                "int",   L_USE_CM,        R_USE_CM        },
    {   "-cm1",               "set1",  L_USE_CM,        R_USE_CM        },
    { "--verbose",            "v",                                      },
    {   "-v",                 "v",                                      },
    { "--verbose_conf",       "vc",                                     },
    {   "-vc",                "vc",                                     },
    { "--verbose_stat",       "vs",                                     },
    {   "-vs",                "vs",                                     },
    { "--verbose_time",       "vt",                                     },
    {   "-vt",                "vt",                                     },
    { "--verbose_used",       "vu",                                     },
    {   "-vu",                "vu",                                     },
    { "--verbose_more",       "vv",                                     },
    {   "-vv",                "vv",                                     },
    { "--verbose_more_conf",  "vvc",                                    },
    {   "-vvc",               "vvc",                                    },
    { "--verbose_more_stat",  "vvs",                                    },
    {   "-vvs",               "vvs",                                    },
    { "--verbose_more_time",  "vvt",                                    },
    {   "-vvt",               "vvt",                                    },
    { "--verbose_more_used",  "vvu",                                    },
    {   "-vvu",               "vvu",                                    },
    { "--version",            "version",                                },
    {   "-V",                 "version",                                },
    { "--wait_server",        "wait",                                   },
    {   "-ws",                "wait",                                   },
};


/*
 * Tests.
 */
#define test(n) { #n, run_client_##n, run_server_##n }
TEST Tests[] ={
    test(conf),
    test(quit),
    test(rds_bw),
    test(rds_lat),
    test(sctp_bw),
    test(sctp_lat),
    test(sdp_bw),
    test(sdp_lat),
    test(tcp_bw),
    test(tcp_lat),
    test(udp_bw),
    test(udp_lat),
#ifdef RDMA
    test(rc_bi_bw),
    test(rc_bw),
    test(rc_compare_swap_mr),
    test(rc_fetch_add_mr),
    test(rc_lat),
    test(rc_rdma_read_bw),
    test(rc_rdma_read_lat),
    test(rc_rdma_write_bw),
    test(rc_rdma_write_lat),
    test(rc_rdma_write_poll_lat),
    test(uc_bi_bw),
    test(uc_bw),
    test(uc_lat),
    test(uc_rdma_write_bw),
    test(uc_rdma_write_lat),
    test(uc_rdma_write_poll_lat),
    test(ud_bi_bw),
    test(ud_bw),
    test(ud_lat),
    test(ver_rc_compare_swap),
    test(ver_rc_fetch_add),
#ifdef HAS_XRC
    test(xrc_bi_bw),
    test(xrc_bw),
    test(xrc_lat),
#endif /* HAS_XRC */
#endif
};


int
main(int argc, char *argv[])
{
    initialize();
    set_signals();
    do_args(&argv[1]);
    return 0;
}


/*
 * Initialize variables.
 */
static void
initialize(void)
{
    int i;

    RemoteFD = -1;
    for (i = 0; i < P_N; ++i)
        if (ParInfo[i].index != i)
            error(BUG, "initialize: ParInfo: out of order: %d", i);
    ProcStatFD = open("/proc/stat", 0);
    if (ProcStatFD < 0)
        error(SYS, "cannot open /proc/stat");
    IStat.no_cpus = sysconf(_SC_NPROCESSORS_ONLN);
    IStat.no_ticks = sysconf(_SC_CLK_TCK);
}


/*
 * Look for a colon and skip past it and any spaces.
 */
static char *
skip_colon(char *s)
{
    for (;;) {
        int c = *s++;

        if (c == ':')
            break;
        if (c == '\0')
            return 0;
    }
    while (*s == ' ')
        s++;
    return s;
}


/*
 * A case insensitive string compare.  s2 must at least contain all of s1 but
 * can be longer.
 */
static int
cmpsub(char *s2, char *s1)
{
    for (;;) {
        int c1 = *s1++;
        int c2 = *s2++;

        if (c1 == '\0')
            return 1;
        if (c2 == '\0')
            return 0;
        if (tolower(c1) != tolower(c2))
            return 0;
    }
}


/*
 * Set up signal handlers.
 */
static void
set_signals(void)
{
    struct sigaction act ={
        .sa_flags = SA_SIGINFO
    };

    act.sa_sigaction = sig_alrm;
    sigaction(SIGALRM, &act, 0);
    sigaction(SIGPIPE, &act, 0);

    act.sa_sigaction = sig_quit;
    sigaction(SIGQUIT, &act, 0);

    act.sa_sigaction = sig_urg;
    sigaction(SIGURG, &act, 0);
}


/*
 * Note that time is up.
 */
static void
sig_alrm(int signo, siginfo_t *siginfo, void *ucontext)
{
    set_finished();
}


/*
 * Our child sends us a quit when it wishes us to exit.
 */
static void
sig_quit(int signo, siginfo_t *siginfo, void *ucontext)
{
    exit(0);
}


/*
 * Called when a TCP/IP out-of-band message is received.
 */
static void
sig_urg(int signo, siginfo_t *siginfo, void *ucontext)
{
    urgent();
}


/*
 * Parse arguments.
 */
static void
do_args(char *args[])
{
    int isClient = 0;
    int testSpecified = 0;

    while (*args) {
        char *arg = *args;
        if (arg[0] == '-') {
            OPTION *option = find_option(arg);
            if (!option)
                error(0, "%s: bad option; try: qperf --help options", arg);
            if (option->type[0] != 'S')
                isClient = 1;
            do_option(option, &args);
        } else {
            isClient = 1;
            if (!ServerName)
                ServerName = arg;
            else {
                TEST *test = find_test(arg);

                if (!test)
                    error(0, "%s: bad test; try: qperf --help tests", arg);
                do_loop(Loops, test);
                testSpecified = 1;
            }
            ++args;
        }
    }

    if (!isClient)
        server();
    else if (!testSpecified) {
        if (!ServerName)
            error(0, "you used a client-only option but did not specify the "
                      "server name.\nDo you want to be a client or server?");
        if (find_test(ServerName))
            error(0, "must specify host name first; try: qperf --help");
        error(0, "must specify a test type; try: qperf --help");
    }
}


/*
 * Loop through a series of tests.
 */
static void
do_loop(LOOP *loop, TEST *test)
{
    if (!loop)
        client(test);
    else {
        long l = loop->init;

        while (l <= loop->last) {
            char   buf[64];
            char  *args[2] = {loop->option->name, buf};
            char **argv = args;

            snprintf(buf, sizeof(buf), "%ld", l);
            do_option(loop->option, &argv);
            do_loop(loop->next, test);
            if (loop->mult)
                l *= loop->incr;
            else
                l += loop->incr;
        }
    }
}


/*
 * Given the name of an option, find it.
 */
static OPTION *
find_option(char *name)
{
    int n;
    DICT *d;
    OPTION *p;

    n = cardof(Renamed);
    d = Renamed;
    for (; n--; ++d) {
        if (streq(name, d->str1)) {
            char *msg = "warning: obsolete option: %s; use %s instead";
            error(RET, msg, name, d->str2);
            name = d->str2;
            break;
        }
    }

    n = cardof(Options);
    p = Options;
    for (; n--; ++p)
        if (streq(name, p->name))
            return p;
    return 0;
}


/*
 * Given the name of a test, find it.
 */
static TEST *
find_test(char *name)
{
    int n = cardof(Tests);
    TEST *p = Tests;

    for (; n--; ++p)
        if (streq(name, p->name))
            return p;
    return 0;
}


/*
 * Handle options.
 */
static void
do_option(OPTION *option, char ***argvp)
{
    char *t = option->type;

    if (*t == 'S')
        ++t;

    if (streq(t, "debug")) {
        Debug = 1;
        *argvp += 1;
    } else if (streq(t, "help")) {
        /* Help */
        char **usage;
        char *category = (*argvp)[1];

        if (!category)
            category = "main";
        for (usage = Usage; *usage; usage += 2)
            if (streq(*usage, category))
                break;
        if (!*usage) {
            error(0,
                "cannot find help category %s; try: qperf --help categories",
                                                                    category);
        }
        printf("%s", usage[1]);
        exit(0);
    } else if (streq(t, "host")) {
        ServerName = arg_strn(argvp);
    } else if (streq(t, "int")) {
        long v = arg_long(argvp);
        setp_u32(option->name, option->arg1, v);
        setp_u32(option->name, option->arg2, v);
    } else if (streq(t, "loop")) {
        parse_loop(argvp);
    } else if (streq(t, "lp")) {
        ListenPort = arg_long(argvp);
    } else if (streq(t, "precision")) {
        Precision = arg_long(argvp);
    } else if (streq(t, "set1")) {
        setp_u32(option->name, option->arg1, 1);
        setp_u32(option->name, option->arg2, 1);
        *argvp += 1;
    } else if (streq(t, "size")) {
        long v = arg_size(argvp);
        setp_u32(option->name, option->arg1, v);
        setp_u32(option->name, option->arg2, v);
    } else if (streq(t, "sl")) {
        long v = arg_long(argvp);
        if (v < 0 || v > 15)
            error(0, "service level must be between 0 and 15: %d given", v);
        setp_u32(option->name, option->arg1, v);
        setp_u32(option->name, option->arg2, v);
    } else if (streq(t, "str")) {
        char *s = arg_strn(argvp);
        setp_str(option->name, option->arg1, s);
        setp_str(option->name, option->arg2, s);
    } else if (streq(t, "time")) {
        long v = arg_time(argvp);
        setp_u32(option->name, option->arg1, v);
        setp_u32(option->name, option->arg2, v);
    } else if (streq(t, "ub")) {
        UseBitsPerSec = 1;
        *argvp += 1;
    } else if (streq(t, "un")) {
        UnifyNodes = 1;
        *argvp += 1;
    } else if (streq(t, "uu")) {
        UnifyUnits = 1;
        *argvp += 1;
    } else if (streq(t, "v")) {
        if (VerboseConf < 1)
            VerboseConf = 1;
        if (VerboseStat < 1)
            VerboseStat = 1;
        if (VerboseTime < 1)
            VerboseTime = 1;
        if (VerboseUsed < 1)
            VerboseUsed = 1;
        *argvp += 1;
    } else if (streq(t, "vc")) {
        VerboseConf = 1;
        *argvp += 1;
    } else if (streq(t, "version")) {
        printf("qperf %d.%d.%d\n", VER_MAJ, VER_MIN, VER_INC);
        exit(0);
    } else if (streq(t, "vs")) {
        VerboseStat = 1;
        *argvp += 1;
    } else if (streq(t, "vt")) {
        VerboseTime = 1;
        *argvp += 1;
    } else if (streq(t, "vu")) {
        VerboseUsed = 1;
        *argvp += 1;
    } else if (streq(t, "vv")) {
        VerboseConf = 2;
        VerboseStat = 2;
        VerboseTime = 2;
        VerboseUsed = 2;
        *argvp += 1;
    } else if (streq(t, "vvc")) {
        VerboseConf = 2;
        *argvp += 1;
    } else if (streq(t, "vvs")) {
        VerboseStat = 2;
        *argvp += 1;
    } else if (streq(t, "vvt")) {
        VerboseTime = 2;
        *argvp += 1;
    } else if (streq(t, "vvu")) {
        VerboseUsed = 2;
        *argvp += 1;
    } else if (streq(t, "wait")) {
        ServerWait = arg_time(argvp);
    } else
        error(BUG, "do_option: unknown type: %s", t);
}


/*
 * Parse a loop option.
 */
static void
parse_loop(char ***argvp)
{
    char *opt  = **argvp;
    char *s    = two_args(argvp);
    char *name = loop_arg(&s);
    char *init = loop_arg(&s);
    char *last = loop_arg(&s);
    char *incr = loop_arg(&s);
    LOOP *loop = qmalloc(sizeof(LOOP));

    memset(loop, 0, sizeof(*loop));

    /* Parse variable name */
    {
        int n = cardof(Options);
        OPTION *p = Options;

        if (!name)
            name = "msg_size";
        for (;;) {
            char *s = p->name;

            if (n-- == 0)
                error(0, "%s: %s: no such variable", opt, name);
            if (*s++ != '-')
                continue;
            if (*s == '-')
                s++;
            if (streq(name, s))
                break;
            p++;
        }
        loop->option = p;
    }

    /* Parse increment */
    if (!incr)
        loop->incr = 0;
    else {
        if (incr[0] == '*') {
            incr++;
            loop->mult = 1;
        }
        loop->incr = str_size(incr, opt);
        if (loop->incr < 1)
            error(0, "%s: %s: increment must be positive", opt, incr);
    }

    /* Parse initial value */
    if (init)
        loop->init = str_size(init, opt);
    else
        loop->init = loop->mult ? 1 : 0;

    /* Parse last value */
    if (!last)
        error(0, "%s: must specify limit", opt);
    loop->last = str_size(last, opt);

    /* Insert into loop list */
    if (!Loops)
        Loops = loop;
    else {
        LOOP *l = Loops;

        while (l->next)
            l = l->next;
        l->next = loop;
    }
}


/*
 * Given a string consisting of arguments separated by colons, return the next
 * argument and prepare for scanning the next one.
 */
static char *
loop_arg(char **pp)
{
    char *a = *pp;
    char *p = a;

    while (*p) {
        if (*p == ':') {
            *p = '\0';
            *pp = p + 1;
            break;
        }
        ++p;
    }
    return a[0] ? a : 0;
}


/*
 * Ensure that two arguments exist.
 */
static char *
two_args(char ***argvp)
{
    char **argv = *argvp;

    if (!argv[1])
        error(0, "%s: missing argument", argv[0]);
    *argvp += 2;
    return argv[1];
}


/*
 * Return the value of a long argument.  It must be non-negative.
 */
static long
arg_long(char ***argvp)
{
    char **argv = *argvp;
    char *p;
    long l;

    if (!argv[1])
        error(0, "missing argument to %s", argv[0]);
    l = strtol(argv[1], &p, 10);
    if (p[0] != '\0')
        error(0, "bad argument: %s", argv[1]);
    if (l < 0)
        error(0, "%s requires a non-negative number", argv[0]);
    *argvp += 2;
    return l;
}


/*
 * Return the value of a size argument.
 */
static long
arg_size(char ***argvp)
{
    long l;
    char **argv = *argvp;

    *argvp += 2;
    if (!argv[1])
        error(0, "missing argument to %s", argv[0]);
    l = str_size(argv[1], argv[0]);
    if (l < 0)
        error(0, "%s requires a non-negative number", argv[0]);
    return l;
}


/*
 * Scan a size argument from a string.
 */
static long
str_size(char *str, char *arg)
{
    char *p;
    long m = 1;
    long double d = strtold(str, &p);

    if (p[0] == '\0')
        m = 1;
    else if (streq(p, "kb") || streq(p, "k"))
        m = 1000;
    else if (streq(p, "mb") || streq(p, "m"))
        m = 1000 * 1000;
    else if (streq(p, "gb") || streq(p, "g"))
        m = 1000 * 1000 * 1000;
    else if (streq(p, "kib") || streq(p, "K"))
        m = 1024;
    else if (streq(p, "mib") || streq(p, "M"))
        m = 1024 * 1024;
    else if (streq(p, "gib") || streq(p, "G"))
        m = 1024 * 1024 * 1024;
    else
        error(0, "%s: bad size: %s", arg, str);

    return d * m;
}


/*
 * Return the value of a string argument.
 */
static char *
arg_strn(char ***argvp)
{
    char **argv = *argvp;

    if (!argv[1])
        error(0, "missing argument to %s", argv[0]);
    *argvp += 2;
    return argv[1];
}


/*
 * Return the value of a size argument.
 */
static long
arg_time(char ***argvp)
{
    char *p;
    long double d;
    long l = 0;
    char **argv = *argvp;

    if (!argv[1])
        error(0, "missing argument to %s", argv[0]);

    d = strtold(argv[1], &p);
    if (d < 0)
        error(0, "%s requires a non-negative number", argv[0]);

    if (p[0] == '\0')
        l = (long)d;
    else {
        int u = *p;
        if (p[1] != '\0')
            error(0, "bad argument: %s", argv[1]);
        if (u == 's' || u == 'S')
            l = (long)d;
        else if (u == 'm' || u == 'M')
            l = (long)(d * (60));
        else if (u == 'h' || u == 'H')
            l = (long)(d * (60 * 60));
        else if (u == 'd' || u == 'D')
            l = (long)(d * (60 * 60 * 24));
        else
            error(0, "bad argument: %s", argv[1]);
    }

    *argvp += 2;
    return l;
}


/*
 * Set a value stored in a 32 bit value without letting anyone know we set it.
 */
void
setv_u32(PAR_INDEX index, uint32_t l)
{
    PAR_INFO *p = par_info(index);
    *((uint32_t *)p->ptr) = l;
}


/*
 * Set an option stored in a 32 bit value.
 */
void
setp_u32(char *name, PAR_INDEX index, uint32_t l)
{
    PAR_INFO *p = par_set(name, index);

    if (!p)
        return;
    *((uint32_t *)p->ptr) = l;
}


/*
 * Set an option stored in a string vector.
 */
void
setp_str(char *name, PAR_INDEX index, char *s)
{
    PAR_INFO *p = par_set(name, index);

    if (!p)
        return;
    if (strlen(s) >= STRSIZE)
        error(0, "%s: too long", s);
    strcpy(p->ptr, s);
}


/*
 * Note a parameter as being used.
 */
void
par_use(PAR_INDEX index)
{
    PAR_INFO *p = par_info(index);

    p->used = 1;
    p->inuse = 1;
}


/*
 * Set the PAR_INFO.name value.
 */
static PAR_INFO *
par_set(char *name, PAR_INDEX index)
{
    PAR_INFO *p = par_info(index);

    if (index == P_NULL)
        return 0;
    if (name) {
        p->name = name;
        p->set = 1;
    } else {
        p->used = 1;
        p->inuse = 1;
        if (p->name)
            return 0;
    }
    return p;
}


/*
 * Determine if a parameter is set.
 */
static int
par_isset(PAR_INDEX index)
{
    return par_info(index)->name != 0;
}


/*
 * Index the ParInfo table.
 */
static PAR_INFO *
par_info(PAR_INDEX index)
{
    PAR_INFO *p = &ParInfo[index];

    if (index != p->index)
        error(BUG, "par_info: table out of order: %d != %d", index, p-index);
    return p;
}


/*
 * If any options were set but were not used, print out a warning message for
 * the user.
 */
void
opt_check(void)
{
    PAR_INFO *p;
    PAR_INFO *q;
    PAR_INFO *r = endof(ParInfo);

    for (p = ParInfo; p < r; ++p) {
        if (p->used || !p->set)
            continue;
        error(RET, "warning: %s set but not used in test %s",
                                                        p->name, TestName);
        for (q = p+1; q < r; ++q)
            if (q->set && q->name == p->name)
                q->set = 0;
    }
}


/*
 * Server.
 */
static void
server(void)
{
    server_listen();
    for (;;) {
        REQ req;
        pid_t pid;
        TEST *test;
        int s = offset(REQ, req_index);

        debug("ready for requests");
        if (!server_recv_request())
            continue;
        pid = fork();
        if (pid < 0) {
            error(SYS|RET, "fork failed");
            continue;
        }
        if (pid > 0) {
            remotefd_close();
            waitpid(pid, 0, 0);
            continue;
        }
        remotefd_setup();

        recv_mesg(&req, s, "request version");
        dec_init(&req);
        dec_req_version(&Req);
        if (Req.ver_maj != VER_MAJ || Req.ver_min != VER_MIN)
            version_error();
        recv_mesg(&req.req_index, sizeof(req)-s, "request data");
        dec_req_data(&Req);
        if (Req.req_index >= cardof(Tests))
            error(0, "bad request index: %d", Req.req_index);

        test = &Tests[Req.req_index];
        TestName = test->name;
        debug("received request: %s", TestName);
        init_lstat();
        set_affinity();
        (test->server)();
        exit(0);
    }
    close(ListenFD);
}


/*
 * If there is a version mismatch of qperf between the client and server, tell
 * the user which needs to be upgraded.
 */
static void
version_error(void)
{
    int hi_maj = Req.ver_maj;
    int hi_min = Req.ver_min;
    int hi_inc = Req.ver_inc;
    int lo_maj = VER_MAJ;
    int lo_min = VER_MIN;
    int lo_inc = VER_INC;
    char *msg = "upgrade qperf on %s from %d.%d.%d to %d.%d.%d";
    char *low = "server";

    if (lo_maj > hi_maj || (lo_maj == hi_maj && lo_min > hi_min)) {
        hi_maj = VER_MAJ;
        hi_min = VER_MIN;
        hi_inc = VER_INC;
        lo_maj = Req.ver_maj;
        lo_min = Req.ver_min;
        lo_inc = Req.ver_inc;
        low   = "client";
    }
    error(0, msg, low, lo_maj, lo_min, lo_inc, hi_maj, hi_min, hi_inc);
}


/*
 * Listen for any requests.
 */
static void
server_listen(void)
{
    AI *ai;
    AI hints ={
        .ai_flags    = AI_PASSIVE | AI_NUMERICSERV,
	.ai_family   = AF_INET6,
        .ai_socktype = SOCK_STREAM
    };
    AI *ailist = getaddrinfo_port(0, ListenPort, &hints);

    for (ai = ailist; ai; ai = ai->ai_next) {
        ListenFD = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
        if (ListenFD < 0)
            continue;
        setsockopt_one(ListenFD, SO_REUSEADDR);
        if (bind(ListenFD, ai->ai_addr, ai->ai_addrlen) == SUCCESS0)
            break;
        close(ListenFD);
    }
    freeaddrinfo(ailist);
    if (!ai)
        error(0, "unable to bind to listen port");

    if (!Req.timeout)
        Req.timeout = DEF_TIMEOUT;
    if (listen(ListenFD, LISTENQ) < 0)
        error(SYS, "listen failed");
}


/*
 * Accept a request from a client.
 */
static int
server_recv_request(void)
{
    socklen_t clientLen;
    SS clientAddr;

    clientLen = sizeof(clientAddr);
    RemoteFD = accept(ListenFD, (struct sockaddr *)&clientAddr, &clientLen);
    if (RemoteFD < 0)
        return error(SYS|RET, "accept failed");
    return 1;
}


/*
 * Client.
 */
static void
client(TEST *test)
{
    int i;

    for (i = 0; i < P_N; ++i)
        ParInfo[i].inuse = 0;
    if (!par_isset(L_NO_MSGS))
        setp_u32(0, L_TIME, DEF_TIME);
    if (!par_isset(R_NO_MSGS))
        setp_u32(0, R_TIME, DEF_TIME);
    setp_u32(0, L_TIMEOUT, DEF_TIMEOUT);
    setp_u32(0, R_TIMEOUT, DEF_TIMEOUT);
    par_use(L_AFFINITY);
    par_use(R_AFFINITY);
    par_use(L_TIME);
    par_use(R_TIME);

    set_affinity();
    RReq.ver_maj = VER_MAJ;
    RReq.ver_min = VER_MIN;
    RReq.ver_inc = VER_INC;
    RReq.req_index = test - Tests;
    TestName = test->name;
    debug("sending request: %s", TestName);
    init_lstat();
    printf("%s:\n", TestName);
    (*test->client)();
    remotefd_close();
    place_show();
}


/*
 * Send a request to the server.
 */
void
client_send_request(void)
{
    REQ req;
    AI *a;
    AI hints ={
        .ai_family   = AF_UNSPEC,
        .ai_socktype = SOCK_STREAM
    };
    AI *ailist = getaddrinfo_port(ServerName, ListenPort, &hints);

    RemoteFD = -1;
    if (ServerWait)
        start_test_timer(ServerWait);
    for (;;) {
        for (a = ailist; a; a = a->ai_next) {
            if (Finished)
                break;
            RemoteFD = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
            if (RemoteFD < 0)
                continue;
            if (connect(RemoteFD, a->ai_addr, a->ai_addrlen) != SUCCESS0) {
                remotefd_close();
                continue;
            }
            ServerAddrLen = a->ai_addrlen;
            memcpy(&ServerAddr, a->ai_addr, ServerAddrLen);
            break;
        }
        if (RemoteFD >= 0 || !ServerWait || Finished)
            break;
        sleep(1);
    }

    if (ServerWait)
        stop_test_timer();
    freeaddrinfo(ailist);

    if (RemoteFD < 0)
        error(0, "%s: failed to connect", ServerName);
    remotefd_setup();
    enc_init(&req);
    enc_req(&RReq);
    send_mesg(&req, sizeof(req), "request data");
}


/*
 * Configure the remote file descriptor.
 */
static void
remotefd_setup(void)
{
    int one = 1;

    if (ioctl(RemoteFD, FIONBIO, &one) < 0)
        error(SYS, "ioctl FIONBIO failed");
    if (fcntl(RemoteFD, F_SETOWN, getpid()) < 0)
        error(SYS, "fcntl F_SETOWN failed");
}


/*
 * Close the remote file descriptor.
 * Set a file descriptor to non-blocking.
 */
static void
remotefd_close(void)
{
    close(RemoteFD);
    RemoteFD = -1;
}


/*
 * Exchange results.  We sync up only to ensure that the client is out of its
 * loop so we can close our socket or whatever communication medium we are
 * using.
 */
void
exchange_results(void)
{
    STAT stat;

    if (is_client()) {
        recv_mesg(&stat, sizeof(stat), "results");
        dec_init(&stat);
        dec_stat(&RStat);
        send_sync("synchronization after test");
    } else {
        enc_init(&stat);
        enc_stat(&LStat);
        send_mesg(&stat, sizeof(stat), "results");
        recv_sync("synchronization after test");
    }
}


/*
 * Initialize local status information.
 */
static void
init_lstat(void)
{
    memcpy(&LStat, &IStat, sizeof(LStat));
}


/*
 * Show configuration (client side).
 */
static void
run_client_conf(void)
{
    CONF lconf;
    CONF rconf;

    client_send_request();
    recv_mesg(&rconf, sizeof(rconf), "configuration");
    get_conf(&lconf);
    view_strn('a', "", "loc_node",  lconf.node);
    view_strn('a', "", "loc_cpu",   lconf.cpu);
    view_strn('a', "", "loc_os",    lconf.os);
    view_strn('a', "", "loc_qperf", lconf.qperf);
    view_strn('a', "", "rem_node",  rconf.node);
    view_strn('a', "", "rem_cpu",   rconf.cpu);
    view_strn('a', "", "rem_os",    rconf.os);
    view_strn('a', "", "rem_qperf", rconf.qperf);
}


/*
 * Show configuration (server side).
 */
static void
run_server_conf(void)
{
    CONF conf;

    get_conf(&conf);
    send_mesg(&conf, sizeof(conf), "configuration");
}


/*
 * Get configuration.
 */
static void
get_conf(CONF *conf)
{
    struct utsname utsname;

    uname(&utsname);
    strncopy(conf->node, utsname.nodename, sizeof(conf->node));
    snprintf(conf->os, sizeof(conf->os), "%s %s", utsname.sysname,
                                                  utsname.release);
    get_cpu(conf);
    snprintf(conf->qperf, sizeof(conf->qperf), "%d.%d.%d",
                                        VER_MAJ, VER_MIN, VER_INC);
}


/*
 * Get CPU information.
 */
static void
get_cpu(CONF *conf)
{
    char count[STRSIZE];
    char speed[STRSIZE];
    char buf[BUFSIZE];
    char cpu[BUFSIZE];
    char mhz[BUFSIZE];
    int cpus = 0;
    int mixed = 0;
    FILE *fp = fopen("/proc/cpuinfo", "r");

    if (!fp)
        error(0, "cannot open /proc/cpuinfo");
    cpu[0] = '\0';
    mhz[0] = '\0';
    while (fgets(buf, sizeof(buf), fp)) {
        int n = strlen(buf);
        if (cmpsub(buf, "model name")) {
            ++cpus;
            if (!mixed) {
                if (cpu[0] == '\0')
                    strncopy(cpu, buf, sizeof(cpu));
                else if (!streq(buf, cpu))
                    mixed = 1;
            }
        } else if (cmpsub(buf, "cpu MHz")) {
            if (!mixed) {
                if (mhz[0] == '\0')
                    strncopy(mhz, buf, sizeof(mhz));
                else if (!streq(buf, mhz))
                    mixed = 1;
            }
        }
        while (n && buf[n-1] != '\n') {
            if (!fgets(buf, sizeof(buf), fp))
                break;
            n = strlen(buf);
        }
    }
    fclose(fp);

    /* CPU name */
    if (mixed)
        strncopy(cpu, "Mixed CPUs", sizeof(cpu));
    else {
        char *p = cpu;
        char *q = skip_colon(cpu);
        if (!q)
            return;
        for (;;) {
            if (*q == '(' && cmpsub(q, "(r)"))
                q += 3;
            else if (*q == '(' && cmpsub(q, "(tm)"))
                q += 4;
            if (tolower(*q) == 'c' && cmpsub(q, "cpu "))
                q += 4;
            if (tolower(*q) == 'p' && cmpsub(q, "processor "))
                q += 10;
            else if (q[0] == ' ' && q[1] == ' ')
                q += 1;
            else if (q[0] == '\n')
                q += 1;
            else if (!(*p++ = *q++))
                break;
        }
    }

    /* CPU speed */
    speed[0] = '\0';
    if (!mixed) {
        int n = strlen(cpu);
        if (n < 3 || cpu[n-2] != 'H' || cpu[n-1] != 'z') {
            char *q = skip_colon(mhz);
            if (q) {
                int freq = atoi(q);
                if (freq < 1000)
                    snprintf(speed, sizeof(speed), " %dMHz", freq);
                else
                    snprintf(speed, sizeof(speed), " %.1fGHz", freq/1000.0);
            }
        }
    }

    /* Number of CPUs */
    if (cpus == 1)
        count[0] = '\0';
    else
        snprintf(count, sizeof(count), "%d Cores: ", cpus);

    snprintf(conf->cpu, sizeof(conf->cpu), "%s%s%s", count, cpu, speed);
}


/*
 * Quit (client side).
 */
static void
run_client_quit(void)
{
    opt_check();
    client_send_request();
    sync_test();
    exit(0);
}


/*
 * Quit (server side).  The read is to ensure that the client first quits to
 * ensure that everything closes down cleanly.
 */
static void
run_server_quit(void)
{
    char buf[1];

    sync_test();
    (void) read(RemoteFD, buf, sizeof(buf));
    kill(getppid(), SIGQUIT);
    exit(0);
}


/*
 * Synchronize the client and server.
 */
void
sync_test(void)
{
    synchronize("synchronization before test");
    start_test_timer(Req.time);
}


/*
 * Start test timer.
 */
static void
start_test_timer(int seconds)
{
    struct itimerval itimerval = {{0}};

    Finished = 0;
    get_times(LStat.time_s);
    setitimer(ITIMER_REAL, &itimerval, 0);
    if (!seconds)
        return;

    debug("starting timer for %d seconds", seconds);
    itimerval.it_value.tv_sec = seconds;
    /*
     * SLES11 has high precision timers; too low an interval will cause timer
     * to fire extremely rapidly after first occurrence.  We set it to 10 ms.
     */
    itimerval.it_interval.tv_usec = 10000;
    setitimer(ITIMER_REAL, &itimerval, 0);
}


/*
 * Stop timing.  Note that the end time is obtained by the first call to
 * set_finished.  In the tests, when SIGALRM goes off, it may be executing a
 * system call which gets interrupted.  If SIGALRM goes off after Finished is
 * checked but before the system call is initiated, the system call will be
 * executed and it will take the second SIGALRM call generated by the interval
 * timer to wake it up.  Hence, we save the end times in sig_alrm.  Note that
 * if Finished is set, we reject any packets that are sent or arrive in order
 * not to cheat.  We clear Finished since code assumes that it is the default
 * state.
 */
void
stop_test_timer(void)
{
    struct itimerval itimerval = {{0}};

    set_finished();
    setitimer(ITIMER_REAL, &itimerval, 0);
    Finished = 0;
    debug("stopping timer");
}


/*
 * Establish the current test as finished.
 */
void
set_finished(void)
{
    if (Finished++ == 0)
        get_times(LStat.time_e);
}


/*
 * Show results.
 */
void
show_results(MEASURE measure)
{
    calc_results();
    show_info(measure);
}


/*
 * Calculate results.
 */
static void
calc_results(void)
{
    double no_msgs;
    double locTime;
    double remTime;
    double midTime;
    double gB = 1000 * 1000 * 1000;

    add_ustat(&LStat.s, &RStat.rem_s);
    add_ustat(&LStat.r, &RStat.rem_r);
    add_ustat(&RStat.s, &LStat.rem_s);
    add_ustat(&RStat.r, &LStat.rem_r);

    memset(&Res, 0, sizeof(Res));
    calc_node(&Res.l, &LStat);
    calc_node(&Res.r, &RStat);
    no_msgs = LStat.r.no_msgs + RStat.r.no_msgs;
    if (no_msgs)
        Res.latency = Res.l.time_real / no_msgs;

    locTime = Res.l.time_real;
    remTime = Res.r.time_real;
    midTime = (locTime + remTime) / 2;

    if (locTime == 0 || remTime == 0)
        return;

    /* Calculate messaging rate */
    if (!RStat.r.no_msgs)
        Res.msg_rate = LStat.r.no_msgs / remTime;
    else if (!LStat.r.no_msgs)
        Res.msg_rate = RStat.r.no_msgs / locTime;
    else
        Res.msg_rate = (LStat.r.no_msgs + RStat.r.no_msgs) / midTime;

    /* Calculate send bandwidth */
    if (!RStat.s.no_bytes)
        Res.send_bw = LStat.s.no_bytes / locTime;
    else if (!LStat.s.no_bytes)
        Res.send_bw = RStat.s.no_bytes / remTime;
    else
        Res.send_bw = (LStat.s.no_bytes + RStat.s.no_bytes) / midTime;

    /* Calculate receive bandwidth. */
    if (!RStat.r.no_bytes)
        Res.recv_bw = LStat.r.no_bytes / locTime;
    else if (!LStat.r.no_bytes)
        Res.recv_bw = RStat.r.no_bytes / remTime;
    else
        Res.recv_bw = (LStat.r.no_bytes + RStat.r.no_bytes) / midTime;

    /* Calculate costs */
    if (LStat.s.no_bytes && !LStat.r.no_bytes && !RStat.s.no_bytes)
        Res.send_cost = Res.l.time_cpu*gB / LStat.s.no_bytes;
    else if (RStat.s.no_bytes && !RStat.r.no_bytes && !LStat.s.no_bytes)
        Res.send_cost = Res.r.time_cpu*gB / RStat.s.no_bytes;
    if (RStat.r.no_bytes && !RStat.s.no_bytes && !LStat.r.no_bytes)
        Res.recv_cost = Res.r.time_cpu*gB / RStat.r.no_bytes;
    else if (LStat.r.no_bytes && !LStat.s.no_bytes && !RStat.r.no_bytes)
        Res.recv_cost = Res.l.time_cpu*gB / LStat.r.no_bytes;
}


/*
 * Determine the number of packets left to send.
 */
int
left_to_send(long *sentp, int room)
{
    int n;

    if (!Req.no_msgs)
        return room;
    n = Req.no_msgs - *sentp;
    if (n <= 0)
        return 0;
    if (n > room)
        return room;
    return n;
}


/*
 * Combine statistics that the remote node kept track of with those that the
 * local node kept.
 */
static void
add_ustat(USTAT *l, USTAT *r)
{
    l->no_bytes += r->no_bytes;
    l->no_msgs  += r->no_msgs;
    l->no_errs  += r->no_errs;
}


/*
 * Calculate time values for a node.
 */
static void
calc_node(RESN *resn, STAT *stat)
{
    int i;
    CLOCK cpu;
    double s = stat->time_e[T_REAL] - stat->time_s[T_REAL];

    memset(resn, 0, sizeof(*resn));
    if (s == 0)
        return;
    if (stat->no_ticks == 0)
        return;

    resn->time_real = s / stat->no_ticks;

    cpu = 0;
    for (i = 0; i < T_N; ++i)
        if (i != T_REAL && i != T_IDLE)
            cpu += stat->time_e[i] - stat->time_s[i];
    resn->time_cpu = (float) cpu / stat->no_ticks;

    resn->cpu_user = (stat->time_e[T_USER] - stat->time_s[T_USER]
                   + stat->time_e[T_NICE] - stat->time_s[T_NICE]) / s;

    resn->cpu_intr = (stat->time_e[T_IRQ] - stat->time_s[T_IRQ]
                   +  stat->time_e[T_SOFTIRQ] - stat->time_s[T_SOFTIRQ]) / s;

    resn->cpu_idle = (stat->time_e[T_IDLE] - stat->time_s[T_IDLE]) / s;

    resn->cpu_kernel = (stat->time_e[T_KERNEL] - stat->time_s[T_KERNEL]
                     +  stat->time_e[T_STEAL] - stat->time_s[T_STEAL]) / s;

    resn->cpu_io_wait = (stat->time_e[T_IOWAIT] - stat->time_s[T_IOWAIT]) / s;

    resn->cpu_total = resn->cpu_user + resn->cpu_intr
                    + resn->cpu_kernel + resn->cpu_io_wait;
}


/*
 * Show relevant values.
 */
static void
show_info(MEASURE measure)
{
    if (measure == LATENCY) {
        view_time('a', "", "latency", Res.latency);
        view_rate('s', "", "msg_rate", Res.msg_rate);
    } else if (measure == MSG_RATE) {
        view_rate('a', "", "msg_rate", Res.msg_rate);
    } else if (measure == BANDWIDTH) {
        view_band('a', "", "bw", Res.recv_bw);
        view_rate('s', "", "msg_rate", Res.msg_rate);
    } else if (measure == BANDWIDTH_SR) {
        view_band('a', "", "send_bw", Res.send_bw);
        view_band('a', "", "recv_bw", Res.recv_bw);
        view_rate('s', "", "msg_rate", Res.msg_rate);
    }
    show_used();
    view_cost('t', "", "send_cost", Res.send_cost);
    view_cost('t', "", "recv_cost", Res.recv_cost);
    show_rest();
    if (Debug)
        show_debug();
}


/*
 * Show parameters the user set.
 */
static void
show_used(void)
{
    PAR_NAME *p;
    PAR_NAME *q = endof(ParName);

    if (!VerboseUsed)
        return;
    for (p = ParName; p < q; ++p) {
        PAR_INFO *l = par_info(p->loc_i);
        PAR_INFO *r = par_info(p->rem_i);

        if (!l->inuse && !r->inuse)
            continue;
        if (VerboseUsed < 2 && !l->set & !r->set)
            continue;
        if (l->type == 'l') {
            uint32_t lv = *(uint32_t *)l->ptr;
            uint32_t rv = *(uint32_t *)r->ptr;
            if (lv == rv)
                view_long('u', "", p->name, lv);
            else {
                view_long('u', "loc_", p->name, lv);
                view_long('u', "rem_", p->name, rv);
            }
        } else if (l->type == 'p') {
            if (streq(l->ptr, r->ptr))
                view_strn('u', "", p->name, l->ptr);
            else {
                view_strn('u', "loc_", p->name, l->ptr);
                view_strn('u', "rem_", p->name, r->ptr);
            }
        } else if (l->type == 's') {
            uint32_t lv = *(uint32_t *)l->ptr;
            uint32_t rv = *(uint32_t *)r->ptr;
            if (lv == rv)
                view_size('u', "", p->name, lv);
            else {
                view_size('u', "loc_", p->name, lv);
                view_size('u', "rem_", p->name, rv);
            }
        } else if (l->type == 't') {
            uint32_t lv = *(uint32_t *)l->ptr;
            uint32_t rv = *(uint32_t *)r->ptr;
            if (lv == rv)
                view_time('u', "", p->name, lv);
            else {
                view_time('u', "loc_", p->name, lv);
                view_time('u', "rem_", p->name, rv);
            }
        }
    }
}


/*
 * Show the remaining parameters.
 */
static void
show_rest(void)
{
    RESN *resnS;
    RESN *resnR;
    STAT *statS;
    STAT *statR;
    int srmode = 0;

    if (!UnifyNodes) {
        uint64_t ls = LStat.s.no_bytes;
        uint64_t lr = LStat.r.no_bytes;
        uint64_t rs = RStat.s.no_bytes;
        uint64_t rr = RStat.r.no_bytes;
        
        if (ls && !rs && rr && !lr) {
            srmode = 1;
            resnS = &Res.l;
            resnR = &Res.r;
            statS = &LStat;
            statR = &RStat;
        } else if (rs && !ls && lr && !rr) {
            srmode = 1;
            resnS = &Res.r;
            resnR = &Res.l;
            statS = &RStat;
            statR = &LStat;
        }
    }

    if (srmode) {
        view_cpus('t', "", "send_cpus_used",   resnS->cpu_total);
        view_cpus('T', "", "send_cpus_user",   resnS->cpu_user);
        view_cpus('T', "", "send_cpus_intr",   resnS->cpu_intr);
        view_cpus('T', "", "send_cpus_kernel", resnS->cpu_kernel);
        view_cpus('T', "", "send_cpus_iowait", resnS->cpu_io_wait);
        view_time('T', "", "send_real_time",   resnS->time_real);
        view_time('T', "", "send_cpu_time",    resnS->time_cpu);
        view_long('S', "", "send_errors",      statS->s.no_errs);
        view_size('S', "", "send_bytes",       statS->s.no_bytes);
        view_long('S', "", "send_msgs",        statS->s.no_msgs);
        view_long('S', "", "send_max_cqe",     statS->max_cqes);

        view_cpus('t', "", "recv_cpus_used",   resnR->cpu_total);
        view_cpus('T', "", "recv_cpus_user",   resnR->cpu_user);
        view_cpus('T', "", "recv_cpus_intr",   resnR->cpu_intr);
        view_cpus('T', "", "recv_cpus_kernel", resnR->cpu_kernel);
        view_cpus('T', "", "recv_cpus_iowait", resnR->cpu_io_wait);
        view_time('T', "", "recv_real_time",   resnR->time_real);
        view_time('T', "", "recv_cpu_time",    resnR->time_cpu);
        view_long('S', "", "recv_errors",      statR->r.no_errs);
        view_size('S', "", "recv_bytes",       statR->r.no_bytes);
        view_long('S', "", "recv_msgs",        statR->r.no_msgs);
        view_long('S', "", "recv_max_cqe",     statR->max_cqes);
    } else {
        view_cpus('t', "", "loc_cpus_used",    Res.l.cpu_total);
        view_cpus('T', "", "loc_cpus_user",    Res.l.cpu_user);
        view_cpus('T', "", "loc_cpus_intr",    Res.l.cpu_intr);
        view_cpus('T', "", "loc_cpus_kernel",  Res.l.cpu_kernel);
        view_cpus('T', "", "loc_cpus_iowait",  Res.l.cpu_io_wait);
        view_time('T', "", "loc_real_time",    Res.l.time_real);
        view_time('T', "", "loc_cpu_time",     Res.l.time_cpu);
        view_long('S', "", "loc_send_errors",  LStat.s.no_errs);
        view_long('S', "", "loc_recv_errors",  LStat.r.no_errs);
        view_size('S', "", "loc_send_bytes",   LStat.s.no_bytes);
        view_size('S', "", "loc_recv_bytes",   LStat.r.no_bytes);
        view_long('S', "", "loc_send_msgs",    LStat.s.no_msgs);
        view_long('S', "", "loc_recv_msgs",    LStat.r.no_msgs);
        view_long('S', "", "loc_max_cqe",      LStat.max_cqes);

        view_cpus('t', "", "rem_cpus_used",    Res.r.cpu_total);
        view_cpus('T', "", "rem_cpus_user",    Res.r.cpu_user);
        view_cpus('T', "", "rem_cpus_intr",    Res.r.cpu_intr);
        view_cpus('T', "", "rem_cpus_kernel",  Res.r.cpu_kernel);
        view_cpus('T', "", "rem_cpus_iowait",  Res.r.cpu_io_wait);
        view_time('T', "", "rem_real_time",    Res.r.time_real);
        view_time('T', "", "rem_cpu_time",     Res.r.time_cpu);
        view_long('S', "", "rem_send_errors",  RStat.s.no_errs);
        view_long('S', "", "rem_recv_errors",  RStat.r.no_errs);
        view_size('S', "", "rem_send_bytes",   RStat.s.no_bytes);
        view_size('S', "", "rem_recv_bytes",   RStat.r.no_bytes);
        view_long('S', "", "rem_send_msgs",    RStat.s.no_msgs);
        view_long('S', "", "rem_recv_msgs",    RStat.r.no_msgs);
        view_long('S', "", "rem_max_cqe",      RStat.max_cqes);
    }
}


/*
 * Show all values.
 */
static void
show_debug(void)
{
    /* Local node */
    view_long('d', "", "l_no_cpus",  LStat.no_cpus);
    view_long('d', "", "l_no_ticks", LStat.no_ticks);
    view_long('d', "", "l_max_cqes", LStat.max_cqes);

    if (LStat.no_ticks) {
        double t = LStat.no_ticks;
        CLOCK *s = LStat.time_s;
        CLOCK *e = LStat.time_e;
        double real    = (e[T_REAL]    - s[T_REAL])    / t;
        double user    = (e[T_USER]    - s[T_USER])    / t;
        double nice    = (e[T_NICE]    - s[T_NICE])    / t;
        double system  = (e[T_KERNEL]  - s[T_KERNEL])  / t;
        double idle    = (e[T_IDLE]    - s[T_IDLE])    / t;
        double iowait  = (e[T_IOWAIT]  - s[T_IOWAIT])  / t;
        double irq     = (e[T_IRQ]     - s[T_IRQ])     / t;
        double softirq = (e[T_SOFTIRQ] - s[T_SOFTIRQ]) / t;
        double steal   = (e[T_STEAL]   - s[T_STEAL])   / t;

        view_time('d', "", "l_timer_real",    real);
        view_time('d', "", "l_timer_user",    user);
        view_time('d', "", "l_timer_nice",    nice);
        view_time('d', "", "l_timer_system",  system);
        view_time('d', "", "l_timer_idle",    idle);
        view_time('d', "", "l_timer_iowait",  iowait);
        view_time('d', "", "l_timer_irq",     irq);
        view_time('d', "", "l_timer_softirq", softirq);
        view_time('d', "", "l_timer_steal",   steal);
    }

    view_size('d', "", "l_s_no_bytes", LStat.s.no_bytes);
    view_long('d', "", "l_s_no_msgs",  LStat.s.no_msgs);
    view_long('d', "", "l_s_no_errs",  LStat.s.no_errs);

    view_size('d', "", "l_r_no_bytes", LStat.r.no_bytes);
    view_long('d', "", "l_r_no_msgs",  LStat.r.no_msgs);
    view_long('d', "", "l_r_no_errs",  LStat.r.no_errs);

    view_size('d', "", "l_rem_s_no_bytes", LStat.rem_s.no_bytes);
    view_long('d', "", "l_rem_s_no_msgs",  LStat.rem_s.no_msgs);
    view_long('d', "", "l_rem_s_no_errs",  LStat.rem_s.no_errs);

    view_size('d', "", "l_rem_r_no_bytes", LStat.rem_r.no_bytes);
    view_long('d', "", "l_rem_r_no_msgs",  LStat.rem_r.no_msgs);
    view_long('d', "", "l_rem_r_no_errs",  LStat.rem_r.no_errs);

    /* Remote node */
    view_long('d', "", "r_no_cpus",  RStat.no_cpus);
    view_long('d', "", "r_no_ticks", RStat.no_ticks);
    view_long('d', "", "r_max_cqes", RStat.max_cqes);

    if (RStat.no_ticks) {
        double t = RStat.no_ticks;
        CLOCK *s = RStat.time_s;
        CLOCK *e = RStat.time_e;

        double real    = (e[T_REAL]    - s[T_REAL])    / t;
        double user    = (e[T_USER]    - s[T_USER])    / t;
        double nice    = (e[T_NICE]    - s[T_NICE])    / t;
        double system  = (e[T_KERNEL]  - s[T_KERNEL])  / t;
        double idle    = (e[T_IDLE]    - s[T_IDLE])    / t;
        double iowait  = (e[T_IOWAIT]  - s[T_IOWAIT])  / t;
        double irq     = (e[T_IRQ]     - s[T_IRQ])     / t;
        double softirq = (e[T_SOFTIRQ] - s[T_SOFTIRQ]) / t;
        double steal   = (e[T_STEAL]   - s[T_STEAL])   / t;

        view_time('d', "", "r_timer_real",    real);
        view_time('d', "", "r_timer_user",    user);
        view_time('d', "", "r_timer_nice",    nice);
        view_time('d', "", "r_timer_system",  system);
        view_time('d', "", "r_timer_idle",    idle);
        view_time('d', "", "r_timer_iowait",  iowait);
        view_time('d', "", "r_timer_irq",     irq);
        view_time('d', "", "r_timer_softirq", softirq);
        view_time('d', "", "r_timer_steal",   steal);
    }

    view_size('d', "", "r_s_no_bytes", RStat.s.no_bytes);
    view_long('d', "", "r_s_no_msgs",  RStat.s.no_msgs);
    view_long('d', "", "r_s_no_errs",  RStat.s.no_errs);

    view_size('d', "", "r_r_no_bytes", RStat.r.no_bytes);
    view_long('d', "", "r_r_no_msgs",  RStat.r.no_msgs);
    view_long('d', "", "r_r_no_errs",  RStat.r.no_errs);

    view_size('d', "", "r_rem_s_no_bytes", RStat.rem_s.no_bytes);
    view_long('d', "", "r_rem_s_no_msgs",  RStat.rem_s.no_msgs);
    view_long('d', "", "r_rem_s_no_errs",  RStat.rem_s.no_errs);

    view_size('d', "", "r_rem_r_no_bytes", RStat.rem_r.no_bytes);
    view_long('d', "", "r_rem_r_no_msgs",  RStat.rem_r.no_msgs);
    view_long('d', "", "r_rem_r_no_errs",  RStat.rem_r.no_errs);
}


/*
 * Show a cost in terms of seconds per gigabyte.
 */
static void
view_cost(int type, char *pref, char *name, double value)
{
    int n = 0;
    char *tab[] ={ "ns/GB", "us/GB", "ms/GB", "sec/GB" };

    value *=  1E9;
    if (!verbose(type, value))
        return;
    if (!UnifyUnits) {
        while (value >= 1000 && n < (int)cardof(tab)-1) {
            value /= 1000;
            ++n;
        }
    }
    place_val(pref, name, tab[n], value);
}


/*
 * Show the number of cpus.
 */
static void
view_cpus(int type, char *pref, char *name, double value)
{
    value *= 100;
    if (!verbose(type, value))
        return;
    place_val(pref, name, "% cpus", value);
}


/*
 * Show a messaging rate.
 */
static void
view_rate(int type, char *pref, char *name, double value)
{
    int n = 0;
    char *tab[] ={ "/sec", "K/sec", "M/sec", "G/sec", "T/sec" };

    if (!verbose(type, value))
        return;
    if (!UnifyUnits) {
        while (value >= 1000 && n < (int)cardof(tab)-1) {
            value /= 1000;
            ++n;
        }
    }
    place_val(pref, name, tab[n], value);
}


/*
 * Show a number.
 */
static void
view_long(int type, char *pref, char *name, long long value)
{
    int n = 0;
    double val = value;
    char *tab[] ={ "", "thousand", "million", "billion", "trillion" };

    if (!verbose(type, val))
        return;
    if (!UnifyUnits && val >= 1000*1000) {
        while (val >= 1000 && n < (int)cardof(tab)-1) {
            val /= 1000;
            ++n;
        }
    }
    place_val(pref, name, tab[n], val);
}


/*
 * Show a bandwidth value.
 */
static void
view_band(int type, char *pref, char *name, double value)
{
    int n, s;
    char **tab;

    if (!verbose(type, value))
        return;
    if (UseBitsPerSec) {
        static char *t[] ={ "bits/sec", "Kb/sec", "Mb/sec", "Gb/sec", "Tb/sec" };
        s = cardof(t);
        tab = t;
        value *= 8;
    } else {
        static char *t[] ={ "bytes/sec", "KB/sec", "MB/sec", "GB/sec", "TB/sec" };
        s = cardof(t);
        tab = t;
    }

    n = 0;
    if (!UnifyUnits) {
        while (value >= 1000 && n < s-1) {
            value /= 1000;
            ++n;
        }
    }
    place_val(pref, name, tab[n], value);
}


/*
 * Show a size.
 */
static void
view_size(int type, char *pref, char *name, long long value)
{
    int n = 0;
    double val = value;
    char *tab[] ={ "bytes", "KB", "MB", "GB", "TB" };

    if (!verbose(type, val))
        return;
    if (!UnifyUnits) {
        if (nice_1024(pref, name, value))
            return;
        while (val >= 1000 && n < (int)cardof(tab)-1) {
            val /= 1000;
            ++n;
        }
    }
    place_val(pref, name, tab[n], val);
}


/*
 * Show a number if it can be expressed as a nice multiple of a power of 1024.
 */
static int
nice_1024(char *pref, char *name, long long value)
{
    char *data;
    char *altn;
    int n = 0;
    long long val = value;
    char *tab[] ={ "KiB", "MiB", "GiB", "TiB" };

    if (val < 1024 || val % 1024)
        return 0;
    val /= 1024;
    while (val >= 1024 && n < (int)cardof(tab)-1) {
        if (val % 1024)
            return 0;
        val /= 1024;
        ++n;
    }
    data = qasprintf("%lld", val);
    altn = qasprintf("%lld", value);
    place_any(pref, name, tab[n], commify(data), commify(altn));
    return 1;
}


/*
 * Show a string.
 */
static void
view_strn(int type, char *pref, char *name, char *value)
{
    if (!verbose(type, value[0] != '\0'))
        return;
    place_any(pref, name, 0, strdup(value), 0);
}


/*
 * Show a time.
 */
static void
view_time(int type, char *pref, char *name, double value)
{
    int n = 0;
    char *tab[] ={ "ns", "us", "ms", "sec" };

    value *= 1E9;
    if (!verbose(type, value))
        return;
    if (!UnifyUnits) {
        while (value >= 1000 && n < (int)cardof(tab)-1) {
            value /= 1000;
            ++n;
        }
    }
    place_val(pref, name, tab[n], value);
}


/*
 * Determine if we are verbose enough to show a value.
 */
static int
verbose(int type, double value)
{
    if (type == 'a')
        return 1;
    if (value <= 0)
        return 0;
    switch (type) {
    case 'd': return Debug;
    case 'c': return VerboseConf >= 1;
    case 's': return VerboseStat >= 1;
    case 't': return VerboseTime >= 1;
    case 'u': return VerboseUsed >= 1;
    case 'C': return VerboseConf >= 2;
    case 'S': return VerboseStat >= 2;
    case 'T': return VerboseTime >= 2;
    case 'U': return VerboseUsed >= 2;
    default:  error(BUG, "verbose: bad type: %c (%o)", type, type);
    }
    return 0;
}


/*
 * Place a value to be shown later.
 */
static void
place_val(char *pref, char *name, char *unit, double value)
{
    char *data = qasprintf("%.0f", value);
    char *p    = data;
    int   n    = Precision;

    if (*p == '-')
        ++p;
    while (isdigit(*p++))
        --n;
    if (n > 0) {
        free(data);
        data = qasprintf("%.*f", n, value);
        p = &data[strlen(data)];
        while (p > data && *--p == '0')
            ;
        if (p > data && *p == '.')
            --p;
        p[1] = '\0';
    }
    place_any(pref, name, unit, commify(data), 0);
}


/*
 * Place an entry in our show table.
 */
static void
place_any(char *pref, char *name, char *unit, char *data, char *altn)
{
    SHOW *show = &ShowTable[ShowIndex++];

    if (ShowIndex > cardof(ShowTable))
        error(BUG, "need to increase size of ShowTable");
    show->pref = pref;
    show->name = name;
    show->unit = unit;
    show->data = data;
    show->altn = altn;
}


/*
 * Show all saved values.
 */
static void
place_show(void)
{
    int i;
    int nameLen = 0;
    int dataLen = 0;
    int unitLen = 0;

    /* First compute formating sizes */
    for (i = 0; i < ShowIndex; ++i) {
        int n;
        SHOW *show = &ShowTable[i];
        n = (show->pref ? strlen(show->pref) : 0) + strlen(show->name);
        if (n > nameLen)
            nameLen = n;
        n = strlen(show->data);
        if (show->unit) {
            if (n > dataLen)
                dataLen = n;
            n = strlen(show->unit);
            if (n > unitLen)
                unitLen = n;
        }
    }

    /* Then display results */
    for (i = 0; i < ShowIndex; ++i) {
        int n = 0;
        SHOW *show = &ShowTable[i];

        printf("    ");
        if (show->pref) {
            n = strlen(show->pref);
            printf("%s", show->pref);
        }
        printf("%-*s", nameLen-n, show->name);
        if (show->unit) {
            printf("  =  %*s", dataLen, show->data);
            printf(" %s", show->unit);
        } else
            printf("  =  %s", show->data);
        if (show->altn)
            printf(" (%s)", show->altn);
        printf("\n");
        free(show->data);
        free(show->altn);
    }
    ShowIndex = 0;
}


/*
 * Set the processor affinity.
 */
static void
set_affinity(void)
{
    cpu_set_t set;
    int a = Req.affinity;

    if (!a)
        return;
    CPU_ZERO(&set);
    CPU_SET(a-1, &set);
    if (sched_setaffinity(0, sizeof(set), &set) < 0)
        error(SYS, "cannot set processor affinity (cpu %d)", a-1);
}


/*
 * Encode a REQ structure into a data stream.
 */
static void
enc_req(REQ *host)
{
    enc_int(host->ver_maj,       sizeof(host->ver_maj));
    enc_int(host->ver_min,       sizeof(host->ver_min));
    enc_int(host->ver_inc,       sizeof(host->ver_inc));
    enc_int(host->req_index,     sizeof(host->req_index));
    enc_int(host->access_recv,   sizeof(host->access_recv));
    enc_int(host->affinity,      sizeof(host->affinity));
    enc_int(host->alt_port,      sizeof(host->alt_port));
    enc_int(host->flip,          sizeof(host->flip));
    enc_int(host->msg_size,      sizeof(host->msg_size));
    enc_int(host->mtu_size,      sizeof(host->mtu_size));
    enc_int(host->no_msgs,       sizeof(host->no_msgs));
    enc_int(host->poll_mode,     sizeof(host->poll_mode));
    enc_int(host->port,          sizeof(host->port));
    enc_int(host->rd_atomic,     sizeof(host->rd_atomic));
    enc_int(host->sl,            sizeof(host->sl));
    enc_int(host->sock_buf_size, sizeof(host->sock_buf_size));
    enc_int(host->src_path_bits, sizeof(host->src_path_bits));
    enc_int(host->time,          sizeof(host->time));
    enc_int(host->timeout,       sizeof(host->timeout));
    enc_int(host->use_cm,        sizeof(host->use_cm));
    enc_str(host->id,            sizeof(host->id));
    enc_str(host->static_rate,   sizeof(host->static_rate));
}


/*
 * Decode the version part of a REQ structure from a data stream.  To decode
 * the entire REQ structure, call dec_req_version and dec_req_data in
 * succession.
 */
static void
dec_req_version(REQ *host)
{
    host->ver_maj       = dec_int(sizeof(host->ver_maj));
    host->ver_min       = dec_int(sizeof(host->ver_min));
    host->ver_inc       = dec_int(sizeof(host->ver_inc));
}


/*
 * Decode the data part of a REQ structure from a data stream.
 */
static void
dec_req_data(REQ *host)
{
    host->req_index     = dec_int(sizeof(host->req_index));
    host->access_recv   = dec_int(sizeof(host->access_recv));
    host->affinity      = dec_int(sizeof(host->affinity));
    host->alt_port      = dec_int(sizeof(host->alt_port));
    host->flip          = dec_int(sizeof(host->flip));
    host->msg_size      = dec_int(sizeof(host->msg_size));
    host->mtu_size      = dec_int(sizeof(host->mtu_size));
    host->no_msgs       = dec_int(sizeof(host->no_msgs));
    host->poll_mode     = dec_int(sizeof(host->poll_mode));
    host->port          = dec_int(sizeof(host->port));
    host->rd_atomic     = dec_int(sizeof(host->rd_atomic));
    host->sl            = dec_int(sizeof(host->sl));
    host->sock_buf_size = dec_int(sizeof(host->sock_buf_size));
    host->src_path_bits = dec_int(sizeof(host->src_path_bits));
    host->time          = dec_int(sizeof(host->time));
    host->timeout       = dec_int(sizeof(host->timeout));
    host->use_cm        = dec_int(sizeof(host->use_cm));
                          dec_str(host->id, sizeof(host->id));
                          dec_str(host->static_rate,sizeof(host->static_rate));
}


/*
 * Encode a STAT structure into a data stream.
 */
static void
enc_stat(STAT *host)
{
    int i;

    enc_int(host->no_cpus,  sizeof(host->no_cpus));
    enc_int(host->no_ticks, sizeof(host->no_ticks));
    enc_int(host->max_cqes, sizeof(host->max_cqes));
    for (i = 0; i < T_N; ++i)
        enc_int(host->time_s[i], sizeof(host->time_s[i]));
    for (i = 0; i < T_N; ++i)
        enc_int(host->time_e[i], sizeof(host->time_e[i]));
    enc_ustat(&host->s);
    enc_ustat(&host->r);
    enc_ustat(&host->rem_s);
    enc_ustat(&host->rem_r);
}


/*
 * Decode a STAT structure from a data stream.
 */
static void
dec_stat(STAT *host)
{
    int i;

    host->no_cpus  = dec_int(sizeof(host->no_cpus));
    host->no_ticks = dec_int(sizeof(host->no_ticks));
    host->max_cqes = dec_int(sizeof(host->max_cqes));
    for (i = 0; i < T_N; ++i)
        host->time_s[i] = dec_int(sizeof(host->time_s[i]));
    for (i = 0; i < T_N; ++i)
        host->time_e[i] = dec_int(sizeof(host->time_e[i]));
    dec_ustat(&host->s);
    dec_ustat(&host->r);
    dec_ustat(&host->rem_s);
    dec_ustat(&host->rem_r);
}


/*
 * Encode a USTAT structure into a data stream.
 */
static void
enc_ustat(USTAT *host)
{
    enc_int(host->no_bytes, sizeof(host->no_bytes));
    enc_int(host->no_msgs,  sizeof(host->no_msgs));
    enc_int(host->no_errs,  sizeof(host->no_errs));
}


/*
 * Decode a USTAT structure from a data stream.
 */
static void
dec_ustat(USTAT *host)
{
    host->no_bytes = dec_int(sizeof(host->no_bytes));
    host->no_msgs  = dec_int(sizeof(host->no_msgs));
    host->no_errs  = dec_int(sizeof(host->no_errs));
}


/*
 * Get various temporal parameters.
 */
static void
get_times(CLOCK timex[T_N])
{
    int n;
    char *p;
    char buf[BUFSIZE];
    struct tms tms;

    timex[0] = times(&tms);
    if (lseek(ProcStatFD, 0, 0) < 0)
        error(SYS, "failed to seek /proc/stat");
    n = read(ProcStatFD, buf, sizeof(buf)-1);
    buf[n] = '\0';
    if (strncmp(buf, "cpu ", 4))
        error(0, "/proc/stat does not start with 'cpu '");
    p = &buf[3];
    for (n = 1; n < T_N; ++n) {
        while (*p == ' ')
            ++p;
        if (!isdigit(*p)) {
            if (*p != '\n' || n < T_N-1)
                error(0, "/proc/stat has bad format");
            break;
        }
        timex[n] = strtoll(p, 0, 10);
        while (*p != ' ' && *p != '\n' && *p != '\0')
            ++p;
    }
    while (n < T_N)
        timex[n++] = 0;
}


/*
 * Insert commas within a number for readability.
 */
static char *
commify(char *data)
{
    int s;
    int d;
    int seqS;
    int seqE;
    int dataLen;
    int noCommas;

    if (!data)
        return data;
    if (UnifyUnits)
        return data;
    dataLen = strlen(data);
    seqS = seqE = dataLen;
    while (--seqS >= 0)
        if (!isdigit(data[seqS]))
            break;
    if (seqS >= 0 && data[seqS] == '.') {
        seqE = seqS;
        while (--seqS >= 0)
            if (!isdigit(data[seqS]))
                break;
    }
    noCommas = (--seqE - ++seqS) / 3;
    if (noCommas == 0)
        return data;
    data = realloc(data, dataLen+noCommas+1);
    if (!data)
        error(0, "out of space");
    s = dataLen;
    d = dataLen + noCommas;
    for (;;) {
        int n;
        data[d--] = data[s--];
        n = seqE - s;
        if (n > 0 && n%3 == 0) {
            data[d--] = ',';
            if (--noCommas == 0)
                break;
        }
    }
    return data;
}


/*
 * Like strncpy but ensures the destination is null terminated.
 */
static void
strncopy(char *d, char *s, int n)
{
    strncpy(d, s, n);
    d[n-1] = '\0';
}
