HC Test: update HC config file locations
[csit.git] / resources / libraries / python / 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
16 from resources.libraries.python.ssh import SSH
17
18 __all__ = ["CpuUtils"]
19
20
21 class CpuUtils(object):
22     """CPU utilities"""
23
24     # Number of threads per core.
25     NR_OF_THREADS = 2
26
27     @staticmethod
28     def __str2int(string):
29         """Conversion from string to integer, 0 in case of empty string.
30
31         :param string: Input string.
32         :type string: str
33         :returns: Integer converted from string, 0 in case of ValueError.
34         :rtype: int
35         """
36         try:
37             return int(string)
38         except ValueError:
39             return 0
40
41     @staticmethod
42     def is_smt_enabled(cpu_info):
43         """Uses CPU mapping to find out if SMT is enabled or not. If SMT is
44         enabled, the L1d,L1i,L2,L3 setting is the same for two processors. These
45         two processors are two threads of one core.
46
47         :param cpu_info: CPU info, the output of "lscpu -p".
48         :type cpu_info: list
49         :returns: True if SMT is enabled, False if SMT is disabled.
50         :rtype: bool
51         """
52
53         cpu_mems = [item[-4:] for item in cpu_info]
54         cpu_mems_len = len(cpu_mems) / CpuUtils.NR_OF_THREADS
55         count = 0
56         for cpu_mem in cpu_mems[:cpu_mems_len]:
57             if cpu_mem in cpu_mems[cpu_mems_len:]:
58                 count += 1
59         return bool(count == cpu_mems_len)
60
61     @staticmethod
62     def get_cpu_layout_from_all_nodes(nodes):
63         """Retrieve cpu layout from all nodes, assuming all nodes
64            are Linux nodes.
65
66         :param nodes: DICT__nodes from Topology.DICT__nodes.
67         :type nodes: dict
68         :raises RuntimeError: If the ssh command "lscpu -p" fails.
69         """
70         ssh = SSH()
71         for node in nodes.values():
72             ssh.connect(node)
73             cmd = "lscpu -p"
74             ret, stdout, stderr = ssh.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 len(line) > 0 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