amd-debug-tools 0.2.2__py3-none-any.whl → 0.2.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,3 @@
1
- #!/usr/bin/python3
2
1
  # SPDX-License-Identifier: MIT
3
2
 
4
3
  """
@@ -21,7 +20,7 @@ import pyudev
21
20
 
22
21
  from amd_debug.wake import WakeIRQ
23
22
  from amd_debug.display import Display
24
- from amd_debug.kernel import get_kernel_log, SystemdLogger, DmesgLogger
23
+ from amd_debug.kernel import get_kernel_log, SystemdLogger, CySystemdLogger, DmesgLogger
25
24
  from amd_debug.common import (
26
25
  apply_prefix_wrapper,
27
26
  BIT,
@@ -55,12 +54,15 @@ from amd_debug.failures import (
55
54
  I2CHidBug,
56
55
  KernelRingBufferWrapped,
57
56
  LimitedCores,
57
+ MissingAmdCaptureModule,
58
58
  MissingAmdgpu,
59
59
  MissingAmdgpuFirmware,
60
60
  MissingAmdPmc,
61
+ MissingGpu,
61
62
  MissingDriver,
62
63
  MissingIommuACPI,
63
64
  MissingIommuPolicy,
65
+ MissingIsp4PlatformDriver,
64
66
  MissingThunderbolt,
65
67
  MissingXhciHcd,
66
68
  MSRFailure,
@@ -128,36 +130,56 @@ class PrerequisiteValidator(AmdTool):
128
130
  if not self.db.get_last_prereq_ts():
129
131
  self.run()
130
132
 
133
+ def capture_nvidia(self):
134
+ """Capture the NVIDIA GPU state"""
135
+ p = os.path.join("/", "proc", "driver", "nvidia", "version")
136
+ if not os.path.exists(p):
137
+ return True
138
+ try:
139
+ self.db.record_debug_file(p)
140
+ except PermissionError:
141
+ self.db.record_prereq("NVIDIA GPU version not readable", "👀")
142
+ return True
143
+ p = os.path.join("/", "proc", "driver", "nvidia", "gpus")
144
+ if not os.path.exists(p):
145
+ return True
146
+ for root, _dirs, files in os.walk(p, topdown=False):
147
+ for f in files:
148
+ try:
149
+ self.db.record_debug(f"NVIDIA {f}")
150
+ self.db.record_debug_file(os.path.join(root, f))
151
+ except PermissionError:
152
+ self.db.record_prereq("NVIDIA GPU {f} not readable", "👀")
153
+ return True
154
+
131
155
  def capture_edid(self):
132
156
  """Capture and decode the EDID data"""
133
157
  edids = self.display.get_edid()
134
158
  if len(edids) == 0:
135
159
  self.db.record_debug("No EDID data found")
136
160
  return True
137
-
138
- for name, p in edids.items():
161
+ for p in edids:
139
162
  output = None
140
- for cmd in ["di-edid-decode", "edid-decode"]:
163
+ for tool in ["di-edid-decode", "edid-decode"]:
141
164
  try:
142
- cmd = ["edid-decode", p]
165
+ cmd = [tool, p]
143
166
  output = subprocess.check_output(
144
167
  cmd, stderr=subprocess.DEVNULL
145
- ).decode("utf-8")
168
+ ).decode("utf-8", errors="ignore")
146
169
  break
147
170
  except FileNotFoundError:
148
171
  self.db.record_debug(f"{cmd} not installed")
149
- except subprocess.CalledProcessError as e:
150
- self.db.record_debug(
151
- f"failed to capture edid with {cmd}: {e.output}"
152
- )
172
+ except subprocess.CalledProcessError as _e:
173
+ pass
153
174
  if not output:
154
175
  self.db.record_prereq("Failed to capture EDID table", "👀")
155
176
  else:
156
- self.db.record_debug(apply_prefix_wrapper(f"EDID for {name}:", output))
177
+ self.db.record_debug(apply_prefix_wrapper(f"EDID for {p}:", output))
157
178
  return True
158
179
 
159
180
  def check_amdgpu(self):
160
181
  """Check for the AMDGPU driver"""
182
+ count = 0
161
183
  for device in self.pyudev.list_devices(subsystem="pci"):
162
184
  klass = device.properties.get("PCI_CLASS")
163
185
  if klass not in ["30000", "38000"]:
@@ -165,6 +187,7 @@ class PrerequisiteValidator(AmdTool):
165
187
  pci_id = device.properties.get("PCI_ID")
166
188
  if not pci_id.startswith("1002"):
167
189
  continue
190
+ count += 1
168
191
  if device.properties.get("DRIVER") != "amdgpu":
169
192
  self.db.record_prereq("GPU driver `amdgpu` not loaded", "❌")
170
193
  self.failures += [MissingAmdgpu()]
@@ -172,6 +195,10 @@ class PrerequisiteValidator(AmdTool):
172
195
  slot = device.properties.get("PCI_SLOT_NAME")
173
196
 
174
197
  self.db.record_prereq(f"GPU driver `amdgpu` bound to {slot}", "✅")
198
+ if count == 0:
199
+ self.db.record_prereq("Integrated GPU not found", "❌")
200
+ self.failures += [MissingGpu()]
201
+ return False
175
202
  return True
176
203
 
177
204
  def check_amdgpu_parameters(self):
@@ -245,7 +272,6 @@ class PrerequisiteValidator(AmdTool):
245
272
  for dev in self.pyudev.list_devices(subsystem="pci", DRIVER="nvme"):
246
273
  # https://git.kernel.org/torvalds/c/e79a10652bbd3
247
274
  if minimum_kernel(6, 10):
248
- self.db.record_debug("New enough kernel to avoid NVME check")
249
275
  break
250
276
  pci_slot_name = dev.properties["PCI_SLOT_NAME"]
251
277
  vendor = dev.properties.get("ID_VENDOR_FROM_DATABASE", "")
@@ -319,7 +345,6 @@ class PrerequisiteValidator(AmdTool):
319
345
  # not needed to check in newer kernels
320
346
  # see https://github.com/torvalds/linux/commit/77f1972bdcf7513293e8bbe376b9fe837310ee9c
321
347
  if minimum_kernel(6, 10):
322
- self.db.record_debug("New enough kernel to avoid HSMP check")
323
348
  return True
324
349
  f = os.path.join("/", "boot", f"config-{platform.uname().release}")
325
350
  if os.path.exists(f):
@@ -527,7 +552,7 @@ class PrerequisiteValidator(AmdTool):
527
552
  f"{keys['sys_vendor']} {keys['product_name']} ({keys['product_family']})",
528
553
  "💻",
529
554
  )
530
- debug_str = "DMI data:\n"
555
+ debug_str = "DMI|value\n"
531
556
  for key, value in keys.items():
532
557
  if (
533
558
  "product_name" in key
@@ -535,7 +560,7 @@ class PrerequisiteValidator(AmdTool):
535
560
  or "product_family" in key
536
561
  ):
537
562
  continue
538
- debug_str += f"{key}: {value}\n"
563
+ debug_str += f"{key}| {value}\n"
539
564
  self.db.record_debug(debug_str)
540
565
  return True
541
566
 
@@ -600,6 +625,9 @@ class PrerequisiteValidator(AmdTool):
600
625
  with open(target, "rb") as r:
601
626
  r.seek(0x70)
602
627
  found = struct.unpack("<I", r.read(4))[0] & BIT(21)
628
+ except FileNotFoundError:
629
+ self.db.record_prereq("FADT check unavailable", "🚦")
630
+ return True
603
631
  except PermissionError:
604
632
  self.db.record_prereq("FADT check unavailable", "🚦")
605
633
  return True
@@ -652,7 +680,9 @@ class PrerequisiteValidator(AmdTool):
652
680
  """Check the source for kernel logs"""
653
681
  if isinstance(self.kernel_log, SystemdLogger):
654
682
  self.db.record_prereq("Logs are provided via systemd", "✅")
655
- if isinstance(self.kernel_log, DmesgLogger):
683
+ elif isinstance(self.kernel_log, CySystemdLogger):
684
+ self.db.record_prereq("Logs are provided via cysystemd", "✅")
685
+ elif isinstance(self.kernel_log, DmesgLogger):
656
686
  self.db.record_prereq(
657
687
  "Logs are provided via dmesg, timestamps may not be accurate over multiple cycles",
658
688
  "🚦",
@@ -720,6 +750,9 @@ class PrerequisiteValidator(AmdTool):
720
750
  p = os.path.join("/", "sys", "kernel", "debug", "gpio")
721
751
  try:
722
752
  contents = read_file(p)
753
+ except FileNotFoundError:
754
+ self.db.record_prereq("GPIO debugfs not available", "👀")
755
+ contents = None
723
756
  except PermissionError:
724
757
  self.db.record_debug(f"Unable to capture {p}")
725
758
  contents = None
@@ -756,7 +789,7 @@ class PrerequisiteValidator(AmdTool):
756
789
  "utf-8"
757
790
  )
758
791
  except FileNotFoundError:
759
- self.db.record_prereq(f"ethtool is missing", "👀")
792
+ self.db.record_prereq("ethtool is missing", "👀")
760
793
  return True
761
794
  for line in output.split("\n"):
762
795
  if "Supports Wake-on" in line:
@@ -916,7 +949,7 @@ class PrerequisiteValidator(AmdTool):
916
949
  devices = []
917
950
  for dev in self.pyudev.list_devices(subsystem="pci"):
918
951
  devices.append(dev)
919
- debug_str = "PCI devices\n"
952
+ debug_str = "PCI Slot | Vendor | Class | ID | ACPI path\n"
920
953
  for dev in devices:
921
954
  pci_id = dev.properties["PCI_ID"].lower()
922
955
  pci_slot_name = dev.properties["PCI_SLOT_NAME"]
@@ -939,18 +972,53 @@ class PrerequisiteValidator(AmdTool):
939
972
  p = os.path.join(dev.sys_path, "firmware_node", "path")
940
973
  if os.path.exists(p):
941
974
  acpi = read_file(p)
942
- debug_str += (
943
- f"{prefix}{pci_slot_name} : "
944
- f"{database_vendor} {database_class} [{pci_id}] : {acpi}\n"
945
- )
946
975
  else:
947
- debug_str += (
948
- f"{prefix}{pci_slot_name} : "
949
- f"{database_vendor} {database_class} [{pci_id}]\n"
950
- )
976
+ acpi = ""
977
+ debug_str += (
978
+ f"{prefix}{pci_slot_name} | "
979
+ f"{database_vendor} | {database_class} | {pci_id} | {acpi}\n"
980
+ )
951
981
  if debug_str:
952
982
  self.db.record_debug(debug_str)
953
983
 
984
+ def check_isp4(self):
985
+ """Check if camera supported by ISP is present and set up properly"""
986
+ devices = []
987
+ for dev in self.pyudev.list_devices(subsystem="acpi"):
988
+ # look for ACPI device for camera (OMNI5C10:00 is seen today)
989
+ if not dev.sys_name.startswith("OMNI5C10"):
990
+ continue
991
+ p = os.path.join(dev.sys_path, "path")
992
+ if not os.path.exists(p):
993
+ continue
994
+ devices.append(dev)
995
+ for dev in devices:
996
+ p = os.path.join(dev.sys_path, "physical_node", "driver")
997
+ if os.path.exists(p):
998
+ driver = os.path.basename(os.readlink(p))
999
+ else:
1000
+ driver = None
1001
+ if driver != "amd-isp4":
1002
+ self.db.record_prereq(
1003
+ f"ISP4 platform camera driver 'amd-isp4' not bound to {dev.sys_name}",
1004
+ "❌",
1005
+ )
1006
+ self.failures += [MissingIsp4PlatformDriver()]
1007
+ return False
1008
+ self.db.record_prereq(
1009
+ f"ISP4 platform camera driver 'amd-isp4' bound to {dev.sys_name}", "✅"
1010
+ )
1011
+ # check if `amd_capture` module is loaded
1012
+ p = os.path.join("/", "sys", "module", "amd_capture")
1013
+ if not os.path.exists(p):
1014
+ self.db.record_prereq(
1015
+ "Camera driver module 'amd_capture' not loaded", "❌"
1016
+ )
1017
+ self.failures += [MissingAmdCaptureModule()]
1018
+ return False
1019
+ self.db.record_prereq("Camera driver module 'amd_capture' loaded", "✅")
1020
+ return True
1021
+
954
1022
  def map_acpi_path(self):
955
1023
  """Map of ACPI devices to ACPI paths"""
956
1024
  devices = []
@@ -964,12 +1032,8 @@ class PrerequisiteValidator(AmdTool):
964
1032
  if status == 0:
965
1033
  continue
966
1034
  devices.append(dev)
967
- debug_str = "ACPI name: ACPI path [driver]\n"
1035
+ debug_str = "ACPI name | ACPI path | Kernel driver\n"
968
1036
  for dev in devices:
969
- if dev == devices[-1]:
970
- prefix = "└─"
971
- else:
972
- prefix = "│ "
973
1037
  p = os.path.join(dev.sys_path, "path")
974
1038
  pth = read_file(p)
975
1039
  p = os.path.join(dev.sys_path, "physical_node", "driver")
@@ -977,7 +1041,7 @@ class PrerequisiteValidator(AmdTool):
977
1041
  driver = os.path.basename(os.readlink(p))
978
1042
  else:
979
1043
  driver = None
980
- debug_str += f"{prefix}{dev.sys_name}: {pth} [{driver}]\n"
1044
+ debug_str += f"{dev.sys_name} | {pth} | {driver}\n"
981
1045
  if debug_str:
982
1046
  self.db.record_debug(debug_str)
983
1047
  return True
@@ -1016,6 +1080,21 @@ class PrerequisiteValidator(AmdTool):
1016
1080
  shutil.rmtree(tmpd)
1017
1081
  return True
1018
1082
 
1083
+ def capture_cstates(self):
1084
+ """Capture ACPI C state information for the first CPU (assumes the same for all CPUs)"""
1085
+ base = os.path.join("/", "sys", "bus", "cpu", "devices", "cpu0", "cpuidle")
1086
+ paths = {}
1087
+ for root, _dirs, files in os.walk(base, topdown=False):
1088
+ for fname in files:
1089
+ target = os.path.join(root, fname)
1090
+ with open(target, "rb") as f:
1091
+ paths[target] = f.read()
1092
+ debug_str = "ACPI C-state information\n"
1093
+ for path, data in paths.items():
1094
+ prefix = "│ " if path != list(paths.keys())[-1] else "└─"
1095
+ debug_str += f"{prefix}{path}: {data.decode('utf-8', 'ignore')}"
1096
+ self.db.record_debug(debug_str)
1097
+
1019
1098
  def capture_battery(self):
1020
1099
  """Capture battery information"""
1021
1100
  obj = Batteries()
@@ -1074,11 +1153,22 @@ class PrerequisiteValidator(AmdTool):
1074
1153
  # check for artificially limited CPUs
1075
1154
  p = os.path.join("/", "sys", "devices", "system", "cpu", "kernel_max")
1076
1155
  max_cpus = int(read_file(p)) + 1 # 0 indexed
1077
- # https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24594.pdf
1078
- # Extended Topology Enumeration (NumLogCores)
1079
- # CPUID 0x80000026 subleaf 1
1156
+ # https://docs.amd.com/v/u/en-US/40332-PUB_4.08
1157
+ # E.4.24 Function 8000_0026—Extended CPU Topology
1080
1158
  try:
1081
- _, cpu_count, _, _ = read_cpuid(0, 0x80000026, 1)
1159
+ # Need to discover the socket level to look at all CCDs in the socket
1160
+ for level in range(0, 5):
1161
+ _, _, ecx, _ = read_cpuid(0, 0x80000026, level)
1162
+ level_type = (ecx >> 8) & 0xFF
1163
+ if level_type == 0:
1164
+ self.db.record_prereq(
1165
+ "Unable to discover CPU topology, didn't find socket level",
1166
+ "🚦",
1167
+ )
1168
+ return True
1169
+ if level_type == 4:
1170
+ break
1171
+ _, cpu_count, _, _ = read_cpuid(0, 0x80000026, level)
1082
1172
  if cpu_count > max_cpus:
1083
1173
  self.db.record_prereq(
1084
1174
  f"The kernel has been limited to {max_cpus} CPU cores, "
@@ -1149,32 +1239,17 @@ class PrerequisiteValidator(AmdTool):
1149
1239
  if self.cpu_family == 0x1A and self.cpu_model in affected_1a:
1150
1240
  found_iommu = False
1151
1241
  found_acpi = False
1152
- found_dmar = False
1153
1242
  for dev in self.pyudev.list_devices(subsystem="iommu"):
1154
1243
  found_iommu = True
1155
1244
  debug_str += f"Found IOMMU {dev.sys_path}\n"
1156
1245
  break
1246
+
1247
+ # User turned off IOMMU, no problems
1157
1248
  if not found_iommu:
1158
1249
  self.db.record_prereq("IOMMU disabled", "✅")
1159
1250
  return True
1160
- debug_str += "DMA protection:\n"
1161
- p = os.path.join("/", "sys", "firmware", "acpi", "tables", "IVRS")
1162
- with open(p, "rb") as f:
1163
- data = f.read()
1164
- if len(data) < 40:
1165
- raise ValueError(
1166
- "IVRS table appears too small to contain virtualization info."
1167
- )
1168
- virt_info = struct.unpack_from("I", data, 36)[0]
1169
- debug_str += f"Virtualization info: 0x{virt_info:x}"
1170
- found_dmar = (virt_info & 0x2) != 0
1171
- self.db.record_debug(debug_str)
1172
- if not found_dmar:
1173
- self.db.record_prereq(
1174
- "IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
1175
- )
1176
- self.failures += [DMArNotEnabled()]
1177
- return False
1251
+
1252
+ # Look for MSFT0201 in DSDT/SSDT
1178
1253
  for dev in self.pyudev.list_devices(subsystem="acpi"):
1179
1254
  if "MSFT0201" in dev.sys_path:
1180
1255
  found_acpi = True
@@ -1184,13 +1259,39 @@ class PrerequisiteValidator(AmdTool):
1184
1259
  )
1185
1260
  self.failures += [MissingIommuACPI("MSFT0201")]
1186
1261
  return False
1187
- # check that policy is bound to it
1262
+
1263
+ # Check that policy is bound to it
1188
1264
  for dev in self.pyudev.list_devices(subsystem="platform"):
1189
1265
  if "MSFT0201" in dev.sys_path:
1190
1266
  p = os.path.join(dev.sys_path, "iommu")
1191
1267
  if not os.path.exists(p):
1192
1268
  self.failures += [MissingIommuPolicy("MSFT0201")]
1193
1269
  return False
1270
+
1271
+ # Check pre-boot DMA
1272
+ p = os.path.join("/", "sys", "firmware", "acpi", "tables", "IVRS")
1273
+ with open(p, "rb") as f:
1274
+ data = f.read()
1275
+ if len(data) < 40:
1276
+ raise ValueError(
1277
+ "IVRS table appears too small to contain virtualization info."
1278
+ )
1279
+ virt_info = struct.unpack_from("I", data, 36)[0]
1280
+ debug_str += f"IVRS: Virtualization info: 0x{virt_info:x}\n"
1281
+ found_ivrs_dmar = (virt_info & 0x2) != 0
1282
+
1283
+ # check for MSFT0201 in IVRS (alternative to pre-boot DMA)
1284
+ target_bytes = "MSFT0201".encode("utf-8")
1285
+ found_ivrs_msft0201 = data.find(target_bytes) != -1
1286
+ debug_str += f"IVRS: Found MSFT0201: {found_ivrs_msft0201}"
1287
+
1288
+ self.db.record_debug(debug_str)
1289
+ if not found_ivrs_dmar and not found_ivrs_msft0201:
1290
+ self.db.record_prereq(
1291
+ "IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
1292
+ )
1293
+ self.failures += [DMArNotEnabled()]
1294
+ return False
1194
1295
  self.db.record_prereq("IOMMU properly configured", "✅")
1195
1296
  return True
1196
1297
 
@@ -1200,6 +1301,8 @@ class PrerequisiteValidator(AmdTool):
1200
1301
  return True
1201
1302
  if self.cpu_model not in [0x74, 0x78]:
1202
1303
  return True
1304
+ if not self.smu_version:
1305
+ return True
1203
1306
  if version.parse(self.smu_version) > version.parse("76.60.0"):
1204
1307
  return True
1205
1308
  if version.parse(self.smu_version) < version.parse("76.18.0"):
@@ -1222,9 +1325,8 @@ class PrerequisiteValidator(AmdTool):
1222
1325
  # ignore kernel warnings
1223
1326
  taint &= ~BIT(9)
1224
1327
  if taint != 0:
1225
- self.db.record_prereq(f"Kernel is tainted: {taint}", "")
1328
+ self.db.record_prereq(f"Kernel is tainted: {taint}", "🚦")
1226
1329
  self.failures += [TaintedKernel()]
1227
- return False
1228
1330
  return True
1229
1331
 
1230
1332
  def run(self):
@@ -1239,6 +1341,8 @@ class PrerequisiteValidator(AmdTool):
1239
1341
  self.capture_logind,
1240
1342
  self.capture_pci_acpi,
1241
1343
  self.capture_edid,
1344
+ self.capture_nvidia,
1345
+ self.capture_cstates,
1242
1346
  ]
1243
1347
  checks = []
1244
1348
 
@@ -1268,6 +1372,7 @@ class PrerequisiteValidator(AmdTool):
1268
1372
  self.check_iommu,
1269
1373
  self.check_asus_rog_ally,
1270
1374
  self.check_dpia_pg_dmcub,
1375
+ self.check_isp4,
1271
1376
  ]
1272
1377
 
1273
1378
  checks += [
@@ -1291,7 +1396,7 @@ class PrerequisiteValidator(AmdTool):
1291
1396
  if not check():
1292
1397
  result = False
1293
1398
  if not result:
1294
- self.db.record_prereq(Headers.BrokenPrerequisites, "💯")
1399
+ self.db.record_prereq(Headers.BrokenPrerequisites, "🚫")
1295
1400
  self.db.sync()
1296
1401
  clear_temporary_message(len(msg))
1297
1402
  return result
@@ -1310,7 +1415,7 @@ class PrerequisiteValidator(AmdTool):
1310
1415
  logging.debug(line)
1311
1416
 
1312
1417
  if len(self.failures) == 0:
1313
- return True
1418
+ return
1314
1419
  print_color(Headers.ExplanationReport, "🗣️")
1315
1420
  for item in self.failures:
1316
1421
  item.get_failure()
amd_debug/pstate.py CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/python3
2
1
  # SPDX-License-Identifier: MIT
3
2
  """CPPC triage script for AMD systems"""
4
3
 
@@ -293,22 +292,26 @@ def parse_args():
293
292
  action="store_true",
294
293
  help="Enable tool debug logging",
295
294
  )
296
- subparsers.add_parser("version", help="Show version information")
295
+ parser.add_argument(
296
+ "--version", action="store_true", help="Show version information"
297
+ )
297
298
  if len(sys.argv) == 1:
298
299
  parser.print_help(sys.stderr)
299
300
  sys.exit(1)
300
301
  return parser.parse_args()
301
302
 
302
303
 
303
- def main():
304
+ def main() -> None | int:
304
305
  """Main function"""
305
306
  args = parse_args()
306
307
  ret = False
307
- if args.command == "version":
308
+ if args.version:
308
309
  print(version())
309
- return True
310
+ return
310
311
  elif args.command == "triage":
311
312
  triage = AmdPstateTriage(args.tool_debug)
312
313
  ret = triage.run()
313
314
  show_log_info()
314
- return ret
315
+ if ret is False:
316
+ return 1
317
+ return
amd_debug/s2idle.py CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/python3
2
1
  # SPDX-License-Identifier: MIT
3
2
  """s2idle analysis tool"""
4
3
  import argparse
@@ -11,6 +10,7 @@ from datetime import date, timedelta, datetime
11
10
  from amd_debug.common import (
12
11
  convert_string_to_bool,
13
12
  colorize_choices,
13
+ fatal_error,
14
14
  is_root,
15
15
  relaunch_sudo,
16
16
  show_log_info,
@@ -61,21 +61,22 @@ def display_report_file(fname, fmt) -> None:
61
61
  return
62
62
  user = os.environ.get("SUDO_USER")
63
63
  if user:
64
- # ensure that xdg tools will know how to display the file
65
- # (user may need to call tool with sudo -E)
66
- if os.environ.get("XDG_SESSION_TYPE"):
67
- subprocess.call(["sudo", "-E", "-u", user, "xdg-open", fname])
68
- else:
69
- print(
70
- "To display report automatically in browser launch tool "
71
- f"with '-E' argument (Example: sudo -E {sys.argv[0]})"
72
- )
64
+ cmd = [
65
+ "systemd-run",
66
+ "--user",
67
+ f"--machine={user}@.host",
68
+ "xdg-open",
69
+ os.path.abspath(fname),
70
+ ]
71
+ ret = subprocess.call(cmd)
72
+ if ret:
73
+ fatal_error(f"Failed to open report: {ret}")
73
74
 
74
75
 
75
76
  def get_report_file(report_file, extension) -> str:
76
77
  """Prompt user for report file"""
77
78
  if extension == "stdout":
78
- return None
79
+ return ""
79
80
  if not report_file:
80
81
  return f"amd-s2idle-report-{date.today()}.{extension}"
81
82
  return report_file
@@ -88,7 +89,7 @@ def get_report_format() -> str:
88
89
  return "html"
89
90
 
90
91
 
91
- def prompt_report_arguments(since, until, fname, fmt, report_debug) -> str:
92
+ def prompt_report_arguments(since, until, fname, fmt, report_debug) -> list:
92
93
  """Prompt user for report configuration"""
93
94
  if not since:
94
95
  default = Defaults.since
@@ -120,7 +121,7 @@ def prompt_report_arguments(since, until, fname, fmt, report_debug) -> str:
120
121
  if report_debug is None:
121
122
  inp = (
122
123
  input(
123
- f"{Headers.ReportDebugDescription} ({colorize_choices(Defaults.boolean_choices, "true")})? "
124
+ f"{Headers.ReportDebugDescription} ({colorize_choices(Defaults.boolean_choices, 'true')})? "
124
125
  )
125
126
  .lower()
126
127
  .capitalize()
@@ -211,6 +212,16 @@ def run_test_cycle(
211
212
  print("Failed to install dependencies")
212
213
  return False
213
214
 
215
+ try:
216
+ duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
217
+ total_seconds = (duration + wait) * count
218
+ until_time = datetime.now() + timedelta(seconds=total_seconds)
219
+ since, until, fname, fmt, report_debug = prompt_report_arguments(
220
+ datetime.now().isoformat(), until_time.isoformat(), fname, fmt, True
221
+ )
222
+ except KeyboardInterrupt:
223
+ sys.exit("\nTest cancelled")
224
+
214
225
  try:
215
226
  app = PrerequisiteValidator(debug)
216
227
  run = app.run()
@@ -221,37 +232,33 @@ def run_test_cycle(
221
232
 
222
233
  if run or force:
223
234
  app = SleepValidator(tool_debug=debug, bios_debug=bios_debug)
224
- try:
225
- duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
226
- since, until, fname, fmt, report_debug = prompt_report_arguments(
227
- datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt, True
228
- )
229
- except KeyboardInterrupt:
230
- sys.exit("\nTest cancelled")
231
235
 
232
- app.run(
236
+ run = app.run(
233
237
  duration=duration,
234
238
  wait=wait,
235
239
  count=count,
236
240
  rand=rand,
237
241
  logind=logind,
238
242
  )
243
+ until = datetime.now()
244
+ else:
245
+ since = None
246
+ until = None
247
+
248
+ app = SleepReport(
249
+ since=since,
250
+ until=until,
251
+ fname=fname,
252
+ fmt=fmt,
253
+ tool_debug=debug,
254
+ report_debug=report_debug,
255
+ )
256
+ app.run()
239
257
 
240
- app = SleepReport(
241
- since=since,
242
- until=until,
243
- fname=fname,
244
- fmt=fmt,
245
- tool_debug=debug,
246
- report_debug=report_debug,
247
- )
248
- app.run()
249
-
250
- # open report in browser if it's html
251
- display_report_file(fname, fmt)
258
+ # open report in browser if it's html
259
+ display_report_file(fname, fmt)
252
260
 
253
- return True
254
- return False
261
+ return True
255
262
 
256
263
 
257
264
  def install(debug) -> None:
@@ -308,10 +315,8 @@ def parse_args():
308
315
  test_cmd.add_argument(
309
316
  "--random",
310
317
  action="store_true",
311
- help=(
312
- "Run sleep cycles for random durations and wait, using the "
313
- "--duration and --wait arguments as an upper bound",
314
- ),
318
+ help="Run sleep cycles for random durations and wait, using the "
319
+ "--duration and --wait arguments as an upper bound",
315
320
  )
316
321
  test_cmd.add_argument(
317
322
  "--force",
@@ -384,7 +389,9 @@ def parse_args():
384
389
  help="Enable tool debug logging",
385
390
  )
386
391
 
387
- subparsers.add_parser("version", help="Show version information")
392
+ parser.add_argument(
393
+ "--version", action="store_true", help="Show version information"
394
+ )
388
395
 
389
396
  if len(sys.argv) == 1:
390
397
  parser.print_help(sys.stderr)
@@ -393,7 +400,7 @@ def parse_args():
393
400
  return parser.parse_args()
394
401
 
395
402
 
396
- def main():
403
+ def main() -> None | int:
397
404
  """Main function"""
398
405
  args = parse_args()
399
406
  ret = False
@@ -426,10 +433,12 @@ def main():
426
433
  args.logind,
427
434
  args.bios_debug,
428
435
  )
429
- elif args.action == "version":
436
+ elif args.version:
430
437
  print(version())
431
- return True
438
+ return
432
439
  else:
433
440
  sys.exit("no action specified")
434
441
  show_log_info()
435
- return ret
442
+ if ret is False:
443
+ return 1
444
+ return