Anatoly Burakov, Aug 16, 2024 at 14:16:
In traditional NUMA case, NUMA nodes and physical sockets were used
interchangeably, but there are cases where there can be multiple NUMA
nodes per socket, as well as all CPU's being assigned NUMA node 0 even in
cases of multiple sockets. Use sysfs to print out NUMA information.
Signed-off-by: Anatoly Burakov <anatoly.bura...@intel.com>
---
usertools/cpu_layout.py | 35 ++++++++++++++++++++++++++++++-----
1 file changed, 30 insertions(+), 5 deletions(-)
diff --git a/usertools/cpu_layout.py b/usertools/cpu_layout.py
index be86f06938..e43bdbf343 100755
--- a/usertools/cpu_layout.py
+++ b/usertools/cpu_layout.py
@@ -4,6 +4,7 @@
# Copyright(c) 2017 Cavium, Inc. All rights reserved.
from typing import List, Set, Dict, Tuple
+import glob
Can you keep the import sorted alphabetically?
def _range_expand(rstr: str) -> List[int]:
@@ -26,11 +27,19 @@ def _read_sysfs(path: str) -> str:
return fd.read().strip()
+def _read_numa_node(base: str) -> int:
+ node_glob = f"{base}/node*"
+ node_dirs = glob.glob(node_glob)
+ if not node_dirs:
+ return 0 # default to node 0
+ return int(node_dirs[0].split("node")[1])
Maybe you could make this safer and more "python"-ic as follows:
def read_numa_node(base: str) -> int:
for node in glob.iglob(f"{base}/node*"):
match = re.match(r"^.*/node(\d+)$", node)
if match:
return int(match.group(1))
return 0 # default to node 0
+
+
def _print_row(row: Tuple[str, ...], col_widths: List[int]) -> None:
first, *rest = row
w_first, *w_rest = col_widths
first_end = " " * 4
- rest_end = " " * 10
+ rest_end = " " * 4
print(first.ljust(w_first), end=first_end)
for cell, width in zip(rest, w_rest):
@@ -50,6 +59,7 @@ def _main() -> None:
sockets_s: Set[int] = set()
cores_s: Set[int] = set()
core_map: Dict[Tuple[int, int], List[int]] = {}
+ numa_map: Dict[int, int] = {}
base_path = "/sys/devices/system/cpu"
cpus = _range_expand(_read_sysfs(f"{base_path}/online"))
@@ -58,12 +68,14 @@ def _main() -> None:
lcore_base = f"{base_path}/cpu{cpu}"
core = int(_read_sysfs(f"{lcore_base}/topology/core_id"))
socket = int(_read_sysfs(f"{lcore_base}/topology/physical_package_id"))
+ node = _read_numa_node(lcore_base)
cores_s.add(core)
sockets_s.add(socket)
key = (socket, core)
core_map.setdefault(key, [])
core_map[key].append(cpu)
+ numa_map[cpu] = node
cores = sorted(cores_s)
sockets = sorted(sockets_s)
@@ -73,24 +85,37 @@ def _main() -> None:
print("cores = ", cores)
print("sockets = ", sockets)
+ print("numa = ", sorted(set(numa_map.values())))
print()
- # Core, [Socket, Socket, ...]
- heading_strs = "", *[f"Socket {s}" for s in sockets]
+ # Core, [NUMA, Socket, NUMA, Socket, ...]
+ heading_strs = "", *[v for s in sockets for v in ("", f"Socket {s}")]
sep_strs = tuple("-" * len(hstr) for hstr in heading_strs)
rows: List[Tuple[str, ...]] = []
+ prev_numa = None
for c in cores:
# Core,
row: Tuple[str, ...] = (f"Core {c}",)
- # [lcores, lcores, ...]
+ # assume NUMA changes symmetrically
+ first_lcore = core_map[(0, c)][0]
+ cur_numa = numa_map[first_lcore]
+ numa_changed = prev_numa != cur_numa
+ prev_numa = cur_numa
+
+ # [NUMA, lcores, NUMA, lcores, ...]
for s in sockets:
try:
lcores = core_map[(s, c)]
+ numa = numa_map[lcores[0]]
+ if numa_changed:
+ row += (f"NUMA {numa}",)
+ else:
+ row += ("",)
row += (str(lcores),)
except KeyError:
- row += ("",)
+ row += ("", "")
rows += [row]
# find max widths for each column, including header and rows
--
2.43.5