NFVBENCH-153 Add support for python3
[nfvbench.git] / nfvbench / traffic_gen / traffic_utils.py
index e5dc463..c875a5d 100644 (file)
 
 
 import bitmath
-from traffic_base import AbstractTrafficGenerator
+from nfvbench.utils import multiplier_map
+
+# IMIX frame size including the 4-byte FCS field
+IMIX_L2_SIZES = [64, 594, 1518]
+IMIX_RATIOS = [7, 4, 1]
+# weighted average l2 frame size includng the 4-byte FCS
+IMIX_AVG_L2_FRAME_SIZE = sum(
+    [1.0 * imix[0] * imix[1] for imix in zip(IMIX_L2_SIZES, IMIX_RATIOS)]) / sum(IMIX_RATIOS)
 
 
 def convert_rates(l2frame_size, rate, intf_speed):
+    """Convert a given rate unit into the other rate units.
+
+    l2frame_size: size of the L2 frame in bytes (includes 32-bit FCS) or 'IMIX'
+    rate: a dict that has at least one of the following key:
+          'rate_pps', 'rate_bps', 'rate_percent'
+          with the corresponding input value
+    intf_speed: the line rate speed in bits per second
+    """
     avg_packet_size = get_average_packet_size(l2frame_size)
     if 'rate_pps' in rate:
+        # input = packets/sec
         initial_rate_type = 'rate_pps'
         pps = rate['rate_pps']
         bps = pps_to_bps(pps, avg_packet_size)
         load = bps_to_load(bps, intf_speed)
     elif 'rate_bps' in rate:
+        # input = bits per second
         initial_rate_type = 'rate_bps'
         bps = rate['rate_bps']
         load = bps_to_load(bps, intf_speed)
         pps = bps_to_pps(bps, avg_packet_size)
     elif 'rate_percent' in rate:
+        # input = percentage of the line rate (between 0.0 and 100.0)
         initial_rate_type = 'rate_percent'
         load = rate['rate_percent']
         bps = load_to_bps(load, intf_speed)
         pps = bps_to_pps(bps, avg_packet_size)
     else:
         raise Exception('Traffic config needs to have a rate type key')
-
     return {
         'initial_rate_type': initial_rate_type,
-        'rate_pps': pps,
+        'rate_pps': int(float(pps)),
         'rate_percent': load,
-        'rate_bps': bps
+        'rate_bps': int(float(bps))
     }
 
 
 def get_average_packet_size(l2frame_size):
+    """Retrieve the average L2 frame size
+
+    l2frame_size: an L2 frame size in bytes (including FCS) or 'IMIX'
+    return: average l2 frame size inlcuding the 32-bit FCS
+    """
     if l2frame_size.upper() == 'IMIX':
-        return AbstractTrafficGenerator.imix_avg_l2_size
-    else:
-        return float(l2frame_size)
+        return IMIX_AVG_L2_FRAME_SIZE
+    return float(l2frame_size)
 
 
 def load_to_bps(load_percentage, intf_speed):
@@ -70,15 +91,16 @@ def pps_to_bps(pps, avg_packet_size):
 
 def weighted_avg(weight, count):
     if sum(weight):
-        return sum(map(lambda x: x[0] * x[1], zip(weight, count))) / sum(weight)
-    else:
-        return float('nan')
 
-multiplier_map = {
-    'K': 1000,
-    'M': 1000000,
-    'G': 1000000000
-}
+        return sum([x[0] * x[1] for x in zip(weight, count)]) / sum(weight)
+    return float('nan')
+
+def _get_bitmath_rate(rate_bps):
+    rate = rate_bps.replace('ps', '').strip()
+    bitmath_rate = bitmath.parse_string(rate)
+    if bitmath_rate.bits <= 0:
+        raise Exception('%s is out of valid range' % rate_bps)
+    return bitmath_rate
 
 def parse_rate_str(rate_str):
     if rate_str.endswith('pps'):
@@ -90,24 +112,43 @@ def parse_rate_str(rate_str):
             rate_pps = rate_pps[:-1]
         except KeyError:
             multiplier = 1
-        rate_pps = int(rate_pps.strip()) * multiplier
+        rate_pps = int(float(rate_pps.strip()) * multiplier)
         if rate_pps <= 0:
             raise Exception('%s is out of valid range' % rate_str)
         return {'rate_pps': str(rate_pps)}
-    elif rate_str.endswith('ps'):
+    if rate_str.endswith('ps'):
         rate = rate_str.replace('ps', '').strip()
         bit_rate = bitmath.parse_string(rate).bits
         if bit_rate <= 0:
             raise Exception('%s is out of valid range' % rate_str)
         return {'rate_bps': str(int(bit_rate))}
-    elif rate_str.endswith('%'):
+    if rate_str.endswith('%'):
         rate_percent = float(rate_str.replace('%', '').strip())
         if rate_percent <= 0 or rate_percent > 100.0:
             raise Exception('%s is out of valid range (must be 1-100%%)' % rate_str)
         return {'rate_percent': str(rate_percent)}
+    raise Exception('Unknown rate string format %s' % rate_str)
+
+def get_load_from_rate(rate_str, avg_frame_size=64, line_rate='10Gbps'):
+    '''From any rate string (with unit) return the corresponding load (in % unit)
+
+    :param str rate_str: the rate to convert - must end with a unit (e.g. 1Mpps, 30%, 1Gbps)
+    :param int avg_frame_size: average frame size in bytes (needed only if pps is given)
+    :param str line_rate: line rate ending with bps unit (e.g. 1Mbps, 10Gbps) is the rate that
+                      corresponds to 100% rate
+    :return float: the corresponding rate in % of line rate
+    '''
+    rate_dict = parse_rate_str(rate_str)
+    if 'rate_percent' in rate_dict:
+        return float(rate_dict['rate_percent'])
+    lr_bps = _get_bitmath_rate(line_rate).bits
+    if 'rate_bps' in rate_dict:
+        bps = int(rate_dict['rate_bps'])
     else:
-        raise Exception('Unknown rate string format %s' % rate_str)
-
+        # must be rate_pps
+        pps = rate_dict['rate_pps']
+        bps = pps_to_bps(pps, avg_frame_size)
+    return bps_to_load(bps, lr_bps)
 
 def divide_rate(rate, divisor):
     if 'rate_pps' in rate:
@@ -129,19 +170,20 @@ def to_rate_str(rate):
     if 'rate_pps' in rate:
         pps = rate['rate_pps']
         return '{}pps'.format(pps)
-    elif 'rate_bps' in rate:
+    if 'rate_bps' in rate:
         bps = rate['rate_bps']
         return '{}bps'.format(bps)
-    elif 'rate_percent' in rate:
+    if 'rate_percent' in rate:
         load = rate['rate_percent']
         return '{}%'.format(load)
-    else:
-        assert False
+    assert False
+    # avert pylint warning
+    return None
 
 
 def nan_replace(d):
     """Replaces every occurence of 'N/A' with float nan."""
-    for k, v in d.iteritems():
+    for k, v in d.items():
         if isinstance(v, dict):
             nan_replace(v)
         elif v == 'N/A':
@@ -156,5 +198,5 @@ def mac_to_int(mac):
 def int_to_mac(i):
     """Converts integer representation of MAC address to hex string."""
     mac = format(i, 'x').zfill(12)
-    blocks = [mac[x:x + 2] for x in xrange(0, len(mac), 2)]
+    blocks = [mac[x:x + 2] for x in range(0, len(mac), 2)]
     return ':'.join(blocks)