amd-debug-tools 0.2.0__py3-none-any.whl → 0.2.2__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.
- amd_debug/common.py +48 -0
- amd_debug/display.py +34 -0
- amd_debug/failures.py +13 -1
- amd_debug/installer.py +69 -5
- amd_debug/prerequisites.py +157 -56
- amd_debug/s2idle.py +46 -17
- amd_debug/sleep_report.py +2 -2
- amd_debug/templates/md +0 -7
- amd_debug/validator.py +3 -5
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/METADATA +4 -3
- amd_debug_tools-0.2.2.dist-info/RECORD +45 -0
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/WHEEL +1 -1
- amd_debug_tools-0.2.2.dist-info/top_level.txt +18 -0
- launcher.py +35 -0
- test_acpi.py +90 -0
- test_batteries.py +92 -0
- test_bios.py +250 -0
- test_common.py +444 -0
- test_database.py +284 -0
- test_display.py +143 -0
- test_failures.py +146 -0
- test_installer.py +281 -0
- test_kernel.py +205 -0
- test_launcher.py +53 -0
- test_prerequisites.py +1935 -0
- test_pstate.py +164 -0
- test_s2idle.py +868 -0
- test_sleep_report.py +167 -0
- test_validator.py +723 -0
- test_wake.py +216 -0
- amd_debug_tools-0.2.0.dist-info/RECORD +0 -27
- amd_debug_tools-0.2.0.dist-info/top_level.txt +0 -1
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/entry_points.txt +0 -0
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/licenses/LICENSE +0 -0
amd_debug/common.py
CHANGED
|
@@ -14,6 +14,7 @@ import struct
|
|
|
14
14
|
import subprocess
|
|
15
15
|
import re
|
|
16
16
|
import sys
|
|
17
|
+
from ast import literal_eval
|
|
17
18
|
from datetime import date, timedelta
|
|
18
19
|
|
|
19
20
|
|
|
@@ -81,6 +82,15 @@ def print_color(message, group) -> None:
|
|
|
81
82
|
print(f"{prefix}{color}{message}{suffix}")
|
|
82
83
|
|
|
83
84
|
|
|
85
|
+
def colorize_choices(choices, default) -> str:
|
|
86
|
+
"""Output a list of choices with colors, where the default is highlighted"""
|
|
87
|
+
if default not in choices:
|
|
88
|
+
raise ValueError(f"Default choice '{default}' not in choices")
|
|
89
|
+
choices = [c for c in choices if c != default]
|
|
90
|
+
choices = [f"{Colors.OK}{default}{Colors.ENDC}"] + choices
|
|
91
|
+
return ", ".join(choices)
|
|
92
|
+
|
|
93
|
+
|
|
84
94
|
def fatal_error(message):
|
|
85
95
|
"""Prints a fatal error message and exits"""
|
|
86
96
|
_configure_log(None)
|
|
@@ -88,6 +98,21 @@ def fatal_error(message):
|
|
|
88
98
|
sys.exit(1)
|
|
89
99
|
|
|
90
100
|
|
|
101
|
+
def apply_prefix_wrapper(header, message):
|
|
102
|
+
"""Apply a prefix to wrap a newline delimitted message"""
|
|
103
|
+
s = f"{header.strip()}\n"
|
|
104
|
+
lines = message.strip().split("\n")
|
|
105
|
+
for i, line in enumerate(lines):
|
|
106
|
+
line = line.strip()
|
|
107
|
+
if not line:
|
|
108
|
+
continue
|
|
109
|
+
if i == len(lines) - 1:
|
|
110
|
+
s += f"└─ {line}\n"
|
|
111
|
+
continue
|
|
112
|
+
s += f"│ {line}\n"
|
|
113
|
+
return s
|
|
114
|
+
|
|
115
|
+
|
|
91
116
|
def show_log_info():
|
|
92
117
|
"""Show log information"""
|
|
93
118
|
logger = logging.getLogger()
|
|
@@ -254,6 +279,19 @@ def get_property_pyudev(properties, key, fallback=""):
|
|
|
254
279
|
return ""
|
|
255
280
|
|
|
256
281
|
|
|
282
|
+
def find_ip_version(base_path, kind, hw_ver) -> bool:
|
|
283
|
+
"""Determine if an IP version is present on the system"""
|
|
284
|
+
b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
|
|
285
|
+
for key, expected_value in hw_ver.items():
|
|
286
|
+
p = os.path.join(b, key)
|
|
287
|
+
if not os.path.exists(p):
|
|
288
|
+
return False
|
|
289
|
+
v = int(read_file(p))
|
|
290
|
+
if v != expected_value:
|
|
291
|
+
return False
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
|
|
257
295
|
def read_msr(msr, cpu):
|
|
258
296
|
"""Read a Model-Specific Register (MSR) value from the CPU."""
|
|
259
297
|
p = f"/dev/cpu/{cpu}/msr"
|
|
@@ -284,6 +322,16 @@ def running_ssh():
|
|
|
284
322
|
return "SSH_CLIENT" in os.environ or "SSH_TTY" in os.environ
|
|
285
323
|
|
|
286
324
|
|
|
325
|
+
def convert_string_to_bool(str_value) -> bool:
|
|
326
|
+
"""convert a string to a boolean value"""
|
|
327
|
+
try:
|
|
328
|
+
value = literal_eval(str_value)
|
|
329
|
+
except (SyntaxError, ValueError):
|
|
330
|
+
value = None
|
|
331
|
+
sys.exit(f"Invalid entry: {str_value}")
|
|
332
|
+
return bool(value)
|
|
333
|
+
|
|
334
|
+
|
|
287
335
|
def _git_describe() -> str:
|
|
288
336
|
"""Get the git description of the current commit"""
|
|
289
337
|
try:
|
amd_debug/display.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""Display analysis"""
|
|
4
|
+
import os
|
|
5
|
+
from pyudev import Context
|
|
6
|
+
|
|
7
|
+
from amd_debug.common import read_file
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Display:
|
|
11
|
+
"""Display analysis"""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.pyudev = Context()
|
|
15
|
+
self.edid = {}
|
|
16
|
+
|
|
17
|
+
for dev in self.pyudev.list_devices(subsystem="drm"):
|
|
18
|
+
if not "card" in dev.device_path:
|
|
19
|
+
continue
|
|
20
|
+
p = os.path.join(dev.sys_path, "status")
|
|
21
|
+
if not os.path.exists(p):
|
|
22
|
+
continue
|
|
23
|
+
f = read_file(p)
|
|
24
|
+
if f != "connected":
|
|
25
|
+
continue
|
|
26
|
+
p = os.path.join(dev.sys_path, "enabled")
|
|
27
|
+
f = read_file(p)
|
|
28
|
+
if f != "enabled":
|
|
29
|
+
continue
|
|
30
|
+
self.edid[dev.sys_name] = os.path.join(dev.sys_path, "edid")
|
|
31
|
+
|
|
32
|
+
def get_edid(self) -> list:
|
|
33
|
+
"""Get the path for EDID data for all connected displays"""
|
|
34
|
+
return self.edid
|
amd_debug/failures.py
CHANGED
|
@@ -416,7 +416,7 @@ class LowHardwareSleepResidency(S0i3Failure):
|
|
|
416
416
|
super().__init__()
|
|
417
417
|
self.description = "System had low hardware sleep residency"
|
|
418
418
|
self.explanation = (
|
|
419
|
-
f"The system was asleep for {timedelta(seconds=duration)}, but only spent {percent:.2%} "
|
|
419
|
+
f"The system was asleep for {timedelta(seconds=duration)}, but only spent {percent/100:.2%} "
|
|
420
420
|
"of this time in a hardware sleep state. In sleep cycles that are at least "
|
|
421
421
|
"60 seconds long it's expected you spend above 90 percent of the cycle in "
|
|
422
422
|
"hardware sleep."
|
|
@@ -586,3 +586,15 @@ class RogAllyMcuPowerSave(S0i3Failure):
|
|
|
586
586
|
"The MCU powersave feature is disabled which will cause problems "
|
|
587
587
|
"with the controller after suspend/resume."
|
|
588
588
|
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
class DmcubTooOld(S0i3Failure):
|
|
592
|
+
"""DMCUB microcode is too old"""
|
|
593
|
+
|
|
594
|
+
def __init__(self, current, expected):
|
|
595
|
+
super().__init__()
|
|
596
|
+
self.description = "DMCUB microcode is too old"
|
|
597
|
+
self.explanation = (
|
|
598
|
+
f"The DMCUB microcode version {hex(current)} is older than the"
|
|
599
|
+
f"minimum suggested version {hex(expected)}."
|
|
600
|
+
)
|
amd_debug/installer.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/python3
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
+
"""
|
|
5
|
+
This module contains installer support for amd-debug-tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
4
8
|
import argparse
|
|
5
9
|
import os
|
|
6
10
|
import shutil
|
|
@@ -21,6 +25,8 @@ class Headers: # pylint: disable=too-few-public-methods
|
|
|
21
25
|
"""Headers for the script"""
|
|
22
26
|
|
|
23
27
|
MissingIasl = "ACPI extraction tool `iasl` is missing"
|
|
28
|
+
MissingEdidDecode = "EDID decoding tool `edid-decode` is missing"
|
|
29
|
+
MissingDiEdidDecode = "EDID decoding tool `di-edid-decode` is missing"
|
|
24
30
|
MissingEthtool = "Ethtool is missing"
|
|
25
31
|
InstallAction = "Attempting to install"
|
|
26
32
|
MissingFwupd = "Firmware update library `fwupd` is missing"
|
|
@@ -30,6 +36,7 @@ class Headers: # pylint: disable=too-few-public-methods
|
|
|
30
36
|
MissingTabulate = "Data library `tabulate` is missing"
|
|
31
37
|
MissingJinja2 = "Template library `jinja2` is missing"
|
|
32
38
|
MissingSeaborn = "Data visualization library `seaborn` is missing"
|
|
39
|
+
UnknownDistro = "No distro installation support available, install manually"
|
|
33
40
|
|
|
34
41
|
|
|
35
42
|
class DistroPackage:
|
|
@@ -66,7 +73,8 @@ class DistroPackage:
|
|
|
66
73
|
return False
|
|
67
74
|
installer = ["pacman", "-Sy", self.arch]
|
|
68
75
|
else:
|
|
69
|
-
|
|
76
|
+
print_color(Headers.UnknownDistro, "👀")
|
|
77
|
+
return True
|
|
70
78
|
|
|
71
79
|
try:
|
|
72
80
|
subprocess.check_call(installer)
|
|
@@ -171,6 +179,30 @@ class EthtoolPackage(DistroPackage):
|
|
|
171
179
|
)
|
|
172
180
|
|
|
173
181
|
|
|
182
|
+
class EdidDecodePackage(DistroPackage):
|
|
183
|
+
"""Edid-Decode package"""
|
|
184
|
+
|
|
185
|
+
def __init__(self):
|
|
186
|
+
super().__init__(
|
|
187
|
+
deb="edid-decode",
|
|
188
|
+
rpm="edid-decode",
|
|
189
|
+
arch=None,
|
|
190
|
+
message=Headers.MissingEdidDecode,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class DisplayInfoPackage(DistroPackage):
|
|
195
|
+
"""display info package"""
|
|
196
|
+
|
|
197
|
+
def __init__(self):
|
|
198
|
+
super().__init__(
|
|
199
|
+
deb="libdisplay-info-bin",
|
|
200
|
+
rpm="libdisplay-info",
|
|
201
|
+
arch="libdisplay-info",
|
|
202
|
+
message=Headers.MissingDiEdidDecode,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
174
206
|
class FwupdPackage(DistroPackage):
|
|
175
207
|
"""Fwupd package"""
|
|
176
208
|
|
|
@@ -202,14 +234,14 @@ class Installer(AmdTool):
|
|
|
202
234
|
# test if fwupd can report device firmware versions
|
|
203
235
|
try:
|
|
204
236
|
import gi # pylint: disable=import-outside-toplevel
|
|
205
|
-
from gi.repository import (
|
|
237
|
+
from gi.repository import ( # pylint: disable=import-outside-toplevel
|
|
206
238
|
GLib as _,
|
|
207
|
-
)
|
|
239
|
+
)
|
|
208
240
|
|
|
209
241
|
gi.require_version("Fwupd", "2.0")
|
|
210
|
-
from gi.repository import (
|
|
242
|
+
from gi.repository import ( # pylint: disable=import-outside-toplevel
|
|
211
243
|
Fwupd as _,
|
|
212
|
-
)
|
|
244
|
+
)
|
|
213
245
|
|
|
214
246
|
self.fwupd = True
|
|
215
247
|
except ImportError:
|
|
@@ -244,6 +276,37 @@ class Installer(AmdTool):
|
|
|
244
276
|
package = EthtoolPackage()
|
|
245
277
|
if not package.install():
|
|
246
278
|
return False
|
|
279
|
+
# can be satisified by either edid-decode or di-edid-decode
|
|
280
|
+
if "edid-decode" in self.requirements:
|
|
281
|
+
try:
|
|
282
|
+
di_edid = (
|
|
283
|
+
subprocess.call(
|
|
284
|
+
["di-edid-decode", "--help"],
|
|
285
|
+
stdout=subprocess.DEVNULL,
|
|
286
|
+
stderr=subprocess.DEVNULL,
|
|
287
|
+
)
|
|
288
|
+
== 255
|
|
289
|
+
)
|
|
290
|
+
except FileNotFoundError:
|
|
291
|
+
di_edid = False
|
|
292
|
+
try:
|
|
293
|
+
edid = (
|
|
294
|
+
subprocess.call(
|
|
295
|
+
["edid-decode", "--help"], stdout=subprocess.DEVNULL
|
|
296
|
+
)
|
|
297
|
+
== 255
|
|
298
|
+
)
|
|
299
|
+
except FileNotFoundError:
|
|
300
|
+
edid = False
|
|
301
|
+
if not di_edid and not edid:
|
|
302
|
+
# try to install di-edid-decode first
|
|
303
|
+
package = DisplayInfoPackage()
|
|
304
|
+
if package.install():
|
|
305
|
+
return True
|
|
306
|
+
# fall back to edid-decode instead
|
|
307
|
+
package = EdidDecodePackage()
|
|
308
|
+
if not package.install():
|
|
309
|
+
return False
|
|
247
310
|
if "fwupd" in self.requirements and not self.fwupd:
|
|
248
311
|
package = FwupdPackage()
|
|
249
312
|
if not package.install():
|
|
@@ -396,6 +459,7 @@ def install_dep_superset() -> bool:
|
|
|
396
459
|
"pandas",
|
|
397
460
|
"seaborn",
|
|
398
461
|
"tabulate",
|
|
462
|
+
"edid-decode",
|
|
399
463
|
)
|
|
400
464
|
ret = tool.install_dependencies()
|
|
401
465
|
if ret:
|