ovs-benchmark: New utility.
authorBen Pfaff <blp@nicira.com>
Wed, 27 Jul 2011 23:23:06 +0000 (16:23 -0700)
committerBen Pfaff <blp@nicira.com>
Wed, 27 Jul 2011 23:23:06 +0000 (16:23 -0700)
debian/openvswitch-common.install
debian/openvswitch-common.manpages
rhel/openvswitch.spec.in
utilities/automake.mk
utilities/ovs-benchmark.1.in [new file with mode: 0644]
utilities/ovs-benchmark.c [new file with mode: 0644]
xenserver/openvswitch-xen.spec

index 758c59c..df261d0 100644 (file)
@@ -1,5 +1,6 @@
 etc/openvswitch/bugtool-plugins
 usr/bin/ovs-appctl
+usr/bin/ovs-benchmark
 usr/bin/ovs-ofctl
 usr/bin/ovs-parse-leaks
 usr/bin/ovs-pki
index e81295b..1e99479 100644 (file)
@@ -1,6 +1,7 @@
 _debian/ovsdb/ovsdb-client.1
 _debian/ovsdb/ovsdb-tool.1
 _debian/utilities/ovs-appctl.8
+_debian/utilities/ovs-benchmark.1
 _debian/utilities/ovs-ofctl.8
 _debian/utilities/ovs-pki.8
 utilities/bugtool/ovs-bugtool.8
index 47b51ff..1305990 100644 (file)
@@ -103,6 +103,7 @@ exit 0
 /etc/logrotate.d/openvswitch
 /etc/openvswitch/bugtool-plugins/*
 /usr/bin/ovs-appctl
+/usr/bin/ovs-benchmark
 /usr/bin/ovs-dpctl
 /usr/bin/ovs-ofctl
 /usr/bin/ovs-parse-leaks
@@ -116,6 +117,7 @@ exit 0
 /usr/sbin/ovs-bugtool
 /usr/sbin/ovs-vswitchd
 /usr/sbin/ovsdb-server
+/usr/share/man/man1/ovs-benchmark.1.gz
 /usr/share/man/man1/ovs-pcap.1.gz
 /usr/share/man/man1/ovs-tcpundump.1.gz
 /usr/share/man/man1/ovsdb-client.1.gz
index aab85a2..dc731bf 100644 (file)
@@ -16,6 +16,7 @@ scripts_SCRIPTS += utilities/ovs-ctl utilities/ovs-lib.sh utilities/ovs-save
 
 EXTRA_DIST += \
        utilities/ovs-appctl.8.in \
+       utilities/ovs-benchmark.1.in \
        utilities/ovs-controller.8.in \
        utilities/ovs-ctl.in \
        utilities/ovs-dpctl.8.in \
@@ -39,6 +40,7 @@ EXTRA_DIST += \
 DISTCLEANFILES += \
        utilities/ovs-appctl.8 \
        utilities/ovs-ctl \
+       utilities/ovs-benchmark.1 \
        utilities/ovs-controller.8 \
        utilities/ovs-dpctl.8 \
        utilities/ovs-lib.sh \
@@ -58,6 +60,7 @@ DISTCLEANFILES += \
 
 man_MANS += \
        utilities/ovs-appctl.8 \
+       utilities/ovs-benchmark.1 \
        utilities/ovs-controller.8 \
        utilities/ovs-dpctl.8 \
        utilities/ovs-ofctl.8 \
@@ -98,4 +101,8 @@ utilities_nlmon_SOURCES = utilities/nlmon.c
 utilities_nlmon_LDADD = lib/libopenvswitch.a
 endif
 
+bin_PROGRAMS += utilities/ovs-benchmark
+utilities_ovs_benchmark_SOURCES = utilities/ovs-benchmark.c
+utilities_ovs_benchmark_LDADD = lib/libopenvswitch.a
+
 include utilities/bugtool/automake.mk
diff --git a/utilities/ovs-benchmark.1.in b/utilities/ovs-benchmark.1.in
new file mode 100644 (file)
index 0000000..f0ec370
--- /dev/null
@@ -0,0 +1,202 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.TH ovs\-benchmark 1 "July 2011" "Open vSwitch" "Open vSwitch Manual"
+.
+.SH NAME
+ovs\-benchmark \- flow setup benchmark utility for Open vSwitch
+.
+.SH SYNOPSIS
+.
+.SY ovs\-benchmark\ latency
+\fB\-\-remote \fIip\fR[\fB:\fIports\fR]
+.OP \-\-sockets nsocks
+.OP \-\-batches nbatches
+.OP \-\-local \fR[\fIip\fR][\fB:\fIports\fR]
+.YS
+.
+.SY ovs\-benchmark\ rate
+\fB\-\-remote \fIip\fR[\fB:\fIports\fR]
+.OP \-\-max\-rate rate
+.OP \-\-timeout maxsecs
+.OP \-\-sockets nsocks
+.OP \-\-batches nbatches
+.OP \-\-local \fR[\fIip\fR][\fB:\fIports\fR]
+.YS
+.
+.SY ovs\-benchmark\ listen
+.OP \-\-local \fR[\fIip\fR]\fB:\fIports
+.YS
+.
+.SY ovs\-benchmark\ help
+.YS
+.
+.SH DESCRIPTION
+\fBovs\-benchmark\fR tests the performance of Open vSwitch flow setup
+by setting up a number of TCP connections and measuring the time
+required.  It can also be used with the Linux bridge or without any
+bridging software, which allows one to measure the bandwidth and
+latency cost of bridging.
+.PP
+Each \fBovs\-benchmark\fR command is described separately below.
+.
+.SH "The ``latency'' command"
+.
+.PP
+This command initiates \fInsocks\fR TCP connections (by default, 100)
+as quickly as possible, waits for each one to complete with success or
+failure, and prints a bar chart of completion times on standard
+output, followed by a summary line.  Each line in the bar chart lists
+a time to connection completion in milliseconds followed by a number
+of \fB.\fR or \fB!\fR symbols, one for each TCP connection that
+completed in that many milliseconds.  A successful connection prints a
+\fB.\fR, and an unsuccessful connection (e.g. to a port on which no
+process is listening) prints a \fB!\fR.
+.
+.PP
+If \fInbatches\fR is given, the entire procedure is repeated the
+specified number of times.  Only a single summary line is printed at
+the end.
+.
+.PP
+Results vary widely based on the number of sockets and whether the
+remote host is listening for connections on the specified ports.  With
+a small number of sockets, all connection times typically remain
+within a handful of milliseconds.  As the number of sockets increases,
+the distribution of connection times clusters around the sending TCP
+stack's SYN retransmission interval.  (This pattern occurs with or
+without Open vSwitch on the network path.)
+.
+.SH "The ``rate'' command"
+.
+.PP
+This command initiates \fInsocks\fR TCP connections (by default, 100)
+as quickly as possible (limited by \fImaxrate\fR, if
+\fB\-\-max\-rate\fR is specified).  Each time a connection completes
+with success or failure, it closes that connection and initiates a new
+one.  It continues to do so either forever or, if \fB\-\-timeout\fR is
+specified, until \fImaxsecs\fR seconds have elapsed.  During the test,
+it prints statistics about time elapsed, successful and unsuccessful
+connections, and the average number of completed (succeeded or failed)
+connections per second over the run.
+.
+.PP
+Without \fB\-\-max\-rate\fR, the \fBrate\fR command measures the
+maximum sustained flow setup rate for an Open vSwitch instance.  This
+naturally tends to drive \fBovs\-vswitchd\fR CPU usage to 100% on the
+host receiving the traffic.
+.
+.PP
+When \fB\-\-max\-rate\fR is specified with a value below the maximum
+rate that an Open vSwitch instance can handle, then \fBrate\fR can
+also be used to measure the kernel and userspace CPU cost of flow
+setups at specific flow rates.
+.
+.PP
+Results tend to fluctuate greatly for the first few seconds of a run,
+then settle down.  The displayed average is calculated over the entire
+run and so tends to converge asymptotically on the ``correct'' value.
+To converge more quickly, try running for 5 to 10 seconds, then
+killing and restarting the run.
+.
+.SH "The ``listen'' command"
+.
+.PP
+This command listens on one or more TCP ports for incoming
+connections.  It accepts connections and immediately closes them.  It
+can be paired with the \fBrate\fR or \fBlatency\fR commands for
+observing effects of successful vs. unsuccessful TCP connections.
+.
+.PP
+It is easier to reproduce and interpret \fBovs\-benchmark\fR results
+when there is no listener (see \fBNOTES\fR below).
+.
+.SH "The ``help'' command"
+.
+.PP
+Prints a usage message and exits successfully.
+.
+.SH OPTIONS
+.
+.IP "\fB\-r \fIip\fR[\fB:\fIports\fR]"
+.IQ "\fB\-\-remote \fIip\fR[\fB:\fIports\fR]"
+This option, required on \fBlatency\fR and \fBrate\fR commands,
+minimally specifies the remote host to connect to (as an IP address or
+DNS name) as \fIip\fR.
+.
+.IP
+A TCP port or range of ports (separated by \fB\-\fR) may also be
+specified.  If a range is specified then each port in the range is
+used in round-robin order.  The default port is 6630 if none is
+specified.
+.
+.IP "\fB\-l \fR[\fIip\fR][\fB:\fIports\fR]"
+.IQ "\fB\-\-local \fR[\fIip\fR][\fB:\fIports\fR]"
+On the \fBlatency\fR and \fBrate\fR, without this option, outgoing
+connections will not bind a specific TCP port.  The local TCP stack
+will pick a local TCP port to bind.  When this option is specified,
+the specified port or range of ports will be used in turn.  (If a port
+range is specified on both \fB\-\-local\fR and \fB\-\-remote\fR, then
+each local port in its range will be used before the remote port is
+incremented to the next port in its range.)
+.
+.IP
+On the \fBlisten\fR command, this option specifies the local port or
+ports and IP addresses on which to listen.  If it is omitted, port
+6630 on any IP address is used.
+.
+.IP "\fB\-s \fInsocks\fR"
+.IQ "\fB\-\-sockets \fInsocks\fR"
+For \fBlatency\fR, sets the number of connections to initiate per
+batch.  For \fBrate\fR, sets the number of outstanding connections
+attempts to maintain at any given time.  The default is 100.
+.
+.IP "\fB\-b \fInbatches\fR"
+.IQ "\fB\-\-batches \fInbatches\fR"
+For \fBlatency\fR, sets the number of times to initiate and wait for
+all of the connections to complete.  The default is 1.
+.
+.IP "\fB\-c \fImaxrate\fR"
+.IQ "\fB\-\-max\-rate \fImaxrate\fR"
+For \fBrate\fR, caps the maximum rate at which connections will be
+attempted to \fImaxrate\fR connections per second.  By default there
+is no limit.
+.
+.IP "\fB\-T \fImaxsecs\fR"
+.IQ "\fB\-\-timeout \fImaxsecs\fR"
+For \fBrate\fR, stops the benchmark after \fImaxsecs\fR seconds have
+elapsed.  By default, the benchmark continues until interrupted by a
+signal.
+.
+.SH NOTES
+.PP
+\fBovs\-benchmark\fR uses standard POSIX socket calls for network
+access, so it shares the strengths and limitations of TCP/IP and its
+implementations in the local and remote TCP/IP stacks.  Particularly,
+TCP and its implementations limit the number of successfully completed
+and then closed TCP connections.  This means that \fBovs\-benchmark\fR
+tests tend to slow down if run for long intervals or with large
+numbers of sockets or batches, if the remote system is listening on
+the port or ports being contacted.  The problem does not occur when
+the remote system is not listening.  \fBovs\-benchmark\fR results are
+therefore much more reliable and repeatable when the remote system is
+not listening on the port or ports being contacted.  Even a single
+listening socket (e.g. range of ports 8000 to 9000 with one listener
+on port 8080) can cause anomalies in results.
+.
+.PP
+Be sure that the remote TCP/IP stack's firewall allows the benchmark's
+traffic to be processed.  For Open vSwitch benchmarking purposes, you
+might want to disable the firewall with, e.g., \fBiptables \-F\fR.
+.
+.PP
+\fBovs\-benchmark\fR is single-threaded.  A multithreaded process
+might be able to initiate connections more quickly.
+.
+.PP
+A TCP connection consists of two flows (one in each direction), so
+multiply the TCP connection statistics that \fBovs\-benchmark\fR
+reports by 2 to get flow statistics.
diff --git a/utilities/ovs-benchmark.c b/utilities/ovs-benchmark.c
new file mode 100644 (file)
index 0000000..e4e9225
--- /dev/null
@@ -0,0 +1,619 @@
+/*
+ * Copyright (c) 2010, 2011 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <stddef.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+#define DEFAULT_PORT 6630
+
+#define MAX_SOCKETS 65535
+static int n_batches = 1;
+static int n_sockets = 100;
+
+static struct in_addr local_addr;
+static unsigned short int local_min_port, local_max_port;
+
+static struct in_addr remote_addr;
+static unsigned short int remote_min_port, remote_max_port;
+
+static double max_rate;
+
+static double timeout;
+
+static const struct command all_commands[];
+
+static void parse_options(int argc, char *argv[]);
+static void usage(void);
+
+static long long int
+time_in_msec(void)
+{
+    struct timeval tv;
+
+    if (gettimeofday(&tv, NULL) < 0) {
+        ovs_fatal(errno, "gettimeofday");
+    }
+
+    return tv.tv_sec * 1000LL + tv.tv_usec / 1000;
+}
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    vlog_set_levels(NULL, VLF_ANY_FACILITY, VLL_EMER);
+    parse_options(argc, argv);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_target(const char *s_, struct in_addr *addr,
+             unsigned short int *min, unsigned short int *max)
+{
+    char *s = xstrdup(s_);
+    char *colon;
+    int error;
+
+    colon = strchr(s, ':');
+    if (colon) {
+        *colon = '\0';
+    }
+
+    if (*s != '\0') {
+        error = lookup_hostname(s, addr);
+        if (error) {
+            ovs_fatal(error, "failed to look up IP address for \"%s\"", s_);
+        }
+    } else {
+        addr->s_addr = htonl(INADDR_ANY);
+    }
+
+    *min = *max = 0;
+    if (colon && colon[1] != '\0') {
+        const char *ports = colon + 1;
+        if (sscanf(ports, "%hu-%hu", min, max) == 2) {
+            if (*min > *max) {
+                ovs_fatal(0, "%s: minimum is greater than maximum", s_);
+            }
+        } else if (sscanf(ports, "%hu", min) == 1) {
+            *max = *min;
+        } else {
+            ovs_fatal(0, "%s: number or range expected", s_);
+        }
+    }
+
+    free(s);
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"local", required_argument, NULL, 'l'},
+        {"remote", required_argument, NULL, 'r'},
+        {"batches", required_argument, NULL, 'b'},
+        {"sockets", required_argument, NULL, 's'},
+        {"max-rate", required_argument, NULL, 'c'},
+        {"timeout", required_argument, NULL, 'T'},
+        {"help", no_argument, NULL, 'h'},
+        {"version", no_argument, NULL, 'V'},
+        {NULL, 0, NULL, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    local_addr.s_addr = htonl(INADDR_ANY);
+    local_min_port = local_max_port = 0;
+
+    remote_addr.s_addr = htonl(0);
+    remote_min_port = remote_max_port = 0;
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'l':
+            parse_target(optarg,
+                         &local_addr, &local_min_port, &local_max_port);
+            break;
+
+        case 'r':
+            parse_target(optarg,
+                         &remote_addr, &remote_min_port, &remote_max_port);
+            if (remote_addr.s_addr == htonl(INADDR_ANY)) {
+                ovs_fatal(0, "remote IP address is required");
+            }
+            break;
+
+        case 'b':
+            n_batches = atoi(optarg);
+            if (n_batches < 0) {
+                ovs_fatal(0, "--batches or -b argument must be at least 1");
+            }
+            break;
+
+        case 's':
+            n_sockets = atoi(optarg);
+            if (n_sockets < 1 || n_sockets > MAX_SOCKETS) {
+                ovs_fatal(0, "--sockets or -s argument must be between 1 "
+                          "and %d (inclusive)", MAX_SOCKETS);
+            }
+            break;
+
+        case 'c':
+            max_rate = atof(optarg);
+            if (max_rate <= 0.0) {
+                ovs_fatal(0, "--max-rate or -c argument must be positive");
+            }
+            break;
+
+        case 'T':
+            timeout = atoi(optarg);
+            if (!timeout) {
+                ovs_fatal(0, "-T or --timeout argument must be positive");
+            }
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("\
+%s: Open vSwitch flow setup benchmark utility\n\
+usage: %s [OPTIONS] COMMAND [ARG...]\n\
+  latency                     connect many times all at once\n\
+  rate                        measure sustained flow setup rate\n\
+  listen                      accept TCP connections\n\
+  help                        display this help message\n\
+\n\
+Command options:\n\
+  -l, --local [IP][:PORTS]    use local IP and range of PORTS\n\
+  -r, --remote IP[:PORTS]     connect to remote IP and PORTS\n\
+  -s, --sockets N             number of sockets for \"rate\" or \"latency\"\n\
+  -b, --batches N             number of connection batches for \"latency\"\n\
+  -c, --max-rate NPERSEC      connection rate limit for \"rate\"\n\
+  -T, --timeout MAXSECS       max number of seconds to run for \"rate\"\n\
+\n\
+Other options:\n\
+  -h, --help                  display this help message\n\
+  -V, --version               display version information\n",
+           program_name, program_name);
+    exit(EXIT_SUCCESS);
+}
+
+static void
+cmd_listen(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    struct pollfd *fds;
+    int n_fds;
+    int port;
+    int i;
+
+    if (!local_min_port && !local_max_port) {
+        local_min_port = local_max_port = DEFAULT_PORT;
+    }
+    fds = xmalloc((1 + local_max_port - local_min_port) * sizeof *fds);
+    n_fds = 0;
+    for (port = local_min_port; port <= local_max_port; port++) {
+        struct sockaddr_in sin;
+        unsigned int yes = 1;
+        int error;
+        int fd;
+
+        /* Create socket, set SO_REUSEADDR. */
+        fd = socket(AF_INET, SOCK_STREAM, 0);
+        if (fd < 0) {
+            ovs_fatal(errno, "failed to create socket");
+        }
+        error = set_nonblocking(fd);
+        if (error) {
+            ovs_fatal(error, "failed to set non-blocking mode");
+        }
+        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0) {
+            ovs_fatal(errno, "setsockopt(SO_REUSEADDR) failed");
+        }
+
+        /* Bind. */
+        sin.sin_family = AF_INET;
+        sin.sin_addr = remote_addr;
+        sin.sin_port = htons(port);
+        if (bind(fd, (struct sockaddr *) &sin, sizeof sin) < 0) {
+            ovs_fatal(errno, "bind failed");
+        }
+
+        /* Listen. */
+        if (listen(fd, 10000) < 0) {
+            ovs_fatal(errno, "listen failed");
+        }
+
+        fds[n_fds].fd = fd;
+        fds[n_fds].events = POLLIN;
+        n_fds++;
+    }
+
+    for (;;) {
+        int retval;
+
+        do {
+            retval = poll(fds, n_fds, -1);
+        } while (retval < 0 && errno == EINTR);
+        if (retval < 0) {
+            ovs_fatal(errno, "poll failed");
+        }
+
+        for (i = 0; i < n_fds; i++) {
+            if (fds[i].revents & POLLIN) {
+                int newfd;
+
+                do {
+                    newfd = accept(fds[i].fd, NULL, NULL);
+                } while (newfd < 0 && errno == EINTR);
+
+                if (newfd >= 0) {
+                    close(newfd);
+                } else if (errno != EAGAIN) {
+                    ovs_fatal(errno, "accept failed");
+                }
+            }
+        }
+    }
+}
+
+/* Increments '*value' within the range 'min...max' inclusive.  Returns true
+ * if '*value' wraps around to 'min', otherwise false. */
+static bool
+increment(unsigned short int *value,
+          unsigned short int min, unsigned short int max)
+{
+    if (*value < max) {
+        ++*value;
+        return false;
+    } else {
+        *value = min;
+        return true;
+    }
+}
+
+static void
+next_ports(unsigned short int *local_port, unsigned short int *remote_port)
+{
+    if (increment(local_port, local_min_port, local_max_port)) {
+        increment(remote_port, remote_min_port, remote_max_port);
+    }
+}
+
+static void
+bind_local_port(int fd, unsigned short int *local_port,
+                unsigned short int *remote_port)
+{
+    int error;
+
+    if (!local_min_port && !local_max_port) {
+        next_ports(local_port, remote_port);
+        return;
+    }
+
+    do {
+        struct sockaddr_in local;
+
+        memset(&local, 0, sizeof local);
+        local.sin_family = AF_INET;
+        local.sin_addr = local_addr;
+        local.sin_port = htons(*local_port);
+        error = (bind(fd, (struct sockaddr *) &local, sizeof local) < 0
+                 ? errno : 0);
+        next_ports(local_port, remote_port);
+    } while (error == EADDRINUSE || error == EINTR);
+    if (error) {
+        ovs_fatal(error, "bind failed");
+    }
+}
+
+static void
+cmd_rate(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    unsigned short int local_port;
+    unsigned short int remote_port;
+    unsigned int completed = 0;
+    unsigned int failures = 0;
+    long long int start, prev;
+    struct pollfd *fds;
+    int n_fds;
+
+    if (!remote_addr.s_addr) {
+        ovs_fatal(0, "remote address must be specified with -r or --remote");
+    }
+    if (!remote_min_port && !remote_max_port) {
+        remote_min_port = remote_max_port = DEFAULT_PORT;
+    }
+
+    local_port = local_min_port;
+    remote_port = remote_min_port;
+    fds = xmalloc(n_sockets * sizeof *fds);
+    n_fds = 0;
+    start = prev = time_in_msec();
+    for (;;) {
+        long long int now;
+        long long int may_open;
+        int delay;
+        int error;
+        int j;
+
+        if (max_rate > 0) {
+            long long int cur_total = completed + n_fds;
+            long long int max_total = (time_in_msec() - start) * (max_rate / 1000.0);
+            if (max_total > cur_total) {
+                may_open = MIN(n_sockets, max_total - cur_total);
+            } else {
+                may_open = 0;
+            }
+            delay = 1000.0 / max_rate;
+        } else {
+            may_open = n_sockets;
+            delay = 1000;
+        }
+
+        while (may_open-- > 0 && n_fds < n_sockets) {
+            struct sockaddr_in remote;
+            int error;
+            int fd;
+
+            fd = socket(AF_INET, SOCK_STREAM, 0);
+            if (fd < 0) {
+                ovs_fatal(errno, "socket failed");
+            }
+
+            error = set_nonblocking(fd);
+            if (error) {
+                ovs_fatal(error, "set_nonblocking failed");
+            }
+
+            bind_local_port(fd, &local_port, &remote_port);
+
+            memset(&remote, 0, sizeof remote);
+            remote.sin_family = AF_INET;
+            remote.sin_addr = remote_addr;
+            remote.sin_port = htons(remote_port);
+            if (connect(fd, (struct sockaddr *) &remote, sizeof remote) < 0) {
+                if (errno == EINPROGRESS) {
+                    fds[n_fds].fd = fd;
+                    fds[n_fds].events = POLLOUT;
+                    fds[n_fds].revents = 0;
+                    n_fds++;
+                } else if (errno != ECONNREFUSED) {
+                    ovs_fatal(errno, "connect");
+                }
+            } else {
+                /* Success, I guess. */
+                shutdown(fd, 2);
+                close(fd);
+                completed++;
+            }
+        }
+
+        if (n_fds == n_sockets) {
+            delay = 1000;
+        }
+
+        do {
+            error = poll(fds, n_fds, delay) < 0 ? errno : 0;
+        } while (error == EINTR);
+        if (error) {
+            ovs_fatal(errno, "poll");
+        }
+
+        for (j = 0; j < n_fds; ) {
+            if (fds[j].revents) {
+                if (fds[j].revents & POLLERR) {
+                    failures++;
+                }
+                shutdown(fds[j].fd, 2);
+                close(fds[j].fd);
+                fds[j] = fds[--n_fds];
+                completed++;
+            } else {
+                j++;
+            }
+        }
+
+        now = time_in_msec();
+        if (now >= prev + 10) {
+            long long int elapsed = now - start;
+            printf("%.3f s elapsed, %u OK, %u failed, avg %.1f/s     \r",
+                   elapsed / 1000.0, completed - failures, failures,
+                   completed / (elapsed / 1000.0));
+            fflush(stdout);
+            prev = now;
+
+            if (timeout && elapsed > timeout * 1000LL) {
+                break;
+            }
+        }
+    }
+}
+
+static void
+timer_end(long long int start, bool error,
+          int *min, int *max, unsigned long long int *total)
+{
+    int elapsed = time_in_msec() - start;
+    static int last_elapsed = INT_MIN;
+    char c = error ? '!' : '.';
+
+    if (last_elapsed != elapsed) {
+        if (last_elapsed != INT_MIN) {
+            putchar('\n');
+        }
+        printf("%5d %c", elapsed, c);
+        fflush(stdout);
+        last_elapsed = elapsed;
+    } else {
+        putchar(c);
+        fflush(stdout);
+    }
+
+    if (elapsed < *min) {
+        *min = elapsed;
+    }
+    if (elapsed > *max) {
+        *max = elapsed;
+    }
+    *total += elapsed;
+}
+
+static void
+cmd_latency(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    unsigned short int local_port;
+    unsigned short int remote_port;
+    int min = INT_MAX;
+    int max = 0;
+    unsigned long long int total = 0;
+    int i;
+
+    if (!remote_addr.s_addr) {
+        ovs_fatal(0, "remote address must be specified with -r or --rate");
+    }
+    if (!remote_min_port && !remote_max_port) {
+        remote_min_port = remote_max_port = DEFAULT_PORT;
+    }
+
+    local_port = local_min_port;
+    remote_port = remote_min_port;
+    for (i = 0; i < n_batches; i++) {
+        struct pollfd fds[MAX_SOCKETS];
+        long long int start;
+        int n_fds;
+        int j;
+
+        start = time_in_msec();
+        n_fds = 0;
+        for (j = 0; j < n_sockets; j++) {
+            struct sockaddr_in remote;
+            int error;
+            int fd;
+
+            fd = socket(AF_INET, SOCK_STREAM, 0);
+            if (fd < 0) {
+                ovs_fatal(errno, "socket failed");
+            }
+
+            error = set_nonblocking(fd);
+            if (error) {
+                ovs_fatal(error, "set_nonblocking failed");
+            }
+
+            bind_local_port(fd, &local_port, &remote_port);
+
+            memset(&remote, 0, sizeof remote);
+            remote.sin_family = AF_INET;
+            remote.sin_addr = remote_addr;
+            remote.sin_port = htons(remote_port);
+            if (connect(fd, (struct sockaddr *) &remote, sizeof remote) < 0) {
+                if (errno == EINPROGRESS) {
+                    fds[n_fds].fd = fd;
+                    fds[n_fds].events = POLLOUT;
+                    fds[n_fds].revents = 0;
+                    n_fds++;
+                } else if (errno != ECONNREFUSED) {
+                    ovs_fatal(errno, "connect");
+                }
+            } else {
+                /* Success, I guess. */
+                close(fd);
+                timer_end(start, 0, &min, &max, &total);
+            }
+        }
+
+        while (n_fds > 0) {
+            int error;
+
+            do {
+                error = poll(fds, n_fds, -1) < 0 ? errno : 0;
+            } while (error == EINTR);
+            if (error) {
+                ovs_fatal(errno, "poll");
+            }
+
+            for (j = 0; j < n_fds; ) {
+                if (fds[j].revents) {
+                    timer_end(start,
+                              fds[j].revents & (POLLERR|POLLHUP) ? 1 : 0,
+                              &min, &max, &total);
+                    close(fds[j].fd);
+                    fds[j] = fds[--n_fds];
+                } else {
+                    j++;
+                }
+            }
+        }
+        putchar('\n');
+    }
+
+    printf("min %d ms, max %d ms, avg %llu ms\n",
+           min, max, total / (1ULL * n_sockets * n_batches));
+}
+
+static void
+cmd_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    usage();
+}
+
+static const struct command all_commands[] = {
+    { "listen", 0, 0, cmd_listen },
+    { "rate", 0, 0, cmd_rate },
+    { "latency", 0, 0, cmd_latency },
+    { "help", 0, 0, cmd_help },
+    { NULL, 0, 0, NULL },
+};
index 16ea2a1..32c99fc 100644 (file)
@@ -111,9 +111,11 @@ mv $RPM_BUILD_ROOT/etc/openvswitch/bugtool-plugins $RPM_BUILD_ROOT/etc/xensource
 
 # Get rid of stuff we don't want to make RPM happy.
 rm \
+    $RPM_BUILD_ROOT/usr/bin/ovs-benchmark \
     $RPM_BUILD_ROOT/usr/sbin/ovs-bugtool \
     $RPM_BUILD_ROOT/usr/bin/ovs-controller \
     $RPM_BUILD_ROOT/usr/bin/ovs-pki \
+    $RPM_BUILD_ROOT/usr/share/man/man8/ovs-benchmark.1 \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-bugtool.8 \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-controller.8 \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-pki.8