collector: Support for collectd as one of the collectors.
[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         'trex' : os.path.join(S.getValue('ROOT_DIR'), 'src/trex/trex'),
234     }
235
236
237
238     app_version = None
239     app_git_tag = None
240
241     if app_name.lower().startswith('ovs'):
242         app_version = get_bin_version('{} --version'.format(S.getValue('TOOLS')['ovs-vswitchd']),
243                                       app_version_file['ovs'])
244         if 'vswitch_src' in S.getValue('TOOLS'):
245             app_git_tag = get_git_tag(S.getValue('TOOLS')['vswitch_src'])
246     elif app_name.lower() in ['dpdk', 'testpmd']:
247         app_version = get_bin_version('{} -v -h'.format(S.getValue('TOOLS')['testpmd']),
248                                       app_version_file['testpmd'])
249         # we have to consult PATHS settings to be sure, that dpdk/testpmd
250         # were build from the sources
251         if S.getValue('PATHS')[app_name.lower()]['type'] == 'src':
252             app_git_tag = get_git_tag(S.getValue('TOOLS')['dpdk_src'])
253     elif app_name.lower() == 'loopback_testpmd':
254         # testpmd inside the guest is compiled from downloaded sources
255         # stored at TOOS['dpdk_src'] directory
256         tmp_ver = ['', '', '']
257         dpdk_16 = False
258         # TOOLS dictionary is created during runtime and it is not
259         # available in some vsperf modes (e.g. -m trafficgen), thus
260         # following definition can't be part of app_version_file dict above
261         app_file = os.path.join(S.getValue('TOOLS')['dpdk_src'],
262                                 'lib/librte_eal/common/include/rte_version.h')
263         with open(app_file) as file_:
264             for line in file_:
265                 if not line.strip():
266                     continue
267                 # DPDK version < 16
268                 if line.startswith('#define RTE_VER_MAJOR'):
269                     tmp_ver[0] = line.rstrip('\n').split(' ')[2]
270                 # DPDK version < 16
271                 elif line.startswith('#define RTE_VER_PATCH_LEVEL'):
272                     tmp_ver[2] = line.rstrip('\n').split(' ')[2]
273                 # DPDK version < 16
274                 elif line.startswith('#define RTE_VER_PATCH_RELEASE'):
275                     release = line.rstrip('\n').split(' ')[2]
276                     if not '16' in release:
277                         tmp_ver[2] += line.rstrip('\n').split(' ')[2]
278                 # DPDK all versions
279                 elif line.startswith('#define RTE_VER_MINOR'):
280                     if dpdk_16:
281                         tmp_ver[2] = line.rstrip('\n').split(' ')[2]
282                     else:
283                         tmp_ver[1] = line.rstrip('\n').split(' ')[2]
284                 # DPDK all versions
285                 elif line.startswith('#define RTE_VER_SUFFIX'):
286                     tmp_ver[2] += line.rstrip('\n').split('"')[1]
287                 # DPDK version >= 16
288                 elif line.startswith('#define RTE_VER_YEAR'):
289                     dpdk_16 = True
290                     tmp_ver[0] = line.rstrip('\n').split(' ')[2]
291                 # DPDK version >= 16
292                 elif line.startswith('#define RTE_VER_MONTH'):
293                     tmp_ver[1] = '{:0>2}'.format(line.rstrip('\n').split(' ')[2])
294                 # DPDK version >= 16
295                 elif line.startswith('#define RTE_VER_RELEASE'):
296                     release = line.rstrip('\n').split(' ')[2]
297                     if not '16' in release:
298                         tmp_ver[2] += line.rstrip('\n').split(' ')[2]
299
300         if len(tmp_ver[0]):
301             app_version = '.'.join(tmp_ver)
302         app_git_tag = get_git_tag(S.getValue('TOOLS')['dpdk_src'])
303     elif app_name.lower().startswith('qemu'):
304         app_version = get_bin_version('{} --version'.format(S.getValue('TOOLS')['qemu-system']),
305                                       app_version_file['qemu'])
306         if 'qemu_src' in S.getValue('TOOLS'):
307             app_git_tag = get_git_tag(S.getValue('TOOLS')['qemu_src'])
308     elif app_name.lower() == 'ixnet':
309         app_version = match_line(app_version_file['ixnet'], 'package provide IxTclNetwork')
310         if app_version:
311             app_version = app_version.split(' ')[3]
312     elif app_name.lower() == 'ixia':
313         app_version = match_line(app_version_file['ixia'], 'package provide IxTclHal')
314         if app_version:
315             app_version = app_version.split(' ')[3]
316     elif app_name.lower() == 'trex':
317         app_version = match_line(os.path.join(app_version_file['trex'], 'VERSION'), 'v')
318         app_git_tag = get_git_tag(app_version_file['trex'])
319     elif app_name.lower() == 'xena':
320         try:
321             app_version = S.getValue('XENA_VERSION')
322         except AttributeError:
323             # setting was not available after execution
324             app_version = 'N/A'
325     elif app_name.lower() == 'dummy':
326         # get git tag of file with Dummy implementation
327         app_git_tag = get_git_tag(os.path.join(S.getValue('ROOT_DIR'), 'tools/pkt_gen/dummy/dummy.py'))
328     elif app_name.lower() == 'vswitchperf':
329         app_git_tag = get_git_tag(S.getValue('ROOT_DIR'))
330     elif app_name.lower() == 'l2fwd':
331         app_version = match_line(app_version_file['loopback_l2fwd'], 'MODULE_VERSION')
332         if app_version:
333             app_version = app_version.split('"')[1]
334         app_git_tag = get_git_tag(app_version_file['loopback_l2fwd'])
335     elif app_name.lower() in ['linux_bridge', 'buildin']:
336         # without login into running VM, it is not possible to check bridge_utils version
337         app_version = 'NA'
338         app_git_tag = 'NA'
339
340     return Version(app_name, app_version, app_git_tag)
341
342 def get_loopback_version(loopback_app_name):
343     """ Get version of given guest loopback application and its git tag
344
345     :returns: dictionary {'name' : app_name, 'version' : app_version, 'git_tag' : app_git_tag) in case that
346         version or git tag are not known or not applicaple, than None is returned for any unknown value
347     """
348     version = get_version("loopback_{}".format(loopback_app_name))
349     version.set_value('name', loopback_app_name)
350     return version