NFVBENCH-172: Add quartiles and 99 percentile latency values
[nfvbench.git] / nfvbench / traffic_gen / trex_gen.py
index af70cde..b35d13f 100644 (file)
@@ -18,6 +18,7 @@ import os
 import random
 import time
 import traceback
+from functools import reduce
 
 from itertools import count
 # pylint: disable=import-error
@@ -29,6 +30,8 @@ from nfvbench.utils import cast_integer
 from nfvbench.utils import timeout
 from nfvbench.utils import TimeoutError
 
+from hdrh.histogram import HdrHistogram
+
 # pylint: disable=import-error
 from trex.common.services.trex_service_arp import ServiceARP
 from trex.stl.api import bind_layers
@@ -47,6 +50,7 @@ from trex.stl.api import STLScVmRaw
 from trex.stl.api import STLStream
 from trex.stl.api import STLTXCont
 from trex.stl.api import STLVmFixChecksumHw
+from trex.stl.api import STLVmFixIpv4
 from trex.stl.api import STLVmFlowVar
 from trex.stl.api import STLVmFlowVarRepeatableRandom
 from trex.stl.api import STLVmWrFlowVar
@@ -94,6 +98,7 @@ class TRex(AbstractTrafficGenerator):
         self.rates = []
         self.capture_id = None
         self.packet_list = []
+        self.l2_frame_size = 0
 
     def get_version(self):
         """Get the Trex version."""
@@ -114,7 +119,7 @@ class TRex(AbstractTrafficGenerator):
         pg_id = port * TRex.PORT_PG_ID_MASK | chain_id
         return pg_id, pg_id | TRex.LATENCY_PG_ID_MASK
 
-    def extract_stats(self, in_stats):
+    def extract_stats(self, in_stats, ifstats):
         """Extract stats from dict returned by Trex API.
 
         :param in_stats: dict as returned by TRex api
@@ -150,8 +155,36 @@ class TRex(AbstractTrafficGenerator):
 
         total_tx_pkts = result[0]['tx']['total_pkts'] + result[1]['tx']['total_pkts']
         result["total_tx_rate"] = cast_integer(total_tx_pkts / self.config.duration_sec)
+        # actual offered tx rate in bps
+        avg_packet_size = utils.get_average_packet_size(self.l2_frame_size)
+        total_tx_bps = utils.pps_to_bps(result["total_tx_rate"], avg_packet_size)
+        result['offered_tx_rate_bps'] = total_tx_bps
         result["flow_stats"] = in_stats["flow_stats"]
         result["latency"] = in_stats["latency"]
+
+        # Merge HDRHistogram to have an overall value for all chains and ports
+        try:
+            hdrh_list = []
+            if ifstats:
+                for chain_id, _ in enumerate(ifstats):
+                    for ph in self.port_handle:
+                        _, lat_pg_id = self.get_pg_id(ph, chain_id)
+                        hdrh_list.append(
+                            HdrHistogram.decode(in_stats['latency'][lat_pg_id]['latency']['hdrh']))
+            else:
+                for pg_id in in_stats['latency']:
+                    if pg_id != 'global':
+                        hdrh_list.append(
+                            HdrHistogram.decode(in_stats['latency'][pg_id]['latency']['hdrh']))
+
+            def add_hdrh(x, y):
+                x.add(y)
+                return x
+            decoded_hdrh = reduce(add_hdrh, hdrh_list)
+            result["hdrh"] = HdrHistogram.encode(decoded_hdrh).decode('utf-8')
+        except KeyError:
+            pass
+
         return result
 
     def get_stream_stats(self, trex_stats, if_stats, latencies, chain_idx):
@@ -387,19 +420,36 @@ class TRex(AbstractTrafficGenerator):
         udp_args = {}
         if stream_cfg['udp_src_port']:
             udp_args['sport'] = int(stream_cfg['udp_src_port'])
+            if stream_cfg['udp_port_step'] == 'random':
+                step = 1
+            else:
+                step = stream_cfg['udp_port_step']
+            udp_args['sport_step'] = int(step)
+            udp_args['sport_max'] = int(stream_cfg['udp_src_port_max'])
         if stream_cfg['udp_dst_port']:
             udp_args['dport'] = int(stream_cfg['udp_dst_port'])
-        pkt_base /= IP() / UDP(**udp_args)
-
+            if stream_cfg['udp_port_step'] == 'random':
+                step = 1
+            else:
+                step = stream_cfg['udp_port_step']
+            udp_args['dport_step'] = int(step)
+            udp_args['dport_max'] = int(stream_cfg['udp_dst_port_max'])
+
+        pkt_base /= IP(src=stream_cfg['ip_src_addr'], dst=stream_cfg['ip_dst_addr']) / \
+                    UDP(dport=udp_args['dport'], sport=udp_args['sport'])
+        if stream_cfg['ip_src_static'] is True:
+            src_max_ip_value = stream_cfg['ip_src_addr']
+        else:
+            src_max_ip_value = stream_cfg['ip_src_addr_max']
         if stream_cfg['ip_addrs_step'] == 'random':
-            src_fv = STLVmFlowVarRepeatableRandom(
+            src_fv_ip = STLVmFlowVarRepeatableRandom(
                 name="ip_src",
                 min_value=stream_cfg['ip_src_addr'],
-                max_value=stream_cfg['ip_src_addr_max'],
+                max_value=src_max_ip_value,
                 size=4,
                 seed=random.randint(0, 32767),
                 limit=stream_cfg['ip_src_count'])
-            dst_fv = STLVmFlowVarRepeatableRandom(
+            dst_fv_ip = STLVmFlowVarRepeatableRandom(
                 name="ip_dst",
                 min_value=stream_cfg['ip_dst_addr'],
                 max_value=stream_cfg['ip_dst_addr_max'],
@@ -407,14 +457,14 @@ class TRex(AbstractTrafficGenerator):
                 seed=random.randint(0, 32767),
                 limit=stream_cfg['ip_dst_count'])
         else:
-            src_fv = STLVmFlowVar(
+            src_fv_ip = STLVmFlowVar(
                 name="ip_src",
                 min_value=stream_cfg['ip_src_addr'],
-                max_value=stream_cfg['ip_src_addr'],
+                max_value=src_max_ip_value,
                 size=4,
                 op="inc",
                 step=stream_cfg['ip_addrs_step'])
-            dst_fv = STLVmFlowVar(
+            dst_fv_ip = STLVmFlowVar(
                 name="ip_dst",
                 min_value=stream_cfg['ip_dst_addr'],
                 max_value=stream_cfg['ip_dst_addr_max'],
@@ -422,18 +472,53 @@ class TRex(AbstractTrafficGenerator):
                 op="inc",
                 step=stream_cfg['ip_addrs_step'])
 
-        vm_param.extend([
-            src_fv,
+        if stream_cfg['udp_port_step'] == 'random':
+            src_fv_port = STLVmFlowVarRepeatableRandom(
+                name="p_src",
+                min_value=udp_args['sport'],
+                max_value=udp_args['sport_max'],
+                size=2,
+                seed=random.randint(0, 32767),
+                limit=stream_cfg['udp_src_count'])
+            dst_fv_port = STLVmFlowVarRepeatableRandom(
+                name="p_dst",
+                min_value=udp_args['dport'],
+                max_value=udp_args['dport_max'],
+                size=2,
+                seed=random.randint(0, 32767),
+                limit=stream_cfg['udp_dst_count'])
+        else:
+            src_fv_port = STLVmFlowVar(
+                name="p_src",
+                min_value=udp_args['sport'],
+                max_value=udp_args['sport_max'],
+                size=2,
+                op="inc",
+                step=udp_args['sport_step'])
+            dst_fv_port = STLVmFlowVar(
+                name="p_dst",
+                min_value=udp_args['dport'],
+                max_value=udp_args['dport_max'],
+                size=2,
+                op="inc",
+                step=udp_args['dport_step'])
+        vm_param = [
+            src_fv_ip,
             STLVmWrFlowVar(fv_name="ip_src", pkt_offset="IP:{}.src".format(encap_level)),
-            dst_fv,
-            STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level))
-        ])
-
-        for encap in range(int(encap_level), -1, -1):
-            # Fixing the checksums for all encap levels
-            vm_param.append(STLVmFixChecksumHw(l3_offset="IP:{}".format(encap),
-                                               l4_offset="UDP:{}".format(encap),
-                                               l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
+            src_fv_port,
+            STLVmWrFlowVar(fv_name="p_src", pkt_offset="UDP:{}.sport".format(encap_level)),
+            dst_fv_ip,
+            STLVmWrFlowVar(fv_name="ip_dst", pkt_offset="IP:{}.dst".format(encap_level)),
+            dst_fv_port,
+            STLVmWrFlowVar(fv_name="p_dst", pkt_offset="UDP:{}.dport".format(encap_level)),
+        ]
+        # Use HW Offload to calculate the outter IP/UDP packet
+        vm_param.append(STLVmFixChecksumHw(l3_offset="IP:0",
+                                           l4_offset="UDP:0",
+                                           l4_type=CTRexVmInsFixHwCs.L4_TYPE_UDP))
+        # Use software to fix the inner IP/UDP payload for VxLAN packets
+        if int(encap_level):
+            vm_param.append(STLVmFixIpv4(offset="IP:1"))
         pad = max(0, frame_size - len(pkt_base)) * 'x'
 
         return STLPktBuilder(pkt=pkt_base / pad,
@@ -767,6 +852,7 @@ class TRex(AbstractTrafficGenerator):
                 .format(pps=r['rate_pps'],
                         bps=r['rate_bps'],
                         load=r['rate_percent']))
+        self.l2_frame_size = l2frame_size
         # a dict of list of streams indexed by port#
         # in case of fixed size, has self.chain_count * 2 * 2 streams
         # (1 normal + 1 latency stream per direction per chain)
@@ -806,10 +892,10 @@ class TRex(AbstractTrafficGenerator):
         self.client.reset(self.port_handle)
         LOG.info('Cleared all existing streams')
 
-    def get_stats(self):
+    def get_stats(self, if_stats=None):
         """Get stats from Trex."""
         stats = self.client.get_stats()
-        return self.extract_stats(stats)
+        return self.extract_stats(stats, if_stats)
 
     def get_macs(self):
         """Return the Trex local port MAC addresses.
@@ -849,8 +935,8 @@ class TRex(AbstractTrafficGenerator):
         bpf_filter = "ether dst %s or ether dst %s" % (src_mac_list[0], src_mac_list[1])
         # ports must be set in service in order to enable capture
         self.client.set_service_mode(ports=self.port_handle)
-        self.capture_id = self.client.start_capture(rx_ports=self.port_handle,
-                                                    bpf_filter=bpf_filter)
+        self.capture_id = self.client.start_capture \
+            (rx_ports=self.port_handle, bpf_filter=bpf_filter)
 
     def fetch_capture_packets(self):
         """Fetch capture packets in capture mode."""