summaryrefslogtreecommitdiffstats
path: root/extras/vpp_config/vpplib/CpuUtils.py
diff options
context:
space:
mode:
Diffstat (limited to 'extras/vpp_config/vpplib/CpuUtils.py')
-rw-r--r--extras/vpp_config/vpplib/CpuUtils.py287
1 files changed, 287 insertions, 0 deletions
diff --git a/extras/vpp_config/vpplib/CpuUtils.py b/extras/vpp_config/vpplib/CpuUtils.py
new file mode 100644
index 00000000000..f5c23bc141a
--- /dev/null
+++ b/extras/vpp_config/vpplib/CpuUtils.py
@@ -0,0 +1,287 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""CPU utilities library."""
+
+import re
+
+from vpplib.VPPUtil import VPPUtil
+
+__all__ = ["CpuUtils"]
+
+
+class CpuUtils(object):
+ """CPU utilities"""
+
+ # Number of threads per core.
+ NR_OF_THREADS = 2
+
+ @staticmethod
+ def __str2int(string):
+ """Conversion from string to integer, 0 in case of empty string.
+
+ :param string: Input string.
+ :type string: str
+ :returns: Integer converted from string, 0 in case of ValueError.
+ :rtype: int
+ """
+ try:
+ return int(string)
+ except ValueError:
+ return 0
+
+ @staticmethod
+ def is_smt_enabled(cpu_info):
+ """Uses CPU mapping to find out if SMT is enabled or not. If SMT is
+ enabled, the L1d,L1i,L2,L3 setting is the same for two processors. These
+ two processors are two threads of one core.
+
+ :param cpu_info: CPU info, the output of "lscpu -p".
+ :type cpu_info: list
+ :returns: True if SMT is enabled, False if SMT is disabled.
+ :rtype: bool
+ """
+
+ cpu_mems = [item[-4:] for item in cpu_info]
+ cpu_mems_len = len(cpu_mems) / CpuUtils.NR_OF_THREADS
+ count = 0
+ for cpu_mem in cpu_mems[:cpu_mems_len]:
+ if cpu_mem in cpu_mems[cpu_mems_len:]:
+ count += 1
+ return bool(count == cpu_mems_len)
+
+ @staticmethod
+ def get_cpu_layout_from_all_nodes(nodes):
+ """Retrieve cpu layout from all nodes, assuming all nodes
+ are Linux nodes.
+
+ :param nodes: DICT__nodes from Topology.DICT__nodes.
+ :type nodes: dict
+ :raises RuntimeError: If the ssh command "lscpu -p" fails.
+ """
+ for node in nodes.values():
+ cmd = "lscpu -p"
+ ret, stdout, stderr = VPPUtil.exec_command(cmd)
+ # parsing of "lscpu -p" output:
+ # # CPU,Core,Socket,Node,,L1d,L1i,L2,L3
+ # 0,0,0,0,,0,0,0,0
+ # 1,1,0,0,,1,1,1,0
+ if ret != 0:
+ raise RuntimeError(
+ "Failed to execute ssh command, ret: {} err: {}".format(
+ ret, stderr))
+ node['cpuinfo'] = list()
+ for line in stdout.split("\n"):
+ if line != '' and line[0] != "#":
+ node['cpuinfo'].append([CpuUtils.__str2int(x) for x in
+ line.split(",")])
+
+ @staticmethod
+ def cpu_node_count(node):
+ """Return count of numa nodes.
+
+ :param node: Targeted node.
+ :type node: dict
+ :returns: Count of numa nodes.
+ :rtype: int
+ :raises RuntimeError: If node cpuinfo is not available.
+ """
+ cpu_info = node.get("cpuinfo")
+ if cpu_info is not None:
+ return node["cpuinfo"][-1][3] + 1
+ else:
+ raise RuntimeError("Node cpuinfo not available.")
+
+ @staticmethod
+ def cpu_list_per_node(node, cpu_node, smt_used=False):
+ """Return node related list of CPU numbers.
+
+ :param node: Node dictionary with cpuinfo.
+ :param cpu_node: Numa node number.
+ :param smt_used: True - we want to use SMT, otherwise false.
+ :type node: dict
+ :type cpu_node: int
+ :type smt_used: bool
+ :returns: List of cpu numbers related to numa from argument.
+ :rtype: list of int
+ :raises RuntimeError: If node cpuinfo is not available or if SMT is not
+ enabled.
+ """
+
+ cpu_node = int(cpu_node)
+ cpu_info = node.get("cpuinfo")
+ if cpu_info is None:
+ raise RuntimeError("Node cpuinfo not available.")
+
+ smt_enabled = CpuUtils.is_smt_enabled(cpu_info)
+ if not smt_enabled and smt_used:
+ raise RuntimeError("SMT is not enabled.")
+
+ cpu_list = []
+ for cpu in cpu_info:
+ if cpu[3] == cpu_node:
+ cpu_list.append(cpu[0])
+
+ if not smt_enabled or smt_enabled and smt_used:
+ pass
+
+ if smt_enabled and not smt_used:
+ cpu_list_len = len(cpu_list)
+ cpu_list = cpu_list[:cpu_list_len / CpuUtils.NR_OF_THREADS]
+
+ return cpu_list
+
+ @staticmethod
+ def cpu_slice_of_list_per_node(node, cpu_node, skip_cnt=0, cpu_cnt=0,
+ smt_used=False):
+ """Return string of node related list of CPU numbers.
+
+ :param node: Node dictionary with cpuinfo.
+ :param cpu_node: Numa node number.
+ :param skip_cnt: Skip first "skip_cnt" CPUs.
+ :param cpu_cnt: Count of cpus to return, if 0 then return all.
+ :param smt_used: True - we want to use SMT, otherwise false.
+ :type node: dict
+ :type cpu_node: int
+ :type skip_cnt: int
+ :type cpu_cnt: int
+ :type smt_used: bool
+ :returns: Cpu numbers related to numa from argument.
+ :rtype: list
+ :raises RuntimeError: If we require more cpus than available.
+ """
+
+ cpu_list = CpuUtils.cpu_list_per_node(node, cpu_node, smt_used)
+
+ cpu_list_len = len(cpu_list)
+ if cpu_cnt + skip_cnt > cpu_list_len:
+ raise RuntimeError("cpu_cnt + skip_cnt > length(cpu list).")
+
+ if cpu_cnt == 0:
+ cpu_cnt = cpu_list_len - skip_cnt
+
+ if smt_used:
+ cpu_list_0 = cpu_list[:cpu_list_len / CpuUtils.NR_OF_THREADS]
+ cpu_list_1 = cpu_list[cpu_list_len / CpuUtils.NR_OF_THREADS:]
+ cpu_list = [cpu for cpu in cpu_list_0[skip_cnt:skip_cnt + cpu_cnt]]
+ cpu_list_ex = [cpu for cpu in
+ cpu_list_1[skip_cnt:skip_cnt + cpu_cnt]]
+ cpu_list.extend(cpu_list_ex)
+ else:
+ cpu_list = [cpu for cpu in cpu_list[skip_cnt:skip_cnt + cpu_cnt]]
+
+ return cpu_list
+
+ @staticmethod
+ def cpu_list_per_node_str(node, cpu_node, skip_cnt=0, cpu_cnt=0, sep=",",
+ smt_used=False):
+ """Return string of node related list of CPU numbers.
+
+ :param node: Node dictionary with cpuinfo.
+ :param cpu_node: Numa node number.
+ :param skip_cnt: Skip first "skip_cnt" CPUs.
+ :param cpu_cnt: Count of cpus to return, if 0 then return all.
+ :param sep: Separator, default: 1,2,3,4,....
+ :param smt_used: True - we want to use SMT, otherwise false.
+ :type node: dict
+ :type cpu_node: int
+ :type skip_cnt: int
+ :type cpu_cnt: int
+ :type sep: str
+ :type smt_used: bool
+ :returns: Cpu numbers related to numa from argument.
+ :rtype: str
+ """
+
+ cpu_list = CpuUtils.cpu_slice_of_list_per_node(node, cpu_node,
+ skip_cnt=skip_cnt,
+ cpu_cnt=cpu_cnt,
+ smt_used=smt_used)
+ return sep.join(str(cpu) for cpu in cpu_list)
+
+ @staticmethod
+ def cpu_range_per_node_str(node, cpu_node, skip_cnt=0, cpu_cnt=0, sep="-",
+ smt_used=False):
+ """Return string of node related range of CPU numbers, e.g. 0-4.
+
+ :param node: Node dictionary with cpuinfo.
+ :param cpu_node: Numa node number.
+ :param skip_cnt: Skip first "skip_cnt" CPUs.
+ :param cpu_cnt: Count of cpus to return, if 0 then return all.
+ :param sep: Separator, default: "-".
+ :param smt_used: True - we want to use SMT, otherwise false.
+ :type node: dict
+ :type cpu_node: int
+ :type skip_cnt: int
+ :type cpu_cnt: int
+ :type sep: str
+ :type smt_used: bool
+ :returns: String of node related range of CPU numbers.
+ :rtype: str
+ """
+
+ cpu_list = CpuUtils.cpu_slice_of_list_per_node(node, cpu_node,
+ skip_cnt=skip_cnt,
+ cpu_cnt=cpu_cnt,
+ smt_used=smt_used)
+ if smt_used:
+ cpu_list_len = len(cpu_list)
+ cpu_list_0 = cpu_list[:cpu_list_len / CpuUtils.NR_OF_THREADS]
+ cpu_list_1 = cpu_list[cpu_list_len / CpuUtils.NR_OF_THREADS:]
+ cpu_range = "{}{}{},{}{}{}".format(cpu_list_0[0], sep,
+ cpu_list_0[-1],
+ cpu_list_1[0], sep,
+ cpu_list_1[-1])
+ else:
+ cpu_range = "{}{}{}".format(cpu_list[0], sep, cpu_list[-1])
+
+ return cpu_range
+
+ @staticmethod
+ def get_cpu_info_per_node(node):
+ """Return node related list of CPU numbers.
+
+ :param node: Node dictionary with cpuinfo.
+ :type node: dict
+ :returns: Important CPU information.
+ :rtype: dict
+ """
+
+ cmd = "lscpu"
+ ret, stdout, stderr = VPPUtil.exec_command(cmd)
+ if ret != 0:
+ raise RuntimeError("lscpu command failed on node {} {}."
+ .format(node['host'], stderr))
+
+ cpuinfo = {}
+ lines = stdout.split('\n')
+ for line in lines:
+ if line != '':
+ linesplit = re.split(r':\s+', line)
+ cpuinfo[linesplit[0]] = linesplit[1]
+
+ cmd = "cat /proc/*/task/*/stat | awk '{print $1" "$2" "$39}'"
+ ret, stdout, stderr = VPPUtil.exec_command(cmd)
+ if ret != 0:
+ raise RuntimeError("cat command failed on node {} {}."
+ .format(node['host'], stderr))
+
+ vpp_processes = {}
+ vpp_lines = re.findall(r'\w+\(vpp_\w+\)\w+', stdout)
+ for line in vpp_lines:
+ linesplit = re.split(r'\w+\(', line)[1].split(')')
+ vpp_processes[linesplit[0]] = linesplit[1]
+
+ cpuinfo['vpp_processes'] = vpp_processes
+
+ return cpuinfo