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
16 from math import isnan
24 from functools import wraps
29 class TimeoutError(Exception):
33 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
35 def _handle_timeout(_signum, _frame):
36 raise TimeoutError(error_message)
38 def wrapper(*args, **kwargs):
39 signal.signal(signal.SIGALRM, _handle_timeout)
42 result = func(*args, **kwargs)
47 return wraps(func)(wrapper)
52 def save_json_result(result, json_file, std_json_path, service_chain, service_chain_count,
53 flow_count, frame_sizes):
54 """Save results in json format file."""
57 filepaths.append(json_file)
59 name_parts = [service_chain, str(service_chain_count), str(flow_count)] + list(frame_sizes)
60 filename = '-'.join(name_parts) + '.json'
61 filepaths.append(os.path.join(std_json_path, filename))
64 for file_path in filepaths:
65 LOG.info('Saving results in json file: %s...', file_path)
66 with open(file_path, 'w') as jfp:
71 separators=(',', ': '),
72 default=lambda obj: obj.to_json())
75 def byteify(data, ignore_dicts=False):
76 # if this is a unicode string, return its string representation
77 if isinstance(data, str):
78 return data.encode('utf-8')
79 # if this is a list of values, return list of byteified values
80 if isinstance(data, list):
81 return [byteify(item, ignore_dicts=ignore_dicts) for item in data]
82 # if this is a dictionary, return dictionary of byteified keys and values
83 # but only if we haven't already byteified it
84 if isinstance(data, dict) and not ignore_dicts:
85 return {byteify(key, ignore_dicts=ignore_dicts): byteify(value, ignore_dicts=ignore_dicts)
86 for key, value in list(data.items())}
87 # if it's anything else, return it in its original form
91 def dict_to_json_dict(record):
92 return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
95 def get_intel_pci(nic_slot=None, nic_ports=None):
96 """Returns two PCI address that will be used for NFVbench
98 @param nic_slot: The physical PCIe slot number in motherboard
99 @param nic_ports: Array of two integers indicating the ports to use on the NIC
101 When nic_slot and nic_ports are both supplied, the function will just return
102 the PCI addresses for them. The logic used is:
103 (1) Run "dmidecode -t slot"
104 (2) Grep for "SlotID:" with given nic_slot, and derive the bus address;
105 (3) Based on given nic_ports, generate the pci addresses based on above
108 When either nic_slot or nic_ports is not supplied, the function will
109 traverse all Intel NICs which use i40e or ixgbe driver, sorted by PCI
110 address, and return first two available ports which are not bonded
114 if nic_slot and nic_ports:
115 dmidecode = subprocess.check_output(['dmidecode', '-t', 'slot'])
116 regex = r"(?<=SlotID:{}).*?(....:..:..\..)".format(nic_slot)
117 match = re.search(regex, dmidecode.decode('utf-8'), flags=re.DOTALL)
122 # On some servers, the "Bus Address" returned by dmidecode is not the
123 # base pci address of the NIC. So only keeping the bus part of the
124 # address for better compability.
125 bus = match.group(1)[:match.group(1).rindex(':') + 1] + "00."
126 for port in nic_ports:
127 pcis.append(bus + str(port))
132 regex = r'({hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}})).*(drv={driver}|.*unused=.*{driver})'
135 trex_base_dir = '/opt/trex'
136 contents = os.listdir(trex_base_dir)
137 trex_dir = os.path.join(trex_base_dir, contents[0])
138 process = subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
140 stdout=subprocess.PIPE,
141 stderr=subprocess.PIPE)
142 devices, _ = process.communicate()
146 for driver in ['i40e', 'ixgbe']:
147 matches = re.findall(regex.format(hx=hx, driver=driver), devices.decode("utf-8"))
152 device_list = list(x[0].split('.')[0] for x in matches)
153 device_ports_list = {i: {'ports': device_list.count(i)} for i in device_list}
155 intf_name = glob.glob("/sys/bus/pci/devices/%s/net/*" % port[0])
157 intf_name = intf_name[0][intf_name[0].rfind('/') + 1:]
158 process = subprocess.Popen(['ip', '-o', '-d', 'link', 'show', intf_name],
159 stdout=subprocess.PIPE,
160 stderr=subprocess.PIPE)
161 intf_info, _ = process.communicate()
162 if re.search('team_slave|bond_slave', intf_info.decode("utf-8")):
163 device_ports_list[port[0].split('.')[0]]['busy'] = True
165 if not device_ports_list[port[0].split('.')[0]].get('busy'):
180 def parse_flow_count(flow_count):
181 flow_count = str(flow_count)
182 input_fc = flow_count
184 if flow_count[-1].upper() in multiplier_map:
185 multiplier = multiplier_map[flow_count[-1].upper()]
186 flow_count = flow_count[:-1]
189 flow_count = int(flow_count)
191 raise Exception("Unknown flow count format '{}'".format(input_fc))
193 return flow_count * multiplier
196 def cast_integer(value):
197 return int(value) if not isnan(value) else value
200 class RunLock(object):
202 Attempts to lock file and run current instance of NFVbench as the first,
203 otherwise raises exception.
206 def __init__(self, path='/tmp/nfvbench.lock'):
212 self._fd = os.open(self._path, os.O_CREAT)
213 fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
214 except (OSError, IOError):
215 raise Exception('Other NFVbench process is running. Please wait')
217 def __exit__(self, *args):
218 fcntl.flock(self._fd, fcntl.LOCK_UN)
222 # Try to remove the lock file, but don't try too hard because it is unnecessary.
224 os.unlink(self._path)
225 except (OSError, IOError):