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()) |