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 redfish 17 18import argparse 19import json 20import re 21import subprocess 22import sys 23 24linux_cpu_map = dict() 25success = True 26 27def get_linux_output(): 28 cmd = "/usr/bin/env intel-speed-select --debug --format json perf-profile info".split() 29 process = subprocess.run(cmd, capture_output=True, text=True) 30 process.check_returncode() 31 result = json.loads(process.stderr) 32 33 global linux_cpu_map 34 linux_cpu_map = dict() 35 for line in process.stdout.split('\n'): 36 match = re.search("logical_cpu:(\d+).*punit_core:(\d+)", line) 37 if not match: 38 continue 39 logical_thread = int(match.group(1)) 40 physical_core = int(match.group(2)) 41 linux_cpu_map[logical_thread] = physical_core 42 43 cmd = "/usr/bin/env intel-speed-select --format json perf-profile get-config-current-level".split() 44 process = subprocess.run(cmd, capture_output=True, text=True) 45 current_level = json.loads(process.stderr) 46 47 for proc, data in current_level.items(): 48 result[proc].update(data) 49 50 return result 51 52 53def compare(redfish_val, linux_val, description): 54 err = "" 55 if None in (redfish_val, linux_val): 56 err = "MISSING VALUE" 57 elif redfish_val != linux_val: 58 err = "!! MISMATCH !!" 59 global success 60 success = False 61 print(f"{description}: {err}") 62 print(f" Redfish: {redfish_val}") 63 print(f" Linux: {linux_val}") 64 65 66def get_linux_package(linux_data, redfish_id): 67 match = re.match("cpu(\d+)", redfish_id) 68 if not match: 69 raise RuntimeError(f"Redfish CPU name is unexpected: {redfish_id}") 70 num = match.group(1) 71 matching_keys = [] 72 for key in linux_data.keys(): 73 if re.match(f"^package-{num}:.*", key): 74 matching_keys.append(key) 75 if len(matching_keys) != 1: 76 raise RuntimeError(f"Unexpected number of matching linux objects for {redfish_id}") 77 return linux_data[matching_keys[0]] 78 79 80def compare_config(redfish_config, linux_config): 81 print(f"--Checking {redfish_config['Id']}--") 82 compare(redfish_config["BaseSpeedMHz"], int(linux_config["base-frequency(MHz)"]), "Base Speed") 83 84 actual_hp_p1 = actual_lp_p1 = 0 85 actual_hp_cores = set() 86 for bf in redfish_config["BaseSpeedPrioritySettings"]: 87 if not actual_hp_p1 or bf["BaseSpeedMHz"] > actual_hp_p1: 88 actual_hp_p1 = bf["BaseSpeedMHz"] 89 actual_hp_cores = set(bf["CoreIDs"]) 90 if not actual_lp_p1 or bf["BaseSpeedMHz"] < actual_lp_p1: 91 actual_lp_p1 = bf["BaseSpeedMHz"] 92 93 exp_hp_p1 = exp_lp_p1 = 0 94 exp_hp_cores = set() 95 if "speed-select-base-freq-properties" in linux_config: 96 exp_bf_props = linux_config["speed-select-base-freq-properties"] 97 exp_hp_p1 = int(exp_bf_props["high-priority-base-frequency(MHz)"]) 98 exp_hp_cores = set(map(lambda x: linux_cpu_map[x], 99 map(int, exp_bf_props["high-priority-cpu-list"].split(",")))) 100 exp_lp_p1 = int(exp_bf_props["low-priority-base-frequency(MHz)"]) 101 102 compare(actual_hp_p1, exp_hp_p1, "SST-BF High Priority P1 Freq") 103 compare(actual_hp_cores, exp_hp_cores, "SST-BF High Priority Core List") 104 compare(actual_lp_p1, exp_lp_p1, "SST-BF Low Priority P1 Freq") 105 106 107 compare(redfish_config["MaxJunctionTemperatureCelsius"], 108 int(linux_config["tjunction-max(C)"]), 109 "Junction Temperature") 110 # There is no equivalent value in linux for the per-level P0_1 freq. 111 compare(redfish_config["MaxSpeedMHz"], 112 None, 113 "SSE Max Turbo Speed") 114 compare(redfish_config["TDPWatts"], 115 int(linux_config["thermal-design-power(W)"]), 116 "TDP") 117 compare(redfish_config["TotalAvailableCoreCount"], 118 int(linux_config["enable-cpu-count"])//2, 119 "Enabled Core Count") 120 121 actual_turbo = [(x["ActiveCoreCount"], x["MaxSpeedMHz"]) for x in redfish_config["TurboProfile"]] 122 linux_turbo = linux_config["turbo-ratio-limits-sse"] 123 exp_turbo = [] 124 for bucket_key in sorted(linux_turbo.keys()): 125 bucket = linux_turbo[bucket_key] 126 exp_turbo.append((int(bucket["core-count"]), int(bucket["max-turbo-frequency(MHz)"]))) 127 compare(actual_turbo, exp_turbo, "SSE Turbo Profile") 128 129 130def get_level_from_config_id(config_id): 131 match = re.match("config(\d+)", config_id) 132 if not match: 133 raise RuntimeError(f"Invalid config name {config_id}") 134 return match.group(1) 135 136 137def main(): 138 parser = argparse.ArgumentParser(description="Compare Redfish SST properties against Linux tools") 139 parser.add_argument("hostname") 140 parser.add_argument("--username", "-u", default="root") 141 parser.add_argument("--password", "-p", default="0penBmc") 142 args = parser.parse_args() 143 144 linux_data = get_linux_output() 145 146 bmc = redfish.redfish_client(base_url=f"https://{args.hostname}", 147 username=args.username, password=args.password) 148 bmc.login(auth="session") 149 150 # Load the ProcessorCollection 151 resp = json.loads(bmc.get("/redfish/v1/Systems/system/Processors").text) 152 for proc_member in resp["Members"]: 153 proc_resp = json.loads(bmc.get(proc_member["@odata.id"]).text) 154 proc_id = proc_resp["Id"] 155 print() 156 print(f"----Checking Processor {proc_id}----") 157 158 if proc_resp["Status"]["State"] == "Absent": 159 print("Not populated") 160 continue 161 162 # Get subset of intel-speed-select data which applies to this CPU 163 pkg_data = get_linux_package(linux_data, proc_id) 164 165 # Check currently applied config 166 applied_config = proc_resp["AppliedOperatingConfig"]["@odata.id"].split('/')[-1] 167 current_level = get_level_from_config_id(applied_config) 168 compare(current_level, pkg_data["get-config-current_level"], "Applied Config") 169 170 exp_cur_level_data = pkg_data[f"perf-profile-level-{current_level}"] 171 172 # Check whether SST-BF is enabled 173 bf_enabled = proc_resp["BaseSpeedPriorityState"].lower() 174 exp_bf_enabled = exp_cur_level_data["speed-select-base-freq"] 175 if exp_bf_enabled == "unsupported": 176 exp_bf_enabled = "disabled" 177 compare(bf_enabled, exp_bf_enabled, "SST-BF Enabled?") 178 179 # Check high speed core list 180 hscores = set(proc_resp["HighSpeedCoreIDs"]) 181 exp_hscores = set() 182 if "speed-select-base-freq-properties" in exp_cur_level_data: 183 exp_hscores = exp_cur_level_data["speed-select-base-freq-properties"]["high-priority-cpu-list"] 184 exp_hscores = set([linux_cpu_map[int(x)] for x in exp_hscores.split(",")]) 185 compare(hscores, exp_hscores, "High Speed Core List") 186 187 # Load the OperatingConfigCollection 188 resp = json.loads(bmc.get(proc_resp["OperatingConfigs"]["@odata.id"]).text) 189 190 # Check number of available configs 191 profile_keys = list(filter(lambda x: x.startswith("perf-profile-level"), pkg_data.keys())) 192 compare(resp["Members@odata.count"], int(len(profile_keys)), "Number of profiles") 193 194 for config_member in resp["Members"]: 195 # Load each OperatingConfig and compare all its contents 196 config_resp = json.loads(bmc.get(config_member["@odata.id"]).text) 197 level = get_level_from_config_id(config_resp["Id"]) 198 exp_level_data = pkg_data[f"perf-profile-level-{level}"] 199 compare_config(config_resp, exp_level_data) 200 201 print() 202 if success: 203 print("Everything matched! :)") 204 return 0 205 else: 206 print("There were mismatches, please check output :(") 207 return 1 208 209if __name__ == "__main__": 210 sys.exit(main()) 211