xref: /dpdk/usertools/cpu_layout.py (revision 2b091dea3d37d076b6f11e6e676924be77122770)
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