blob: 23f418d33bec6a33f933511ac8c18e299261fc87 [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(
81 "Failed to execute ssh command, ret: {} err: {}".format(
82 ret, stderr))
83 node['cpuinfo'] = list()
84 for line in stdout.split("\n"):
85 if line != '' and line[0] != "#":
86 node['cpuinfo'].append([CpuUtils.__str2int(x) for x in
87 line.split(",")])
88
89 @staticmethod
90 def cpu_node_count(node):
91 """Return count of numa nodes.
92
93 :param node: Targeted node.
94 :type node: dict
95 :returns: Count of numa nodes.
96 :rtype: int
97 :raises RuntimeError: If node cpuinfo is not available.
98 """
99 cpu_info = node.get("cpuinfo")
100 if cpu_info is not None:
101 return node["cpuinfo"][-1][3] + 1
102 else:
103 raise RuntimeError("Node cpuinfo not available.")
104
105 @staticmethod
106 def cpu_list_per_node(node, cpu_node, smt_used=False):
107 """Return node related list of CPU numbers.
108
109 :param node: Node dictionary with cpuinfo.
110 :param cpu_node: Numa node number.
111 :param smt_used: True - we want to use SMT, otherwise false.
112 :type node: dict
113 :type cpu_node: int
114 :type smt_used: bool
115 :returns: List of cpu numbers related to numa from argument.
116 :rtype: list of int
117 :raises RuntimeError: If node cpuinfo is not available or if SMT is not
118 enabled.
119 """
120
121 cpu_node = int(cpu_node)
122 cpu_info = node.get("cpuinfo")
123 if cpu_info is None:
124 raise RuntimeError("Node cpuinfo not available.")
125
126 smt_enabled = CpuUtils.is_smt_enabled(cpu_info)
127 if not smt_enabled and smt_used:
128 raise RuntimeError("SMT is not enabled.")
129
130 cpu_list = []
131 for cpu in cpu_info:
132 if cpu[3] == cpu_node:
133 cpu_list.append(cpu[0])
134
135 if not smt_enabled or smt_enabled and smt_used:
136 pass
137
138 if smt_enabled and not smt_used:
139 cpu_list_len = len(cpu_list)
Paul Vinciguerra339bc6b2018-12-19 02:05:25 -0800140 cpu_list = cpu_list[:cpu_list_len // CpuUtils.NR_OF_THREADS]
John DeNisco68b0ee32017-09-27 16:35:23 -0400141
142 return cpu_list
143
144 @staticmethod
145 def cpu_slice_of_list_per_node(node, cpu_node, skip_cnt=0, cpu_cnt=0,
146 smt_used=False):
147 """Return string of node related list of CPU numbers.
148
149 :param node: Node dictionary with cpuinfo.
150 :param cpu_node: Numa node number.
151 :param skip_cnt: Skip first "skip_cnt" CPUs.
152 :param cpu_cnt: Count of cpus to return, if 0 then return all.
153 :param smt_used: True - we want to use SMT, otherwise false.
154 :type node: dict
155 :type cpu_node: int
156 :type skip_cnt: int
157 :type cpu_cnt: int
158 :type smt_used: bool
159 :returns: Cpu numbers related to numa from argument.
160 :rtype: list
161 :raises RuntimeError: If we require more cpus than available.
162 """
163
164 cpu_list = CpuUtils.cpu_list_per_node(node, cpu_node, smt_used)
165
166 cpu_list_len = len(cpu_list)
167 if cpu_cnt + skip_cnt > cpu_list_len:
168 raise RuntimeError("cpu_cnt + skip_cnt > length(cpu list).")
169
170 if cpu_cnt == 0:
171 cpu_cnt = cpu_list_len - skip_cnt
172
173 if smt_used:
Paul Vinciguerra339bc6b2018-12-19 02:05:25 -0800174 cpu_list_0 = cpu_list[:cpu_list_len // CpuUtils.NR_OF_THREADS]
175 cpu_list_1 = cpu_list[cpu_list_len // CpuUtils.NR_OF_THREADS:]
John DeNisco68b0ee32017-09-27 16:35:23 -0400176 cpu_list = [cpu for cpu in cpu_list_0[skip_cnt:skip_cnt + cpu_cnt]]
177 cpu_list_ex = [cpu for cpu in
178 cpu_list_1[skip_cnt:skip_cnt + cpu_cnt]]
179 cpu_list.extend(cpu_list_ex)
180 else:
181 cpu_list = [cpu for cpu in cpu_list[skip_cnt:skip_cnt + cpu_cnt]]
182
183 return cpu_list
184
185 @staticmethod
186 def cpu_list_per_node_str(node, cpu_node, skip_cnt=0, cpu_cnt=0, sep=",",
187 smt_used=False):
188 """Return string of node related list of CPU numbers.
189
190 :param node: Node dictionary with cpuinfo.
191 :param cpu_node: Numa node number.
192 :param skip_cnt: Skip first "skip_cnt" CPUs.
193 :param cpu_cnt: Count of cpus to return, if 0 then return all.
194 :param sep: Separator, default: 1,2,3,4,....
195 :param smt_used: True - we want to use SMT, otherwise false.
196 :type node: dict
197 :type cpu_node: int
198 :type skip_cnt: int
199 :type cpu_cnt: int
200 :type sep: str
201 :type smt_used: bool
202 :returns: Cpu numbers related to numa from argument.
203 :rtype: str
204 """
205
206 cpu_list = CpuUtils.cpu_slice_of_list_per_node(node, cpu_node,
207 skip_cnt=skip_cnt,
208 cpu_cnt=cpu_cnt,
209 smt_used=smt_used)
210 return sep.join(str(cpu) for cpu in cpu_list)
211
212 @staticmethod
213 def cpu_range_per_node_str(node, cpu_node, skip_cnt=0, cpu_cnt=0, sep="-",
214 smt_used=False):
215 """Return string of node related range of CPU numbers, e.g. 0-4.
216
217 :param node: Node dictionary with cpuinfo.
218 :param cpu_node: Numa node number.
219 :param skip_cnt: Skip first "skip_cnt" CPUs.
220 :param cpu_cnt: Count of cpus to return, if 0 then return all.
221 :param sep: Separator, default: "-".
222 :param smt_used: True - we want to use SMT, otherwise false.
223 :type node: dict
224 :type cpu_node: int
225 :type skip_cnt: int
226 :type cpu_cnt: int
227 :type sep: str
228 :type smt_used: bool
229 :returns: String of node related range of CPU numbers.
230 :rtype: str
231 """
232
233 cpu_list = CpuUtils.cpu_slice_of_list_per_node(node, cpu_node,
234 skip_cnt=skip_cnt,
235 cpu_cnt=cpu_cnt,
236 smt_used=smt_used)
237 if smt_used:
238 cpu_list_len = len(cpu_list)
Paul Vinciguerra339bc6b2018-12-19 02:05:25 -0800239 cpu_list_0 = cpu_list[:cpu_list_len // CpuUtils.NR_OF_THREADS]
240 cpu_list_1 = cpu_list[cpu_list_len // CpuUtils.NR_OF_THREADS:]
John DeNisco68b0ee32017-09-27 16:35:23 -0400241 cpu_range = "{}{}{},{}{}{}".format(cpu_list_0[0], sep,
242 cpu_list_0[-1],
243 cpu_list_1[0], sep,
244 cpu_list_1[-1])
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("lscpu command failed on node {} {}."
264 .format(node['host'], stderr))
265
266 cpuinfo = {}
267 lines = stdout.split('\n')
268 for line in lines:
269 if line != '':
270 linesplit = re.split(r':\s+', line)
271 cpuinfo[linesplit[0]] = linesplit[1]
272
273 cmd = "cat /proc/*/task/*/stat | awk '{print $1" "$2" "$39}'"
274 ret, stdout, stderr = VPPUtil.exec_command(cmd)
275 if ret != 0:
276 raise RuntimeError("cat command failed on node {} {}."
277 .format(node['host'], stderr))
278
279 vpp_processes = {}
280 vpp_lines = re.findall(r'\w+\(vpp_\w+\)\w+', stdout)
281 for line in vpp_lines:
282 linesplit = re.split(r'\w+\(', line)[1].split(')')
283 vpp_processes[linesplit[0]] = linesplit[1]
284
285 cpuinfo['vpp_processes'] = vpp_processes
286
287 return cpuinfo