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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """CPU utilities library."""
15 from __future__ import absolute_import, division
18 from vpplib.VPPUtil import VPPUtil
20 __all__ = ["CpuUtils"]
23 class CpuUtils(object):
26 # Number of threads per core.
30 def __str2int(string):
31 """Conversion from string to integer, 0 in case of empty string.
33 :param string: Input string.
35 :returns: Integer converted from string, 0 in case of ValueError.
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.
49 :param cpu_info: CPU info, the output of "lscpu -p".
51 :returns: True if SMT is enabled, False if SMT is disabled.
55 cpu_mems = [item[-4:] for item in cpu_info]
56 cpu_mems_len = len(cpu_mems) // CpuUtils.NR_OF_THREADS
58 for cpu_mem in cpu_mems[:cpu_mems_len]:
59 if cpu_mem in cpu_mems[cpu_mems_len:]:
61 return bool(count == cpu_mems_len)
64 def get_cpu_layout_from_all_nodes(nodes):
65 """Retrieve cpu layout from all nodes, assuming all nodes
68 :param nodes: DICT__nodes from Topology.DICT__nodes.
70 :raises RuntimeError: If the ssh command "lscpu -p" fails.
72 for node in nodes.values():
74 ret, stdout, stderr = VPPUtil.exec_command(cmd)
75 # parsing of "lscpu -p" output:
76 # # CPU,Core,Socket,Node,,L1d,L1i,L2,L3
81 "Failed to execute ssh command, ret: {} err: {}".format(ret, stderr)
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(",")]
91 def cpu_node_count(node):
92 """Return count of numa nodes.
94 :param node: Targeted node.
96 :returns: Count of numa nodes.
98 :raises RuntimeError: If node cpuinfo is not available.
100 cpu_info = node.get("cpuinfo")
101 if cpu_info is not None:
102 return node["cpuinfo"][-1][3] + 1
104 raise RuntimeError("Node cpuinfo not available.")
107 def cpu_list_per_node(node, cpu_node, smt_used=False):
108 """Return node related list of CPU numbers.
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.
116 :returns: List of cpu numbers related to numa from argument.
118 :raises RuntimeError: If node cpuinfo is not available or if SMT is not
122 cpu_node = int(cpu_node)
123 cpu_info = node.get("cpuinfo")
125 raise RuntimeError("Node cpuinfo not available.")
127 smt_enabled = CpuUtils.is_smt_enabled(cpu_info)
128 if not smt_enabled and smt_used:
129 raise RuntimeError("SMT is not enabled.")
133 if cpu[3] == cpu_node:
134 cpu_list.append(cpu[0])
136 if not smt_enabled or smt_enabled and smt_used:
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]
146 def cpu_slice_of_list_per_node(
147 node, cpu_node, skip_cnt=0, cpu_cnt=0, smt_used=False
149 """Return string of node related list of CPU numbers.
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.
161 :returns: Cpu numbers related to numa from argument.
163 :raises RuntimeError: If we require more cpus than available.
166 cpu_list = CpuUtils.cpu_list_per_node(node, cpu_node, smt_used)
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).")
173 cpu_cnt = cpu_list_len - skip_cnt
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)
182 cpu_list = [cpu for cpu in cpu_list[skip_cnt : skip_cnt + cpu_cnt]]
187 def cpu_list_per_node_str(
188 node, cpu_node, skip_cnt=0, cpu_cnt=0, sep=",", smt_used=False
190 """Return string of node related list of CPU numbers.
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.
204 :returns: Cpu numbers related to numa from argument.
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
211 return sep.join(str(cpu) for cpu in cpu_list)
214 def cpu_range_per_node_str(
215 node, cpu_node, skip_cnt=0, cpu_cnt=0, sep="-", smt_used=False
217 """Return string of node related range of CPU numbers, e.g. 0-4.
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.
231 :returns: String of node related range of CPU numbers.
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
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]
246 cpu_range = "{}{}{}".format(cpu_list[0], sep, cpu_list[-1])
251 def get_cpu_info_per_node(node):
252 """Return node related list of CPU numbers.
254 :param node: Node dictionary with cpuinfo.
256 :returns: Important CPU information.
261 ret, stdout, stderr = VPPUtil.exec_command(cmd)
264 "lscpu command failed on node {} {}.".format(node["host"], stderr)
268 lines = stdout.split("\n")
271 linesplit = re.split(r":\s+", line)
272 cpuinfo[linesplit[0]] = linesplit[1]
274 cmd = "cat /proc/*/task/*/stat | awk '{print $1" "$2" "$39}'"
275 ret, stdout, stderr = VPPUtil.exec_command(cmd)
278 "cat command failed on node {} {}.".format(node["host"], stderr)
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]
287 cpuinfo["vpp_processes"] = vpp_processes