13f6f8362SLouise Kilheeney#!/usr/bin/env python3 29c75ffc4SHemant Agrawal# SPDX-License-Identifier: BSD-3-Clause 39c75ffc4SHemant Agrawal# Copyright(c) 2010-2014 Intel Corporation 43abcd29fSJerin Jacob# Copyright(c) 2017 Cavium, Inc. All rights reserved. 59c75ffc4SHemant Agrawal 6fa269c9eSAnatoly Burakov"""Display CPU topology information.""" 7fa269c9eSAnatoly Burakov 8*2b091deaSAnatoly Burakovimport glob 9fa269c9eSAnatoly Burakovimport typing as T 10fa269c9eSAnatoly Burakov 11fa269c9eSAnatoly Burakov 12fa269c9eSAnatoly Burakovdef range_expand(rstr: str) -> T.List[int]: 13fa269c9eSAnatoly Burakov """Expand a range string into a list of integers.""" 14fa269c9eSAnatoly Burakov # 0,1-3 => [0, 1-3] 15fa269c9eSAnatoly Burakov ranges = rstr.split(",") 16fa269c9eSAnatoly Burakov valset: T.List[int] = [] 17fa269c9eSAnatoly Burakov for r in ranges: 18fa269c9eSAnatoly Burakov # 1-3 => [1, 2, 3] 19fa269c9eSAnatoly Burakov if "-" in r: 20fa269c9eSAnatoly Burakov start, end = r.split("-") 21fa269c9eSAnatoly Burakov valset.extend(range(int(start), int(end) + 1)) 22fa269c9eSAnatoly Burakov else: 23fa269c9eSAnatoly Burakov valset.append(int(r)) 24fa269c9eSAnatoly Burakov return valset 25fa269c9eSAnatoly Burakov 26fa269c9eSAnatoly Burakov 27fa269c9eSAnatoly Burakovdef read_sysfs(path: str) -> str: 28fa269c9eSAnatoly Burakov """Read a sysfs file and return its contents.""" 29fa269c9eSAnatoly Burakov with open(path, encoding="utf-8") as fd: 30fa269c9eSAnatoly Burakov return fd.read().strip() 31fa269c9eSAnatoly Burakov 32fa269c9eSAnatoly Burakov 33*2b091deaSAnatoly Burakovdef read_numa_node(base: str) -> int: 34*2b091deaSAnatoly Burakov """Read the NUMA node of a CPU.""" 35*2b091deaSAnatoly Burakov node_glob = f"{base}/node*" 36*2b091deaSAnatoly Burakov node_dirs = glob.glob(node_glob) 37*2b091deaSAnatoly Burakov if not node_dirs: 38*2b091deaSAnatoly Burakov return 0 # default to node 0 39*2b091deaSAnatoly Burakov return int(node_dirs[0].split("node")[1]) 40*2b091deaSAnatoly Burakov 41*2b091deaSAnatoly Burakov 42fa269c9eSAnatoly Burakovdef print_row(row: T.Tuple[str, ...], col_widths: T.List[int]) -> None: 43fa269c9eSAnatoly Burakov """Print a row of a table with the given column widths.""" 44fa269c9eSAnatoly Burakov first, *rest = row 45fa269c9eSAnatoly Burakov w_first, *w_rest = col_widths 46fa269c9eSAnatoly Burakov first_end = " " * 4 47*2b091deaSAnatoly Burakov rest_end = " " * 4 48fa269c9eSAnatoly Burakov 49fa269c9eSAnatoly Burakov print(first.ljust(w_first), end=first_end) 50fa269c9eSAnatoly Burakov for cell, width in zip(rest, w_rest): 51fa269c9eSAnatoly Burakov print(cell.rjust(width), end=rest_end) 52fa269c9eSAnatoly Burakov print() 53fa269c9eSAnatoly Burakov 54fa269c9eSAnatoly Burakov 55fa269c9eSAnatoly Burakovdef print_section(heading: str) -> None: 56fa269c9eSAnatoly Burakov """Print a section heading.""" 57fa269c9eSAnatoly Burakov sep = "=" * len(heading) 58fa269c9eSAnatoly Burakov print(sep) 59fa269c9eSAnatoly Burakov print(heading) 60fa269c9eSAnatoly Burakov print(sep) 61fa269c9eSAnatoly Burakov print() 62fa269c9eSAnatoly Burakov 63fa269c9eSAnatoly Burakov 64fa269c9eSAnatoly Burakovdef main() -> None: 65fa269c9eSAnatoly Burakov """Print CPU topology information.""" 66fa269c9eSAnatoly Burakov sockets_s: T.Set[int] = set() 67fa269c9eSAnatoly Burakov cores_s: T.Set[int] = set() 68fa269c9eSAnatoly Burakov core_map: T.Dict[T.Tuple[int, int], T.List[int]] = {} 69*2b091deaSAnatoly Burakov numa_map: T.Dict[int, int] = {} 70deb87e67SAndriy Berestovskyy base_path = "/sys/devices/system/cpu" 71fa269c9eSAnatoly Burakov 72fa269c9eSAnatoly Burakov cpus = range_expand(read_sysfs(f"{base_path}/online")) 73fa269c9eSAnatoly Burakov 74fa269c9eSAnatoly Burakov for cpu in cpus: 75fa269c9eSAnatoly Burakov lcore_base = f"{base_path}/cpu{cpu}" 76fa269c9eSAnatoly Burakov core = int(read_sysfs(f"{lcore_base}/topology/core_id")) 77fa269c9eSAnatoly Burakov socket = int(read_sysfs(f"{lcore_base}/topology/physical_package_id")) 78*2b091deaSAnatoly Burakov node = read_numa_node(lcore_base) 79fa269c9eSAnatoly Burakov 80fa269c9eSAnatoly Burakov cores_s.add(core) 81fa269c9eSAnatoly Burakov sockets_s.add(socket) 82deb87e67SAndriy Berestovskyy key = (socket, core) 83fa269c9eSAnatoly Burakov core_map.setdefault(key, []) 84deb87e67SAndriy Berestovskyy core_map[key].append(cpu) 85*2b091deaSAnatoly Burakov numa_map[cpu] = node 86c6dab2a8SThomas Monjalon 87fa269c9eSAnatoly Burakov cores = sorted(cores_s) 88fa269c9eSAnatoly Burakov sockets = sorted(sockets_s) 89fa269c9eSAnatoly Burakov 90fa269c9eSAnatoly Burakov print_section(f"Core and Socket Information (as reported by '{base_path}')") 91fa269c9eSAnatoly Burakov 92c6dab2a8SThomas Monjalon print("cores = ", cores) 93c6dab2a8SThomas Monjalon print("sockets = ", sockets) 94*2b091deaSAnatoly Burakov print("numa = ", sorted(set(numa_map.values()))) 95fa269c9eSAnatoly Burakov print() 96c6dab2a8SThomas Monjalon 97*2b091deaSAnatoly Burakov # Core, [NUMA, Socket, NUMA, Socket, ...] 98*2b091deaSAnatoly Burakov heading_strs = "", *[v for s in sockets for v in ("", f"Socket {s}")] 99fa269c9eSAnatoly Burakov sep_strs = tuple("-" * len(hstr) for hstr in heading_strs) 100fa269c9eSAnatoly Burakov rows: T.List[T.Tuple[str, ...]] = [] 101c6dab2a8SThomas Monjalon 102*2b091deaSAnatoly Burakov # track NUMA changes per socket 103*2b091deaSAnatoly Burakov prev_numa: T.Dict[int, T.Optional[int]] = {socket: None for socket in sockets} 104c6dab2a8SThomas Monjalon for c in cores: 105fa269c9eSAnatoly Burakov # Core, 106fa269c9eSAnatoly Burakov row: T.Tuple[str, ...] = (f"Core {c}",) 107fa269c9eSAnatoly Burakov 108*2b091deaSAnatoly Burakov # [NUMA, lcores, NUMA, lcores, ...] 109c6dab2a8SThomas Monjalon for s in sockets: 110fa269c9eSAnatoly Burakov try: 111fa269c9eSAnatoly Burakov lcores = core_map[(s, c)] 112*2b091deaSAnatoly Burakov 113*2b091deaSAnatoly Burakov numa = numa_map[lcores[0]] 114*2b091deaSAnatoly Burakov numa_changed = prev_numa[s] != numa 115*2b091deaSAnatoly Burakov prev_numa[s] = numa 116*2b091deaSAnatoly Burakov 117*2b091deaSAnatoly Burakov if numa_changed: 118*2b091deaSAnatoly Burakov row += (f"NUMA {numa}",) 119*2b091deaSAnatoly Burakov else: 120*2b091deaSAnatoly Burakov row += ("",) 121fa269c9eSAnatoly Burakov row += (str(lcores),) 122fa269c9eSAnatoly Burakov except KeyError: 123*2b091deaSAnatoly Burakov row += ("", "") 124fa269c9eSAnatoly Burakov rows += [row] 125fa269c9eSAnatoly Burakov 126fa269c9eSAnatoly Burakov # find max widths for each column, including header and rows 127fa269c9eSAnatoly Burakov col_widths = [ 128fa269c9eSAnatoly Burakov max(len(tup[col_idx]) for tup in rows + [heading_strs]) 129fa269c9eSAnatoly Burakov for col_idx in range(len(heading_strs)) 130fa269c9eSAnatoly Burakov ] 131fa269c9eSAnatoly Burakov 132fa269c9eSAnatoly Burakov # print out table taking row widths into account 133fa269c9eSAnatoly Burakov print_row(heading_strs, col_widths) 134fa269c9eSAnatoly Burakov print_row(sep_strs, col_widths) 135fa269c9eSAnatoly Burakov for row in rows: 136fa269c9eSAnatoly Burakov print_row(row, col_widths) 137fa269c9eSAnatoly Burakov 138fa269c9eSAnatoly Burakov 139fa269c9eSAnatoly Burakovif __name__ == "__main__": 140fa269c9eSAnatoly Burakov main() 141