[PATCH] Netperf: major refactorization

olichtne at redhat.com olichtne at redhat.com
Fri Jun 12 13:10:39 UTC 2015


From: Ondrej Lichtner <olichtne at redhat.com>

This commit brings major changes to the client side of the Netperf test
module.

* The first change is that the return message to the controller now
  contains data about the measured speed.
* Secondly threshold and result parsing were separated into their own
  functions so that they're easier to manage.
* The client measures speeds in kilobits by default, previously this
  wasn't set so whatever Netperf deemed appropriate was used. This helps
  with result parsing.
* Added a new option 'runs' which you can use to make Netperf do
  multiple runs with specified parameters. The final result is an
  average of all the results and a standard deviation is computed.
* When the result is 0.0 it is automatically assumed that the test has
  failed even when no threshold was set.

Signed-off-by: Ondrej Lichtner <olichtne at redhat.com>
---
 test_modules/Netperf.py | 204 +++++++++++++++++++++++++++++-------------------
 1 file changed, 125 insertions(+), 79 deletions(-)

diff --git a/test_modules/Netperf.py b/test_modules/Netperf.py
index a098d5a..95f88ef 100644
--- a/test_modules/Netperf.py
+++ b/test_modules/Netperf.py
@@ -11,6 +11,7 @@ import errno
 import re
 from lnst.Common.TestsCommon import TestGeneric
 from lnst.Common.ShellProcess import ShellProcess
+from lnst.Common.Utils import std_deviation
 
 class Netperf(TestGeneric):
 
@@ -27,7 +28,7 @@ class Netperf(TestGeneric):
             duration = self.get_opt("duration")
             port = self.get_opt("port")
             testname = self.get_opt("testname")
-            cmd = "netperf -H %s" % netperf_server
+            cmd = "netperf -H %s -f k" % netperf_server
             if port is not None:
                 """
                 client connects on this port
@@ -76,7 +77,7 @@ class Netperf(TestGeneric):
                 cmd += " %s" % netperf_opts
         return cmd
 
-    def _parse_output(self, threshold, output):
+    def _parse_output(self, output):
         testname = self.get_opt("testname")
         if testname == "UDP_STREAM":
             # pattern for UDP_STREAM throughput output
@@ -98,69 +99,60 @@ class Netperf(TestGeneric):
             # decimal decimal decimal float (float)
             pattern_sctp = "\d+\s+\d+\s+\d+\s+\d+\.\d+\s+(\d+(\.\d+){0,1})"
             r2 = re.search(pattern_sctp, output.lower())
-        if threshold is not None:
-            # pattern for threshold
-            # group(1) ... threshold value
-            # group(3) ... threshold units
-            # group(4) ... bytes/bits
-            if (testname == "TCP_STREAM" or testname == "UDP_STREAM" or
-               testname == "SCTP_STREAM" or testname == "SCTP_STREAM_MANY"):
-                pattern_stream = "(\d*(\.\d*){0,1})\s*([ kmgt])(bits|bytes)\/sec"
-                r1 = re.search(pattern_stream, threshold.lower())
-                if r1 is None:
-                    return (False, "Invalid unit type in the throughput option")
-                threshold_rate = float(r1.group(1))
-                threshold_unit_size = r1.group(3)
-                threshold_unit_type = r1.group(4)
-            elif (testname == "TCP_RR" or testname == "UDP_RR" or
-                 testname == "SCTP_RR"):
-                pattern_rr =  "(\d*(\.\d*){0,1})\s*trans\.\/sec"
-                r1 = re.search(pattern_rr, threshold.lower())
-                if r1 is None:
-                    return (False, "Invalid unit type in the throughput option")
-                threshold_rate = float(r1.group(1))
-                threshold_unit_size = ""
-                threshold_unit_type = "Trans./sec"
-            throughput_rate = float(r2.group(1))
-            """
-            this part converts threshold and throughput rates to same format
-            user will get output in format specified in threshold option
-            if no threshold option is put in, default format is Mbits
-            """
-            if (testname == "UDP_STREAM" or testname == "TCP_STREAM" or
-               testname == "SCTP_STREAM" or testname == "SCTP_STREAM_MANY"):
-                if threshold_unit_size == 'k':
-                    throughput_rate *= 1000
-                elif threshold_unit_size == 'g':
-                    throughput_rate /= 1000
-                elif threshold_unit_size == 't':
-                    throughput_rate /= 1000 * 1000
-                if threshold_unit_type == "bytes":
-                    throughput_rate /= 8
-            if threshold_rate > throughput_rate:
-                return (False, "Measured rate (%s %s%s) is below threshold "
-                               "(%s %s%s)!" % (throughput_rate,
-                                               threshold_unit_size.upper(),
-                                               threshold_unit_type,
-                                               threshold_rate,
-                                               threshold_unit_size.upper(),
-                                               threshold_unit_type))
-            else:
-                return (True, "Measured rate (%s %s%s) is over threshold "
-                              "(%s %s%s)." % (throughput_rate,
-                                              threshold_unit_size.upper(),
-                                              threshold_unit_type,
-                                              threshold_rate,
-                                              threshold_unit_size.upper(),
-                                              threshold_unit_type))
-        else:
-            if (testname == "TCP_RR" or testname == "UDP_RR" or
-               testname == "SCTP_RR"):
-                return (True, "Measured rate: %s Trans./sec" % r2.group(1))
-            else:
-                return (True, "Measured rate: %s Mbits/sec" % r2.group(1))
+
+        rate_in_kb = float(r2.group(1))
+
+        return {"rate": rate_in_kb*1000,
+                "unit": "bps"}
 
 
+    def _parse_threshold(self, threshold):
+        if threshold is None:
+            return None
+        # pattern for threshold
+        # group(1) ... threshold value
+        # group(3) ... threshold units
+        # group(4) ... bytes/bits
+        if (testname == "TCP_STREAM" or testname == "UDP_STREAM" or
+           testname == "SCTP_STREAM" or testname == "SCTP_STREAM_MANY"):
+            pattern_stream = "(\d*(\.\d*)?)\s*([ kmgtKMGT])(bits|bytes)\/sec"
+            r1 = re.search(pattern_stream, threshold)
+            if r1 is None:
+                res_data["msg"] = "Invalid unit type in the "\
+                                  "throughput option"
+                return (False, res_data)
+            threshold_rate = float(r1.group(1))
+            threshold_unit_size = r1.group(3)
+            threshold_unit_type = r1.group(4)
+            if threshold_unit_size == 'k':
+                threshold_rate *= 1000
+            elif threshold_unit_size == 'K':
+                threshold_rate *= 1024
+            elif threshold_unit_size == 'g':
+                threshold_rate *= 1000*1000
+            elif threshold_unit_size == 'G':
+                threshold_rate *= 1024*1024
+            elif threshold_unit_size == 't':
+                threshold_rate *= 1000 * 1000 * 1000
+            elif threshold_unit_size == 'T':
+                threshold_rate *= 1024 * 1024 * 1024
+            if threshold_unit_type == "bytes":
+                threshold_rate *= 8
+            threshold_unit_type = "bps"
+        elif (testname == "TCP_RR" or testname == "UDP_RR" or
+             testname == "SCTP_RR"):
+            pattern_rr =  "(\d*(\.\d*)?)\s*trans\.\/sec"
+            r1 = re.search(pattern_rr, threshold.lower())
+            if r1 is None:
+                res_data["msg"] = "Invalid unit type in the "\
+                                  "throughput option"
+                return (False, res_data)
+            threshold_rate = float(r1.group(1))
+            threshold_unit_size = ""
+            threshold_unit_type = "tps"
+
+        return {"rate": threshold_rate,
+                "unit": threshold_unit_type}
 
     def _run_server(self, cmd):
         logging.debug("running as server...")
@@ -173,29 +165,83 @@ class Netperf(TestGeneric):
 
     def _run_client(self, cmd):
         logging.debug("running as client...")
-        client = ShellProcess(cmd)
-        try:
-            rv = client.wait()
-        except OSError as e:
-            if e.errno == errno.EINTR:
-                client.kill()
-        output = client.read_nonblocking()
+
+        res_data = {}
+
+        rv = 0
+        runs = self.get_opt("runs", default=1)
+        results = []
+        rates = []
+        for i in range(1, runs+1):
+            if runs > 1:
+                logging.info("Netperf starting run %d" % i)
+            client = ShellProcess(cmd)
+            try:
+                rv += client.wait()
+            except OSError as e:
+                if e.errno == errno.EINTR:
+                    client.kill()
+            output = client.read_nonblocking()
+            results.append(self._parse_output(output))
+            rates.append(results[-1]["rate"])
+
+        if runs > 1:
+            res_data["results"] = results
+
+        rate = sum(rates)/len(rates)
+        rate_std_deviation = std_deviation(rates)
+        res_data["rate"] = rate
+        res_data["rate_std_deviation"] = rate_std_deviation
+
+        threshold = self._parse_threshold(self.get_opt("threshold"))
+        threshold_std_deviation = self._parse_threshold(self.get_opt("threshold_std_deviation"))
+
+        res_val = False
+        if threshold is not None:
+            threshold = threshold["rate"]
+            if threshold_std_deviation is None:
+                threshold_std_deviation = 0.0
+            else:
+                threshold_std_deviation = threshold_std_deviation["rate"]
+            result_interval = (rate - rate_std_deviation,
+                               rate + rate_std_deviation)
+            threshold_interval = (threshold - threshold_std_deviation,
+                                  threshold + threshold_std_deviation)
+
+            if threshold_interval[0] > result_interval[1]:
+                res_val = False
+                res_data["msg"] = "Measured rate %.2f +-%.2f bps is lower "\
+                                  "than threshold %.2f +-%.2f" %\
+                                  (rate, rate_std_deviation,
+                                   threshold, threshold_std_deviation)
+            else:
+                res_val = True
+                res_data["msg"] = "Measured rate %.2f +-%.2f bps is higher "\
+                                  "than threshold %.2f +-.2%f" %\
+                                  (rate, rate_std_deviation,
+                                   threshold, threshold_std_deviation)
+        else:
+            if rate > 0.0:
+                res_val = True
+            else:
+                res_val = False
+            res_data["msg"] = "Measured rate was %.2f +-%.2f bps" %\
+                                                (rate, rate_std_deviation)
+
         if rv != 0:
-            logging.info("Could not get performance throughput! Are you sure "
-                         "netperf is installed on both machines and machines "
-                         "are mutually accessible?")
-            return (False, "Could not get performance throughput! Are you "
-                           "sure netperf is installed on both machines and "
-                           "machines are mutually accessible?")
-        return self._parse_output(self.get_opt("threshold"), output)
+            res_data["msg"] = "Could not get performance throughput! Are you "\
+                              "sure netperf is installed on both machines and "\
+                              "machines are mutually accessible?"
+            logging.info(res_data["msg"])
+            return (False, res_data)
+        return (res_val, res_data)
 
     def run(self):
         self.role = self.get_mopt("role")
         cmd = self._compose_cmd(self.role)
         logging.debug("compiled command: %s" % cmd)
         if self.role == "client":
-            (rv, message) = self._run_client(cmd)
-            res_data = {"msg" : message}
+            (rv, res_data) = self._run_client(cmd)
             if rv == False:
                 return self.set_fail(res_data)
             return self.set_pass(res_data)
-- 
2.1.0



More information about the LNST-developers mailing list