Cleanup requirements & tox config, update pylint
[nfvbench.git] / nfvbench / traffic_gen / traffic_utils.py
1 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
2 #
3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
4 #    not use this file except in compliance with the License. You may obtain
5 #    a copy of the License at
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
9 #    Unless required by applicable law or agreed to in writing, software
10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 #    License for the specific language governing permissions and limitations
13 #    under the License.
14
15
16 import bitmath
17
18 # IMIX frame size including the 4-byte FCS field
19 IMIX_L2_SIZES = [64, 594, 1518]
20 IMIX_RATIOS = [7, 4, 1]
21 # weighted average l2 frame size includng the 4-byte FCS
22 IMIX_AVG_L2_FRAME_SIZE = sum(
23     [1.0 * imix[0] * imix[1] for imix in zip(IMIX_L2_SIZES, IMIX_RATIOS)]) / sum(IMIX_RATIOS)
24
25 multiplier_map = {
26     'K': 1000,
27     'M': 1000000,
28     'G': 1000000000
29 }
30
31 def convert_rates(l2frame_size, rate, intf_speed):
32     """Convert a given rate unit into the other rate units.
33
34     l2frame_size: size of the L2 frame in bytes (includes 32-bit FCS) or 'IMIX'
35     rate: a dict that has at least one of the following key:
36           'rate_pps', 'rate_bps', 'rate_percent'
37           with the corresponding input value
38     intf_speed: the line rate speed in bits per second
39     """
40     avg_packet_size = get_average_packet_size(l2frame_size)
41     if 'rate_pps' in rate:
42         # input = packets/sec
43         initial_rate_type = 'rate_pps'
44         pps = rate['rate_pps']
45         bps = pps_to_bps(pps, avg_packet_size)
46         load = bps_to_load(bps, intf_speed)
47     elif 'rate_bps' in rate:
48         # input = bits per second
49         initial_rate_type = 'rate_bps'
50         bps = rate['rate_bps']
51         load = bps_to_load(bps, intf_speed)
52         pps = bps_to_pps(bps, avg_packet_size)
53     elif 'rate_percent' in rate:
54         # input = percentage of the line rate (between 0.0 and 100.0)
55         initial_rate_type = 'rate_percent'
56         load = rate['rate_percent']
57         bps = load_to_bps(load, intf_speed)
58         pps = bps_to_pps(bps, avg_packet_size)
59     else:
60         raise Exception('Traffic config needs to have a rate type key')
61     return {
62         'initial_rate_type': initial_rate_type,
63         'rate_pps': int(float(pps)),
64         'rate_percent': load,
65         'rate_bps': int(float(bps))
66     }
67
68
69 def get_average_packet_size(l2frame_size):
70     """Retrieve the average L2 frame size
71
72     l2frame_size: an L2 frame size in bytes (including FCS) or 'IMIX'
73     return: average l2 frame size inlcuding the 32-bit FCS
74     """
75     if l2frame_size.upper() == 'IMIX':
76         return IMIX_AVG_L2_FRAME_SIZE
77     return float(l2frame_size)
78
79
80 def load_to_bps(load_percentage, intf_speed):
81     return float(load_percentage) / 100.0 * intf_speed
82
83
84 def bps_to_load(bps, intf_speed):
85     return float(bps) / intf_speed * 100.0
86
87
88 def bps_to_pps(bps, avg_packet_size):
89     return float(bps) / (avg_packet_size + 20.0) / 8
90
91
92 def pps_to_bps(pps, avg_packet_size):
93     return float(pps) * (avg_packet_size + 20.0) * 8
94
95
96 def weighted_avg(weight, count):
97     if sum(weight):
98
99         return sum([x[0] * x[1] for x in zip(weight, count)]) / sum(weight)
100     return float('nan')
101
102 def _get_bitmath_rate(rate_bps):
103     rate = rate_bps.replace('ps', '').strip()
104     bitmath_rate = bitmath.parse_string(rate)
105     if bitmath_rate.bits <= 0:
106         raise Exception('%s is out of valid range' % rate_bps)
107     return bitmath_rate
108
109 def parse_rate_str(rate_str):
110     if rate_str.endswith('pps'):
111         rate_pps = rate_str[:-3]
112         if not rate_pps:
113             raise Exception('%s is missing a numeric value' % rate_str)
114         try:
115             multiplier = multiplier_map[rate_pps[-1].upper()]
116             rate_pps = rate_pps[:-1]
117         except KeyError:
118             multiplier = 1
119         rate_pps = int(float(rate_pps.strip()) * multiplier)
120         if rate_pps <= 0:
121             raise Exception('%s is out of valid range' % rate_str)
122         return {'rate_pps': str(rate_pps)}
123     if rate_str.endswith('ps'):
124         rate = rate_str.replace('ps', '').strip()
125         bit_rate = bitmath.parse_string(rate).bits
126         if bit_rate <= 0:
127             raise Exception('%s is out of valid range' % rate_str)
128         return {'rate_bps': str(int(bit_rate))}
129     if rate_str.endswith('%'):
130         rate_percent = float(rate_str.replace('%', '').strip())
131         if rate_percent <= 0 or rate_percent > 100.0:
132             raise Exception('%s is out of valid range (must be 1-100%%)' % rate_str)
133         return {'rate_percent': str(rate_percent)}
134     raise Exception('Unknown rate string format %s' % rate_str)
135
136 def get_load_from_rate(rate_str, avg_frame_size=64, line_rate='10Gbps'):
137     '''From any rate string (with unit) return the corresponding load (in % unit)
138
139     :param str rate_str: the rate to convert - must end with a unit (e.g. 1Mpps, 30%, 1Gbps)
140     :param int avg_frame_size: average frame size in bytes (needed only if pps is given)
141     :param str line_rate: line rate ending with bps unit (e.g. 1Mbps, 10Gbps) is the rate that
142                       corresponds to 100% rate
143     :return float: the corresponding rate in % of line rate
144     '''
145     rate_dict = parse_rate_str(rate_str)
146     if 'rate_percent' in rate_dict:
147         return float(rate_dict['rate_percent'])
148     lr_bps = _get_bitmath_rate(line_rate).bits
149     if 'rate_bps' in rate_dict:
150         bps = int(rate_dict['rate_bps'])
151     else:
152         # must be rate_pps
153         pps = rate_dict['rate_pps']
154         bps = pps_to_bps(pps, avg_frame_size)
155     return bps_to_load(bps, lr_bps)
156
157 def divide_rate(rate, divisor):
158     if 'rate_pps' in rate:
159         key = 'rate_pps'
160         value = int(rate[key])
161     elif 'rate_bps' in rate:
162         key = 'rate_bps'
163         value = int(rate[key])
164     else:
165         key = 'rate_percent'
166         value = float(rate[key])
167     value /= divisor
168     rate = dict(rate)
169     rate[key] = str(value) if value else str(1)
170     return rate
171
172
173 def to_rate_str(rate):
174     if 'rate_pps' in rate:
175         pps = rate['rate_pps']
176         return '{}pps'.format(pps)
177     if 'rate_bps' in rate:
178         bps = rate['rate_bps']
179         return '{}bps'.format(bps)
180     if 'rate_percent' in rate:
181         load = rate['rate_percent']
182         return '{}%'.format(load)
183     assert False
184     # avert pylint warning
185     return None
186
187
188 def nan_replace(d):
189     """Replaces every occurence of 'N/A' with float nan."""
190     for k, v in d.items():
191         if isinstance(v, dict):
192             nan_replace(v)
193         elif v == 'N/A':
194             d[k] = float('nan')
195
196
197 def mac_to_int(mac):
198     """Converts MAC address to integer representation."""
199     return int(mac.translate(None, ":.- "), 16)
200
201
202 def int_to_mac(i):
203     """Converts integer representation of MAC address to hex string."""
204     mac = format(i, 'x').zfill(12)
205     blocks = [mac[x:x + 2] for x in range(0, len(mac), 2)]
206     return ':'.join(blocks)