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/s2idle.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""s2idle analysis tool"""
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sqlite3
|
|
9
|
+
|
|
10
|
+
from datetime import date, timedelta, datetime
|
|
11
|
+
from amd_debug.common import is_root, relaunch_sudo, show_log_info, version, running_ssh
|
|
12
|
+
|
|
13
|
+
from amd_debug.validator import SleepValidator
|
|
14
|
+
from amd_debug.installer import Installer
|
|
15
|
+
from amd_debug.prerequisites import PrerequisiteValidator
|
|
16
|
+
from amd_debug.sleep_report import SleepReport
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Defaults:
|
|
20
|
+
"""Default values for the script"""
|
|
21
|
+
|
|
22
|
+
duration = 10
|
|
23
|
+
wait = 4
|
|
24
|
+
count = 1
|
|
25
|
+
since = date.today() - timedelta(days=60)
|
|
26
|
+
until = date.today() + timedelta(days=1)
|
|
27
|
+
format_choices = ["txt", "md", "html", "stdout"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Headers:
|
|
31
|
+
"""Headers for the script"""
|
|
32
|
+
|
|
33
|
+
DurationDescription = "How long should suspend cycles last (seconds)"
|
|
34
|
+
WaitDescription = "How long to wait in between suspend cycles (seconds)"
|
|
35
|
+
CountDescription = "How many suspend cycles to run"
|
|
36
|
+
SinceDescription = "What date to start report data"
|
|
37
|
+
UntilDescription = "What date to end report data"
|
|
38
|
+
LogDescription = "Location of log file"
|
|
39
|
+
ReportFileDescription = "Location of report file"
|
|
40
|
+
FormatDescription = "What format to output the report in"
|
|
41
|
+
MaxDurationDescription = "What is the maximum suspend cycle length (seconds)"
|
|
42
|
+
MaxWaitDescription = "What is the maximum time between suspend cycles (seconds)"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def display_report_file(fname, fmt) -> None:
|
|
46
|
+
"""Display report file"""
|
|
47
|
+
if fmt != "html":
|
|
48
|
+
return
|
|
49
|
+
if not is_root():
|
|
50
|
+
subprocess.call(["xdg-open", fname])
|
|
51
|
+
return
|
|
52
|
+
user = os.environ.get("SUDO_USER")
|
|
53
|
+
if user:
|
|
54
|
+
# ensure that xdg tools will know how to display the file (user may need to call tool with sudo -E)
|
|
55
|
+
if os.environ.get("XDG_SESSION_TYPE"):
|
|
56
|
+
subprocess.call(["sudo", "-E", "-u", user, "xdg-open", fname])
|
|
57
|
+
else:
|
|
58
|
+
print(
|
|
59
|
+
f"To display report automatically in browser launch tool with '-E' argument (Example: sudo -E {sys.argv[0]})"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_report_file(report_file, extension) -> str:
|
|
64
|
+
"""Prompt user for report file"""
|
|
65
|
+
if extension == "stdout":
|
|
66
|
+
return None
|
|
67
|
+
if not report_file:
|
|
68
|
+
return f"amd-s2idle-report-{date.today()}.{extension}"
|
|
69
|
+
return report_file
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_report_format() -> str:
|
|
73
|
+
"""Get report format"""
|
|
74
|
+
if running_ssh():
|
|
75
|
+
return "txt"
|
|
76
|
+
return "html"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def prompt_report_arguments(since, until, fname, fmt) -> str:
|
|
80
|
+
"""Prompt user for report configuration"""
|
|
81
|
+
if not since:
|
|
82
|
+
default = Defaults.since
|
|
83
|
+
since = input(f"{Headers.SinceDescription} (default {default})? ")
|
|
84
|
+
if not since:
|
|
85
|
+
since = default.isoformat()
|
|
86
|
+
try:
|
|
87
|
+
since = datetime.fromisoformat(since)
|
|
88
|
+
except ValueError as e:
|
|
89
|
+
sys.exit(f"Invalid date, use YYYY-MM-DD: {e}")
|
|
90
|
+
if not until:
|
|
91
|
+
default = Defaults.until
|
|
92
|
+
until = input(f"{Headers.SinceDescription} (default {default})? ")
|
|
93
|
+
if not until:
|
|
94
|
+
until = default.isoformat()
|
|
95
|
+
try:
|
|
96
|
+
until = datetime.fromisoformat(until)
|
|
97
|
+
except ValueError as e:
|
|
98
|
+
sys.exit(f"Invalid date, use YYYY-MM-DD: {e}")
|
|
99
|
+
|
|
100
|
+
if not fmt:
|
|
101
|
+
fmt = input(f"{Headers.FormatDescription} (default {get_report_format()})? ")
|
|
102
|
+
if not fmt:
|
|
103
|
+
fmt = get_report_format()
|
|
104
|
+
if fmt not in Defaults.format_choices:
|
|
105
|
+
sys.exit(f"Invalid format: {fmt}")
|
|
106
|
+
return [since, until, get_report_file(fname, fmt), fmt]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def prompt_test_arguments(duration, wait, count, rand) -> list:
|
|
110
|
+
"""Prompt user for test configuration"""
|
|
111
|
+
if not duration:
|
|
112
|
+
if rand:
|
|
113
|
+
question = Headers.MaxDurationDescription
|
|
114
|
+
else:
|
|
115
|
+
question = Headers.DurationDescription
|
|
116
|
+
duration = input(f"{question} (default {Defaults.duration})? ")
|
|
117
|
+
if not duration:
|
|
118
|
+
duration = Defaults.duration
|
|
119
|
+
try:
|
|
120
|
+
duration = int(duration)
|
|
121
|
+
except ValueError as e:
|
|
122
|
+
sys.exit(f"Invalid duration: {e}")
|
|
123
|
+
if not wait:
|
|
124
|
+
if rand:
|
|
125
|
+
question = Headers.MaxWaitDescription
|
|
126
|
+
else:
|
|
127
|
+
question = Headers.WaitDescription
|
|
128
|
+
wait = input(f"{question} (default {Defaults.wait})? ")
|
|
129
|
+
if not wait:
|
|
130
|
+
wait = Defaults.wait
|
|
131
|
+
try:
|
|
132
|
+
wait = int(wait)
|
|
133
|
+
except ValueError as e:
|
|
134
|
+
sys.exit(f"Invalid wait: {e}")
|
|
135
|
+
if not count:
|
|
136
|
+
count = input(f"{Headers.CountDescription} (default {Defaults.count})? ")
|
|
137
|
+
if not count:
|
|
138
|
+
count = Defaults.count
|
|
139
|
+
try:
|
|
140
|
+
count = int(count)
|
|
141
|
+
except ValueError as e:
|
|
142
|
+
sys.exit(f"Invalid count: {e}")
|
|
143
|
+
return [duration, wait, count]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def report(since, until, fname, fmt, tool_debug, report_debug) -> bool:
|
|
147
|
+
"""Generate a report from previous sleep cycles"""
|
|
148
|
+
try:
|
|
149
|
+
since, until, fname, fmt = prompt_report_arguments(since, until, fname, fmt)
|
|
150
|
+
except KeyboardInterrupt:
|
|
151
|
+
sys.exit("\nReport generation cancelled")
|
|
152
|
+
try:
|
|
153
|
+
app = SleepReport(
|
|
154
|
+
since=since,
|
|
155
|
+
until=until,
|
|
156
|
+
fname=fname,
|
|
157
|
+
fmt=fmt,
|
|
158
|
+
tool_debug=tool_debug,
|
|
159
|
+
report_debug=report_debug,
|
|
160
|
+
)
|
|
161
|
+
except sqlite3.OperationalError as e:
|
|
162
|
+
print(f"Failed to generate report: {e}")
|
|
163
|
+
return False
|
|
164
|
+
except PermissionError as e:
|
|
165
|
+
print(f"Failed to generate report: {e}")
|
|
166
|
+
return False
|
|
167
|
+
try:
|
|
168
|
+
app.run()
|
|
169
|
+
except PermissionError as e:
|
|
170
|
+
print(f"Failed to generate report: {e}")
|
|
171
|
+
return False
|
|
172
|
+
except ValueError as e:
|
|
173
|
+
print(f"Failed to generate report: {e}")
|
|
174
|
+
return False
|
|
175
|
+
display_report_file(fname, fmt)
|
|
176
|
+
return True
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test(
|
|
180
|
+
duration, wait, count, fmt, fname, force, debug, rand, logind, bios_debug
|
|
181
|
+
) -> bool:
|
|
182
|
+
"""Run a test"""
|
|
183
|
+
app = Installer(tool_debug=debug)
|
|
184
|
+
app.set_requirements("iasl", "ethtool")
|
|
185
|
+
if not app.install_dependencies():
|
|
186
|
+
print("Failed to install dependencies")
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
app = PrerequisiteValidator(debug)
|
|
191
|
+
run = app.run()
|
|
192
|
+
except PermissionError as e:
|
|
193
|
+
print(f"Failed to run prerequisite check: {e}")
|
|
194
|
+
return False
|
|
195
|
+
app.report()
|
|
196
|
+
|
|
197
|
+
if run or force:
|
|
198
|
+
app = SleepValidator(tool_debug=debug, bios_debug=bios_debug)
|
|
199
|
+
try:
|
|
200
|
+
duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
|
|
201
|
+
since, until, fname, fmt = prompt_report_arguments(
|
|
202
|
+
datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt
|
|
203
|
+
)
|
|
204
|
+
except KeyboardInterrupt:
|
|
205
|
+
sys.exit("\nTest cancelled")
|
|
206
|
+
|
|
207
|
+
app.run(
|
|
208
|
+
duration=duration,
|
|
209
|
+
wait=wait,
|
|
210
|
+
count=count,
|
|
211
|
+
rand=rand,
|
|
212
|
+
logind=logind,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
app = SleepReport(
|
|
216
|
+
since=since,
|
|
217
|
+
until=until,
|
|
218
|
+
fname=fname,
|
|
219
|
+
fmt=fmt,
|
|
220
|
+
tool_debug=debug,
|
|
221
|
+
report_debug=True,
|
|
222
|
+
)
|
|
223
|
+
app.run()
|
|
224
|
+
|
|
225
|
+
# open report in browser if it's html
|
|
226
|
+
display_report_file(fname, fmt)
|
|
227
|
+
|
|
228
|
+
return True
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def install(debug) -> None:
|
|
233
|
+
"""Install the tool"""
|
|
234
|
+
installer = Installer(tool_debug=debug)
|
|
235
|
+
installer.set_requirements("iasl", "ethtool")
|
|
236
|
+
if not installer.install_dependencies():
|
|
237
|
+
sys.exit("Failed to install dependencies")
|
|
238
|
+
try:
|
|
239
|
+
app = PrerequisiteValidator(debug)
|
|
240
|
+
run = app.run()
|
|
241
|
+
except PermissionError as e:
|
|
242
|
+
sys.exit(f"Failed to run prerequisite check: {e}")
|
|
243
|
+
if not run:
|
|
244
|
+
app.report()
|
|
245
|
+
sys.exit("Failed to meet prerequisites")
|
|
246
|
+
|
|
247
|
+
if not installer.install():
|
|
248
|
+
sys.exit("Failed to install")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def uninstall(debug) -> None:
|
|
252
|
+
"""Uninstall the tool"""
|
|
253
|
+
app = Installer(tool_debug=debug)
|
|
254
|
+
if not app.remove():
|
|
255
|
+
sys.exit("Failed to remove")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def parse_args():
|
|
259
|
+
"""Parse command line arguments"""
|
|
260
|
+
parser = argparse.ArgumentParser(
|
|
261
|
+
description="Swiss army knife for analyzing Linux s2idle problems",
|
|
262
|
+
epilog="The tool can run an immediate test with the 'test' command or can be used to hook into systemd for building reports later.\n"
|
|
263
|
+
"All optional arguments will be prompted if needed.\n"
|
|
264
|
+
"To use non-interactively, please populate all optional arguments.",
|
|
265
|
+
)
|
|
266
|
+
subparsers = parser.add_subparsers(help="Possible commands", dest="action")
|
|
267
|
+
|
|
268
|
+
# 'test' command
|
|
269
|
+
test_cmd = subparsers.add_parser("test", help="Run amd-s2idle test and report")
|
|
270
|
+
test_cmd.add_argument("--count", help=Headers.CountDescription)
|
|
271
|
+
test_cmd.add_argument(
|
|
272
|
+
"--duration",
|
|
273
|
+
help=Headers.DurationDescription,
|
|
274
|
+
)
|
|
275
|
+
test_cmd.add_argument(
|
|
276
|
+
"--wait",
|
|
277
|
+
help=Headers.WaitDescription,
|
|
278
|
+
)
|
|
279
|
+
test_cmd.add_argument(
|
|
280
|
+
"--logind", action="store_true", help="Use logind to suspend system"
|
|
281
|
+
)
|
|
282
|
+
test_cmd.add_argument(
|
|
283
|
+
"--random",
|
|
284
|
+
action="store_true",
|
|
285
|
+
help="Run sleep cycles for random durations and wait, using the --duration and --wait arguments as an upper bound",
|
|
286
|
+
)
|
|
287
|
+
test_cmd.add_argument(
|
|
288
|
+
"--force",
|
|
289
|
+
action="store_true",
|
|
290
|
+
help="Run suspend test even if prerequisites failed",
|
|
291
|
+
)
|
|
292
|
+
test_cmd.add_argument(
|
|
293
|
+
"--format",
|
|
294
|
+
choices=Defaults.format_choices,
|
|
295
|
+
help="Report format",
|
|
296
|
+
)
|
|
297
|
+
test_cmd.add_argument(
|
|
298
|
+
"--tool-debug",
|
|
299
|
+
action="store_true",
|
|
300
|
+
help="Enable tool debug logging",
|
|
301
|
+
)
|
|
302
|
+
test_cmd.add_argument(
|
|
303
|
+
"--bios-debug",
|
|
304
|
+
action="store_true",
|
|
305
|
+
help="Enable BIOS debug logging instead of notify logging",
|
|
306
|
+
)
|
|
307
|
+
test_cmd.add_argument("--report-file", help=Headers.ReportFileDescription)
|
|
308
|
+
|
|
309
|
+
# 'report' command
|
|
310
|
+
report_cmd = subparsers.add_parser(
|
|
311
|
+
"report", help="Generate amd-s2idle report from previous runs"
|
|
312
|
+
)
|
|
313
|
+
report_cmd.add_argument(
|
|
314
|
+
"--since",
|
|
315
|
+
help=Headers.SinceDescription,
|
|
316
|
+
)
|
|
317
|
+
report_cmd.add_argument(
|
|
318
|
+
"--until",
|
|
319
|
+
default=Defaults.until.isoformat(),
|
|
320
|
+
help=Headers.UntilDescription,
|
|
321
|
+
)
|
|
322
|
+
report_cmd.add_argument("--report-file", help=Headers.ReportFileDescription)
|
|
323
|
+
report_cmd.add_argument(
|
|
324
|
+
"--format",
|
|
325
|
+
choices=Defaults.format_choices,
|
|
326
|
+
help="Report format",
|
|
327
|
+
)
|
|
328
|
+
report_cmd.add_argument(
|
|
329
|
+
"--tool-debug",
|
|
330
|
+
action="store_true",
|
|
331
|
+
help="Enable tool debug logging",
|
|
332
|
+
)
|
|
333
|
+
report_cmd.add_argument(
|
|
334
|
+
"--report-debug",
|
|
335
|
+
action="store_true",
|
|
336
|
+
help="Include debug messages in report (WARNING: can significantly increase report size)",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# if running in a venv, install/uninstall hook options
|
|
340
|
+
if sys.prefix != sys.base_prefix:
|
|
341
|
+
install_cmd = subparsers.add_parser(
|
|
342
|
+
"install", help="Install systemd s2idle hook"
|
|
343
|
+
)
|
|
344
|
+
uninstall_cmd = subparsers.add_parser(
|
|
345
|
+
"uninstall", help="Uninstall systemd s2idle hook"
|
|
346
|
+
)
|
|
347
|
+
install_cmd.add_argument(
|
|
348
|
+
"--tool-debug",
|
|
349
|
+
action="store_true",
|
|
350
|
+
help="Enable tool debug logging",
|
|
351
|
+
)
|
|
352
|
+
uninstall_cmd.add_argument(
|
|
353
|
+
"--tool-debug",
|
|
354
|
+
action="store_true",
|
|
355
|
+
help="Enable tool debug logging",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
subparsers.add_parser("version", help="Show version information")
|
|
359
|
+
|
|
360
|
+
if len(sys.argv) == 1:
|
|
361
|
+
parser.print_help(sys.stderr)
|
|
362
|
+
sys.exit(1)
|
|
363
|
+
|
|
364
|
+
return parser.parse_args()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def main():
|
|
368
|
+
"""Main function"""
|
|
369
|
+
args = parse_args()
|
|
370
|
+
ret = False
|
|
371
|
+
if args.action == "install":
|
|
372
|
+
relaunch_sudo()
|
|
373
|
+
install(args.tool_debug)
|
|
374
|
+
elif args.action == "uninstall":
|
|
375
|
+
relaunch_sudo()
|
|
376
|
+
uninstall(args.tool_debug)
|
|
377
|
+
elif args.action == "report":
|
|
378
|
+
ret = report(
|
|
379
|
+
args.since,
|
|
380
|
+
args.until,
|
|
381
|
+
args.report_file,
|
|
382
|
+
args.format,
|
|
383
|
+
args.tool_debug,
|
|
384
|
+
args.report_debug,
|
|
385
|
+
)
|
|
386
|
+
elif args.action == "test":
|
|
387
|
+
relaunch_sudo()
|
|
388
|
+
ret = test(
|
|
389
|
+
args.duration,
|
|
390
|
+
args.wait,
|
|
391
|
+
args.count,
|
|
392
|
+
args.format,
|
|
393
|
+
args.report_file,
|
|
394
|
+
args.force,
|
|
395
|
+
args.tool_debug,
|
|
396
|
+
args.random,
|
|
397
|
+
args.logind,
|
|
398
|
+
args.bios_debug,
|
|
399
|
+
)
|
|
400
|
+
elif args.action == "version":
|
|
401
|
+
print(version())
|
|
402
|
+
return True
|
|
403
|
+
else:
|
|
404
|
+
sys.exit("no action specified")
|
|
405
|
+
show_log_info()
|
|
406
|
+
return ret
|