blob: f5c23bc141a800c022b6a34318f0019ea81004ef [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."""
15
16import 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
46 enabled, the L1d,L1i,L2,L3 setting is the same for two processors. These
47 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(
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)
140 cpu_list = cpu_list[:cpu_list_len / CpuUtils.NR_OF_THREADS]
141
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:
174 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:]
176 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)
239 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:]
241 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