xref: /openbmc/smbios-mdr/tools/sst-compare-redfish-os.py (revision f5a2d2ad082b5704205349ecc929c37e5e8b3f1d)
1#!/usr/bin/env python3
2
3# This tool runs on the host CPU and gathers all SST related configuration from
4# the BMC (Redfish) and from the linux driver, and compares them to catch any
5# errors or disagreement. Only required arguments are the details to start a
6# Redfish session.
7#
8# This was tested running on a live Arch Linux ISO environment. Any Linux
9# installation should work, but best to get the latest tools and kernel driver.
10#
11# Required dependencies:
12# * DMTF's redfish python library. This is available in pip.
13# * intel-speed-select tool from the kernel source tree
14#   (tools/power/x86/intel-speed-select), and available in the PATH.
15
16import argparse
17import json
18import re
19import subprocess
20import sys
21
22import redfish
23
24linux_cpu_map = dict()
25success = True
26
27
28def get_linux_output():
29    cmd = [
30        "/usr/bin/env",
31        "intel-speed-select",
32        "--debug",
33        "--format=json",
34        "perf-profile",
35        "info",
36    ]
37    process = subprocess.run(cmd, capture_output=True, text=True)
38    process.check_returncode()
39    result = json.loads(process.stderr)
40
41    global linux_cpu_map
42    linux_cpu_map = dict()
43    for line in process.stdout.split("\n"):
44        match = re.search("logical_cpu:(\\d+).*punit_core:(\\d+)", line)
45        if not match:
46            continue
47        logical_thread = int(match.group(1))
48        physical_core = int(match.group(2))
49        linux_cpu_map[logical_thread] = physical_core
50
51    cmd = [
52        "/usr/bin/env",
53        "intel-speed-select",
54        "--format=json",
55        "perf-profile",
56        "get-config-current-level",
57    ]
58    process = subprocess.run(cmd, capture_output=True, text=True)
59    current_level = json.loads(process.stderr)
60
61    for proc, data in current_level.items():
62        result[proc].update(data)
63
64    return result
65
66
67def compare(redfish_val, linux_val, description):
68    err = ""
69    if None in (redfish_val, linux_val):
70        err = "MISSING VALUE"
71    elif redfish_val != linux_val:
72        err = "!! MISMATCH !!"
73        global success
74        success = False
75    print(f"{description}: {err}")
76    print(f"  Redfish: {redfish_val}")
77    print(f"  Linux: {linux_val}")
78
79
80def get_linux_package(linux_data, redfish_id):
81    match = re.match("cpu(\\d+)", redfish_id)
82    if not match:
83        raise RuntimeError(f"Redfish CPU name is unexpected: {redfish_id}")
84    num = match.group(1)
85    matching_keys = []
86    for key in linux_data.keys():
87        if re.match(f"^package-{num}:.*", key):
88            matching_keys.append(key)
89    if len(matching_keys) != 1:
90        raise RuntimeError(
91            f"Unexpected number of matching linux objects for {redfish_id}"
92        )
93    return linux_data[matching_keys[0]]
94
95
96def compare_config(redfish_config, linux_config):
97    print(f"--Checking {redfish_config['Id']}--")
98    compare(
99        redfish_config["BaseSpeedMHz"],
100        int(linux_config["base-frequency(MHz)"]),
101        "Base Speed",
102    )
103
104    actual_hp_p1 = actual_lp_p1 = 0
105    actual_hp_cores = set()
106    for bf in redfish_config["BaseSpeedPrioritySettings"]:
107        if not actual_hp_p1 or bf["BaseSpeedMHz"] > actual_hp_p1:
108            actual_hp_p1 = bf["BaseSpeedMHz"]
109            actual_hp_cores = set(bf["CoreIDs"])
110        if not actual_lp_p1 or bf["BaseSpeedMHz"] < actual_lp_p1:
111            actual_lp_p1 = bf["BaseSpeedMHz"]
112
113    exp_hp_p1 = exp_lp_p1 = 0
114    exp_hp_cores = set()
115    if "speed-select-base-freq-properties" in linux_config:
116        exp_bf_props = linux_config["speed-select-base-freq-properties"]
117        exp_hp_p1 = int(exp_bf_props["high-priority-base-frequency(MHz)"])
118        exp_hp_cores = set(
119            map(
120                lambda x: linux_cpu_map[x],
121                map(int, exp_bf_props["high-priority-cpu-list"].split(",")),
122            )
123        )
124        exp_lp_p1 = int(exp_bf_props["low-priority-base-frequency(MHz)"])
125
126    compare(actual_hp_p1, exp_hp_p1, "SST-BF High Priority P1 Freq")
127    compare(actual_hp_cores, exp_hp_cores, "SST-BF High Priority Core List")
128    compare(actual_lp_p1, exp_lp_p1, "SST-BF Low Priority P1 Freq")
129
130    compare(
131        redfish_config["MaxJunctionTemperatureCelsius"],
132        int(linux_config["tjunction-max(C)"]),
133        "Junction Temperature",
134    )
135    # There is no equivalent value in linux for the per-level P0_1 freq.
136    compare(redfish_config["MaxSpeedMHz"], None, "SSE Max Turbo Speed")
137    compare(
138        redfish_config["TDPWatts"],
139        int(linux_config["thermal-design-power(W)"]),
140        "TDP",
141    )
142    compare(
143        redfish_config["TotalAvailableCoreCount"],
144        int(linux_config["enable-cpu-count"]) // 2,
145        "Enabled Core Count",
146    )
147
148    actual_turbo = [
149        (x["ActiveCoreCount"], x["MaxSpeedMHz"])
150        for x in redfish_config["TurboProfile"]
151    ]
152    linux_turbo = linux_config["turbo-ratio-limits-sse"]
153    exp_turbo = []
154    for bucket_key in sorted(linux_turbo.keys()):
155        bucket = linux_turbo[bucket_key]
156        exp_turbo.append(
157            (
158                int(bucket["core-count"]),
159                int(bucket["max-turbo-frequency(MHz)"]),
160            )
161        )
162    compare(actual_turbo, exp_turbo, "SSE Turbo Profile")
163
164
165def get_level_from_config_id(config_id):
166    match = re.match("config(\\d+)", config_id)
167    if not match:
168        raise RuntimeError(f"Invalid config name {config_id}")
169    return match.group(1)
170
171
172def main():
173    parser = argparse.ArgumentParser(
174        description="Compare Redfish SST properties against Linux tools"
175    )
176    parser.add_argument("hostname")
177    parser.add_argument("--username", "-u", default="root")
178    parser.add_argument("--password", "-p", default="0penBmc")
179    args = parser.parse_args()
180
181    linux_data = get_linux_output()
182
183    bmc = redfish.redfish_client(
184        base_url=f"https://{args.hostname}",
185        username=args.username,
186        password=args.password,
187    )
188    bmc.login(auth="session")
189
190    # Load the ProcessorCollection
191    resp = json.loads(bmc.get("/redfish/v1/Systems/system/Processors").text)
192    for proc_member in resp["Members"]:
193        proc_resp = json.loads(bmc.get(proc_member["@odata.id"]).text)
194        proc_id = proc_resp["Id"]
195        print()
196        print(f"----Checking Processor {proc_id}----")
197
198        if proc_resp["Status"]["State"] == "Absent":
199            print("Not populated")
200            continue
201
202        # Get subset of intel-speed-select data which applies to this CPU
203        pkg_data = get_linux_package(linux_data, proc_id)
204
205        # Check currently applied config
206        applied_config = proc_resp["AppliedOperatingConfig"][
207            "@odata.id"
208        ].split("/")[-1]
209        current_level = get_level_from_config_id(applied_config)
210        compare(
211            current_level,
212            pkg_data["get-config-current_level"],
213            "Applied Config",
214        )
215
216        exp_cur_level_data = pkg_data[f"perf-profile-level-{current_level}"]
217
218        # Check whether SST-BF is enabled
219        bf_enabled = proc_resp["BaseSpeedPriorityState"].lower()
220        exp_bf_enabled = exp_cur_level_data["speed-select-base-freq"]
221        if exp_bf_enabled == "unsupported":
222            exp_bf_enabled = "disabled"
223        compare(bf_enabled, exp_bf_enabled, "SST-BF Enabled?")
224
225        # Check high speed core list
226        hscores = set(proc_resp["HighSpeedCoreIDs"])
227        exp_hscores = set()
228        if "speed-select-base-freq-properties" in exp_cur_level_data:
229            exp_hscores = exp_cur_level_data[
230                "speed-select-base-freq-properties"
231            ]["high-priority-cpu-list"]
232            exp_hscores = set(
233                [linux_cpu_map[int(x)] for x in exp_hscores.split(",")]
234            )
235        compare(hscores, exp_hscores, "High Speed Core List")
236
237        # Load the OperatingConfigCollection
238        resp = json.loads(
239            bmc.get(proc_resp["OperatingConfigs"]["@odata.id"]).text
240        )
241
242        # Check number of available configs
243        profile_keys = list(
244            filter(
245                lambda x: x.startswith("perf-profile-level"), pkg_data.keys()
246            )
247        )
248        compare(
249            resp["Members@odata.count"],
250            int(len(profile_keys)),
251            "Number of profiles",
252        )
253
254        for config_member in resp["Members"]:
255            # Load each OperatingConfig and compare all its contents
256            config_resp = json.loads(bmc.get(config_member["@odata.id"]).text)
257            level = get_level_from_config_id(config_resp["Id"])
258            exp_level_data = pkg_data[f"perf-profile-level-{level}"]
259            compare_config(config_resp, exp_level_data)
260
261    print()
262    if success:
263        print("Everything matched! :)")
264        return 0
265    else:
266        print("There were mismatches, please check output :(")
267        return 1
268
269
270if __name__ == "__main__":
271    sys.exit(main())
272