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