opnfvresultdb: Update data reported to result DB
[vswitchperf.git] / tools / systeminfo.py
1 # Copyright 2015-2017 Intel Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain 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,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """Tools for access to OS details
16 """
17
18 import os
19 import platform
20 import subprocess
21 import locale
22 import re
23 import distro
24
25 from conf import settings as S
26 from tools.version import Version
27
28 def match_line(file_name, pattern):
29     """ loops through given file and returns first line matching given pattern
30
31     :returns: string with the matching line without end of line or None
32     """
33     try:
34         with open(file_name, encoding="latin-1") as file_:
35             for line in file_:
36                 if not line.strip():
37                     continue
38                 if not line.strip().startswith(pattern):
39                     continue
40
41                 return line.strip().rstrip('\n')
42         return None
43     except OSError:
44         return None
45
46 def get_os():
47     """Get distro name.
48
49     :returns: Return distro name as a string
50     """
51     return ' '.join(distro.linux_distribution())
52
53 def get_kernel():
54     """Get kernel version.
55
56     :returns: Return kernel version as a string
57     """
58     return platform.release()
59
60 def get_cpu():
61     """Get CPU information.
62
63     :returns: Return CPU information as a string
64     """
65     cpu = match_line('/proc/cpuinfo', 'model name')
66     return cpu.split(':')[1] if cpu else cpu
67
68 def get_nic():
69     """Get NIC(s) information.
70
71     :returns: Return NIC(s) information as a list
72     """
73     nics = []
74     output = subprocess.check_output('lspci', shell=True)
75     output = output.decode(locale.getdefaultlocale()[1])
76     for line in output.split('\n'):
77         for nic in S.getValue('NICS'):
78             # lspci shows PCI addresses without domain part, i.e. last 7 chars
79             if line.startswith(nic['pci'][-7:]):
80                 nics.append(''.join(line.split(':')[2:]).strip())
81     return nics
82
83 def get_platform():
84     """Get platform information.
85
86     Currently this is the motherboard vendor, name and socket
87     count.
88
89     :returns: Return platform information as a string
90     """
91     output = []
92
93     with open('/sys/class/dmi/id/board_vendor', 'r') as file_:
94         output.append(file_.readline().rstrip())
95
96     with open('/sys/class/dmi/id/board_name', 'r') as file_:
97         output.append(file_.readline().rstrip())
98
99     num_nodes = len([name for name in os.listdir(
100         '/sys/devices/system/node/') if name.startswith('node')])
101     output.append(''.join(['[', str(num_nodes), ' sockets]']))
102
103     return ' '.join(output).strip()
104
105 def get_cpu_cores():
106     """Get number of CPU cores.
107
108     :returns: Return number of CPU cores
109     """
110     cores = 0
111     with open('/proc/cpuinfo') as file_:
112         for line in file_:
113             if line.rstrip('\n').startswith('processor'):
114                 cores += 1
115             continue
116
117     # this code must be executed by at leat one core...
118     if cores < 1:
119         cores = 1
120     return cores
121
122 def get_memory():
123     """Get memory information.
124
125     :returns: amount of system memory as string together with unit
126     """
127     memory = match_line('/proc/meminfo', 'MemTotal')
128     return memory.split(':')[1].strip() if memory else memory
129
130 def get_memory_bytes():
131     """Get memory information in bytes
132
133     :returns: amount of system memory
134     """
135     mem_list = get_memory().split(' ')
136     mem = float(mem_list[0].strip())
137     if mem_list.__len__() > 1:
138         unit = mem_list[1].strip().lower()
139         if unit == 'kb':
140             mem *= 1024
141         elif unit == 'mb':
142             mem *= 1024 ** 2
143         elif unit == 'gb':
144             mem *= 1024 ** 3
145         elif unit == 'tb':
146             mem *= 1024 ** 4
147
148     return int(mem)
149
150 def get_pids(proc_names_list):
151     """ Get pid(s) of process(es) with given name(s)
152
153     :returns: list with pid(s) of given processes or None if processes
154         with given names are not running
155     """
156
157     try:
158         pids = subprocess.check_output(['sudo', 'LC_ALL=' + S.getValue('DEFAULT_CMD_LOCALE'), 'pidof']
159                                        + proc_names_list)
160     except subprocess.CalledProcessError:
161         # such process isn't running
162         return None
163
164     return list(map(str, map(int, pids.split())))
165
166 def get_pid(proc_name_str):
167     """ Get pid(s) of process with given name
168
169     :returns: list with pid(s) of given process or None if process
170         with given name is not running
171     """
172     return get_pids([proc_name_str])
173
174 def pid_isalive(pid):
175     """ Checks if given PID is alive
176
177     :param pid: PID of the process
178     :returns: True if given process is running, False otherwise
179     """
180     return os.path.isdir('/proc/' + str(pid))
181
182 def get_bin_version(binary, regex):
183     """ get version of given binary selected by given regex
184
185     :returns: version string or None
186     """
187     try:
188         output = str(subprocess.check_output(
189             binary, stderr=subprocess.STDOUT, shell=True).decode().rstrip('\n'))
190     except subprocess.CalledProcessError:
191         return None
192
193     versions = re.findall(regex, output)
194     if len(versions):
195         return versions[0]
196     else:
197         return None
198
199 def get_git_tag(path):
200     """ get tag of recent commit from repository located at 'path'
201
202     :returns: git tag in form of string with commit hash or None if there
203         isn't any git repository at given path
204     """
205     try:
206         if os.path.isdir(path):
207             return subprocess.check_output('cd {}; git rev-parse HEAD'.format(path), shell=True,
208                                            stderr=subprocess.DEVNULL).decode().rstrip('\n')
209         elif os.path.isfile(path):
210             return subprocess.check_output('cd $(dirname {}); git log -1 --pretty="%H" {}'.format(path, path),
211                                            shell=True, stderr=subprocess.DEVNULL).decode().rstrip('\n')
212         else:
213             return None
214     except subprocess.CalledProcessError:
215         return None
216
217 # This function uses long switch per purpose, so let us suppress pylint warning too-many-branches
218 # pylint: disable=too-many-branches, too-many-statements
219 def get_version(app_name):
220     """ Get version of given application and its git tag
221
222     :returns: dictionary {'name' : app_name, 'version' : app_version, 'git_tag' : app_git_tag) in case that
223         version or git tag are not known or not applicaple, than None is returned for any unknown value
224
225     """
226     app_version_file = {
227         'ovs' : r'Open vSwitch\) ([0-9.]+)',
228         'testpmd' : r'RTE Version: \'\S+ ([0-9.]+)',
229         'qemu' : r'QEMU emulator version ([0-9.]+)',
230         'loopback_l2fwd' : os.path.join(S.getValue('ROOT_DIR'), 'src/l2fwd/l2fwd.c'),
231         'ixnet' : os.path.join(S.getValue('TRAFFICGEN_IXNET_LIB_PATH'), 'pkgIndex.tcl'),
232         'ixia' : os.path.join(S.getValue('TRAFFICGEN_IXIA_ROOT_DIR'), 'lib/ixTcl1.0/ixTclHal.tcl'),
233     }
234
235
236
237     app_version = None
238     app_git_tag = None
239
240     if app_name.lower().startswith('ovs'):
241         app_version = get_bin_version('{} --version'.format(S.getValue('TOOLS')['ovs-vswitchd']),
242                                       app_version_file['ovs'])
243         if 'vswitch_src' in S.getValue('TOOLS'):
244             app_git_tag = get_git_tag(S.getValue('TOOLS')['vswitch_src'])
245     elif app_name.lower() in ['dpdk', 'testpmd']:
246         app_version = get_bin_version('{} -v -h'.format(S.getValue('TOOLS')['testpmd']),
247                                       app_version_file['testpmd'])
248         # we have to consult PATHS settings to be sure, that dpdk/testpmd
249         # were build from the sources
250         if S.getValue('PATHS')[app_name.lower()]['type'] == 'src':
251             app_git_tag = get_git_tag(S.getValue('TOOLS')['dpdk_src'])
252     elif app_name.lower() == 'loopback_testpmd':
253         # testpmd inside the guest is compiled from downloaded sources
254         # stored at TOOS['dpdk_src'] directory
255         tmp_ver = ['', '', '']
256         dpdk_16 = False
257         # TOOLS dictionary is created during runtime and it is not
258         # available in some vsperf modes (e.g. -m trafficgen), thus
259         # following definition can't be part of app_version_file dict above
260         app_file = os.path.join(S.getValue('TOOLS')['dpdk_src'],
261                                 'lib/librte_eal/common/include/rte_version.h')
262         with open(app_file) as file_:
263             for line in file_:
264                 if not line.strip():
265                     continue
266                 # DPDK version < 16
267                 if line.startswith('#define RTE_VER_MAJOR'):
268                     tmp_ver[0] = line.rstrip('\n').split(' ')[2]
269                 # DPDK version < 16
270                 elif line.startswith('#define RTE_VER_PATCH_LEVEL'):
271                     tmp_ver[2] = line.rstrip('\n').split(' ')[2]
272                 # DPDK version < 16
273                 elif line.startswith('#define RTE_VER_PATCH_RELEASE'):
274                     release = line.rstrip('\n').split(' ')[2]
275                     if not '16' in release:
276                         tmp_ver[2] += line.rstrip('\n').split(' ')[2]
277                 # DPDK all versions
278                 elif line.startswith('#define RTE_VER_MINOR'):
279                     if dpdk_16:
280                         tmp_ver[2] = line.rstrip('\n').split(' ')[2]
281                     else:
282                         tmp_ver[1] = line.rstrip('\n').split(' ')[2]
283                 # DPDK all versions
284                 elif line.startswith('#define RTE_VER_SUFFIX'):
285                     tmp_ver[2] += line.rstrip('\n').split('"')[1]
286                 # DPDK version >= 16
287                 elif line.startswith('#define RTE_VER_YEAR'):
288                     dpdk_16 = True
289                     tmp_ver[0] = line.rstrip('\n').split(' ')[2]
290                 # DPDK version >= 16
291                 elif line.startswith('#define RTE_VER_MONTH'):
292                     tmp_ver[1] = '{:0>2}'.format(line.rstrip('\n').split(' ')[2])
293                 # DPDK version >= 16
294                 elif line.startswith('#define RTE_VER_RELEASE'):
295                     release = line.rstrip('\n').split(' ')[2]
296                     if not '16' in release:
297                         tmp_ver[2] += line.rstrip('\n').split(' ')[2]
298
299         if len(tmp_ver[0]):
300             app_version = '.'.join(tmp_ver)
301         app_git_tag = get_git_tag(S.getValue('TOOLS')['dpdk_src'])
302     elif app_name.lower().startswith('qemu'):
303         app_version = get_bin_version('{} --version'.format(S.getValue('TOOLS')['qemu-system']),
304                                       app_version_file['qemu'])
305         if 'qemu_src' in S.getValue('TOOLS'):
306             app_git_tag = get_git_tag(S.getValue('TOOLS')['qemu_src'])
307     elif app_name.lower() == 'ixnet':
308         app_version = match_line(app_version_file['ixnet'], 'package provide IxTclNetwork')
309         if app_version:
310             app_version = app_version.split(' ')[3]
311     elif app_name.lower() == 'ixia':
312         app_version = match_line(app_version_file['ixia'], 'package provide IxTclHal')
313         if app_version:
314             app_version = app_version.split(' ')[3]
315     elif app_name.lower() == 'xena':
316         try:
317             app_version = S.getValue('XENA_VERSION')
318         except AttributeError:
319             # setting was not available after execution
320             app_version = 'N/A'
321     elif app_name.lower() == 'dummy':
322         # get git tag of file with Dummy implementation
323         app_git_tag = get_git_tag(os.path.join(S.getValue('ROOT_DIR'), 'tools/pkt_gen/dummy/dummy.py'))
324     elif app_name.lower() == 'vswitchperf':
325         app_git_tag = get_git_tag(S.getValue('ROOT_DIR'))
326     elif app_name.lower() == 'l2fwd':
327         app_version = match_line(app_version_file['loopback_l2fwd'], 'MODULE_VERSION')
328         if app_version:
329             app_version = app_version.split('"')[1]
330         app_git_tag = get_git_tag(app_version_file['loopback_l2fwd'])
331     elif app_name.lower() in ['linux_bridge', 'buildin']:
332         # without login into running VM, it is not possible to check bridge_utils version
333         app_version = 'NA'
334         app_git_tag = 'NA'
335
336     return Version(app_name, app_version, app_git_tag)
337
338 def get_loopback_version(loopback_app_name):
339     """ Get version of given guest loopback application and its git tag
340
341     :returns: dictionary {'name' : app_name, 'version' : app_version, 'git_tag' : app_git_tag) in case that
342         version or git tag are not known or not applicaple, than None is returned for any unknown value
343     """
344     version = get_version("loopback_{}".format(loopback_app_name))
345     version.set_value('name', loopback_app_name)
346     return version