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
27 from nfvbench.traffic_gen.traffic_utils import multiplier_map
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 dict_to_json_dict(record):
76 return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
79 def get_intel_pci(nic_slot=None, nic_ports=None):
80 """Returns two PCI address that will be used for NFVbench
82 @param nic_slot: The physical PCIe slot number in motherboard
83 @param nic_ports: Array of two integers indicating the ports to use on the NIC
85 When nic_slot and nic_ports are both supplied, the function will just return
86 the PCI addresses for them. The logic used is:
87 (1) Run "dmidecode -t slot"
88 (2) Grep for "SlotID:" with given nic_slot, and derive the bus address;
89 (3) Based on given nic_ports, generate the pci addresses based on above
92 When either nic_slot or nic_ports is not supplied, the function will
93 traverse all Intel NICs which use i40e or ixgbe driver, sorted by PCI
94 address, and return first two available ports which are not bonded
98 if nic_slot and nic_ports:
99 dmidecode = subprocess.check_output(['dmidecode', '-t', 'slot'])
100 regex = r"(?<=SlotID:{}).*?(....:..:..\..)".format(nic_slot)
101 match = re.search(regex, dmidecode.decode('utf-8'), flags=re.DOTALL)
106 # On some servers, the "Bus Address" returned by dmidecode is not the
107 # base pci address of the NIC. So only keeping the bus part of the
108 # address for better compability.
109 bus = match.group(1)[:match.group(1).rindex(':') + 1] + "00."
110 for port in nic_ports:
111 pcis.append(bus + str(port))
116 regex = r'({hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}})).*(drv={driver}|.*unused=.*{driver})'
119 trex_base_dir = '/opt/trex'
120 contents = os.listdir(trex_base_dir)
121 trex_dir = os.path.join(trex_base_dir, contents[0])
122 process = subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
124 stdout=subprocess.PIPE,
125 stderr=subprocess.PIPE)
126 devices, _ = process.communicate()
130 for driver in ['i40e', 'ixgbe']:
131 matches = re.findall(regex.format(hx=hx, driver=driver), devices.decode("utf-8"))
136 device_list = list(x[0].split('.')[0] for x in matches)
137 device_ports_list = {i: {'ports': device_list.count(i)} for i in device_list}
139 intf_name = glob.glob("/sys/bus/pci/devices/%s/net/*" % port[0])
141 intf_name = intf_name[0][intf_name[0].rfind('/') + 1:]
142 process = subprocess.Popen(['ip', '-o', '-d', 'link', 'show', intf_name],
143 stdout=subprocess.PIPE,
144 stderr=subprocess.PIPE)
145 intf_info, _ = process.communicate()
146 if re.search('team_slave|bond_slave', intf_info.decode("utf-8")):
147 device_ports_list[port[0].split('.')[0]]['busy'] = True
149 if not device_ports_list[port[0].split('.')[0]].get('busy'):
158 def parse_flow_count(flow_count):
159 flow_count = str(flow_count)
160 input_fc = flow_count
162 if flow_count[-1].upper() in multiplier_map:
163 multiplier = multiplier_map[flow_count[-1].upper()]
164 flow_count = flow_count[:-1]
167 flow_count = int(flow_count)
169 raise Exception("Unknown flow count format '{}'".format(input_fc))
171 return flow_count * multiplier
174 def cast_integer(value):
175 # force 0 value if NaN value from TRex to avoid error in JSON result parsing
176 return int(value) if not isnan(value) else 0
179 class RunLock(object):
181 Attempts to lock file and run current instance of NFVbench as the first,
182 otherwise raises exception.
185 def __init__(self, path='/tmp/nfvbench.lock'):
191 self._fd = os.open(self._path, os.O_CREAT)
192 fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
193 except (OSError, IOError):
194 raise Exception('Other NFVbench process is running. Please wait')
196 def __exit__(self, *args):
197 fcntl.flock(self._fd, fcntl.LOCK_UN)
201 # Try to remove the lock file, but don't try too hard because it is unnecessary.
203 os.unlink(self._path)
204 except (OSError, IOError):