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