blob: d29ea4586034378a24fae59e0a352c2575e6b2be [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
16import redfish
17
18import argparse
19import json
20import re
21import subprocess
22import sys
23
24linux_cpu_map = dict()
25success = True
26
27def get_linux_output():
28 cmd = "/usr/bin/env intel-speed-select --debug --format json perf-profile info".split()
29 process = subprocess.run(cmd, capture_output=True, text=True)
30 process.check_returncode()
31 result = json.loads(process.stderr)
32
33 global linux_cpu_map
34 linux_cpu_map = dict()
35 for line in process.stdout.split('\n'):
36 match = re.search("logical_cpu:(\d+).*punit_core:(\d+)", line)
37 if not match:
38 continue
39 logical_thread = int(match.group(1))
40 physical_core = int(match.group(2))
41 linux_cpu_map[logical_thread] = physical_core
42
43 cmd = "/usr/bin/env intel-speed-select --format json perf-profile get-config-current-level".split()
44 process = subprocess.run(cmd, capture_output=True, text=True)
45 current_level = json.loads(process.stderr)
46
47 for proc, data in current_level.items():
48 result[proc].update(data)
49
50 return result
51
52
53def compare(redfish_val, linux_val, description):
54 err = ""
Jonathan Domana30229e2022-05-13 13:19:02 -070055 if None in (redfish_val, linux_val):
56 err = "MISSING VALUE"
57 elif redfish_val != linux_val:
Jonathan Doman94c94bf2020-10-05 23:25:45 -070058 err = "!! MISMATCH !!"
59 global success
60 success = False
61 print(f"{description}: {err}")
62 print(f" Redfish: {redfish_val}")
63 print(f" Linux: {linux_val}")
64
65
66def get_linux_package(linux_data, redfish_id):
67 match = re.match("cpu(\d+)", redfish_id)
68 if not match:
69 raise RuntimeError(f"Redfish CPU name is unexpected: {redfish_id}")
70 num = match.group(1)
71 matching_keys = []
72 for key in linux_data.keys():
73 if re.match(f"^package-{num}:.*", key):
74 matching_keys.append(key)
75 if len(matching_keys) != 1:
76 raise RuntimeError(f"Unexpected number of matching linux objects for {redfish_id}")
77 return linux_data[matching_keys[0]]
78
79
80def compare_config(redfish_config, linux_config):
81 print(f"--Checking {redfish_config['Id']}--")
82 compare(redfish_config["BaseSpeedMHz"], int(linux_config["base-frequency(MHz)"]), "Base Speed")
83
84 actual_hp_p1 = actual_lp_p1 = 0
85 actual_hp_cores = set()
86 for bf in redfish_config["BaseSpeedPrioritySettings"]:
87 if not actual_hp_p1 or bf["BaseSpeedMHz"] > actual_hp_p1:
88 actual_hp_p1 = bf["BaseSpeedMHz"]
89 actual_hp_cores = set(bf["CoreIDs"])
90 if not actual_lp_p1 or bf["BaseSpeedMHz"] < actual_lp_p1:
91 actual_lp_p1 = bf["BaseSpeedMHz"]
92
93 exp_hp_p1 = exp_lp_p1 = 0
94 exp_hp_cores = set()
95 if "speed-select-base-freq-properties" in linux_config:
96 exp_bf_props = linux_config["speed-select-base-freq-properties"]
97 exp_hp_p1 = int(exp_bf_props["high-priority-base-frequency(MHz)"])
98 exp_hp_cores = set(map(lambda x: linux_cpu_map[x],
99 map(int, exp_bf_props["high-priority-cpu-list"].split(","))))
100 exp_lp_p1 = int(exp_bf_props["low-priority-base-frequency(MHz)"])
101
102 compare(actual_hp_p1, exp_hp_p1, "SST-BF High Priority P1 Freq")
103 compare(actual_hp_cores, exp_hp_cores, "SST-BF High Priority Core List")
104 compare(actual_lp_p1, exp_lp_p1, "SST-BF Low Priority P1 Freq")
105
106
107 compare(redfish_config["MaxJunctionTemperatureCelsius"],
108 int(linux_config["tjunction-max(C)"]),
109 "Junction Temperature")
Jonathan Domana30229e2022-05-13 13:19:02 -0700110 # There is no equivalent value in linux for the per-level P0_1 freq.
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700111 compare(redfish_config["MaxSpeedMHz"],
Jonathan Domana30229e2022-05-13 13:19:02 -0700112 None,
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700113 "SSE Max Turbo Speed")
114 compare(redfish_config["TDPWatts"],
115 int(linux_config["thermal-design-power(W)"]),
116 "TDP")
117 compare(redfish_config["TotalAvailableCoreCount"],
118 int(linux_config["enable-cpu-count"])//2,
119 "Enabled Core Count")
120
121 actual_turbo = [(x["ActiveCoreCount"], x["MaxSpeedMHz"]) for x in redfish_config["TurboProfile"]]
122 linux_turbo = linux_config["turbo-ratio-limits-sse"]
123 exp_turbo = []
124 for bucket_key in sorted(linux_turbo.keys()):
125 bucket = linux_turbo[bucket_key]
126 exp_turbo.append((int(bucket["core-count"]), int(bucket["max-turbo-frequency(MHz)"])))
127 compare(actual_turbo, exp_turbo, "SSE Turbo Profile")
128
129
130def get_level_from_config_id(config_id):
131 match = re.match("config(\d+)", config_id)
132 if not match:
133 raise RuntimeError(f"Invalid config name {config_id}")
134 return match.group(1)
135
136
137def main():
138 parser = argparse.ArgumentParser(description="Compare Redfish SST properties against Linux tools")
139 parser.add_argument("hostname")
140 parser.add_argument("--username", "-u", default="root")
141 parser.add_argument("--password", "-p", default="0penBmc")
142 args = parser.parse_args()
143
144 linux_data = get_linux_output()
145
146 bmc = redfish.redfish_client(base_url=f"https://{args.hostname}",
147 username=args.username, password=args.password)
148 bmc.login(auth="session")
149
150 # Load the ProcessorCollection
151 resp = json.loads(bmc.get("/redfish/v1/Systems/system/Processors").text)
152 for proc_member in resp["Members"]:
153 proc_resp = json.loads(bmc.get(proc_member["@odata.id"]).text)
154 proc_id = proc_resp["Id"]
155 print()
156 print(f"----Checking Processor {proc_id}----")
157
158 if proc_resp["Status"]["State"] == "Absent":
159 print("Not populated")
160 continue
161
162 # Get subset of intel-speed-select data which applies to this CPU
163 pkg_data = get_linux_package(linux_data, proc_id)
164
165 # Check currently applied config
166 applied_config = proc_resp["AppliedOperatingConfig"]["@odata.id"].split('/')[-1]
167 current_level = get_level_from_config_id(applied_config)
168 compare(current_level, pkg_data["get-config-current_level"], "Applied Config")
169
170 exp_cur_level_data = pkg_data[f"perf-profile-level-{current_level}"]
171
172 # Check whether SST-BF is enabled
173 bf_enabled = proc_resp["BaseSpeedPriorityState"].lower()
174 exp_bf_enabled = exp_cur_level_data["speed-select-base-freq"]
175 if exp_bf_enabled == "unsupported":
176 exp_bf_enabled = "disabled"
177 compare(bf_enabled, exp_bf_enabled, "SST-BF Enabled?")
178
179 # Check high speed core list
180 hscores = set(proc_resp["HighSpeedCoreIDs"])
181 exp_hscores = set()
182 if "speed-select-base-freq-properties" in exp_cur_level_data:
183 exp_hscores = exp_cur_level_data["speed-select-base-freq-properties"]["high-priority-cpu-list"]
184 exp_hscores = set([linux_cpu_map[int(x)] for x in exp_hscores.split(",")])
185 compare(hscores, exp_hscores, "High Speed Core List")
186
187 # Load the OperatingConfigCollection
188 resp = json.loads(bmc.get(proc_resp["OperatingConfigs"]["@odata.id"]).text)
189
190 # Check number of available configs
191 profile_keys = list(filter(lambda x: x.startswith("perf-profile-level"), pkg_data.keys()))
192 compare(resp["Members@odata.count"], int(len(profile_keys)), "Number of profiles")
193
194 for config_member in resp["Members"]:
195 # Load each OperatingConfig and compare all its contents
196 config_resp = json.loads(bmc.get(config_member["@odata.id"]).text)
197 level = get_level_from_config_id(config_resp["Id"])
198 exp_level_data = pkg_data[f"perf-profile-level-{level}"]
199 compare_config(config_resp, exp_level_data)
200
201 print()
202 if success:
203 print("Everything matched! :)")
204 return 0
205 else:
206 print("There were mismatches, please check output :(")
207 return 1
208
209if __name__ == "__main__":
210 sys.exit(main())