194c94bfbSJonathan Doman#!/usr/bin/env python3 294c94bfbSJonathan Doman 394c94bfbSJonathan Doman# This tool runs on the host CPU and gathers all SST related configuration from 494c94bfbSJonathan Doman# the BMC (Redfish) and from the linux driver, and compares them to catch any 594c94bfbSJonathan Doman# errors or disagreement. Only required arguments are the details to start a 694c94bfbSJonathan Doman# Redfish session. 794c94bfbSJonathan Doman# 894c94bfbSJonathan Doman# This was tested running on a live Arch Linux ISO environment. Any Linux 994c94bfbSJonathan Doman# installation should work, but best to get the latest tools and kernel driver. 1094c94bfbSJonathan Doman# 1194c94bfbSJonathan Doman# Required dependencies: 1294c94bfbSJonathan Doman# * DMTF's redfish python library. This is available in pip. 1394c94bfbSJonathan Doman# * intel-speed-select tool from the kernel source tree 1494c94bfbSJonathan Doman# (tools/power/x86/intel-speed-select), and available in the PATH. 1594c94bfbSJonathan Doman 1694c94bfbSJonathan Domanimport argparse 1794c94bfbSJonathan Domanimport json 1894c94bfbSJonathan Domanimport re 1994c94bfbSJonathan Domanimport subprocess 2094c94bfbSJonathan Domanimport sys 2194c94bfbSJonathan Doman 22f5a2d2adSPatrick Williamsimport redfish 23f5a2d2adSPatrick Williams 2494c94bfbSJonathan Domanlinux_cpu_map = dict() 2594c94bfbSJonathan Domansuccess = True 2694c94bfbSJonathan Doman 27f5a2d2adSPatrick Williams 28*defbc2acSJonathan Domandef filter_powerdomains(sst_data): 29*defbc2acSJonathan Doman # For TPMI-based CPUs, we only care about powerdomain-0 30*defbc2acSJonathan Doman cpus = list(sst_data.keys()) 31*defbc2acSJonathan Doman for proc in cpus: 32*defbc2acSJonathan Doman match = re.search("powerdomain-(\\d+)", proc) 33*defbc2acSJonathan Doman if not match or match.group(1) == "0": 34*defbc2acSJonathan Doman continue 35*defbc2acSJonathan Doman del sst_data[proc] 36*defbc2acSJonathan Doman 37*defbc2acSJonathan Doman 3894c94bfbSJonathan Domandef get_linux_output(): 39f5a2d2adSPatrick Williams cmd = [ 40f5a2d2adSPatrick Williams "/usr/bin/env", 41f5a2d2adSPatrick Williams "intel-speed-select", 42f5a2d2adSPatrick Williams "--debug", 43f5a2d2adSPatrick Williams "--format=json", 44f5a2d2adSPatrick Williams "perf-profile", 45f5a2d2adSPatrick Williams "info", 46f5a2d2adSPatrick Williams ] 4794c94bfbSJonathan Doman process = subprocess.run(cmd, capture_output=True, text=True) 4894c94bfbSJonathan Doman process.check_returncode() 4994c94bfbSJonathan Doman result = json.loads(process.stderr) 50*defbc2acSJonathan Doman filter_powerdomains(result) 5194c94bfbSJonathan Doman 5294c94bfbSJonathan Doman global linux_cpu_map 5394c94bfbSJonathan Doman linux_cpu_map = dict() 54f5a2d2adSPatrick Williams for line in process.stdout.split("\n"): 55f5a2d2adSPatrick Williams match = re.search("logical_cpu:(\\d+).*punit_core:(\\d+)", line) 5694c94bfbSJonathan Doman if not match: 5794c94bfbSJonathan Doman continue 5894c94bfbSJonathan Doman logical_thread = int(match.group(1)) 5994c94bfbSJonathan Doman physical_core = int(match.group(2)) 6094c94bfbSJonathan Doman linux_cpu_map[logical_thread] = physical_core 6194c94bfbSJonathan Doman 62f5a2d2adSPatrick Williams cmd = [ 63f5a2d2adSPatrick Williams "/usr/bin/env", 64f5a2d2adSPatrick Williams "intel-speed-select", 65f5a2d2adSPatrick Williams "--format=json", 66f5a2d2adSPatrick Williams "perf-profile", 67f5a2d2adSPatrick Williams "get-config-current-level", 68f5a2d2adSPatrick Williams ] 6994c94bfbSJonathan Doman process = subprocess.run(cmd, capture_output=True, text=True) 7094c94bfbSJonathan Doman current_level = json.loads(process.stderr) 71*defbc2acSJonathan Doman filter_powerdomains(current_level) 7294c94bfbSJonathan Doman 7394c94bfbSJonathan Doman for proc, data in current_level.items(): 7494c94bfbSJonathan Doman result[proc].update(data) 7594c94bfbSJonathan Doman 7694c94bfbSJonathan Doman return result 7794c94bfbSJonathan Doman 7894c94bfbSJonathan Doman 7994c94bfbSJonathan Domandef compare(redfish_val, linux_val, description): 8094c94bfbSJonathan Doman err = "" 81a30229e1SJonathan Doman if None in (redfish_val, linux_val): 82a30229e1SJonathan Doman err = "MISSING VALUE" 83a30229e1SJonathan Doman elif redfish_val != linux_val: 8494c94bfbSJonathan Doman err = "!! MISMATCH !!" 8594c94bfbSJonathan Doman global success 8694c94bfbSJonathan Doman success = False 8794c94bfbSJonathan Doman print(f"{description}: {err}") 8894c94bfbSJonathan Doman print(f" Redfish: {redfish_val}") 8994c94bfbSJonathan Doman print(f" Linux: {linux_val}") 9094c94bfbSJonathan Doman 9194c94bfbSJonathan Doman 9294c94bfbSJonathan Domandef get_linux_package(linux_data, redfish_id): 93f5a2d2adSPatrick Williams match = re.match("cpu(\\d+)", redfish_id) 9494c94bfbSJonathan Doman if not match: 9594c94bfbSJonathan Doman raise RuntimeError(f"Redfish CPU name is unexpected: {redfish_id}") 9694c94bfbSJonathan Doman num = match.group(1) 9794c94bfbSJonathan Doman matching_keys = [] 9894c94bfbSJonathan Doman for key in linux_data.keys(): 9994c94bfbSJonathan Doman if re.match(f"^package-{num}:.*", key): 10094c94bfbSJonathan Doman matching_keys.append(key) 10194c94bfbSJonathan Doman if len(matching_keys) != 1: 102f5a2d2adSPatrick Williams raise RuntimeError( 103f5a2d2adSPatrick Williams f"Unexpected number of matching linux objects for {redfish_id}" 104f5a2d2adSPatrick Williams ) 10594c94bfbSJonathan Doman return linux_data[matching_keys[0]] 10694c94bfbSJonathan Doman 10794c94bfbSJonathan Doman 10894c94bfbSJonathan Domandef compare_config(redfish_config, linux_config): 10994c94bfbSJonathan Doman print(f"--Checking {redfish_config['Id']}--") 110f5a2d2adSPatrick Williams compare( 111f5a2d2adSPatrick Williams redfish_config["BaseSpeedMHz"], 112f5a2d2adSPatrick Williams int(linux_config["base-frequency(MHz)"]), 113f5a2d2adSPatrick Williams "Base Speed", 114f5a2d2adSPatrick Williams ) 11594c94bfbSJonathan Doman 11694c94bfbSJonathan Doman actual_hp_p1 = actual_lp_p1 = 0 11794c94bfbSJonathan Doman actual_hp_cores = set() 11894c94bfbSJonathan Doman for bf in redfish_config["BaseSpeedPrioritySettings"]: 11994c94bfbSJonathan Doman if not actual_hp_p1 or bf["BaseSpeedMHz"] > actual_hp_p1: 12094c94bfbSJonathan Doman actual_hp_p1 = bf["BaseSpeedMHz"] 12194c94bfbSJonathan Doman actual_hp_cores = set(bf["CoreIDs"]) 12294c94bfbSJonathan Doman if not actual_lp_p1 or bf["BaseSpeedMHz"] < actual_lp_p1: 12394c94bfbSJonathan Doman actual_lp_p1 = bf["BaseSpeedMHz"] 12494c94bfbSJonathan Doman 12594c94bfbSJonathan Doman exp_hp_p1 = exp_lp_p1 = 0 12694c94bfbSJonathan Doman exp_hp_cores = set() 12794c94bfbSJonathan Doman if "speed-select-base-freq-properties" in linux_config: 12894c94bfbSJonathan Doman exp_bf_props = linux_config["speed-select-base-freq-properties"] 12994c94bfbSJonathan Doman exp_hp_p1 = int(exp_bf_props["high-priority-base-frequency(MHz)"]) 130f5a2d2adSPatrick Williams exp_hp_cores = set( 131f5a2d2adSPatrick Williams map( 132f5a2d2adSPatrick Williams lambda x: linux_cpu_map[x], 133f5a2d2adSPatrick Williams map(int, exp_bf_props["high-priority-cpu-list"].split(",")), 134f5a2d2adSPatrick Williams ) 135f5a2d2adSPatrick Williams ) 13694c94bfbSJonathan Doman exp_lp_p1 = int(exp_bf_props["low-priority-base-frequency(MHz)"]) 13794c94bfbSJonathan Doman 13894c94bfbSJonathan Doman compare(actual_hp_p1, exp_hp_p1, "SST-BF High Priority P1 Freq") 13994c94bfbSJonathan Doman compare(actual_hp_cores, exp_hp_cores, "SST-BF High Priority Core List") 14094c94bfbSJonathan Doman compare(actual_lp_p1, exp_lp_p1, "SST-BF Low Priority P1 Freq") 14194c94bfbSJonathan Doman 142f5a2d2adSPatrick Williams compare( 143f5a2d2adSPatrick Williams redfish_config["MaxJunctionTemperatureCelsius"], 14494c94bfbSJonathan Doman int(linux_config["tjunction-max(C)"]), 145f5a2d2adSPatrick Williams "Junction Temperature", 146f5a2d2adSPatrick Williams ) 147a30229e1SJonathan Doman # There is no equivalent value in linux for the per-level P0_1 freq. 148f5a2d2adSPatrick Williams compare(redfish_config["MaxSpeedMHz"], None, "SSE Max Turbo Speed") 149f5a2d2adSPatrick Williams compare( 150f5a2d2adSPatrick Williams redfish_config["TDPWatts"], 15194c94bfbSJonathan Doman int(linux_config["thermal-design-power(W)"]), 152f5a2d2adSPatrick Williams "TDP", 153f5a2d2adSPatrick Williams ) 154f5a2d2adSPatrick Williams compare( 155f5a2d2adSPatrick Williams redfish_config["TotalAvailableCoreCount"], 15694c94bfbSJonathan Doman int(linux_config["enable-cpu-count"]) // 2, 157f5a2d2adSPatrick Williams "Enabled Core Count", 158f5a2d2adSPatrick Williams ) 15994c94bfbSJonathan Doman 160f5a2d2adSPatrick Williams actual_turbo = [ 161f5a2d2adSPatrick Williams (x["ActiveCoreCount"], x["MaxSpeedMHz"]) 162f5a2d2adSPatrick Williams for x in redfish_config["TurboProfile"] 163f5a2d2adSPatrick Williams ] 164*defbc2acSJonathan Doman linux_turbo = ( 165*defbc2acSJonathan Doman linux_config.get("turbo-ratio-limits-sse") 166*defbc2acSJonathan Doman or linux_config["turbo-ratio-limits-level-0"] 167*defbc2acSJonathan Doman ) 16894c94bfbSJonathan Doman exp_turbo = [] 16994c94bfbSJonathan Doman for bucket_key in sorted(linux_turbo.keys()): 17094c94bfbSJonathan Doman bucket = linux_turbo[bucket_key] 171f5a2d2adSPatrick Williams exp_turbo.append( 172f5a2d2adSPatrick Williams ( 173f5a2d2adSPatrick Williams int(bucket["core-count"]), 174f5a2d2adSPatrick Williams int(bucket["max-turbo-frequency(MHz)"]), 175f5a2d2adSPatrick Williams ) 176f5a2d2adSPatrick Williams ) 17794c94bfbSJonathan Doman compare(actual_turbo, exp_turbo, "SSE Turbo Profile") 17894c94bfbSJonathan Doman 17994c94bfbSJonathan Doman 18094c94bfbSJonathan Domandef get_level_from_config_id(config_id): 181f5a2d2adSPatrick Williams match = re.match("config(\\d+)", config_id) 18294c94bfbSJonathan Doman if not match: 18394c94bfbSJonathan Doman raise RuntimeError(f"Invalid config name {config_id}") 18494c94bfbSJonathan Doman return match.group(1) 18594c94bfbSJonathan Doman 18694c94bfbSJonathan Doman 18794c94bfbSJonathan Domandef main(): 188f5a2d2adSPatrick Williams parser = argparse.ArgumentParser( 189f5a2d2adSPatrick Williams description="Compare Redfish SST properties against Linux tools" 190f5a2d2adSPatrick Williams ) 19194c94bfbSJonathan Doman parser.add_argument("hostname") 19294c94bfbSJonathan Doman parser.add_argument("--username", "-u", default="root") 19394c94bfbSJonathan Doman parser.add_argument("--password", "-p", default="0penBmc") 19494c94bfbSJonathan Doman args = parser.parse_args() 19594c94bfbSJonathan Doman 19694c94bfbSJonathan Doman linux_data = get_linux_output() 19794c94bfbSJonathan Doman 198f5a2d2adSPatrick Williams bmc = redfish.redfish_client( 199f5a2d2adSPatrick Williams base_url=f"https://{args.hostname}", 200f5a2d2adSPatrick Williams username=args.username, 201f5a2d2adSPatrick Williams password=args.password, 202f5a2d2adSPatrick Williams ) 20394c94bfbSJonathan Doman bmc.login(auth="session") 20494c94bfbSJonathan Doman 20594c94bfbSJonathan Doman # Load the ProcessorCollection 20694c94bfbSJonathan Doman resp = json.loads(bmc.get("/redfish/v1/Systems/system/Processors").text) 20794c94bfbSJonathan Doman for proc_member in resp["Members"]: 20894c94bfbSJonathan Doman proc_resp = json.loads(bmc.get(proc_member["@odata.id"]).text) 20994c94bfbSJonathan Doman proc_id = proc_resp["Id"] 21094c94bfbSJonathan Doman print() 21194c94bfbSJonathan Doman print(f"----Checking Processor {proc_id}----") 21294c94bfbSJonathan Doman 21394c94bfbSJonathan Doman if proc_resp["Status"]["State"] == "Absent": 21494c94bfbSJonathan Doman print("Not populated") 21594c94bfbSJonathan Doman continue 21694c94bfbSJonathan Doman 21794c94bfbSJonathan Doman # Get subset of intel-speed-select data which applies to this CPU 21894c94bfbSJonathan Doman pkg_data = get_linux_package(linux_data, proc_id) 21994c94bfbSJonathan Doman 22094c94bfbSJonathan Doman # Check currently applied config 221f5a2d2adSPatrick Williams applied_config = proc_resp["AppliedOperatingConfig"][ 222f5a2d2adSPatrick Williams "@odata.id" 223f5a2d2adSPatrick Williams ].split("/")[-1] 22494c94bfbSJonathan Doman current_level = get_level_from_config_id(applied_config) 225f5a2d2adSPatrick Williams compare( 226f5a2d2adSPatrick Williams current_level, 227f5a2d2adSPatrick Williams pkg_data["get-config-current_level"], 228f5a2d2adSPatrick Williams "Applied Config", 229f5a2d2adSPatrick Williams ) 23094c94bfbSJonathan Doman 23194c94bfbSJonathan Doman exp_cur_level_data = pkg_data[f"perf-profile-level-{current_level}"] 23294c94bfbSJonathan Doman 23394c94bfbSJonathan Doman # Check whether SST-BF is enabled 23494c94bfbSJonathan Doman bf_enabled = proc_resp["BaseSpeedPriorityState"].lower() 23594c94bfbSJonathan Doman exp_bf_enabled = exp_cur_level_data["speed-select-base-freq"] 23694c94bfbSJonathan Doman if exp_bf_enabled == "unsupported": 23794c94bfbSJonathan Doman exp_bf_enabled = "disabled" 23894c94bfbSJonathan Doman compare(bf_enabled, exp_bf_enabled, "SST-BF Enabled?") 23994c94bfbSJonathan Doman 24094c94bfbSJonathan Doman # Check high speed core list 24194c94bfbSJonathan Doman hscores = set(proc_resp["HighSpeedCoreIDs"]) 24294c94bfbSJonathan Doman exp_hscores = set() 24394c94bfbSJonathan Doman if "speed-select-base-freq-properties" in exp_cur_level_data: 244f5a2d2adSPatrick Williams exp_hscores = exp_cur_level_data[ 245f5a2d2adSPatrick Williams "speed-select-base-freq-properties" 246f5a2d2adSPatrick Williams ]["high-priority-cpu-list"] 247f5a2d2adSPatrick Williams exp_hscores = set( 248f5a2d2adSPatrick Williams [linux_cpu_map[int(x)] for x in exp_hscores.split(",")] 249f5a2d2adSPatrick Williams ) 25094c94bfbSJonathan Doman compare(hscores, exp_hscores, "High Speed Core List") 25194c94bfbSJonathan Doman 25294c94bfbSJonathan Doman # Load the OperatingConfigCollection 253f5a2d2adSPatrick Williams resp = json.loads( 254f5a2d2adSPatrick Williams bmc.get(proc_resp["OperatingConfigs"]["@odata.id"]).text 255f5a2d2adSPatrick Williams ) 25694c94bfbSJonathan Doman 25794c94bfbSJonathan Doman # Check number of available configs 258f5a2d2adSPatrick Williams profile_keys = list( 259f5a2d2adSPatrick Williams filter( 260f5a2d2adSPatrick Williams lambda x: x.startswith("perf-profile-level"), pkg_data.keys() 261f5a2d2adSPatrick Williams ) 262f5a2d2adSPatrick Williams ) 263f5a2d2adSPatrick Williams compare( 264f5a2d2adSPatrick Williams resp["Members@odata.count"], 265f5a2d2adSPatrick Williams int(len(profile_keys)), 266f5a2d2adSPatrick Williams "Number of profiles", 267f5a2d2adSPatrick Williams ) 26894c94bfbSJonathan Doman 26994c94bfbSJonathan Doman for config_member in resp["Members"]: 27094c94bfbSJonathan Doman # Load each OperatingConfig and compare all its contents 27194c94bfbSJonathan Doman config_resp = json.loads(bmc.get(config_member["@odata.id"]).text) 27294c94bfbSJonathan Doman level = get_level_from_config_id(config_resp["Id"]) 27394c94bfbSJonathan Doman exp_level_data = pkg_data[f"perf-profile-level-{level}"] 27494c94bfbSJonathan Doman compare_config(config_resp, exp_level_data) 27594c94bfbSJonathan Doman 27694c94bfbSJonathan Doman print() 27794c94bfbSJonathan Doman if success: 27894c94bfbSJonathan Doman print("Everything matched! :)") 27994c94bfbSJonathan Doman return 0 28094c94bfbSJonathan Doman else: 28194c94bfbSJonathan Doman print("There were mismatches, please check output :(") 28294c94bfbSJonathan Doman return 1 28394c94bfbSJonathan Doman 284f5a2d2adSPatrick Williams 28594c94bfbSJonathan Domanif __name__ == "__main__": 28694c94bfbSJonathan Doman sys.exit(main()) 287