tests: replace pycodestyle with black
[vpp.git] / extras / vpp_config / vpplib / CpuUtils.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """CPU utilities library."""
15 from __future__ import absolute_import, division
16 import re
17
18 from vpplib.VPPUtil import VPPUtil
19
20 __all__ = ["CpuUtils"]
21
22
23 class CpuUtils(object):
24     """CPU utilities"""
25
26     # Number of threads per core.
27     NR_OF_THREADS = 2
28
29     @staticmethod
30     def __str2int(string):
31         """Conversion from string to integer, 0 in case of empty string.
32
33         :param string: Input string.
34         :type string: str
35         :returns: Integer converted from string, 0 in case of ValueError.
36         :rtype: int
37         """
38         try:
39             return int(string)
40         except ValueError:
41             return 0
42
43     @staticmethod
44     def is_smt_enabled(cpu_info):
45         """Uses CPU mapping to find out if SMT is enabled or not. If SMT is
46         enabled, the L1d,L1i,L2,L3 setting is the same for two processors.
47         These two processors are two threads of one core.
48
49         :param cpu_info: CPU info, the output of "lscpu -p".
50         :type cpu_info: list
51         :returns: True if SMT is enabled, False if SMT is disabled.
52         :rtype: bool
53         """
54
55         cpu_mems = [item[-4:] for item in cpu_info]
56         cpu_mems_len = len(cpu_mems) // CpuUtils.NR_OF_THREADS
57         count = 0
58         for cpu_mem in cpu_mems[:cpu_mems_len]:
59             if cpu_mem in cpu_mems[cpu_mems_len:]:
60                 count += 1
61         return bool(count == cpu_mems_len)
62
63     @staticmethod
64     def get_cpu_layout_from_all_nodes(nodes):
65         """Retrieve cpu layout from all nodes, assuming all nodes
66            are Linux nodes.
67
68         :param nodes: DICT__nodes from Topology.DICT__nodes.
69         :type nodes: dict
70         :raises RuntimeError: If the ssh command "lscpu -p" fails.
71         """
72         for node in nodes.values():
73             cmd = "lscpu -p"
74             ret, stdout, stderr = VPPUtil.exec_command(cmd)
75             #           parsing of "lscpu -p" output:
76             #           # CPU,Core,Socket,Node,,L1d,L1i,L2,L3
77             #           0,0,0,0,,0,0,0,0
78             #           1,1,0,0,,1,1,1,0
79             if ret != 0:
80                 raise RuntimeError(
81                     "Failed to execute ssh command, ret: {} err: {}".format(ret, stderr)
82                 )
83             node["cpuinfo"] = list()
84             for line in stdout.split("\n"):
85                 if line != "" and line[0] != "#":
86                     node["cpuinfo"].append(
87                         [CpuUtils.__str2int(x) for x in line.split(",")]
88                     )
89
90     @staticmethod
91     def cpu_node_count(node):
92         """Return count of numa nodes.
93
94         :param node: Targeted node.
95         :type node: dict
96         :returns: Count of numa nodes.
97         :rtype: int
98         :raises RuntimeError: If node cpuinfo is not available.
99         """
100         cpu_info = node.get("cpuinfo")
101         if cpu_info is not None:
102             return node["cpuinfo"][-1][3] + 1
103         else:
104             raise RuntimeError("Node cpuinfo not available.")
105
106     @staticmethod
107     def cpu_list_per_node(node, cpu_node, smt_used=False):
108         """Return node related list of CPU numbers.
109
110         :param node: Node dictionary with cpuinfo.
111         :param cpu_node: Numa node number.
112         :param smt_used: True - we want to use SMT, otherwise false.
113         :type node: dict
114         :type cpu_node: int
115         :type smt_used: bool
116         :returns: List of cpu numbers related to numa from argument.
117         :rtype: list of int
118         :raises RuntimeError: If node cpuinfo is not available or if SMT is not
119         enabled.
120         """
121
122         cpu_node = int(cpu_node)
123         cpu_info = node.get("cpuinfo")
124         if cpu_info is None:
125             raise RuntimeError("Node cpuinfo not available.")
126
127         smt_enabled = CpuUtils.is_smt_enabled(cpu_info)
128         if not smt_enabled and smt_used:
129             raise RuntimeError("SMT is not enabled.")
130
131         cpu_list = []
132         for cpu in cpu_info:
133             if cpu[3] == cpu_node:
134                 cpu_list.append(cpu[0])
135
136         if not smt_enabled or smt_enabled and smt_used:
137             pass
138
139         if smt_enabled and not smt_used:
140             cpu_list_len = len(cpu_list)
141             cpu_list = cpu_list[: cpu_list_len // CpuUtils.NR_OF_THREADS]
142
143         return cpu_list
144
145     @staticmethod
146     def cpu_slice_of_list_per_node(
147         node, cpu_node, skip_cnt=0, cpu_cnt=0, smt_used=False
148     ):
149         """Return string of node related list of CPU numbers.
150
151         :param node: Node dictionary with cpuinfo.
152         :param cpu_node: Numa node number.
153         :param skip_cnt: Skip first "skip_cnt" CPUs.
154         :param cpu_cnt: Count of cpus to return, if 0 then return all.
155         :param smt_used: True - we want to use SMT, otherwise false.
156         :type node: dict
157         :type cpu_node: int
158         :type skip_cnt: int
159         :type cpu_cnt: int
160         :type smt_used: bool
161         :returns: Cpu numbers related to numa from argument.
162         :rtype: list
163         :raises RuntimeError: If we require more cpus than available.
164         """
165
166         cpu_list = CpuUtils.cpu_list_per_node(node, cpu_node, smt_used)
167
168         cpu_list_len = len(cpu_list)
169         if cpu_cnt + skip_cnt > cpu_list_len:
170             raise RuntimeError("cpu_cnt + skip_cnt > length(cpu list).")
171
172         if cpu_cnt == 0:
173             cpu_cnt = cpu_list_len - skip_cnt
174
175         if smt_used:
176             cpu_list_0 = cpu_list[: cpu_list_len // CpuUtils.NR_OF_THREADS]
177             cpu_list_1 = cpu_list[cpu_list_len // CpuUtils.NR_OF_THREADS :]
178             cpu_list = [cpu for cpu in cpu_list_0[skip_cnt : skip_cnt + cpu_cnt]]
179             cpu_list_ex = [cpu for cpu in cpu_list_1[skip_cnt : skip_cnt + cpu_cnt]]
180             cpu_list.extend(cpu_list_ex)
181         else:
182             cpu_list = [cpu for cpu in cpu_list[skip_cnt : skip_cnt + cpu_cnt]]
183
184         return cpu_list
185
186     @staticmethod
187     def cpu_list_per_node_str(
188         node, cpu_node, skip_cnt=0, cpu_cnt=0, sep=",", smt_used=False
189     ):
190         """Return string of node related list of CPU numbers.
191
192         :param node: Node dictionary with cpuinfo.
193         :param cpu_node: Numa node number.
194         :param skip_cnt: Skip first "skip_cnt" CPUs.
195         :param cpu_cnt: Count of cpus to return, if 0 then return all.
196         :param sep: Separator, default: 1,2,3,4,....
197         :param smt_used: True - we want to use SMT, otherwise false.
198         :type node: dict
199         :type cpu_node: int
200         :type skip_cnt: int
201         :type cpu_cnt: int
202         :type sep: str
203         :type smt_used: bool
204         :returns: Cpu numbers related to numa from argument.
205         :rtype: str
206         """
207
208         cpu_list = CpuUtils.cpu_slice_of_list_per_node(
209             node, cpu_node, skip_cnt=skip_cnt, cpu_cnt=cpu_cnt, smt_used=smt_used
210         )
211         return sep.join(str(cpu) for cpu in cpu_list)
212
213     @staticmethod
214     def cpu_range_per_node_str(
215         node, cpu_node, skip_cnt=0, cpu_cnt=0, sep="-", smt_used=False
216     ):
217         """Return string of node related range of CPU numbers, e.g. 0-4.
218
219         :param node: Node dictionary with cpuinfo.
220         :param cpu_node: Numa node number.
221         :param skip_cnt: Skip first "skip_cnt" CPUs.
222         :param cpu_cnt: Count of cpus to return, if 0 then return all.
223         :param sep: Separator, default: "-".
224         :param smt_used: True - we want to use SMT, otherwise false.
225         :type node: dict
226         :type cpu_node: int
227         :type skip_cnt: int
228         :type cpu_cnt: int
229         :type sep: str
230         :type smt_used: bool
231         :returns: String of node related range of CPU numbers.
232         :rtype: str
233         """
234
235         cpu_list = CpuUtils.cpu_slice_of_list_per_node(
236             node, cpu_node, skip_cnt=skip_cnt, cpu_cnt=cpu_cnt, smt_used=smt_used
237         )
238         if smt_used:
239             cpu_list_len = len(cpu_list)
240             cpu_list_0 = cpu_list[: cpu_list_len // CpuUtils.NR_OF_THREADS]
241             cpu_list_1 = cpu_list[cpu_list_len // CpuUtils.NR_OF_THREADS :]
242             cpu_range = "{}{}{},{}{}{}".format(
243                 cpu_list_0[0], sep, cpu_list_0[-1], cpu_list_1[0], sep, cpu_list_1[-1]
244             )
245         else:
246             cpu_range = "{}{}{}".format(cpu_list[0], sep, cpu_list[-1])
247
248         return cpu_range
249
250     @staticmethod
251     def get_cpu_info_per_node(node):
252         """Return node related list of CPU numbers.
253
254         :param node: Node dictionary with cpuinfo.
255         :type node: dict
256         :returns: Important CPU information.
257         :rtype: dict
258         """
259
260         cmd = "lscpu"
261         ret, stdout, stderr = VPPUtil.exec_command(cmd)
262         if ret != 0:
263             raise RuntimeError(
264                 "lscpu command failed on node {} {}.".format(node["host"], stderr)
265             )
266
267         cpuinfo = {}
268         lines = stdout.split("\n")
269         for line in lines:
270             if line != "":
271                 linesplit = re.split(r":\s+", line)
272                 cpuinfo[linesplit[0]] = linesplit[1]
273
274         cmd = "cat /proc/*/task/*/stat | awk '{print $1" "$2" "$39}'"
275         ret, stdout, stderr = VPPUtil.exec_command(cmd)
276         if ret != 0:
277             raise RuntimeError(
278                 "cat command failed on node {} {}.".format(node["host"], stderr)
279             )
280
281         vpp_processes = {}
282         vpp_lines = re.findall(r"\w+\(vpp_\w+\)\w+", stdout)
283         for line in vpp_lines:
284             linesplit = re.split(r"\w+\(", line)[1].split(")")
285             vpp_processes[linesplit[0]] = linesplit[1]
286
287         cpuinfo["vpp_processes"] = vpp_processes
288
289         return cpuinfo