#include #include #include #include #include #include #include #include #include #include #include #define NETPERF_CMD "netperf" #define NETPERF_PATH "/usr/local/bin/" NETPERF_CMD #ifndef __DECONST #define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) #endif #ifndef __unused #define __unused __attribute__((__unused__)) #endif struct netperf_child { int pipes[2]; pid_t pid; }; static struct netperf_child *instance; static int ninstance; static void usage(const char *cmd) { fprintf(stderr, "%s -H host [-H host1] [-l len_s] [-i instances] " "[-m msgsz] [-S sockbuf] [-r|-s] [-x]\n", cmd); exit(1); } static void sigint_handler(int sig __unused) { int i; for (i = 0; i < ninstance; ++i) { if (instance[i].pid != -1) kill(instance[i].pid, SIGKILL); } kill(getpid(), SIGKILL); } int main(int argc, char *argv[]) { char len_str[32], sockbuf_str[64], msgsz_str[64]; char *args[32]; const char *msgsz, *sockbuf, *name1, *name2; const char ** volatile host; volatile int set_minmax = 0, nhost, dual; int len, ninst, ninst_done, host_idx, host_arg_idx, test_arg_idx; int opt, i, null_fd; volatile int reverse = 0, sfile = 0; double result, res_max, res_min, jain; sigset_t nset, oset; dual = 0; ninst = 2; len = 10; msgsz = NULL; sockbuf = NULL; host_idx = 0; nhost = 8; host = malloc(sizeof(const char *) * nhost); if (host == NULL) err(1, "malloc failed"); while ((opt = getopt(argc, argv, "H:S:i:l:m:rsx")) != -1) { switch (opt) { case 'H': if (host_idx == nhost) { const char **new_host; nhost *= 2; new_host = malloc(sizeof(const char *) * nhost); if (new_host == NULL) err(1, "malloc failed"); memcpy(new_host, host, host_idx * sizeof(const char *)); free(host); host = new_host; } host[host_idx++] = optarg; break; case 'S': sockbuf = optarg; break; case 'i': ninst = strtoul(optarg, NULL, 10); break; case 'l': len = strtoul(optarg, NULL, 10); break; case 'm': msgsz = optarg; break; case 'r': reverse = 1; sfile = 0; break; case 's': reverse = 0; sfile = 1; break; case 'x': dual = 1; break; default: usage(argv[0]); } } nhost = host_idx; if (ninst <= 0 || nhost == 0 || len <= 0) usage(argv[0]); snprintf(len_str, sizeof(len_str), "%d", len); i = 0; args[i++] = __DECONST(char *, NETPERF_CMD); args[i++] = __DECONST(char *, "-P0"); args[i++] = __DECONST(char *, "-H"); host_arg_idx = i; args[i++] = __DECONST(char *, NULL); args[i++] = __DECONST(char *, "-l"); args[i++] = __DECONST(char *, len_str); args[i++] = __DECONST(char *, "-t"); test_arg_idx = i; if (reverse) args[i++] = __DECONST(char *, "TCP_MAERTS"); else if (sfile) args[i++] = __DECONST(char *, "TCP_SENDFILE"); else args[i++] = __DECONST(char *, "TCP_STREAM"); if (msgsz != NULL || sockbuf != NULL) { args[i++] = __DECONST(char *, "--"); if (msgsz != NULL) { snprintf(msgsz_str, sizeof(msgsz_str), "%s,%s", msgsz, msgsz); args[i++] = __DECONST(char *, "-m"); args[i++] = __DECONST(char *, msgsz_str); args[i++] = __DECONST(char *, "-M"); args[i++] = __DECONST(char *, msgsz_str); } if (sockbuf != NULL) { snprintf(sockbuf_str, sizeof(sockbuf_str), "%s,%s", sockbuf, sockbuf); args[i++] = __DECONST(char *, "-s"); args[i++] = __DECONST(char *, sockbuf_str); args[i++] = __DECONST(char *, "-S"); args[i++] = __DECONST(char *, sockbuf_str); } } args[i] = NULL; ninstance = ninst * nhost * (dual + 1); instance = calloc(ninstance, sizeof(struct netperf_child)); if (instance == NULL) err(1, "calloc failed"); null_fd = open("/dev/null", O_RDWR); if (null_fd < 0) err(1, "open null failed"); for (i = 0; i < ninstance; ++i) { if (pipe(instance[i].pipes) < 0) err(1, "pipe %dth failed", i); instance[i].pid = -1; } sigemptyset(&nset); sigaddset(&nset, SIGINT); sigprocmask(SIG_BLOCK, &nset, &oset); signal(SIGINT, sigint_handler); for (i = 0; i < ninstance; ++i) { pid_t pid; pid = vfork(); if (pid == 0) { int ret; dup2(instance[i].pipes[1], STDOUT_FILENO); dup2(null_fd, STDERR_FILENO); args[host_arg_idx] = __DECONST(char *, host[i % nhost]); if (dual) { const char *test_type; if ((i / nhost) & dual) { test_type = sfile ? "TCP_SENDFILE" : "TCP_STREAM"; } else { test_type = "TCP_MAERTS"; } args[test_arg_idx] = __DECONST(char *, test_type); } ret = execv(NETPERF_PATH, args); if (ret < 0) { warn("execv %d failed", i); _exit(1); } /* Never reached */ abort(); } else if (pid < 0) { err(1, "vfork %d failed", i); } close(instance[i].pipes[1]); instance[i].pipes[1] = -1; instance[i].pid = pid; } sigprocmask(SIG_SETMASK, &oset, NULL); ninst_done = 0; while (ninst_done < ninstance) { pid_t pid; pid = waitpid(-1, NULL, 0); if (pid < 0) err(1, "waitpid failed"); ++ninst_done; } res_max = 0.0; res_min = 0.0; jain = 0.0; result = 0.0; for (i = 0; i < ninstance; ++i) { char line[128]; FILE *fp; fp = fdopen(instance[i].pipes[0], "r"); if (fp == NULL) err(1, "fdopen %dth failed", i); while (fgets(line, sizeof(line), fp) != NULL) { int n, arg1, arg2, arg3; double res, arg4; n = sscanf(line, "%d%d%d%lf%lf", &arg1, &arg2, &arg3, &arg4, &res); if (n == 5) { if (!set_minmax) { res_max = res; res_min = res; set_minmax = 1; } else { if (res > res_max) res_max = res; if (res < res_min) res_min = res; } jain += (res * res); result += res; break; } } fclose(fp); } jain *= ninstance; jain = (result * result) / jain; name1 = "TCP_STREAM"; name2 = ""; if (dual) { name1 = "TCP_MAERTS"; if (sfile) name2 = "/TCP_SENDFILE"; else name2 = "/TCP_STREAM"; } else if (reverse) { name1 = "TCP_MAERTS"; } else if (sfile) { name1 = "TCP_SENDFILE"; } printf("%s%s %.2f Mbps\n", name1, name2, result); printf("min/max (jain) %.2f Mbps/%.2f Mbps (%f)\n", res_min, res_max, jain); exit(0); }