amd-debug-tools 0.2.0__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.
Potentially problematic release.
This version of amd-debug-tools might be problematic. Click here for more details.
- amd_debug/__init__.py +45 -0
- amd_debug/acpi.py +107 -0
- amd_debug/bash/amd-s2idle +89 -0
- amd_debug/battery.py +87 -0
- amd_debug/bios.py +138 -0
- amd_debug/common.py +324 -0
- amd_debug/database.py +331 -0
- amd_debug/failures.py +588 -0
- amd_debug/installer.py +404 -0
- amd_debug/kernel.py +389 -0
- amd_debug/prerequisites.py +1215 -0
- amd_debug/pstate.py +314 -0
- amd_debug/s2idle-hook +72 -0
- amd_debug/s2idle.py +406 -0
- amd_debug/sleep_report.py +453 -0
- amd_debug/templates/html +427 -0
- amd_debug/templates/md +39 -0
- amd_debug/templates/stdout +13 -0
- amd_debug/templates/txt +23 -0
- amd_debug/validator.py +863 -0
- amd_debug/wake.py +111 -0
- amd_debug_tools-0.2.0.dist-info/METADATA +180 -0
- amd_debug_tools-0.2.0.dist-info/RECORD +27 -0
- amd_debug_tools-0.2.0.dist-info/WHEEL +5 -0
- amd_debug_tools-0.2.0.dist-info/entry_points.txt +4 -0
- amd_debug_tools-0.2.0.dist-info/licenses/LICENSE +19 -0
- amd_debug_tools-0.2.0.dist-info/top_level.txt +1 -0
amd_debug/installer.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from amd_debug.common import (
|
|
9
|
+
print_color,
|
|
10
|
+
get_distro,
|
|
11
|
+
read_file,
|
|
12
|
+
systemd_in_use,
|
|
13
|
+
show_log_info,
|
|
14
|
+
fatal_error,
|
|
15
|
+
relaunch_sudo,
|
|
16
|
+
AmdTool,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Headers: # pylint: disable=too-few-public-methods
|
|
21
|
+
"""Headers for the script"""
|
|
22
|
+
|
|
23
|
+
MissingIasl = "ACPI extraction tool `iasl` is missing"
|
|
24
|
+
MissingEthtool = "Ethtool is missing"
|
|
25
|
+
InstallAction = "Attempting to install"
|
|
26
|
+
MissingFwupd = "Firmware update library `fwupd` is missing"
|
|
27
|
+
MissingPyudev = "Udev access library `pyudev` is missing"
|
|
28
|
+
MissingPackaging = "Python library `packaging` is missing"
|
|
29
|
+
MissingPandas = "Data library `pandas` is missing"
|
|
30
|
+
MissingTabulate = "Data library `tabulate` is missing"
|
|
31
|
+
MissingJinja2 = "Template library `jinja2` is missing"
|
|
32
|
+
MissingSeaborn = "Data visualization library `seaborn` is missing"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DistroPackage:
|
|
36
|
+
"""Base class for distro packages"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, deb, rpm, arch, message):
|
|
39
|
+
self.deb = deb
|
|
40
|
+
self.rpm = rpm
|
|
41
|
+
self.arch = arch
|
|
42
|
+
self.message = message
|
|
43
|
+
|
|
44
|
+
def install(self):
|
|
45
|
+
"""Install the package for a given distro"""
|
|
46
|
+
relaunch_sudo()
|
|
47
|
+
show_install_message(self.message)
|
|
48
|
+
dist = get_distro()
|
|
49
|
+
if dist in ("ubuntu", "debian"):
|
|
50
|
+
if not self.deb:
|
|
51
|
+
return False
|
|
52
|
+
installer = ["apt", "install", self.deb]
|
|
53
|
+
elif dist == "fedora":
|
|
54
|
+
if not self.rpm:
|
|
55
|
+
return False
|
|
56
|
+
release = read_file("/usr/lib/os-release")
|
|
57
|
+
variant = None
|
|
58
|
+
for line in release.split("\n"):
|
|
59
|
+
if line.startswith("VARIANT_ID"):
|
|
60
|
+
variant = line.split("=")[-1]
|
|
61
|
+
if variant != "workstation":
|
|
62
|
+
return False
|
|
63
|
+
installer = ["dnf", "install", "-y", self.rpm]
|
|
64
|
+
elif dist == "arch" or os.path.exists("/etc/arch-release"):
|
|
65
|
+
if not self.arch:
|
|
66
|
+
return False
|
|
67
|
+
installer = ["pacman", "-Sy", self.arch]
|
|
68
|
+
else:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
subprocess.check_call(installer)
|
|
73
|
+
except subprocess.CalledProcessError as e:
|
|
74
|
+
fatal_error(e)
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class PyUdevPackage(DistroPackage):
|
|
79
|
+
"""Pyudev package"""
|
|
80
|
+
|
|
81
|
+
def __init__(self):
|
|
82
|
+
super().__init__(
|
|
83
|
+
deb="python3-pyudev",
|
|
84
|
+
rpm="python3-pyudev",
|
|
85
|
+
arch="python-pyudev",
|
|
86
|
+
message=Headers.MissingPyudev,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class PackagingPackage(DistroPackage):
|
|
91
|
+
"""Packaging package"""
|
|
92
|
+
|
|
93
|
+
def __init__(self):
|
|
94
|
+
super().__init__(
|
|
95
|
+
deb="python3-packaging",
|
|
96
|
+
rpm=None,
|
|
97
|
+
arch="python-packaging",
|
|
98
|
+
message=Headers.MissingPackaging,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class PandasPackage(DistroPackage):
|
|
103
|
+
"""Class for handling the pandas package"""
|
|
104
|
+
|
|
105
|
+
def __init__(self):
|
|
106
|
+
super().__init__(
|
|
107
|
+
deb="python3-pandas",
|
|
108
|
+
rpm="python3-pandas",
|
|
109
|
+
arch="python-pandas",
|
|
110
|
+
message=Headers.MissingPandas,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TabulatePackage(DistroPackage):
|
|
115
|
+
"""Class for handling the tabulate package"""
|
|
116
|
+
|
|
117
|
+
def __init__(self):
|
|
118
|
+
super().__init__(
|
|
119
|
+
deb="python3-tabulate",
|
|
120
|
+
rpm="python3-tabulate",
|
|
121
|
+
arch="python-tabulate",
|
|
122
|
+
message=Headers.MissingTabulate,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class Jinja2Package(DistroPackage):
|
|
127
|
+
"""Class for handling the jinja2 package"""
|
|
128
|
+
|
|
129
|
+
def __init__(self):
|
|
130
|
+
super().__init__(
|
|
131
|
+
deb="python3-jinja2",
|
|
132
|
+
rpm="python3-jinja2",
|
|
133
|
+
arch="python-jinja",
|
|
134
|
+
message=Headers.MissingJinja2,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class SeabornPackage(DistroPackage):
|
|
139
|
+
"""Class for handling the seaborn package"""
|
|
140
|
+
|
|
141
|
+
def __init__(self):
|
|
142
|
+
super().__init__(
|
|
143
|
+
deb="python3-seaborn",
|
|
144
|
+
rpm="python3-seaborn",
|
|
145
|
+
arch="python-seaborn",
|
|
146
|
+
message=Headers.MissingSeaborn,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class IaslPackage(DistroPackage):
|
|
151
|
+
"""Iasl package"""
|
|
152
|
+
|
|
153
|
+
def __init__(self):
|
|
154
|
+
super().__init__(
|
|
155
|
+
deb="acpica-tools",
|
|
156
|
+
rpm="acpica-tools",
|
|
157
|
+
arch="acpica",
|
|
158
|
+
message=Headers.MissingIasl,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class EthtoolPackage(DistroPackage):
|
|
163
|
+
"""Ethtool package"""
|
|
164
|
+
|
|
165
|
+
def __init__(self):
|
|
166
|
+
super().__init__(
|
|
167
|
+
deb="ethtool",
|
|
168
|
+
rpm="ethtool",
|
|
169
|
+
arch="ethtool",
|
|
170
|
+
message=Headers.MissingEthtool,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class FwupdPackage(DistroPackage):
|
|
175
|
+
"""Fwupd package"""
|
|
176
|
+
|
|
177
|
+
def __init__(self):
|
|
178
|
+
super().__init__(
|
|
179
|
+
deb="gir1.2-fwupd-2.0",
|
|
180
|
+
rpm=None,
|
|
181
|
+
arch=None,
|
|
182
|
+
message=Headers.MissingFwupd,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def show_install_message(message):
|
|
187
|
+
"""Show an install message"""
|
|
188
|
+
action = Headers.InstallAction
|
|
189
|
+
message = f"{message}. {action}."
|
|
190
|
+
print_color(message, "👀")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class Installer(AmdTool):
|
|
194
|
+
"""Installer class"""
|
|
195
|
+
|
|
196
|
+
def __init__(self, tool_debug):
|
|
197
|
+
log_prefix = "installer" if tool_debug else None
|
|
198
|
+
super().__init__(log_prefix)
|
|
199
|
+
self.systemd = systemd_in_use()
|
|
200
|
+
self.systemd_path = os.path.join("/", "lib", "systemd", "system-sleep")
|
|
201
|
+
|
|
202
|
+
# test if fwupd can report device firmware versions
|
|
203
|
+
try:
|
|
204
|
+
import gi # pylint: disable=import-outside-toplevel
|
|
205
|
+
from gi.repository import (
|
|
206
|
+
GLib as _,
|
|
207
|
+
) # pylint: disable=import-outside-toplevel
|
|
208
|
+
|
|
209
|
+
gi.require_version("Fwupd", "2.0")
|
|
210
|
+
from gi.repository import (
|
|
211
|
+
Fwupd as _,
|
|
212
|
+
) # pylint: disable=import-outside-toplevel
|
|
213
|
+
|
|
214
|
+
self.fwupd = True
|
|
215
|
+
except ImportError:
|
|
216
|
+
self.fwupd = False
|
|
217
|
+
except ValueError:
|
|
218
|
+
self.fwupd = False
|
|
219
|
+
self.requirements = []
|
|
220
|
+
|
|
221
|
+
def set_requirements(self, *args):
|
|
222
|
+
"""Set the requirements for the installer"""
|
|
223
|
+
self.requirements = args
|
|
224
|
+
|
|
225
|
+
def install_dependencies(self) -> bool:
|
|
226
|
+
"""Install the dependencies"""
|
|
227
|
+
if "iasl" in self.requirements:
|
|
228
|
+
try:
|
|
229
|
+
iasl = subprocess.call(["iasl", "-v"], stdout=subprocess.DEVNULL) == 0
|
|
230
|
+
except FileNotFoundError:
|
|
231
|
+
iasl = False
|
|
232
|
+
if not iasl:
|
|
233
|
+
package = IaslPackage()
|
|
234
|
+
if not package.install():
|
|
235
|
+
return False
|
|
236
|
+
if "ethtool" in self.requirements:
|
|
237
|
+
try:
|
|
238
|
+
ethtool = (
|
|
239
|
+
subprocess.call(["ethtool", "-h"], stdout=subprocess.DEVNULL) == 0
|
|
240
|
+
)
|
|
241
|
+
except FileNotFoundError:
|
|
242
|
+
ethtool = False
|
|
243
|
+
if not ethtool:
|
|
244
|
+
package = EthtoolPackage()
|
|
245
|
+
if not package.install():
|
|
246
|
+
return False
|
|
247
|
+
if "fwupd" in self.requirements and not self.fwupd:
|
|
248
|
+
package = FwupdPackage()
|
|
249
|
+
if not package.install():
|
|
250
|
+
return False
|
|
251
|
+
if "pyudev" in self.requirements:
|
|
252
|
+
try:
|
|
253
|
+
import pyudev as _ # pylint: disable=import-outside-toplevel
|
|
254
|
+
except ModuleNotFoundError:
|
|
255
|
+
package = PyUdevPackage()
|
|
256
|
+
if not package.install():
|
|
257
|
+
return False
|
|
258
|
+
if "packaging" in self.requirements:
|
|
259
|
+
try:
|
|
260
|
+
import packaging as _ # pylint: disable=import-outside-toplevel
|
|
261
|
+
except ModuleNotFoundError:
|
|
262
|
+
package = PackagingPackage()
|
|
263
|
+
if not package.install():
|
|
264
|
+
return False
|
|
265
|
+
if "pandas" in self.requirements:
|
|
266
|
+
try:
|
|
267
|
+
import pandas as _ # pylint: disable=import-outside-toplevel
|
|
268
|
+
except ModuleNotFoundError:
|
|
269
|
+
package = PandasPackage()
|
|
270
|
+
if not package.install():
|
|
271
|
+
return False
|
|
272
|
+
if "tabulate" in self.requirements:
|
|
273
|
+
try:
|
|
274
|
+
import tabulate as _ # pylint: disable=import-outside-toplevel
|
|
275
|
+
except ModuleNotFoundError:
|
|
276
|
+
package = TabulatePackage()
|
|
277
|
+
if not package.install():
|
|
278
|
+
return False
|
|
279
|
+
if "jinja2" in self.requirements:
|
|
280
|
+
try:
|
|
281
|
+
import jinja2 as _ # pylint: disable=import-outside-toplevel
|
|
282
|
+
except ModuleNotFoundError:
|
|
283
|
+
package = Jinja2Package()
|
|
284
|
+
if not package.install():
|
|
285
|
+
return False
|
|
286
|
+
if "seaborn" in self.requirements:
|
|
287
|
+
try:
|
|
288
|
+
import seaborn as _ # pylint: disable=import-outside-toplevel
|
|
289
|
+
except ModuleNotFoundError:
|
|
290
|
+
package = SeabornPackage()
|
|
291
|
+
if not package.install():
|
|
292
|
+
return False
|
|
293
|
+
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
def _check_systemd(self) -> bool:
|
|
297
|
+
"""Check if the systemd path exists"""
|
|
298
|
+
if not os.path.exists(self.systemd_path):
|
|
299
|
+
print_color(
|
|
300
|
+
f"Systemd path does not exist: {self.systemd_path}",
|
|
301
|
+
"❌",
|
|
302
|
+
)
|
|
303
|
+
return os.path.exists(self.systemd_path)
|
|
304
|
+
|
|
305
|
+
def remove(self) -> bool:
|
|
306
|
+
"""Remove the amd-s2idle hook"""
|
|
307
|
+
if self._check_systemd():
|
|
308
|
+
f = "s2idle-hook"
|
|
309
|
+
t = os.path.join(self.systemd_path, f)
|
|
310
|
+
os.remove(t)
|
|
311
|
+
print_color(
|
|
312
|
+
f"Removed {f} from {self.systemd_path}",
|
|
313
|
+
"✅",
|
|
314
|
+
)
|
|
315
|
+
else:
|
|
316
|
+
print_color("Systemd path does not exist, not removing hook", "🚦")
|
|
317
|
+
f = "amd-s2idle"
|
|
318
|
+
d = os.path.join(
|
|
319
|
+
"/",
|
|
320
|
+
"usr",
|
|
321
|
+
"local",
|
|
322
|
+
"share",
|
|
323
|
+
"bash-completion",
|
|
324
|
+
"completions",
|
|
325
|
+
)
|
|
326
|
+
t = os.path.join(d, f)
|
|
327
|
+
if os.path.exists(t):
|
|
328
|
+
os.remove(t)
|
|
329
|
+
print_color(f"Removed {f} from {d}", "✅")
|
|
330
|
+
return True
|
|
331
|
+
|
|
332
|
+
def install(self) -> bool:
|
|
333
|
+
"""Install the amd-s2idle hook"""
|
|
334
|
+
import amd_debug # pylint: disable=import-outside-toplevel
|
|
335
|
+
|
|
336
|
+
d = os.path.dirname(amd_debug.__file__)
|
|
337
|
+
if self._check_systemd():
|
|
338
|
+
f = "s2idle-hook"
|
|
339
|
+
s = os.path.join(d, f)
|
|
340
|
+
t = os.path.join(self.systemd_path, f)
|
|
341
|
+
with open(s, "r", encoding="utf-8") as r:
|
|
342
|
+
with open(t, "w", encoding="utf-8") as w:
|
|
343
|
+
for line in r.readlines():
|
|
344
|
+
if 'parser.add_argument("--path"' in line:
|
|
345
|
+
line = line.replace(
|
|
346
|
+
'default=""',
|
|
347
|
+
f"default=\"{os.path.join(d, '..')}\"",
|
|
348
|
+
)
|
|
349
|
+
w.write(line)
|
|
350
|
+
os.chmod(t, 0o755)
|
|
351
|
+
print_color(
|
|
352
|
+
f"Installed {f} to {self.systemd_path}",
|
|
353
|
+
"✅",
|
|
354
|
+
)
|
|
355
|
+
else:
|
|
356
|
+
print_color("Systemd path does not exist, not installing hook", "🚦")
|
|
357
|
+
f = "amd-s2idle"
|
|
358
|
+
s = os.path.join(d, "bash", f)
|
|
359
|
+
t = os.path.join(
|
|
360
|
+
"/",
|
|
361
|
+
"usr",
|
|
362
|
+
"local",
|
|
363
|
+
"share",
|
|
364
|
+
"bash-completion",
|
|
365
|
+
"completions",
|
|
366
|
+
)
|
|
367
|
+
os.makedirs(t, exist_ok=True)
|
|
368
|
+
shutil.copy(s, t)
|
|
369
|
+
print_color(f"Installed {f} to {t}", "✅")
|
|
370
|
+
return True
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def parse_args():
|
|
374
|
+
"""Parse command line arguments"""
|
|
375
|
+
parser = argparse.ArgumentParser(
|
|
376
|
+
description="Install dependencies for AMD debug tools",
|
|
377
|
+
)
|
|
378
|
+
parser.add_argument(
|
|
379
|
+
"--tool-debug",
|
|
380
|
+
action="store_true",
|
|
381
|
+
help="Enable tool debug logging",
|
|
382
|
+
)
|
|
383
|
+
return parser.parse_args()
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def install_dep_superset() -> bool:
|
|
387
|
+
"""Install all python supserset dependencies"""
|
|
388
|
+
args = parse_args()
|
|
389
|
+
tool = Installer(tool_debug=args.tool_debug)
|
|
390
|
+
tool.set_requirements(
|
|
391
|
+
"iasl",
|
|
392
|
+
"ethtool",
|
|
393
|
+
"jinja2",
|
|
394
|
+
"pyudev",
|
|
395
|
+
"packaging",
|
|
396
|
+
"pandas",
|
|
397
|
+
"seaborn",
|
|
398
|
+
"tabulate",
|
|
399
|
+
)
|
|
400
|
+
ret = tool.install_dependencies()
|
|
401
|
+
if ret:
|
|
402
|
+
print_color("All dependencies installed", "✅")
|
|
403
|
+
show_log_info()
|
|
404
|
+
return ret
|