blob: 96d0ea0b94098bb0f3269610c439c01fc81ab20e [file] [log] [blame]
#!/usr/bin/env python3
# This tool runs on the host CPU and gathers all SST related configuration from
# the BMC (Redfish) and from the linux driver, and compares them to catch any
# errors or disagreement. Only required arguments are the details to start a
# Redfish session.
#
# This was tested running on a live Arch Linux ISO environment. Any Linux
# installation should work, but best to get the latest tools and kernel driver.
#
# Required dependencies:
# * DMTF's redfish python library. This is available in pip.
# * intel-speed-select tool from the kernel source tree
# (tools/power/x86/intel-speed-select), and available in the PATH.
import argparse
import json
import re
import subprocess
import sys
import redfish
linux_cpu_map = dict()
success = True
def filter_powerdomains(sst_data):
# For TPMI-based CPUs, we only care about powerdomain-0
cpus = list(sst_data.keys())
for proc in cpus:
match = re.search("powerdomain-(\\d+)", proc)
if not match or match.group(1) == "0":
continue
del sst_data[proc]
def get_linux_output():
cmd = [
"/usr/bin/env",
"intel-speed-select",
"--debug",
"--format=json",
"perf-profile",
"info",
]
process = subprocess.run(cmd, capture_output=True, text=True)
process.check_returncode()
result = json.loads(process.stderr)
filter_powerdomains(result)
global linux_cpu_map
linux_cpu_map = dict()
for line in process.stdout.split("\n"):
match = re.search("logical_cpu:(\\d+).*punit_core:(\\d+)", line)
if not match:
continue
logical_thread = int(match.group(1))
physical_core = int(match.group(2))
linux_cpu_map[logical_thread] = physical_core
cmd = [
"/usr/bin/env",
"intel-speed-select",
"--format=json",
"perf-profile",
"get-config-current-level",
]
process = subprocess.run(cmd, capture_output=True, text=True)
current_level = json.loads(process.stderr)
filter_powerdomains(current_level)
for proc, data in current_level.items():
result[proc].update(data)
return result
def compare(redfish_val, linux_val, description):
err = ""
if None in (redfish_val, linux_val):
err = "MISSING VALUE"
elif redfish_val != linux_val:
err = "!! MISMATCH !!"
global success
success = False
print(f"{description}: {err}")
print(f" Redfish: {redfish_val}")
print(f" Linux: {linux_val}")
def get_linux_package(linux_data, redfish_id):
match = re.match("cpu(\\d+)", redfish_id)
if not match:
raise RuntimeError(f"Redfish CPU name is unexpected: {redfish_id}")
num = match.group(1)
matching_keys = []
for key in linux_data.keys():
if re.match(f"^package-{num}:.*", key):
matching_keys.append(key)
if len(matching_keys) != 1:
raise RuntimeError(
f"Unexpected number of matching linux objects for {redfish_id}"
)
return linux_data[matching_keys[0]]
def compare_config(redfish_config, linux_config):
print(f"--Checking {redfish_config['Id']}--")
compare(
redfish_config["BaseSpeedMHz"],
int(linux_config["base-frequency(MHz)"]),
"Base Speed",
)
actual_hp_p1 = actual_lp_p1 = 0
actual_hp_cores = set()
for bf in redfish_config["BaseSpeedPrioritySettings"]:
if not actual_hp_p1 or bf["BaseSpeedMHz"] > actual_hp_p1:
actual_hp_p1 = bf["BaseSpeedMHz"]
actual_hp_cores = set(bf["CoreIDs"])
if not actual_lp_p1 or bf["BaseSpeedMHz"] < actual_lp_p1:
actual_lp_p1 = bf["BaseSpeedMHz"]
exp_hp_p1 = exp_lp_p1 = 0
exp_hp_cores = set()
if "speed-select-base-freq-properties" in linux_config:
exp_bf_props = linux_config["speed-select-base-freq-properties"]
exp_hp_p1 = int(exp_bf_props["high-priority-base-frequency(MHz)"])
exp_hp_cores = set(
map(
lambda x: linux_cpu_map[x],
map(int, exp_bf_props["high-priority-cpu-list"].split(",")),
)
)
exp_lp_p1 = int(exp_bf_props["low-priority-base-frequency(MHz)"])
compare(actual_hp_p1, exp_hp_p1, "SST-BF High Priority P1 Freq")
compare(actual_hp_cores, exp_hp_cores, "SST-BF High Priority Core List")
compare(actual_lp_p1, exp_lp_p1, "SST-BF Low Priority P1 Freq")
compare(
redfish_config["MaxJunctionTemperatureCelsius"],
int(linux_config["tjunction-max(C)"]),
"Junction Temperature",
)
# There is no equivalent value in linux for the per-level P0_1 freq.
compare(redfish_config["MaxSpeedMHz"], None, "SSE Max Turbo Speed")
compare(
redfish_config["TDPWatts"],
int(linux_config["thermal-design-power(W)"]),
"TDP",
)
compare(
redfish_config["TotalAvailableCoreCount"],
int(linux_config["enable-cpu-count"]) // 2,
"Enabled Core Count",
)
actual_turbo = [
(x["ActiveCoreCount"], x["MaxSpeedMHz"])
for x in redfish_config["TurboProfile"]
]
linux_turbo = (
linux_config.get("turbo-ratio-limits-sse")
or linux_config["turbo-ratio-limits-level-0"]
)
exp_turbo = []
for bucket_key in sorted(linux_turbo.keys()):
bucket = linux_turbo[bucket_key]
exp_turbo.append(
(
int(bucket["core-count"]),
int(bucket["max-turbo-frequency(MHz)"]),
)
)
compare(actual_turbo, exp_turbo, "SSE Turbo Profile")
def get_level_from_config_id(config_id):
match = re.match("config(\\d+)", config_id)
if not match:
raise RuntimeError(f"Invalid config name {config_id}")
return match.group(1)
def main():
parser = argparse.ArgumentParser(
description="Compare Redfish SST properties against Linux tools"
)
parser.add_argument("hostname")
parser.add_argument("--username", "-u", default="root")
parser.add_argument("--password", "-p", default="0penBmc")
args = parser.parse_args()
linux_data = get_linux_output()
bmc = redfish.redfish_client(
base_url=f"https://{args.hostname}",
username=args.username,
password=args.password,
)
bmc.login(auth="session")
# Load the ProcessorCollection
resp = json.loads(bmc.get("/redfish/v1/Systems/system/Processors").text)
for proc_member in resp["Members"]:
proc_resp = json.loads(bmc.get(proc_member["@odata.id"]).text)
proc_id = proc_resp["Id"]
print()
print(f"----Checking Processor {proc_id}----")
if proc_resp["Status"]["State"] == "Absent":
print("Not populated")
continue
# Get subset of intel-speed-select data which applies to this CPU
pkg_data = get_linux_package(linux_data, proc_id)
# Check currently applied config
applied_config = proc_resp["AppliedOperatingConfig"][
"@odata.id"
].split("/")[-1]
current_level = get_level_from_config_id(applied_config)
compare(
current_level,
pkg_data["get-config-current_level"],
"Applied Config",
)
exp_cur_level_data = pkg_data[f"perf-profile-level-{current_level}"]
# Check whether SST-BF is enabled
bf_enabled = proc_resp["BaseSpeedPriorityState"].lower()
exp_bf_enabled = exp_cur_level_data["speed-select-base-freq"]
if exp_bf_enabled == "unsupported":
exp_bf_enabled = "disabled"
compare(bf_enabled, exp_bf_enabled, "SST-BF Enabled?")
# Check high speed core list
hscores = set(proc_resp["HighSpeedCoreIDs"])
exp_hscores = set()
if "speed-select-base-freq-properties" in exp_cur_level_data:
exp_hscores = exp_cur_level_data[
"speed-select-base-freq-properties"
]["high-priority-cpu-list"]
exp_hscores = set(
[linux_cpu_map[int(x)] for x in exp_hscores.split(",")]
)
compare(hscores, exp_hscores, "High Speed Core List")
# Load the OperatingConfigCollection
resp = json.loads(
bmc.get(proc_resp["OperatingConfigs"]["@odata.id"]).text
)
# Check number of available configs
profile_keys = list(
filter(
lambda x: x.startswith("perf-profile-level"), pkg_data.keys()
)
)
compare(
resp["Members@odata.count"],
int(len(profile_keys)),
"Number of profiles",
)
for config_member in resp["Members"]:
# Load each OperatingConfig and compare all its contents
config_resp = json.loads(bmc.get(config_member["@odata.id"]).text)
level = get_level_from_config_id(config_resp["Id"])
exp_level_data = pkg_data[f"perf-profile-level-{level}"]
compare_config(config_resp, exp_level_data)
print()
if success:
print("Everything matched! :)")
return 0
else:
print("There were mismatches, please check output :(")
return 1
if __name__ == "__main__":
sys.exit(main())