| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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 |  | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 16 | import argparse | 
|  | 17 | import json | 
|  | 18 | import re | 
|  | 19 | import subprocess | 
|  | 20 | import sys | 
|  | 21 |  | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 22 | import redfish | 
|  | 23 |  | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 24 | linux_cpu_map = dict() | 
|  | 25 | success = True | 
|  | 26 |  | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 27 |  | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 28 | def get_linux_output(): | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 29 | cmd = [ | 
|  | 30 | "/usr/bin/env", | 
|  | 31 | "intel-speed-select", | 
|  | 32 | "--debug", | 
|  | 33 | "--format=json", | 
|  | 34 | "perf-profile", | 
|  | 35 | "info", | 
|  | 36 | ] | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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() | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 43 | for line in process.stdout.split("\n"): | 
|  | 44 | match = re.search("logical_cpu:(\\d+).*punit_core:(\\d+)", line) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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 |  | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 51 | cmd = [ | 
|  | 52 | "/usr/bin/env", | 
|  | 53 | "intel-speed-select", | 
|  | 54 | "--format=json", | 
|  | 55 | "perf-profile", | 
|  | 56 | "get-config-current-level", | 
|  | 57 | ] | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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 |  | 
|  | 67 | def compare(redfish_val, linux_val, description): | 
|  | 68 | err = "" | 
| Jonathan Doman | a30229e | 2022-05-13 13:19:02 -0700 | [diff] [blame] | 69 | if None in (redfish_val, linux_val): | 
|  | 70 | err = "MISSING VALUE" | 
|  | 71 | elif redfish_val != linux_val: | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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 |  | 
|  | 80 | def get_linux_package(linux_data, redfish_id): | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 81 | match = re.match("cpu(\\d+)", redfish_id) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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: | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 90 | raise RuntimeError( | 
|  | 91 | f"Unexpected number of matching linux objects for {redfish_id}" | 
|  | 92 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 93 | return linux_data[matching_keys[0]] | 
|  | 94 |  | 
|  | 95 |  | 
|  | 96 | def compare_config(redfish_config, linux_config): | 
|  | 97 | print(f"--Checking {redfish_config['Id']}--") | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 98 | compare( | 
|  | 99 | redfish_config["BaseSpeedMHz"], | 
|  | 100 | int(linux_config["base-frequency(MHz)"]), | 
|  | 101 | "Base Speed", | 
|  | 102 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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)"]) | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 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 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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 |  | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 130 | compare( | 
|  | 131 | redfish_config["MaxJunctionTemperatureCelsius"], | 
|  | 132 | int(linux_config["tjunction-max(C)"]), | 
|  | 133 | "Junction Temperature", | 
|  | 134 | ) | 
| Jonathan Doman | a30229e | 2022-05-13 13:19:02 -0700 | [diff] [blame] | 135 | # There is no equivalent value in linux for the per-level P0_1 freq. | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 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 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 147 |  | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 148 | actual_turbo = [ | 
|  | 149 | (x["ActiveCoreCount"], x["MaxSpeedMHz"]) | 
|  | 150 | for x in redfish_config["TurboProfile"] | 
|  | 151 | ] | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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] | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 156 | exp_turbo.append( | 
|  | 157 | ( | 
|  | 158 | int(bucket["core-count"]), | 
|  | 159 | int(bucket["max-turbo-frequency(MHz)"]), | 
|  | 160 | ) | 
|  | 161 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 162 | compare(actual_turbo, exp_turbo, "SSE Turbo Profile") | 
|  | 163 |  | 
|  | 164 |  | 
|  | 165 | def get_level_from_config_id(config_id): | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 166 | match = re.match("config(\\d+)", config_id) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 167 | if not match: | 
|  | 168 | raise RuntimeError(f"Invalid config name {config_id}") | 
|  | 169 | return match.group(1) | 
|  | 170 |  | 
|  | 171 |  | 
|  | 172 | def main(): | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 173 | parser = argparse.ArgumentParser( | 
|  | 174 | description="Compare Redfish SST properties against Linux tools" | 
|  | 175 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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 |  | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 183 | bmc = redfish.redfish_client( | 
|  | 184 | base_url=f"https://{args.hostname}", | 
|  | 185 | username=args.username, | 
|  | 186 | password=args.password, | 
|  | 187 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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 | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 206 | applied_config = proc_resp["AppliedOperatingConfig"][ | 
|  | 207 | "@odata.id" | 
|  | 208 | ].split("/")[-1] | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 209 | current_level = get_level_from_config_id(applied_config) | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 210 | compare( | 
|  | 211 | current_level, | 
|  | 212 | pkg_data["get-config-current_level"], | 
|  | 213 | "Applied Config", | 
|  | 214 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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: | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 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 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 235 | compare(hscores, exp_hscores, "High Speed Core List") | 
|  | 236 |  | 
|  | 237 | # Load the OperatingConfigCollection | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 238 | resp = json.loads( | 
|  | 239 | bmc.get(proc_resp["OperatingConfigs"]["@odata.id"]).text | 
|  | 240 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 241 |  | 
|  | 242 | # Check number of available configs | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 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 | ) | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 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 |  | 
| Patrick Williams | f5a2d2a | 2022-12-04 15:40:52 -0600 | [diff] [blame] | 269 |  | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame] | 270 | if __name__ == "__main__": | 
|  | 271 | sys.exit(main()) |