1 # Copyright 2016 Cisco Systems, Inc. All rights reserved.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
17 from math import isnan
25 from functools import wraps
28 from nfvbench.traffic_gen.traffic_utils import multiplier_map
30 class TimeoutError(Exception):
34 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
36 def _handle_timeout(_signum, _frame):
37 raise TimeoutError(error_message)
39 def wrapper(*args, **kwargs):
40 signal.signal(signal.SIGALRM, _handle_timeout)
43 result = func(*args, **kwargs)
48 return wraps(func)(wrapper)
53 def save_json_result(result, json_file, std_json_path, service_chain, service_chain_count,
54 flow_count, frame_sizes):
55 """Save results in json format file."""
58 filepaths.append(json_file)
60 name_parts = [service_chain, str(service_chain_count), str(flow_count)] + list(frame_sizes)
61 filename = '-'.join(name_parts) + '.json'
62 filepaths.append(os.path.join(std_json_path, filename))
65 for file_path in filepaths:
66 LOG.info('Saving results in json file: %s...', file_path)
67 with open(file_path, 'w') as jfp:
72 separators=(',', ': '),
73 default=lambda obj: obj.to_json())
76 def dict_to_json_dict(record):
77 return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
80 def get_intel_pci(nic_slot=None, nic_ports=None):
81 """Returns two PCI address that will be used for NFVbench
83 @param nic_slot: The physical PCIe slot number in motherboard
84 @param nic_ports: Array of two integers indicating the ports to use on the NIC
86 When nic_slot and nic_ports are both supplied, the function will just return
87 the PCI addresses for them. The logic used is:
88 (1) Run "dmidecode -t slot"
89 (2) Grep for "SlotID:" with given nic_slot, and derive the bus address;
90 (3) Based on given nic_ports, generate the pci addresses based on above
93 When either nic_slot or nic_ports is not supplied, the function will
94 traverse all Intel NICs which use i40e or ixgbe driver, sorted by PCI
95 address, and return first two available ports which are not bonded
99 if nic_slot and nic_ports:
100 dmidecode = subprocess.check_output(['dmidecode', '-t', 'slot'])
101 regex = r"(?<=SlotID:{}).*?(....:..:..\..)".format(nic_slot)
102 match = re.search(regex, dmidecode.decode('utf-8'), flags=re.DOTALL)
107 # On some servers, the "Bus Address" returned by dmidecode is not the
108 # base pci address of the NIC. So only keeping the bus part of the
109 # address for better compability.
110 bus = match.group(1)[:match.group(1).rindex(':') + 1] + "00."
111 for port in nic_ports:
112 pcis.append(bus + str(port))
117 regex = r'({hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}})).*(drv={driver}|.*unused=.*{driver})'
120 trex_base_dir = '/opt/trex'
121 contents = os.listdir(trex_base_dir)
122 trex_dir = os.path.join(trex_base_dir, contents[0])
123 process = subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
125 stdout=subprocess.PIPE,
126 stderr=subprocess.PIPE)
127 devices, _ = process.communicate()
131 for driver in ['i40e', 'ixgbe']:
132 matches = re.findall(regex.format(hx=hx, driver=driver), devices.decode("utf-8"))
137 device_list = list(x[0].split('.')[0] for x in matches)
138 device_ports_list = {i: {'ports': device_list.count(i)} for i in device_list}
140 intf_name = glob.glob("/sys/bus/pci/devices/%s/net/*" % port[0])
142 intf_name = intf_name[0][intf_name[0].rfind('/') + 1:]
143 process = subprocess.Popen(['ip', '-o', '-d', 'link', 'show', intf_name],
144 stdout=subprocess.PIPE,
145 stderr=subprocess.PIPE)
146 intf_info, _ = process.communicate()
147 if re.search('team_slave|bond_slave', intf_info.decode("utf-8")):
148 device_ports_list[port[0].split('.')[0]]['busy'] = True
150 if not device_ports_list[port[0].split('.')[0]].get('busy'):
159 def parse_flow_count(flow_count):
160 flow_count = str(flow_count)
161 input_fc = flow_count
163 if flow_count[-1].upper() in multiplier_map:
164 multiplier = multiplier_map[flow_count[-1].upper()]
165 flow_count = flow_count[:-1]
168 flow_count = int(flow_count)
170 raise Exception("Unknown flow count format '{}'".format(input_fc)) from ValueError
172 return flow_count * multiplier
175 def cast_integer(value):
176 # force 0 value if NaN value from TRex to avoid error in JSON result parsing
177 return int(value) if not isnan(value) else 0
180 class RunLock(object):
182 Attempts to lock file and run current instance of NFVbench as the first,
183 otherwise raises exception.
186 def __init__(self, path='/tmp/nfvbench.lock'):
192 self._fd = os.open(self._path, os.O_CREAT)
193 fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
194 except (OSError, IOError) as e:
195 raise Exception('Other NFVbench process is running. Please wait') from e
197 def __exit__(self, *args):
198 fcntl.flock(self._fd, fcntl.LOCK_UN)
202 # Try to remove the lock file, but don't try too hard because it is unnecessary.
204 os.unlink(self._path)
205 except (OSError, IOError):
210 for i in range(1, int(n / 2) + 1):
218 Calculate the maximum possible value for both IP and ports,
219 eventually for maximum possible flow.
221 if a != 0 and b != 0:
222 lcm_value = a * b // gcd(a, b)
224 raise TypeError(" IP size or port range can't be zero !")
227 def find_tuples_equal_to_lcm_value(a, b, lcm_value):
229 Find numbers from two list matching a LCM value.
233 if lcm(x, y) == lcm_value:
237 def find_max_size(max_size, tuples, flow):
239 if max_size > tuples[-1][0]:
240 max_size = tuples[-1][0]
242 if max_size > tuples[-1][1]:
243 max_size = tuples[-1][1]
246 for i in range(max_size, 1, -1):