blob: 96d0ea0b94098bb0f3269610c439c01fc81ab20e [file] [log] [blame]
Jonathan Doman94c94bf2020-10-05 23:25:45 -07001#!/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 Doman94c94bf2020-10-05 23:25:45 -070016import argparse
17import json
18import re
19import subprocess
20import sys
21
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -060022import redfish
23
Jonathan Doman94c94bf2020-10-05 23:25:45 -070024linux_cpu_map = dict()
25success = True
26
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -060027
Jonathan Domandefbc2a2023-04-14 12:45:21 -070028def 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 Doman94c94bf2020-10-05 23:25:45 -070038def get_linux_output():
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -060039 cmd = [
40 "/usr/bin/env",
41 "intel-speed-select",
42 "--debug",
43 "--format=json",
44 "perf-profile",
45 "info",
46 ]
Jonathan Doman94c94bf2020-10-05 23:25:45 -070047 process = subprocess.run(cmd, capture_output=True, text=True)
48 process.check_returncode()
49 result = json.loads(process.stderr)
Jonathan Domandefbc2a2023-04-14 12:45:21 -070050 filter_powerdomains(result)
Jonathan Doman94c94bf2020-10-05 23:25:45 -070051
52 global linux_cpu_map
53 linux_cpu_map = dict()
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -060054 for line in process.stdout.split("\n"):
55 match = re.search("logical_cpu:(\\d+).*punit_core:(\\d+)", line)
Jonathan Doman94c94bf2020-10-05 23:25:45 -070056 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 Williamsf5a2d2a2022-12-04 15:40:52 -060062 cmd = [
63 "/usr/bin/env",
64 "intel-speed-select",
65 "--format=json",
66 "perf-profile",
67 "get-config-current-level",
68 ]
Jonathan Doman94c94bf2020-10-05 23:25:45 -070069 process = subprocess.run(cmd, capture_output=True, text=True)
70 current_level = json.loads(process.stderr)
Jonathan Domandefbc2a2023-04-14 12:45:21 -070071 filter_powerdomains(current_level)
Jonathan Doman94c94bf2020-10-05 23:25:45 -070072
73 for proc, data in current_level.items():
74 result[proc].update(data)
75
76 return result
77
78
79def compare(redfish_val, linux_val, description):
80 err = ""
Jonathan Domana30229e2022-05-13 13:19:02 -070081 if None in (redfish_val, linux_val):
82 err = "MISSING VALUE"
83 elif redfish_val != linux_val:
Jonathan Doman94c94bf2020-10-05 23:25:45 -070084 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
92def get_linux_package(linux_data, redfish_id):
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -060093 match = re.match("cpu(\\d+)", redfish_id)
Jonathan Doman94c94bf2020-10-05 23:25:45 -070094 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 Williamsf5a2d2a2022-12-04 15:40:52 -0600102 raise RuntimeError(
103 f"Unexpected number of matching linux objects for {redfish_id}"
104 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700105 return linux_data[matching_keys[0]]
106
107
108def compare_config(redfish_config, linux_config):
109 print(f"--Checking {redfish_config['Id']}--")
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600110 compare(
111 redfish_config["BaseSpeedMHz"],
112 int(linux_config["base-frequency(MHz)"]),
113 "Base Speed",
114 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700115
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 Williamsf5a2d2a2022-12-04 15:40:52 -0600130 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 Doman94c94bf2020-10-05 23:25:45 -0700136 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 Williamsf5a2d2a2022-12-04 15:40:52 -0600142 compare(
143 redfish_config["MaxJunctionTemperatureCelsius"],
144 int(linux_config["tjunction-max(C)"]),
145 "Junction Temperature",
146 )
Jonathan Domana30229e2022-05-13 13:19:02 -0700147 # There is no equivalent value in linux for the per-level P0_1 freq.
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600148 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 Doman94c94bf2020-10-05 23:25:45 -0700159
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600160 actual_turbo = [
161 (x["ActiveCoreCount"], x["MaxSpeedMHz"])
162 for x in redfish_config["TurboProfile"]
163 ]
Jonathan Domandefbc2a2023-04-14 12:45:21 -0700164 linux_turbo = (
165 linux_config.get("turbo-ratio-limits-sse")
166 or linux_config["turbo-ratio-limits-level-0"]
167 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700168 exp_turbo = []
169 for bucket_key in sorted(linux_turbo.keys()):
170 bucket = linux_turbo[bucket_key]
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600171 exp_turbo.append(
172 (
173 int(bucket["core-count"]),
174 int(bucket["max-turbo-frequency(MHz)"]),
175 )
176 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700177 compare(actual_turbo, exp_turbo, "SSE Turbo Profile")
178
179
180def get_level_from_config_id(config_id):
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600181 match = re.match("config(\\d+)", config_id)
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700182 if not match:
183 raise RuntimeError(f"Invalid config name {config_id}")
184 return match.group(1)
185
186
187def main():
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600188 parser = argparse.ArgumentParser(
189 description="Compare Redfish SST properties against Linux tools"
190 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700191 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 Williamsf5a2d2a2022-12-04 15:40:52 -0600198 bmc = redfish.redfish_client(
199 base_url=f"https://{args.hostname}",
200 username=args.username,
201 password=args.password,
202 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700203 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 Williamsf5a2d2a2022-12-04 15:40:52 -0600221 applied_config = proc_resp["AppliedOperatingConfig"][
222 "@odata.id"
223 ].split("/")[-1]
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700224 current_level = get_level_from_config_id(applied_config)
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600225 compare(
226 current_level,
227 pkg_data["get-config-current_level"],
228 "Applied Config",
229 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700230
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 Williamsf5a2d2a2022-12-04 15:40:52 -0600244 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 Doman94c94bf2020-10-05 23:25:45 -0700250 compare(hscores, exp_hscores, "High Speed Core List")
251
252 # Load the OperatingConfigCollection
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600253 resp = json.loads(
254 bmc.get(proc_resp["OperatingConfigs"]["@odata.id"]).text
255 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700256
257 # Check number of available configs
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600258 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 Doman94c94bf2020-10-05 23:25:45 -0700268
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 Williamsf5a2d2a2022-12-04 15:40:52 -0600284
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700285if __name__ == "__main__":
286 sys.exit(main())