NFVBENCH-177: Add a config item 'user_info' and theoretical max rate value
[nfvbench.git] / nfvbench / traffic_client.py
index f26a747..72fbb5d 100755 (executable)
@@ -13,7 +13,6 @@
 #    under the License.
 
 """Interface to the traffic generator clients including NDR/PDR binary search."""
-
 from math import gcd
 import socket
 import struct
@@ -21,6 +20,7 @@ import time
 
 from attrdict import AttrDict
 import bitmath
+from hdrh.histogram import HdrHistogram
 from netaddr import IPNetwork
 # pylint: disable=import-error
 from trex.stl.api import Ether
@@ -246,11 +246,8 @@ class Device(object):
         else:
             self.ip_block = IpBlock(self.ip, step, self.ip_size)
 
-        if generator_config.gen_config.udp_port_step == 'random':
-            step = 1
-        else:
-            step = generator_config.gen_config.udp_port_step
-        self.udp_ports = UdpPorts(src_min, src_max, dst_min, dst_max, step)
+        self.udp_ports = UdpPorts(src_min, src_max, dst_min, dst_max,
+                                  generator_config.gen_config.udp_port_step)
         self.gw_ip_block = IpBlock(generator_config.gateway_ips[port],
                                    generator_config.gateway_ip_addrs_step,
                                    self.chain_count)
@@ -291,7 +288,7 @@ class Device(object):
                 value = int((ip_size / step)) + 1
             return value
         except ZeroDivisionError:
-            raise ZeroDivisionError("step can't be zero !")
+            raise ZeroDivisionError("step can't be zero !") from ZeroDivisionError
 
     def set_mac(self, mac):
         """Set the local MAC for this port device."""
@@ -489,15 +486,24 @@ class GeneratorConfig(object):
             self.cores = config.cores
         else:
             self.cores = gen_config.get('cores', 1)
+        # let's report the value actually used in the end
+        config.cores_used = self.cores
         self.mbuf_factor = config.mbuf_factor
         self.mbuf_64 = config.mbuf_64
         self.hdrh = not config.disable_hdrh
-        if gen_config.intf_speed:
-            # interface speed is overriden from config
-            self.intf_speed = bitmath.parse_string(gen_config.intf_speed.replace('ps', '')).bits
+        if config.intf_speed:
+            # interface speed is overriden from the command line
+            self.intf_speed = config.intf_speed
+        elif gen_config.intf_speed:
+            # interface speed is overriden from the generator config
+            self.intf_speed = gen_config.intf_speed
         else:
+            self.intf_speed = "auto"
+        if self.intf_speed == "auto" or self.intf_speed == "0":
             # interface speed is discovered/provided by the traffic generator
             self.intf_speed = 0
+        else:
+            self.intf_speed = bitmath.parse_string(self.intf_speed.replace('ps', '')).bits
         self.name = gen_config.name
         self.zmq_pub_port = gen_config.get('zmq_pub_port', 4500)
         self.zmq_rpc_port = gen_config.get('zmq_rpc_port', 4501)
@@ -716,13 +722,17 @@ class TrafficClient(object):
             # interface speed is overriden from config
             if self.intf_speed != tg_if_speed:
                 # Warn the user if the speed in the config is different
-                LOG.warning('Interface speed provided is different from actual speed (%d Gbps)',
-                            intf_speeds[0])
+                LOG.warning(
+                    'Interface speed provided (%g Gbps) is different from actual speed (%d Gbps)',
+                    self.intf_speed / 1000000000.0, intf_speeds[0])
         else:
             # interface speed not provisioned by config
             self.intf_speed = tg_if_speed
             # also update the speed in the tg config
             self.generator_config.intf_speed = tg_if_speed
+        # let's report detected and actually used interface speed
+        self.config.intf_speed_detected = tg_if_speed
+        self.config.intf_speed_used = self.intf_speed
 
         # Save the traffic generator local MAC
         for mac, device in zip(self.gen.get_macs(), self.generator_config.devices):
@@ -864,8 +874,10 @@ class TrafficClient(object):
 
         if self.config.no_latency_streams:
             LOG.info("Latency streams are disabled")
+        # in service mode, we must disable flow stats (e2e=True)
         self.gen.create_traffic(frame_size, self.run_config['rates'], bidirectional,
-                                latency=not self.config.no_latency_streams)
+                                latency=not self.config.no_latency_streams,
+                                e2e=self.runner.service_mode)
 
     def _modify_load(self, load):
         self.current_total_rate = {'rate_percent': str(load)}
@@ -922,8 +934,11 @@ class TrafficClient(object):
 
     def get_stats(self):
         """Collect final stats for previous run."""
-        stats = self.gen.get_stats()
-        retDict = {'total_tx_rate': stats['total_tx_rate']}
+        stats = self.gen.get_stats(self.ifstats)
+        retDict = {'total_tx_rate': stats['total_tx_rate'],
+                   'offered_tx_rate_bps': stats['offered_tx_rate_bps'],
+                   'theoretical_tx_rate_bps': stats['theoretical_tx_rate_bps'],
+                   'theoretical_tx_rate_pps': stats['theoretical_tx_rate_pps']}
 
         tx_keys = ['total_pkts', 'total_pkt_bytes', 'pkt_rate', 'pkt_bit_rate']
         rx_keys = tx_keys + ['dropped_pkts']
@@ -969,6 +984,18 @@ class TrafficClient(object):
             for key in ['pkt_bit_rate', 'pkt_rate']:
                 for dirc in ['tx', 'rx']:
                     retDict['overall'][dirc][key] /= 2.0
+                retDict['overall']['hdrh'] = stats.get('hdrh', None)
+                if retDict['overall']['hdrh']:
+                    decoded_histogram = HdrHistogram.decode(retDict['overall']['hdrh'])
+                    # override min max and avg from hdrh
+                    retDict['overall']['rx']['min_delay_usec'] = decoded_histogram.get_min_value()
+                    retDict['overall']['rx']['max_delay_usec'] = decoded_histogram.get_max_value()
+                    retDict['overall']['rx']['avg_delay_usec'] = decoded_histogram.get_mean_value()
+                    retDict['overall']['rx']['lat_percentile'] = {}
+                    for percentile in self.config.lat_percentiles:
+                        retDict['overall']['rx']['lat_percentile'][percentile] = \
+                            decoded_histogram.get_value_at_percentile(percentile)
+
         else:
             retDict['overall'] = retDict[ports[0]]
         retDict['overall']['drop_rate_percent'] = self.__get_dropped_rate(retDict['overall'])
@@ -999,6 +1026,20 @@ class TrafficClient(object):
                 'min_delay_usec': interface['rx']['min_delay_usec'],
             }
 
+            if key == 'overall':
+                stats[key]['hdrh'] = interface.get('hdrh', None)
+                if stats[key]['hdrh']:
+                    decoded_histogram = HdrHistogram.decode(stats[key]['hdrh'])
+                    # override min max and avg from hdrh
+                    stats[key]['min_delay_usec'] = decoded_histogram.get_min_value()
+                    stats[key]['max_delay_usec'] = decoded_histogram.get_max_value()
+                    stats[key]['avg_delay_usec'] = decoded_histogram.get_mean_value()
+                    stats[key]['lat_percentile'] = {}
+                    for percentile in self.config.lat_percentiles:
+                        stats[key]['lat_percentile'][percentile] = decoded_histogram.\
+                            get_value_at_percentile(percentile)
+
+
         return stats
 
     def __targets_found(self, rate, targets, results):
@@ -1216,7 +1257,7 @@ class TrafficClient(object):
                         for chain_idx in range(self.config.service_chain_count)]
         # note that we need to make a copy of the ifs list so that any modification in the
         # list from pps will not change the list saved in self.ifstats
-        self.pps_list = [PacketPathStats(list(ifs)) for ifs in self.ifstats]
+        self.pps_list = [PacketPathStats(self.config, list(ifs)) for ifs in self.ifstats]
         # insert the corresponding pps in the passed list
         pps_list.extend(self.pps_list)
 
@@ -1235,7 +1276,7 @@ class TrafficClient(object):
         ]
         """
         if diff:
-            stats = self.gen.get_stats()
+            stats = self.gen.get_stats(self.ifstats)
             for chain_idx, ifs in enumerate(self.ifstats):
                 # each ifs has exactly 2 InterfaceStats and 2 Latency instances
                 # corresponding to the