d9e3fca7d75acb17dc4a5bdc1bd78f63ba952849
[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 class CpuUtils(object):
21     """CPU utilities"""
22
23     @staticmethod
24     def __str2int(string):
25         """Conversion from string to integer, 0 in case of empty string.
26
27         :param string: Input string.
28         :type string: str
29         :returns: Integer converted from string, 0 in case of ValueError.
30         :rtype: int
31         """
32         try:
33             return int(string)
34         except ValueError:
35             return 0
36
37     @staticmethod
38     def get_cpu_layout_from_all_nodes(nodes):
39         """Retrieve cpu layout from all nodes, assuming all nodes
40            are Linux nodes.
41
42         :param nodes: DICT__nodes from Topology.DICT__nodes.
43         :type nodes: dict
44         :raises RuntimeError: If the ssh command "lscpu -p" fails.
45         """
46         ssh = SSH()
47         for node in nodes.values():
48             ssh.connect(node)
49             cmd = "lscpu -p"
50             ret, stdout, stderr = ssh.exec_command(cmd)
51 #           parsing of "lscpu -p" output:
52 #           # CPU,Core,Socket,Node,,L1d,L1i,L2,L3
53 #           0,0,0,0,,0,0,0,0
54 #           1,1,0,0,,1,1,1,0
55             if ret != 0:
56                 raise RuntimeError(
57                     "Failed to execute ssh command, ret: {} err: {}".format(
58                         ret, stderr))
59             node['cpuinfo'] = list()
60             for line in stdout.split("\n"):
61                 if len(line) > 0 and line[0] != "#":
62                     node['cpuinfo'].append([CpuUtils.__str2int(x) for x in
63                                             line.split(",")])
64
65     @staticmethod
66     def cpu_node_count(node):
67         """Return count of numa nodes.
68
69         :param node: Targeted node.
70         :type node: dict
71         :returns: Count of numa nodes.
72         :rtype: int
73         :raises RuntimeError: If node cpuinfo is not available.
74         """
75         cpuinfo = node.get("cpuinfo")
76         if cpuinfo is not None:
77             return node["cpuinfo"][-1][3] + 1
78         else:
79             raise RuntimeError("Node cpuinfo not available.")
80
81     @staticmethod
82     def cpu_list_per_node(node, cpu_node):
83         """Return node related list of CPU numbers.
84
85         :param node: Node dictionary with cpuinfo.
86         :param cpu_node: Numa node number.
87         :type node: dict
88         :type cpu_node: int
89         :returns: List of cpu numbers related to numa from argument.
90         :rtype: list of int
91         :raises RuntimeError: If node cpuinfo is not available.
92         """
93         cpu_node = int(cpu_node)
94         cpuinfo = node.get("cpuinfo")
95         cpulist = []
96         if cpuinfo is not None:
97             for cpu in cpuinfo:
98                 if cpu[3] == cpu_node:
99                     cpulist.append(cpu[0])
100         else:
101             raise RuntimeError("Node cpuinfo not available.")
102
103         return cpulist
104
105     @staticmethod
106     def cpu_list_per_node_str(node, cpu_node, skip_cnt=0,
107                               cpu_cnt=0, sep=","):
108         """Return string of node related list of CPU numbers.
109
110         :param node: Node dictionary with cpuinfo.
111         :param cpu_node: Numa node number.
112         :param skip_cnt: Skip first "skip_cnt" CPUs.
113         :param cpu_cnt: Count of cpus to return, if 0 then return all.
114         :param sep: Separator, default: 1,2,3,4,....
115         :type node: dict
116         :type cpu_node: int
117         :type skip_cnt: int
118         :type cpu_cnt: int
119         :type sep: str
120         :returns: Cpu numbers related to numa from argument.
121         :rtype: str
122         :raises RuntimeError: If we require more cpus than available.
123         """
124
125         cpu_list = CpuUtils.cpu_list_per_node(node, cpu_node)
126         cpu_list_len = len(cpu_list)
127         if cpu_cnt == 0:
128             cpu_cnt = cpu_list_len - skip_cnt
129
130         if cpu_cnt + skip_cnt > cpu_list_len:
131             raise RuntimeError("cpu_cnt + skip_cnt > length(cpu list).")
132
133         cpu_flist = sep.join(str(a) for a in
134                              cpu_list[skip_cnt:skip_cnt+cpu_cnt])
135
136         return cpu_flist
137
138     @staticmethod
139     def cpu_range_per_node_str(node, cpu_node, skip_cnt=0, cpu_cnt=0, sep="-"):
140         """Return string of node related range of CPU numbers, e.g. 0-4.
141
142         :param node: Node dictionary with cpuinfo.
143         :param cpu_node: Numa node number.
144         :param skip_cnt: Skip first "skip_cnt" CPUs.
145         :param cpu_cnt: Count of cpus to return, if 0 then return all.
146         :param sep: Separator, default: 0-4.
147         :type node: dict
148         :type cpu_node: int
149         :type skip_cnt: int
150         :type cpu_cnt: int
151         :type sep: str
152         :returns: String of node related range of CPU numbers.
153         :rtype: str
154         :raises RuntimeError: If we require more cpus than available.
155         """
156
157         cpu_list = CpuUtils.cpu_list_per_node(node, cpu_node)
158         cpu_list_len = len(cpu_list)
159         if cpu_cnt == 0:
160             cpu_cnt = cpu_list_len - skip_cnt
161
162         if cpu_cnt + skip_cnt > cpu_list_len:
163             raise RuntimeError("cpu_cnt + skip_cnt > length(cpu list).")
164
165         first = cpu_list[skip_cnt]
166         last = cpu_list[skip_cnt + cpu_cnt - 1]
167         return "{}{}{}".format(first, sep, last)