NFVBENCH-32 Check nan result values before integer casting
[nfvbench.git] / nfvbench / 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 import errno
16 import fcntl
17 from functools import wraps
18 import json
19 from log import LOG
20 from math import isnan
21 import os
22 import re
23 import signal
24 import subprocess
25
26
27 class TimeoutError(Exception):
28     pass
29
30
31 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
32     def decorator(func):
33         def _handle_timeout(signum, frame):
34             raise TimeoutError(error_message)
35
36         def wrapper(*args, **kwargs):
37             signal.signal(signal.SIGALRM, _handle_timeout)
38             signal.alarm(seconds)
39             try:
40                 result = func(*args, **kwargs)
41             finally:
42                 signal.alarm(0)
43             return result
44
45         return wraps(func)(wrapper)
46
47     return decorator
48
49
50 def save_json_result(result, json_file, std_json_path, service_chain, service_chain_count,
51                      flow_count, frame_sizes):
52     """Save results in json format file."""
53     filepaths = []
54     if json_file:
55         filepaths.append(json_file)
56     if std_json_path:
57         name_parts = [service_chain, str(service_chain_count), str(flow_count)] + list(frame_sizes)
58         filename = '-'.join(name_parts) + '.json'
59         filepaths.append(os.path.join(std_json_path, filename))
60
61     if filepaths:
62         for file_path in filepaths:
63             LOG.info('Saving results in json file: ' + file_path + "...")
64             with open(file_path, 'w') as jfp:
65                 json.dump(result,
66                           jfp,
67                           indent=4,
68                           sort_keys=True,
69                           separators=(',', ': '),
70                           default=lambda obj: obj.to_json())
71
72
73 def byteify(data, ignore_dicts=False):
74     # if this is a unicode string, return its string representation
75     if isinstance(data, unicode):
76         return data.encode('utf-8')
77     # if this is a list of values, return list of byteified values
78     if isinstance(data, list):
79         return [byteify(item, ignore_dicts=ignore_dicts) for item in data]
80     # if this is a dictionary, return dictionary of byteified keys and values
81     # but only if we haven't already byteified it
82     if isinstance(data, dict) and not ignore_dicts:
83         return {byteify(key, ignore_dicts=ignore_dicts): byteify(value, ignore_dicts=ignore_dicts)
84                 for key, value in data.iteritems()}
85     # if it's anything else, return it in its original form
86     return data
87
88
89 def dict_to_json_dict(record):
90     return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
91
92
93 def get_intel_pci(nic_ports):
94     """Returns the first two PCI addresses of sorted PCI list for Intel NIC (i40e, ixgbe)"""
95     hx = r'[0-9a-fA-F]'
96     regex = r'{hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}}).*(drv={driver}|.*unused=.*{driver})'
97
98     try:
99         trex_base_dir = '/opt/trex'
100         contents = os.listdir(trex_base_dir)
101         trex_dir = os.path.join(trex_base_dir, contents[0])
102         process = subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
103                                    cwd=trex_dir,
104                                    stdout=subprocess.PIPE,
105                                    stderr=subprocess.PIPE)
106         devices, _ = process.communicate()
107     except Exception:
108         devices = ''
109
110     for driver in ['i40e', 'ixgbe']:
111         matches = re.findall(regex.format(hx=hx, driver=driver), devices)
112         if matches:
113             pcis = map(lambda x: x[0], matches)
114             if len(pcis) < 2:
115                 continue
116             pcis.sort()
117             return [pcis[port_index] for port_index in nic_ports]
118
119     return []
120
121
122 multiplier_map = {
123     'K': 1000,
124     'M': 1000000,
125     'G': 1000000000
126 }
127
128
129 def parse_flow_count(flow_count):
130     flow_count = str(flow_count)
131     input_fc = flow_count
132     multiplier = 1
133     if flow_count[-1].upper() in multiplier_map:
134         multiplier = multiplier_map[flow_count[-1].upper()]
135         flow_count = flow_count[:-1]
136
137     try:
138         flow_count = int(flow_count)
139     except ValueError:
140         raise Exception("Unknown flow count format '{}'".format(input_fc))
141
142     return flow_count * multiplier
143
144
145 def cast_integer(value):
146     return int(value) if not isnan(value) else value
147
148
149 class RunLock(object):
150     """
151     Attempts to lock file and run current instance of NFVbench as the first,
152     otherwise raises exception.
153     """
154
155     def __init__(self, path='/tmp/nfvbench.lock'):
156         self._path = path
157         self._fd = None
158
159     def __enter__(self):
160         try:
161             self._fd = os.open(self._path, os.O_CREAT)
162             fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
163         except (OSError, IOError):
164             raise Exception('Other NFVbench process is running. Please wait')
165
166     def __exit__(self, *args):
167         fcntl.flock(self._fd, fcntl.LOCK_UN)
168         os.close(self._fd)
169         self._fd = None
170
171         # Try to remove the lock file, but don't try too hard because it is unnecessary.
172         try:
173             os.unlink(self._path)
174         except (OSError, IOError):
175             pass