blob: f6ba3d747469ce0e987d15777fb0056aba91a07e [file] [log] [blame]
John DeNisco68b0ee32017-09-27 16:35:23 -04001# 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."""
Paul Vinciguerra339bc6b2018-12-19 02:05:25 -080015from __future__ import absolute_import, division
John DeNisco68b0ee32017-09-27 16:35:23 -040016import re
17
18from vpplib.VPPUtil import VPPUtil
19
20__all__ = ["CpuUtils"]
21
22
23class 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
Paul Vinciguerra339bc6b2018-12-19 02:05:25 -080046 enabled, the L1d,L1i,L2,L3 setting is the same for two processors.
47 These two processors are two threads of one core.
John DeNisco68b0ee32017-09-27 16:35:23 -040048
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]
Paul Vinciguerra339bc6b2018-12-19 02:05:25 -080056 cpu_mems_len = len(cpu_mems) // CpuUtils.NR_OF_THREADS
John DeNisco68b0ee32017-09-27 16:35:23 -040057 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(
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020081 "Failed to execute ssh command, ret: {} err: {}".format(ret, stderr)
82 )
83 node["cpuinfo"] = list()
John DeNisco68b0ee32017-09-27 16:35:23 -040084 for line in stdout.split("\n"):
Klement Sekerad9b0c6f2022-04-26 19:02:15 +020085 if line != "" and line[0] != "#":
86 node["cpuinfo"].append(
87 [CpuUtils.__str2int(x) for x in line.split(",")]
88 )
John DeNisco68b0ee32017-09-27 16:35:23 -040089
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)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200141 cpu_list = cpu_list[: cpu_list_len // CpuUtils.NR_OF_THREADS]
John DeNisco68b0ee32017-09-27 16:35:23 -0400142
143 return cpu_list
144
145 @staticmethod
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200146 def cpu_slice_of_list_per_node(
147 node, cpu_node, skip_cnt=0, cpu_cnt=0, smt_used=False
148 ):
John DeNisco68b0ee32017-09-27 16:35:23 -0400149 """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:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200176 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]]
John DeNisco68b0ee32017-09-27 16:35:23 -0400180 cpu_list.extend(cpu_list_ex)
181 else:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200182 cpu_list = [cpu for cpu in cpu_list[skip_cnt : skip_cnt + cpu_cnt]]
John DeNisco68b0ee32017-09-27 16:35:23 -0400183
184 return cpu_list
185
186 @staticmethod
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200187 def cpu_list_per_node_str(
188 node, cpu_node, skip_cnt=0, cpu_cnt=0, sep=",", smt_used=False
189 ):
John DeNisco68b0ee32017-09-27 16:35:23 -0400190 """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
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200208 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 )
John DeNisco68b0ee32017-09-27 16:35:23 -0400211 return sep.join(str(cpu) for cpu in cpu_list)
212
213 @staticmethod
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200214 def cpu_range_per_node_str(
215 node, cpu_node, skip_cnt=0, cpu_cnt=0, sep="-", smt_used=False
216 ):
John DeNisco68b0ee32017-09-27 16:35:23 -0400217 """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
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200235 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 )
John DeNisco68b0ee32017-09-27 16:35:23 -0400238 if smt_used:
239 cpu_list_len = len(cpu_list)
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200240 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 )
John DeNisco68b0ee32017-09-27 16:35:23 -0400245 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:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200263 raise RuntimeError(
264 "lscpu command failed on node {} {}.".format(node["host"], stderr)
265 )
John DeNisco68b0ee32017-09-27 16:35:23 -0400266
267 cpuinfo = {}
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200268 lines = stdout.split("\n")
John DeNisco68b0ee32017-09-27 16:35:23 -0400269 for line in lines:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200270 if line != "":
271 linesplit = re.split(r":\s+", line)
John DeNisco68b0ee32017-09-27 16:35:23 -0400272 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:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200277 raise RuntimeError(
278 "cat command failed on node {} {}.".format(node["host"], stderr)
279 )
John DeNisco68b0ee32017-09-27 16:35:23 -0400280
281 vpp_processes = {}
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200282 vpp_lines = re.findall(r"\w+\(vpp_\w+\)\w+", stdout)
John DeNisco68b0ee32017-09-27 16:35:23 -0400283 for line in vpp_lines:
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200284 linesplit = re.split(r"\w+\(", line)[1].split(")")
John DeNisco68b0ee32017-09-27 16:35:23 -0400285 vpp_processes[linesplit[0]] = linesplit[1]
286
Klement Sekerad9b0c6f2022-04-26 19:02:15 +0200287 cpuinfo["vpp_processes"] = vpp_processes
John DeNisco68b0ee32017-09-27 16:35:23 -0400288
289 return cpuinfo