blob: 6318550dc863fcbb0f58a8fdd6b600df918f82d7 [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 Doman94c94bf2020-10-05 23:25:45 -070028def get_linux_output():
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -060029 cmd = [
30 "/usr/bin/env",
31 "intel-speed-select",
32 "--debug",
33 "--format=json",
34 "perf-profile",
35 "info",
36 ]
Jonathan Doman94c94bf2020-10-05 23:25:45 -070037 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 Williamsf5a2d2a2022-12-04 15:40:52 -060043 for line in process.stdout.split("\n"):
44 match = re.search("logical_cpu:(\\d+).*punit_core:(\\d+)", line)
Jonathan Doman94c94bf2020-10-05 23:25:45 -070045 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 Williamsf5a2d2a2022-12-04 15:40:52 -060051 cmd = [
52 "/usr/bin/env",
53 "intel-speed-select",
54 "--format=json",
55 "perf-profile",
56 "get-config-current-level",
57 ]
Jonathan Doman94c94bf2020-10-05 23:25:45 -070058 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 = ""
Jonathan Domana30229e2022-05-13 13:19:02 -070069 if None in (redfish_val, linux_val):
70 err = "MISSING VALUE"
71 elif redfish_val != linux_val:
Jonathan Doman94c94bf2020-10-05 23:25:45 -070072 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):
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -060081 match = re.match("cpu(\\d+)", redfish_id)
Jonathan Doman94c94bf2020-10-05 23:25:45 -070082 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 Williamsf5a2d2a2022-12-04 15:40:52 -060090 raise RuntimeError(
91 f"Unexpected number of matching linux objects for {redfish_id}"
92 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -070093 return linux_data[matching_keys[0]]
94
95
96def compare_config(redfish_config, linux_config):
97 print(f"--Checking {redfish_config['Id']}--")
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -060098 compare(
99 redfish_config["BaseSpeedMHz"],
100 int(linux_config["base-frequency(MHz)"]),
101 "Base Speed",
102 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700103
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 Williamsf5a2d2a2022-12-04 15:40:52 -0600118 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 Doman94c94bf2020-10-05 23:25:45 -0700124 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 Williamsf5a2d2a2022-12-04 15:40:52 -0600130 compare(
131 redfish_config["MaxJunctionTemperatureCelsius"],
132 int(linux_config["tjunction-max(C)"]),
133 "Junction Temperature",
134 )
Jonathan Domana30229e2022-05-13 13:19:02 -0700135 # There is no equivalent value in linux for the per-level P0_1 freq.
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600136 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 Doman94c94bf2020-10-05 23:25:45 -0700147
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600148 actual_turbo = [
149 (x["ActiveCoreCount"], x["MaxSpeedMHz"])
150 for x in redfish_config["TurboProfile"]
151 ]
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700152 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 Williamsf5a2d2a2022-12-04 15:40:52 -0600156 exp_turbo.append(
157 (
158 int(bucket["core-count"]),
159 int(bucket["max-turbo-frequency(MHz)"]),
160 )
161 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700162 compare(actual_turbo, exp_turbo, "SSE Turbo Profile")
163
164
165def get_level_from_config_id(config_id):
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600166 match = re.match("config(\\d+)", config_id)
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700167 if not match:
168 raise RuntimeError(f"Invalid config name {config_id}")
169 return match.group(1)
170
171
172def main():
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600173 parser = argparse.ArgumentParser(
174 description="Compare Redfish SST properties against Linux tools"
175 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700176 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 Williamsf5a2d2a2022-12-04 15:40:52 -0600183 bmc = redfish.redfish_client(
184 base_url=f"https://{args.hostname}",
185 username=args.username,
186 password=args.password,
187 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700188 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 Williamsf5a2d2a2022-12-04 15:40:52 -0600206 applied_config = proc_resp["AppliedOperatingConfig"][
207 "@odata.id"
208 ].split("/")[-1]
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700209 current_level = get_level_from_config_id(applied_config)
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600210 compare(
211 current_level,
212 pkg_data["get-config-current_level"],
213 "Applied Config",
214 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700215
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 Williamsf5a2d2a2022-12-04 15:40:52 -0600229 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 Doman94c94bf2020-10-05 23:25:45 -0700235 compare(hscores, exp_hscores, "High Speed Core List")
236
237 # Load the OperatingConfigCollection
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600238 resp = json.loads(
239 bmc.get(proc_resp["OperatingConfigs"]["@odata.id"]).text
240 )
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700241
242 # Check number of available configs
Patrick Williamsf5a2d2a2022-12-04 15:40:52 -0600243 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 Doman94c94bf2020-10-05 23:25:45 -0700253
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 Williamsf5a2d2a2022-12-04 15:40:52 -0600269
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700270if __name__ == "__main__":
271 sys.exit(main())