amd-debug-tools 0.2.2__py3-none-any.whl → 0.2.3__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/failures.py +1 -1
- amd_debug/kernel.py +8 -0
- amd_debug/prerequisites.py +14 -27
- amd_debug/s2idle.py +27 -26
- amd_debug/sleep_report.py +128 -85
- amd_debug/templates/html +4 -2
- amd_debug/validator.py +17 -10
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.3.dist-info}/METADATA +1 -1
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.3.dist-info}/RECORD +17 -17
- test_prerequisites.py +10 -17
- test_s2idle.py +38 -8
- test_sleep_report.py +29 -0
- test_validator.py +116 -7
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.3.dist-info}/WHEEL +0 -0
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.3.dist-info}/entry_points.txt +0 -0
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.3.dist-info}/top_level.txt +0 -0
amd_debug/failures.py
CHANGED
|
@@ -142,7 +142,7 @@ class MissingDriver(S0i3Failure):
|
|
|
142
142
|
self.description = f"{slot} driver is missing"
|
|
143
143
|
self.explanation = (
|
|
144
144
|
f"No driver has been bound to PCI device {slot} "
|
|
145
|
-
"Without a driver, the hardware may be able to enter a low power
|
|
145
|
+
"Without a driver, the hardware may be able to enter a low power "
|
|
146
146
|
"state, but there may be spurious wake up events."
|
|
147
147
|
)
|
|
148
148
|
|
amd_debug/kernel.py
CHANGED
|
@@ -19,6 +19,7 @@ def get_kernel_command_line() -> str:
|
|
|
19
19
|
"apparmor",
|
|
20
20
|
"audit",
|
|
21
21
|
"auto",
|
|
22
|
+
"bluetooth.disable_ertm",
|
|
22
23
|
"boot",
|
|
23
24
|
"BOOT_IMAGE",
|
|
24
25
|
"console",
|
|
@@ -29,6 +30,7 @@ def get_kernel_command_line() -> str:
|
|
|
29
30
|
"earlycon",
|
|
30
31
|
"earlyprintk",
|
|
31
32
|
"ether",
|
|
33
|
+
"init",
|
|
32
34
|
"initrd",
|
|
33
35
|
"ip",
|
|
34
36
|
"LANG",
|
|
@@ -47,7 +49,9 @@ def get_kernel_command_line() -> str:
|
|
|
47
49
|
"nfs.nfs4_unique_id",
|
|
48
50
|
"nfsroot",
|
|
49
51
|
"noplymouth",
|
|
52
|
+
"nowatchdog",
|
|
50
53
|
"ostree",
|
|
54
|
+
"preempt",
|
|
51
55
|
"quiet",
|
|
52
56
|
"rd.dm.uuid",
|
|
53
57
|
"rd.luks.allow-discards",
|
|
@@ -66,12 +70,15 @@ def get_kernel_command_line() -> str:
|
|
|
66
70
|
"ro",
|
|
67
71
|
"root",
|
|
68
72
|
"rootflags",
|
|
73
|
+
"rootfstype",
|
|
69
74
|
"roothash",
|
|
70
75
|
"rw",
|
|
71
76
|
"security",
|
|
77
|
+
"selinux",
|
|
72
78
|
"showopts",
|
|
73
79
|
"splash",
|
|
74
80
|
"swap",
|
|
81
|
+
"systemd.machine_id",
|
|
75
82
|
"systemd.mask",
|
|
76
83
|
"systemd.show_status",
|
|
77
84
|
"systemd.unit",
|
|
@@ -82,6 +89,7 @@ def get_kernel_command_line() -> str:
|
|
|
82
89
|
"verbose",
|
|
83
90
|
"vt.handoff",
|
|
84
91
|
"zfs",
|
|
92
|
+
"zswap.enabled",
|
|
85
93
|
]
|
|
86
94
|
# remove anything that starts with something in filtered from cmdline
|
|
87
95
|
return " ".join([x for x in cmdline.split() if not x.startswith(tuple(filtered))])
|
amd_debug/prerequisites.py
CHANGED
|
@@ -134,22 +134,19 @@ class PrerequisiteValidator(AmdTool):
|
|
|
134
134
|
if len(edids) == 0:
|
|
135
135
|
self.db.record_debug("No EDID data found")
|
|
136
136
|
return True
|
|
137
|
-
|
|
138
137
|
for name, p in edids.items():
|
|
139
138
|
output = None
|
|
140
|
-
for
|
|
139
|
+
for tool in ["di-edid-decode", "edid-decode"]:
|
|
141
140
|
try:
|
|
142
|
-
cmd = [
|
|
141
|
+
cmd = [tool, p]
|
|
143
142
|
output = subprocess.check_output(
|
|
144
143
|
cmd, stderr=subprocess.DEVNULL
|
|
145
|
-
).decode("utf-8")
|
|
144
|
+
).decode("utf-8", errors="ignore")
|
|
146
145
|
break
|
|
147
146
|
except FileNotFoundError:
|
|
148
147
|
self.db.record_debug(f"{cmd} not installed")
|
|
149
148
|
except subprocess.CalledProcessError as e:
|
|
150
|
-
|
|
151
|
-
f"failed to capture edid with {cmd}: {e.output}"
|
|
152
|
-
)
|
|
149
|
+
pass
|
|
153
150
|
if not output:
|
|
154
151
|
self.db.record_prereq("Failed to capture EDID table", "👀")
|
|
155
152
|
else:
|
|
@@ -245,7 +242,6 @@ class PrerequisiteValidator(AmdTool):
|
|
|
245
242
|
for dev in self.pyudev.list_devices(subsystem="pci", DRIVER="nvme"):
|
|
246
243
|
# https://git.kernel.org/torvalds/c/e79a10652bbd3
|
|
247
244
|
if minimum_kernel(6, 10):
|
|
248
|
-
self.db.record_debug("New enough kernel to avoid NVME check")
|
|
249
245
|
break
|
|
250
246
|
pci_slot_name = dev.properties["PCI_SLOT_NAME"]
|
|
251
247
|
vendor = dev.properties.get("ID_VENDOR_FROM_DATABASE", "")
|
|
@@ -319,7 +315,6 @@ class PrerequisiteValidator(AmdTool):
|
|
|
319
315
|
# not needed to check in newer kernels
|
|
320
316
|
# see https://github.com/torvalds/linux/commit/77f1972bdcf7513293e8bbe376b9fe837310ee9c
|
|
321
317
|
if minimum_kernel(6, 10):
|
|
322
|
-
self.db.record_debug("New enough kernel to avoid HSMP check")
|
|
323
318
|
return True
|
|
324
319
|
f = os.path.join("/", "boot", f"config-{platform.uname().release}")
|
|
325
320
|
if os.path.exists(f):
|
|
@@ -527,7 +522,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
527
522
|
f"{keys['sys_vendor']} {keys['product_name']} ({keys['product_family']})",
|
|
528
523
|
"💻",
|
|
529
524
|
)
|
|
530
|
-
debug_str = "DMI
|
|
525
|
+
debug_str = "DMI|value\n"
|
|
531
526
|
for key, value in keys.items():
|
|
532
527
|
if (
|
|
533
528
|
"product_name" in key
|
|
@@ -535,7 +530,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
535
530
|
or "product_family" in key
|
|
536
531
|
):
|
|
537
532
|
continue
|
|
538
|
-
debug_str += f"{key}
|
|
533
|
+
debug_str += f"{key}| {value}\n"
|
|
539
534
|
self.db.record_debug(debug_str)
|
|
540
535
|
return True
|
|
541
536
|
|
|
@@ -916,7 +911,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
916
911
|
devices = []
|
|
917
912
|
for dev in self.pyudev.list_devices(subsystem="pci"):
|
|
918
913
|
devices.append(dev)
|
|
919
|
-
debug_str = "PCI
|
|
914
|
+
debug_str = "PCI Slot | Vendor | Class | ID | ACPI path\n"
|
|
920
915
|
for dev in devices:
|
|
921
916
|
pci_id = dev.properties["PCI_ID"].lower()
|
|
922
917
|
pci_slot_name = dev.properties["PCI_SLOT_NAME"]
|
|
@@ -939,15 +934,12 @@ class PrerequisiteValidator(AmdTool):
|
|
|
939
934
|
p = os.path.join(dev.sys_path, "firmware_node", "path")
|
|
940
935
|
if os.path.exists(p):
|
|
941
936
|
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
937
|
else:
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
938
|
+
acpi = ""
|
|
939
|
+
debug_str += (
|
|
940
|
+
f"{prefix}{pci_slot_name} | "
|
|
941
|
+
f"{database_vendor} | {database_class} | {pci_id} | {acpi}\n"
|
|
942
|
+
)
|
|
951
943
|
if debug_str:
|
|
952
944
|
self.db.record_debug(debug_str)
|
|
953
945
|
|
|
@@ -964,12 +956,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
964
956
|
if status == 0:
|
|
965
957
|
continue
|
|
966
958
|
devices.append(dev)
|
|
967
|
-
debug_str = "ACPI name
|
|
959
|
+
debug_str = "ACPI name | ACPI path | Kernel driver\n"
|
|
968
960
|
for dev in devices:
|
|
969
|
-
if dev == devices[-1]:
|
|
970
|
-
prefix = "└─"
|
|
971
|
-
else:
|
|
972
|
-
prefix = "│ "
|
|
973
961
|
p = os.path.join(dev.sys_path, "path")
|
|
974
962
|
pth = read_file(p)
|
|
975
963
|
p = os.path.join(dev.sys_path, "physical_node", "driver")
|
|
@@ -977,7 +965,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
977
965
|
driver = os.path.basename(os.readlink(p))
|
|
978
966
|
else:
|
|
979
967
|
driver = None
|
|
980
|
-
debug_str += f"{
|
|
968
|
+
debug_str += f"{dev.sys_name} | {pth} | {driver}\n"
|
|
981
969
|
if debug_str:
|
|
982
970
|
self.db.record_debug(debug_str)
|
|
983
971
|
return True
|
|
@@ -1157,7 +1145,6 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1157
1145
|
if not found_iommu:
|
|
1158
1146
|
self.db.record_prereq("IOMMU disabled", "✅")
|
|
1159
1147
|
return True
|
|
1160
|
-
debug_str += "DMA protection:\n"
|
|
1161
1148
|
p = os.path.join("/", "sys", "firmware", "acpi", "tables", "IVRS")
|
|
1162
1149
|
with open(p, "rb") as f:
|
|
1163
1150
|
data = f.read()
|
amd_debug/s2idle.py
CHANGED
|
@@ -211,6 +211,14 @@ def run_test_cycle(
|
|
|
211
211
|
print("Failed to install dependencies")
|
|
212
212
|
return False
|
|
213
213
|
|
|
214
|
+
try:
|
|
215
|
+
duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
|
|
216
|
+
since, until, fname, fmt, report_debug = prompt_report_arguments(
|
|
217
|
+
datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt, True
|
|
218
|
+
)
|
|
219
|
+
except KeyboardInterrupt:
|
|
220
|
+
sys.exit("\nTest cancelled")
|
|
221
|
+
|
|
214
222
|
try:
|
|
215
223
|
app = PrerequisiteValidator(debug)
|
|
216
224
|
run = app.run()
|
|
@@ -221,37 +229,32 @@ def run_test_cycle(
|
|
|
221
229
|
|
|
222
230
|
if run or force:
|
|
223
231
|
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
232
|
|
|
232
|
-
app.run(
|
|
233
|
+
run = app.run(
|
|
233
234
|
duration=duration,
|
|
234
235
|
wait=wait,
|
|
235
236
|
count=count,
|
|
236
237
|
rand=rand,
|
|
237
238
|
logind=logind,
|
|
238
239
|
)
|
|
240
|
+
else:
|
|
241
|
+
since = None
|
|
242
|
+
until = None
|
|
243
|
+
|
|
244
|
+
app = SleepReport(
|
|
245
|
+
since=since,
|
|
246
|
+
until=until,
|
|
247
|
+
fname=fname,
|
|
248
|
+
fmt=fmt,
|
|
249
|
+
tool_debug=debug,
|
|
250
|
+
report_debug=report_debug,
|
|
251
|
+
)
|
|
252
|
+
app.run()
|
|
239
253
|
|
|
240
|
-
|
|
241
|
-
|
|
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)
|
|
254
|
+
# open report in browser if it's html
|
|
255
|
+
display_report_file(fname, fmt)
|
|
252
256
|
|
|
253
|
-
|
|
254
|
-
return False
|
|
257
|
+
return True
|
|
255
258
|
|
|
256
259
|
|
|
257
260
|
def install(debug) -> None:
|
|
@@ -308,10 +311,8 @@ def parse_args():
|
|
|
308
311
|
test_cmd.add_argument(
|
|
309
312
|
"--random",
|
|
310
313
|
action="store_true",
|
|
311
|
-
help=
|
|
312
|
-
|
|
313
|
-
"--duration and --wait arguments as an upper bound",
|
|
314
|
-
),
|
|
314
|
+
help="Run sleep cycles for random durations and wait, using the "
|
|
315
|
+
"--duration and --wait arguments as an upper bound",
|
|
315
316
|
)
|
|
316
317
|
test_cmd.add_argument(
|
|
317
318
|
"--force",
|
amd_debug/sleep_report.py
CHANGED
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import re
|
|
6
6
|
import math
|
|
7
7
|
from datetime import datetime, timedelta
|
|
8
|
+
import numpy as np
|
|
8
9
|
from tabulate import tabulate
|
|
9
10
|
from jinja2 import Environment, FileSystemLoader
|
|
10
11
|
import pandas as pd
|
|
@@ -73,6 +74,8 @@ def format_percent(val):
|
|
|
73
74
|
|
|
74
75
|
def format_timedelta(val):
|
|
75
76
|
"""Format seconds as a nicer format"""
|
|
77
|
+
if math.isnan(val):
|
|
78
|
+
val = 0
|
|
76
79
|
return str(timedelta(seconds=val))
|
|
77
80
|
|
|
78
81
|
|
|
@@ -97,8 +100,23 @@ class SleepReport(AmdTool):
|
|
|
97
100
|
self.debug = report_debug
|
|
98
101
|
self.format = fmt
|
|
99
102
|
self.failures = []
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
if since and until:
|
|
104
|
+
self.df = self.db.report_summary_dataframe(self.since, self.until)
|
|
105
|
+
self.pre_process_dataframe()
|
|
106
|
+
else:
|
|
107
|
+
self.df = pd.DataFrame(
|
|
108
|
+
columns=[
|
|
109
|
+
"t0",
|
|
110
|
+
"t1",
|
|
111
|
+
"requested",
|
|
112
|
+
"hw",
|
|
113
|
+
"b0",
|
|
114
|
+
"b1",
|
|
115
|
+
"full",
|
|
116
|
+
"wake_irq",
|
|
117
|
+
"gpio",
|
|
118
|
+
]
|
|
119
|
+
)
|
|
102
120
|
self.battery_svg = None
|
|
103
121
|
self.hwsleep_svg = None
|
|
104
122
|
|
|
@@ -136,6 +154,7 @@ class SleepReport(AmdTool):
|
|
|
136
154
|
self.df["Duration"] = self.df["t1"].apply(format_as_seconds) - self.df[
|
|
137
155
|
"t0"
|
|
138
156
|
].apply(format_as_seconds)
|
|
157
|
+
self.df["Duration"] = self.df["Duration"].replace(0, np.nan)
|
|
139
158
|
self.df["Hardware Sleep"] = (self.df["hw"] / self.df["Duration"]).apply(
|
|
140
159
|
parse_hw_sleep
|
|
141
160
|
)
|
|
@@ -187,7 +206,8 @@ class SleepReport(AmdTool):
|
|
|
187
206
|
format_watts
|
|
188
207
|
)
|
|
189
208
|
|
|
190
|
-
def
|
|
209
|
+
def convert_table_dataframe(self, content):
|
|
210
|
+
"""Convert a table like dataframe to an HTML table"""
|
|
191
211
|
header = False
|
|
192
212
|
rows = []
|
|
193
213
|
for line in content.split("\n"):
|
|
@@ -196,17 +216,25 @@ class SleepReport(AmdTool):
|
|
|
196
216
|
if header:
|
|
197
217
|
continue
|
|
198
218
|
header = True
|
|
219
|
+
line = line.strip("│")
|
|
220
|
+
line = line.replace("├─", "└─")
|
|
199
221
|
if "|" in line:
|
|
200
222
|
# first column missing '|'
|
|
201
223
|
rows.append(line.replace("\t", "|"))
|
|
202
224
|
columns = [row.split("|") for row in rows]
|
|
203
225
|
df = pd.DataFrame(columns[1:], columns=columns[0])
|
|
204
|
-
return df.to_html(index=False,
|
|
226
|
+
return df.to_html(index=False, justify="center", col_space=30)
|
|
205
227
|
|
|
206
228
|
def get_prereq_data(self):
|
|
207
229
|
"""Get the prereq data"""
|
|
208
230
|
prereq = []
|
|
209
231
|
prereq_debug = []
|
|
232
|
+
tables = [
|
|
233
|
+
"int|active",
|
|
234
|
+
"ACPI name",
|
|
235
|
+
"PCI Slot",
|
|
236
|
+
"DMI|value",
|
|
237
|
+
]
|
|
210
238
|
ts = self.db.get_last_prereq_ts()
|
|
211
239
|
if not ts:
|
|
212
240
|
return [], "", []
|
|
@@ -216,23 +244,24 @@ class SleepReport(AmdTool):
|
|
|
216
244
|
if self.debug:
|
|
217
245
|
for row in self.db.report_debug(t0):
|
|
218
246
|
content = row[0]
|
|
219
|
-
if self.format == "html" and
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)
|
|
247
|
+
if self.format == "html" and [
|
|
248
|
+
table for table in tables if table in content
|
|
249
|
+
]:
|
|
250
|
+
content = self.convert_table_dataframe(content)
|
|
251
|
+
prereq_debug.append({"data": f"{content.strip()}"})
|
|
224
252
|
return prereq, t0, prereq_debug
|
|
225
253
|
|
|
226
254
|
def get_cycle_data(self):
|
|
227
255
|
"""Get the cycle data"""
|
|
228
256
|
cycles = []
|
|
229
257
|
debug = []
|
|
258
|
+
tables = ["Wakeup Source"]
|
|
230
259
|
num = 0
|
|
231
260
|
for cycle in self.df["Start Time"]:
|
|
232
261
|
if self.format == "html":
|
|
233
262
|
data = ""
|
|
234
263
|
for line in self.db.report_cycle_data(cycle).split("\n"):
|
|
235
|
-
data += "<p>{line}</p>"
|
|
264
|
+
data += f"<p>{line}</p>"
|
|
236
265
|
cycles.append({"cycle_num": num, "data": data})
|
|
237
266
|
else:
|
|
238
267
|
cycles.append([num, self.db.report_cycle_data(cycle)])
|
|
@@ -240,7 +269,12 @@ class SleepReport(AmdTool):
|
|
|
240
269
|
messages = []
|
|
241
270
|
priorities = []
|
|
242
271
|
for row in self.db.report_debug(cycle):
|
|
243
|
-
|
|
272
|
+
content = row[0]
|
|
273
|
+
if self.format == "html" and [
|
|
274
|
+
table for table in tables if table in content
|
|
275
|
+
]:
|
|
276
|
+
content = self.convert_table_dataframe(content)
|
|
277
|
+
messages.append(content)
|
|
244
278
|
priorities.append(get_log_priority(row[1]))
|
|
245
279
|
debug.append(
|
|
246
280
|
{"cycle_num": num, "messages": messages, "priorities": priorities}
|
|
@@ -265,58 +299,69 @@ class SleepReport(AmdTool):
|
|
|
265
299
|
prereq, prereq_date, prereq_debug = self.get_prereq_data()
|
|
266
300
|
|
|
267
301
|
# Load the cycle and/or debug data
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
self.failures,
|
|
278
|
-
headers=["Cycle", "Problem", "Explanation"],
|
|
279
|
-
tablefmt="pipe",
|
|
302
|
+
if not self.df.empty:
|
|
303
|
+
cycles, debug = self.get_cycle_data()
|
|
304
|
+
|
|
305
|
+
self.post_process_dataframe()
|
|
306
|
+
failures = None
|
|
307
|
+
if self.format == "md":
|
|
308
|
+
summary = self.df.to_markdown(floatfmt=".02f")
|
|
309
|
+
cycle_data = tabulate(
|
|
310
|
+
cycles, headers=["Cycle", "data"], tablefmt="pipe"
|
|
280
311
|
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
headers=
|
|
290
|
-
|
|
312
|
+
if self.failures:
|
|
313
|
+
failures = tabulate(
|
|
314
|
+
self.failures,
|
|
315
|
+
headers=["Cycle", "Problem", "Explanation"],
|
|
316
|
+
tablefmt="pipe",
|
|
317
|
+
)
|
|
318
|
+
elif self.format == "txt":
|
|
319
|
+
summary = tabulate(
|
|
320
|
+
self.df, headers=self.df.columns, tablefmt="fancy_grid"
|
|
321
|
+
)
|
|
322
|
+
cycle_data = tabulate(
|
|
323
|
+
cycles, headers=["Cycle", "data"], tablefmt="fancy_grid"
|
|
291
324
|
)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
"\n"
|
|
298
|
-
):
|
|
299
|
-
if "<tr>" in line:
|
|
300
|
-
line = line.replace(
|
|
301
|
-
"<tr>",
|
|
302
|
-
'<tr class="row-low" onclick="pick_summary_cycle(%d)">' % row,
|
|
325
|
+
if self.failures:
|
|
326
|
+
failures = tabulate(
|
|
327
|
+
self.failures,
|
|
328
|
+
headers=["Cycle", "Problem", "Explanation"],
|
|
329
|
+
tablefmt="fancy_grid",
|
|
303
330
|
)
|
|
304
|
-
|
|
305
|
-
summary
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
331
|
+
elif self.format == "html":
|
|
332
|
+
summary = ""
|
|
333
|
+
row = 0
|
|
334
|
+
# we will use javascript to highlight the high values
|
|
335
|
+
for line in self.df.to_html(
|
|
336
|
+
table_id="summary", render_links=True
|
|
337
|
+
).split("\n"):
|
|
338
|
+
if "<tr>" in line:
|
|
339
|
+
line = line.replace(
|
|
340
|
+
"<tr>",
|
|
341
|
+
f'<tr class="row-low" onclick="pick_summary_cycle({row})">',
|
|
342
|
+
)
|
|
343
|
+
row = row + 1
|
|
344
|
+
summary += line
|
|
345
|
+
cycle_data = cycles
|
|
346
|
+
failures = self.failures
|
|
347
|
+
# only show one cycle in stdout output even if we found more
|
|
316
348
|
else:
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
349
|
+
df = self.df.tail(1)
|
|
350
|
+
summary = tabulate(
|
|
351
|
+
df, headers=self.df.columns, tablefmt="fancy_grid", showindex=False
|
|
352
|
+
)
|
|
353
|
+
if cycles[-1][0] == df.index.start:
|
|
354
|
+
cycle_data = cycles[-1][-1]
|
|
355
|
+
else:
|
|
356
|
+
cycle_data = None
|
|
357
|
+
if self.failures and self.failures[-1][0] == df.index.start:
|
|
358
|
+
failures = self.failures[-1][-1]
|
|
359
|
+
else:
|
|
360
|
+
cycles = []
|
|
361
|
+
debug = []
|
|
362
|
+
cycle_data = []
|
|
363
|
+
summary = "No sleep cycles found in the database."
|
|
364
|
+
failures = None
|
|
320
365
|
|
|
321
366
|
# let it burn
|
|
322
367
|
context = {
|
|
@@ -333,7 +378,7 @@ class SleepReport(AmdTool):
|
|
|
333
378
|
"failures": failures,
|
|
334
379
|
}
|
|
335
380
|
if self.fname:
|
|
336
|
-
with open(self.fname, "w") as f:
|
|
381
|
+
with open(self.fname, "w", encoding="utf-8") as f:
|
|
337
382
|
f.write(template.render(context))
|
|
338
383
|
if "SUDO_UID" in os.environ:
|
|
339
384
|
os.chown(
|
|
@@ -345,30 +390,30 @@ class SleepReport(AmdTool):
|
|
|
345
390
|
|
|
346
391
|
def build_battery_chart(self):
|
|
347
392
|
"""Build a battery chart using matplotlib and seaborn"""
|
|
348
|
-
import matplotlib.pyplot as plt
|
|
349
|
-
import seaborn as sns
|
|
350
|
-
import io
|
|
393
|
+
import matplotlib.pyplot as plt # pylint: disable=import-outside-toplevel
|
|
394
|
+
import seaborn as sns # pylint: disable=import-outside-toplevel
|
|
395
|
+
import io # pylint: disable=import-outside-toplevel
|
|
351
396
|
|
|
352
397
|
if "Battery Ave Rate" not in self.df.columns:
|
|
353
398
|
return
|
|
354
399
|
|
|
355
400
|
plt.set_loglevel("warning")
|
|
356
|
-
|
|
357
|
-
|
|
401
|
+
_fig, ax1 = plt.subplots()
|
|
402
|
+
ax1.plot(
|
|
358
403
|
self.df["Battery Ave Rate"], color="green", label="Charge/Discharge Rate"
|
|
359
404
|
)
|
|
360
405
|
|
|
361
406
|
ax2 = ax1.twinx()
|
|
362
|
-
|
|
407
|
+
sns.barplot(
|
|
363
408
|
x=self.df.index,
|
|
364
409
|
y=self.df["Battery Delta"],
|
|
365
410
|
color="grey",
|
|
366
411
|
label="Battery Change",
|
|
367
412
|
alpha=0.3,
|
|
368
413
|
)
|
|
369
|
-
|
|
370
|
-
if
|
|
371
|
-
ax1.set_xticks(range(0, len(self.df.index),
|
|
414
|
+
max_range = int(len(self.df.index) / 10)
|
|
415
|
+
if max_range:
|
|
416
|
+
ax1.set_xticks(range(0, len(self.df.index), max_range))
|
|
372
417
|
ax1.set_xlabel("Cycle")
|
|
373
418
|
ax1.set_ylabel("Rate (Watts)")
|
|
374
419
|
ax2.set_ylabel("Battery Change (%)")
|
|
@@ -385,20 +430,20 @@ class SleepReport(AmdTool):
|
|
|
385
430
|
|
|
386
431
|
def build_hw_sleep_chart(self):
|
|
387
432
|
"""Build the hardware sleep chart using matplotlib and seaborn"""
|
|
388
|
-
import matplotlib.pyplot as plt
|
|
389
|
-
import seaborn as sns
|
|
390
|
-
import io
|
|
433
|
+
import matplotlib.pyplot as plt # pylint: disable=import-outside-toplevel
|
|
434
|
+
import seaborn as sns # pylint: disable=import-outside-toplevel
|
|
435
|
+
import io # pylint: disable=import-outside-toplevel
|
|
391
436
|
|
|
392
437
|
plt.set_loglevel("warning")
|
|
393
|
-
|
|
394
|
-
|
|
438
|
+
_fig, ax1 = plt.subplots()
|
|
439
|
+
ax1.plot(
|
|
395
440
|
self.df["Hardware Sleep"],
|
|
396
441
|
color="red",
|
|
397
442
|
label="Hardware Sleep",
|
|
398
443
|
)
|
|
399
444
|
|
|
400
445
|
ax2 = ax1.twinx()
|
|
401
|
-
|
|
446
|
+
sns.barplot(
|
|
402
447
|
x=self.df.index,
|
|
403
448
|
y=self.df["Duration"] / 60,
|
|
404
449
|
color="grey",
|
|
@@ -406,9 +451,9 @@ class SleepReport(AmdTool):
|
|
|
406
451
|
alpha=0.3,
|
|
407
452
|
)
|
|
408
453
|
|
|
409
|
-
|
|
410
|
-
if
|
|
411
|
-
ax1.set_xticks(range(0, len(self.df.index),
|
|
454
|
+
max_range = int(len(self.df.index) / 10)
|
|
455
|
+
if max_range:
|
|
456
|
+
ax1.set_xticks(range(0, len(self.df.index), max_range))
|
|
412
457
|
ax1.set_xlabel("Cycle")
|
|
413
458
|
ax1.set_ylabel("Percent")
|
|
414
459
|
ax2.set_yscale("log")
|
|
@@ -427,15 +472,13 @@ class SleepReport(AmdTool):
|
|
|
427
472
|
def run(self, inc_prereq=True):
|
|
428
473
|
"""Run the report"""
|
|
429
474
|
|
|
430
|
-
if self.df.empty:
|
|
431
|
-
raise ValueError(f"No data found between {self.since} and {self.until}")
|
|
432
|
-
|
|
433
475
|
characters = print_temporary_message("Building report, please wait...")
|
|
434
476
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
self.
|
|
438
|
-
|
|
477
|
+
if not self.df.empty:
|
|
478
|
+
# Build charts in the page for html format
|
|
479
|
+
if len(self.df.index) > 1 and self.format == "html":
|
|
480
|
+
self.build_battery_chart()
|
|
481
|
+
self.build_hw_sleep_chart()
|
|
439
482
|
|
|
440
483
|
# Render the template using jinja
|
|
441
484
|
msg = self.build_template(inc_prereq)
|
amd_debug/templates/html
CHANGED
|
@@ -42,12 +42,14 @@
|
|
|
42
42
|
table,
|
|
43
43
|
th,
|
|
44
44
|
td {
|
|
45
|
-
border-width:
|
|
45
|
+
border-width: 1;
|
|
46
|
+
border-collapse: collapse;
|
|
46
47
|
table-layout: fixed;
|
|
47
48
|
font-family: sans-serif;
|
|
48
49
|
letter-spacing: 0.02em;
|
|
49
50
|
color: #000000;
|
|
50
|
-
|
|
51
|
+
text-align: left;
|
|
52
|
+
padding: 3px;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
.○ {
|
amd_debug/validator.py
CHANGED
|
@@ -290,13 +290,12 @@ class SleepValidator(AmdTool):
|
|
|
290
290
|
sys_name = pnp.sys_name
|
|
291
291
|
|
|
292
292
|
name = name.replace('"', "")
|
|
293
|
-
devices.append(f"{name}
|
|
293
|
+
devices.append(f"{name}|{sys_name}|{wake_en}")
|
|
294
294
|
devices.sort()
|
|
295
|
-
|
|
295
|
+
debug_str = "Wakeup Source|Linux Device|Status\n"
|
|
296
296
|
for dev in devices:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
self.db.record_debug(f"{prefix}{dev}")
|
|
297
|
+
debug_str += f"{dev}\n"
|
|
298
|
+
self.db.record_debug(debug_str)
|
|
300
299
|
|
|
301
300
|
def capture_lid(self) -> None:
|
|
302
301
|
"""Capture lid state"""
|
|
@@ -747,16 +746,18 @@ class SleepValidator(AmdTool):
|
|
|
747
746
|
return False
|
|
748
747
|
else:
|
|
749
748
|
old = get_wakeup_count()
|
|
749
|
+
p = os.path.join("/", "sys", "power", "state")
|
|
750
|
+
fd = os.open(p, os.O_WRONLY | os.O_SYNC)
|
|
750
751
|
try:
|
|
751
|
-
|
|
752
|
-
with open(p, "w", encoding="utf-8") as w:
|
|
753
|
-
w.write("mem")
|
|
752
|
+
os.write(fd, b"mem")
|
|
754
753
|
except OSError as e:
|
|
755
754
|
new = get_wakeup_count()
|
|
756
755
|
self.db.record_cycle_data(
|
|
757
756
|
f"Failed to set suspend state ({old} -> {new}): {e}", "❌"
|
|
758
757
|
)
|
|
759
758
|
return False
|
|
759
|
+
finally:
|
|
760
|
+
os.close(fd)
|
|
760
761
|
return True
|
|
761
762
|
|
|
762
763
|
def unlock_session(self):
|
|
@@ -780,6 +781,7 @@ class SleepValidator(AmdTool):
|
|
|
780
781
|
|
|
781
782
|
def run(self, duration, count, wait, rand, logind):
|
|
782
783
|
"""Run the suspend test"""
|
|
784
|
+
min_duration = 4
|
|
783
785
|
if not count:
|
|
784
786
|
return True
|
|
785
787
|
|
|
@@ -787,8 +789,13 @@ class SleepValidator(AmdTool):
|
|
|
787
789
|
self.logind = True
|
|
788
790
|
|
|
789
791
|
if rand:
|
|
792
|
+
if duration <= min_duration:
|
|
793
|
+
print_color(f"Invalid max duration {duration}", "❌")
|
|
794
|
+
self.db.sync()
|
|
795
|
+
self.report_cycle()
|
|
796
|
+
return False
|
|
790
797
|
print_color(
|
|
791
|
-
f"Running {count} cycle random test with max duration of {duration}s and a max wait of {wait}s",
|
|
798
|
+
f"Running {count} cycle random test with min duration of {min_duration}s, max duration of {duration}s and a max wait of {wait}s",
|
|
792
799
|
"🗣️",
|
|
793
800
|
)
|
|
794
801
|
elif count > 1:
|
|
@@ -799,7 +806,7 @@ class SleepValidator(AmdTool):
|
|
|
799
806
|
)
|
|
800
807
|
for i in range(1, count + 1):
|
|
801
808
|
if rand:
|
|
802
|
-
self.requested_duration = random.randint(
|
|
809
|
+
self.requested_duration = random.randint(min_duration, duration)
|
|
803
810
|
requested_wait = random.randint(1, wait)
|
|
804
811
|
else:
|
|
805
812
|
self.requested_duration = duration
|
|
@@ -9,11 +9,11 @@ test_failures.py,sha256=H1UxXeVjhJs9-j9yas4vwAha676GX1Es7Kz8RN2B590,6845
|
|
|
9
9
|
test_installer.py,sha256=oDMCvaKqqAWjTggltacnasQ-s1gyUvXPDcNrCUGnux4,10216
|
|
10
10
|
test_kernel.py,sha256=RW-eLbae02Bhwfu1cegqA1pTj6AS5IqD5lLe-6T0Rjo,7871
|
|
11
11
|
test_launcher.py,sha256=govYHL0Cpj9d5msteV5SfR7Covft31rJuzRkDeytHcY,1461
|
|
12
|
-
test_prerequisites.py,sha256=
|
|
12
|
+
test_prerequisites.py,sha256=VXN822W-7ZZHXZkJYH5MeKsDVBC-ttUZggYlcoPjVyM,83335
|
|
13
13
|
test_pstate.py,sha256=a9oAJ9-LANX32XNQhplz6Y75VNYc__QqoSBKIrwvANg,6058
|
|
14
|
-
test_s2idle.py,sha256
|
|
15
|
-
test_sleep_report.py,sha256=
|
|
16
|
-
test_validator.py,sha256
|
|
14
|
+
test_s2idle.py,sha256=6NaqGp9VOLr_Tr3KczSvfSo3M882aYEbSvRV9xvUMcA,33534
|
|
15
|
+
test_sleep_report.py,sha256=ANuxYi_C1oSKAi4xUU2wBu4SwJtcZA7VPpazBe3_WUQ,6922
|
|
16
|
+
test_validator.py,sha256=-MfrWfhwef_aRqOSD_dJGhH0shsghhtOBgzeijzyLW4,33975
|
|
17
17
|
test_wake.py,sha256=6zi5GVFHQKU1sTWw3O5-aGriB9uu5713QLn4l2wjhpM,7152
|
|
18
18
|
amd_debug/__init__.py,sha256=aOtpIEKGLUStrh0e4qgilHW7HgF4Od-r9pOoZ87NwAM,1105
|
|
19
19
|
amd_debug/acpi.py,sha256=fkD3Sov8cRT5ryPlakRlT7Z9jiCLT9x_MPWxt3xU_tc,3161
|
|
@@ -22,24 +22,24 @@ amd_debug/bios.py,sha256=wmPKDsTZeQqsHjWpv-YHdgRNlCtFdzHQ6jJf0H3hjN8,3971
|
|
|
22
22
|
amd_debug/common.py,sha256=H9tIRlRFOMwe0d3f2-vXQeK2rJl5Z1WJzkpQM9ivpOc,10347
|
|
23
23
|
amd_debug/database.py,sha256=GkRg3cmaNceyQ2_hy0MBAlMbnTDPHo2co2o4ObWpnQg,10621
|
|
24
24
|
amd_debug/display.py,sha256=5L9x9tI_UoulHpIvuxuVASRtdXta7UCW_JjTb5StEB0,953
|
|
25
|
-
amd_debug/failures.py,sha256=
|
|
25
|
+
amd_debug/failures.py,sha256=z4O4Q-akv3xYGssSZFCqE0cDE4P9F_aw1hxil3McoD4,22910
|
|
26
26
|
amd_debug/installer.py,sha256=r6r_nVWv8qYdrqAvnAzQhRiS5unBDOkXsqUfHvFK8uM,14249
|
|
27
|
-
amd_debug/kernel.py,sha256=
|
|
28
|
-
amd_debug/prerequisites.py,sha256=
|
|
27
|
+
amd_debug/kernel.py,sha256=UAlxlXNuZxtHVtrfCmTp12YombVaUs4mizOxwuXTX2M,12038
|
|
28
|
+
amd_debug/prerequisites.py,sha256=r4_IFTL-1YcPptlt6Nump7iscRx1bBCGF33hqNQB0X0,49867
|
|
29
29
|
amd_debug/pstate.py,sha256=akGdJkIxBp0bx3AeGv6ictNxwv8m0j9vQ2IZB0Jx3dM,9518
|
|
30
30
|
amd_debug/s2idle-hook,sha256=LLiaqPtGd0qetu9n6EYxKHZaIdHpVQDONdOuSc0pfFg,1695
|
|
31
|
-
amd_debug/s2idle.py,sha256=
|
|
32
|
-
amd_debug/sleep_report.py,sha256=
|
|
33
|
-
amd_debug/validator.py,sha256=
|
|
31
|
+
amd_debug/s2idle.py,sha256=Ei5ONnJyHz9aQbstRZYnofhJ_sJOAOZQxLgIuWfvcng,13218
|
|
32
|
+
amd_debug/sleep_report.py,sha256=hhqu711AKtjeYF2xmGcejyCyyPtmq4-gC_hROUCrC0g,17317
|
|
33
|
+
amd_debug/validator.py,sha256=nZ5UpvvsABw4yTea_pZ8DHaLbSoM5VHz3uvlrdWAb34,33444
|
|
34
34
|
amd_debug/wake.py,sha256=xT8WrFrN6voCmXWo5dsn4mQ7iR2QJxHrrYBd3EREG-Q,3936
|
|
35
35
|
amd_debug/bash/amd-s2idle,sha256=g_cle1ElCJpwE4wcLezL6y-BdasDKTnNMhrtzKLE9ks,1142
|
|
36
|
-
amd_debug/templates/html,sha256=
|
|
36
|
+
amd_debug/templates/html,sha256=JfGhpmHIB2C2GItdGI1kuC8uayqEVgrpQvAWAj35eZ4,14580
|
|
37
37
|
amd_debug/templates/md,sha256=r8X2aehnH2gzj0WHYTZ5K9wAqC5y39i_3nkDORSC0uM,787
|
|
38
38
|
amd_debug/templates/stdout,sha256=hyoOJ96K2dJfnWRWhyCuariLKbEHXvs9mstV_g5aMdI,469
|
|
39
39
|
amd_debug/templates/txt,sha256=nNdsvbPFOhGdL7VA-_4k5aN3nB-6ouGQt6AsWst7T3w,649
|
|
40
|
-
amd_debug_tools-0.2.
|
|
41
|
-
amd_debug_tools-0.2.
|
|
42
|
-
amd_debug_tools-0.2.
|
|
43
|
-
amd_debug_tools-0.2.
|
|
44
|
-
amd_debug_tools-0.2.
|
|
45
|
-
amd_debug_tools-0.2.
|
|
40
|
+
amd_debug_tools-0.2.3.dist-info/licenses/LICENSE,sha256=RBlZI6r3MRGzymI2VDX2iW__D2APDbMhu_Xg5t6BWeo,1066
|
|
41
|
+
amd_debug_tools-0.2.3.dist-info/METADATA,sha256=-DdxkPvWEXMifkgAjfyNIpleo_rHFPIH8nS6v3AWJTk,6877
|
|
42
|
+
amd_debug_tools-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
43
|
+
amd_debug_tools-0.2.3.dist-info/entry_points.txt,sha256=HC11T2up0pPfroAn6Pg5M2jOZXhkWIipToJ1YPTKqu8,116
|
|
44
|
+
amd_debug_tools-0.2.3.dist-info/top_level.txt,sha256=XYjxExbUTEtiIlag_5iQvZSVOC1EIxhKM4NLklReQ0k,234
|
|
45
|
+
amd_debug_tools-0.2.3.dist-info/RECORD,,
|
test_prerequisites.py
CHANGED
|
@@ -500,9 +500,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
500
500
|
result = self.validator.map_acpi_path()
|
|
501
501
|
self.assertTrue(result)
|
|
502
502
|
self.mock_db.record_debug.assert_called_with(
|
|
503
|
-
"ACPI name
|
|
504
|
-
"│ device1: mocked_path [driver]\n"
|
|
505
|
-
"└─device2: mocked_path [driver]\n"
|
|
503
|
+
"ACPI name | ACPI path | Kernel driver\ndevice1 | mocked_path | driver\ndevice2 | mocked_path | driver\n"
|
|
506
504
|
)
|
|
507
505
|
|
|
508
506
|
@patch("amd_debug.prerequisites.os.path.exists")
|
|
@@ -545,7 +543,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
545
543
|
result = self.validator.map_acpi_path()
|
|
546
544
|
self.assertTrue(result)
|
|
547
545
|
self.mock_db.record_debug.assert_called_with(
|
|
548
|
-
"ACPI name
|
|
546
|
+
"ACPI name | ACPI path | Kernel driver\ndevice1 | mocked_path | None\n"
|
|
549
547
|
)
|
|
550
548
|
|
|
551
549
|
@patch("amd_debug.prerequisites.read_file")
|
|
@@ -569,8 +567,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
569
567
|
|
|
570
568
|
self.validator.capture_pci_acpi()
|
|
571
569
|
self.mock_db.record_debug.assert_called_with(
|
|
572
|
-
"PCI
|
|
573
|
-
"└─0000:00:1f.0 : Intel Corporation ISA bridge [1234abcd] : mocked_acpi_path\n"
|
|
570
|
+
"PCI Slot | Vendor | Class | ID | ACPI path\n└─0000:00:1f.0 | Intel Corporation | ISA bridge | 1234abcd | mocked_acpi_path\n"
|
|
574
571
|
)
|
|
575
572
|
|
|
576
573
|
@patch("amd_debug.prerequisites.read_file")
|
|
@@ -593,8 +590,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
593
590
|
|
|
594
591
|
self.validator.capture_pci_acpi()
|
|
595
592
|
self.mock_db.record_debug.assert_called_with(
|
|
596
|
-
"PCI
|
|
597
|
-
"└─0000:01:00.0 : NVIDIA Corporation VGA compatible controller [5678efgh]\n"
|
|
593
|
+
"PCI Slot | Vendor | Class | ID | ACPI path\n└─0000:01:00.0 | NVIDIA Corporation | VGA compatible controller | 5678efgh | \n"
|
|
598
594
|
)
|
|
599
595
|
|
|
600
596
|
@patch("amd_debug.prerequisites.read_file")
|
|
@@ -628,9 +624,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
628
624
|
|
|
629
625
|
self.validator.capture_pci_acpi()
|
|
630
626
|
self.mock_db.record_debug.assert_called_with(
|
|
631
|
-
"PCI
|
|
632
|
-
"│ 0000:00:1f.0 : Intel Corporation ISA bridge [1234abcd] : mocked_acpi_path\n"
|
|
633
|
-
"└─0000:01:00.0 : NVIDIA Corporation VGA compatible controller [5678efgh] : mocked_acpi_path\n"
|
|
627
|
+
"PCI Slot | Vendor | Class | ID | ACPI path\n│ 0000:00:1f.0 | Intel Corporation | ISA bridge | 1234abcd | mocked_acpi_path\n└─0000:01:00.0 | NVIDIA Corporation | VGA compatible controller | 5678efgh | mocked_acpi_path\n"
|
|
634
628
|
)
|
|
635
629
|
|
|
636
630
|
def test_capture_pci_acpi_no_devices(self):
|
|
@@ -638,7 +632,9 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
638
632
|
self.mock_pyudev.list_devices.return_value = []
|
|
639
633
|
|
|
640
634
|
self.validator.capture_pci_acpi()
|
|
641
|
-
self.mock_db.record_debug.assert_called_with(
|
|
635
|
+
self.mock_db.record_debug.assert_called_with(
|
|
636
|
+
"PCI Slot | Vendor | Class | ID | ACPI path\n"
|
|
637
|
+
)
|
|
642
638
|
|
|
643
639
|
@patch("amd_debug.prerequisites.read_file")
|
|
644
640
|
def test_check_aspm_default_policy(self, mock_read_file):
|
|
@@ -1006,7 +1002,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
1006
1002
|
"MockVendor MockProduct (MockFamily)", "💻"
|
|
1007
1003
|
)
|
|
1008
1004
|
self.mock_db.record_debug.assert_called_with(
|
|
1009
|
-
"DMI
|
|
1005
|
+
"DMI|value\nchassis_type| Desktop\n"
|
|
1010
1006
|
)
|
|
1011
1007
|
|
|
1012
1008
|
@patch("amd_debug.prerequisites.os.walk")
|
|
@@ -1034,7 +1030,7 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
1034
1030
|
self.mock_db.record_prereq.assert_called_with(
|
|
1035
1031
|
"MockVendor MockProduct (MockFamily)", "💻"
|
|
1036
1032
|
)
|
|
1037
|
-
self.mock_db.record_debug.assert_called_with("DMI
|
|
1033
|
+
self.mock_db.record_debug.assert_called_with("DMI|value\n")
|
|
1038
1034
|
|
|
1039
1035
|
@patch("amd_debug.prerequisites.os.walk")
|
|
1040
1036
|
@patch("amd_debug.prerequisites.read_file")
|
|
@@ -1710,9 +1706,6 @@ class TestPrerequisiteValidator(unittest.TestCase):
|
|
|
1710
1706
|
]
|
|
1711
1707
|
result = self.validator.check_storage()
|
|
1712
1708
|
self.assertTrue(result)
|
|
1713
|
-
self.mock_db.record_debug.assert_called_with(
|
|
1714
|
-
"New enough kernel to avoid NVME check"
|
|
1715
|
-
)
|
|
1716
1709
|
|
|
1717
1710
|
def test_check_storage_no_kernel_log(self):
|
|
1718
1711
|
"""Test check_storage when kernel log is unavailable"""
|
test_s2idle.py
CHANGED
|
@@ -455,12 +455,18 @@ class TestTestFunction(unittest.TestCase):
|
|
|
455
455
|
|
|
456
456
|
@patch("amd_debug.s2idle.Installer")
|
|
457
457
|
@patch("amd_debug.s2idle.PrerequisiteValidator")
|
|
458
|
-
@patch("amd_debug.
|
|
459
|
-
@patch("amd_debug.
|
|
458
|
+
@patch("amd_debug.s2idle.SleepValidator")
|
|
459
|
+
@patch("amd_debug.s2idle.SleepReport")
|
|
460
|
+
@patch("amd_debug.s2idle.prompt_test_arguments")
|
|
461
|
+
@patch("amd_debug.s2idle.prompt_report_arguments")
|
|
462
|
+
@patch("amd_debug.s2idle.display_report_file")
|
|
460
463
|
def test_test_prerequisite_failure(
|
|
461
464
|
self,
|
|
462
|
-
|
|
463
|
-
|
|
465
|
+
mock_display_report_file,
|
|
466
|
+
mock_prompt_report_arguments,
|
|
467
|
+
mock_prompt_test_arguments,
|
|
468
|
+
mock_sleep_report,
|
|
469
|
+
mock_sleep_validator,
|
|
464
470
|
mock_prerequisite_validator,
|
|
465
471
|
mock_installer,
|
|
466
472
|
):
|
|
@@ -471,6 +477,17 @@ class TestTestFunction(unittest.TestCase):
|
|
|
471
477
|
mock_prerequisite_instance = mock_prerequisite_validator.return_value
|
|
472
478
|
mock_prerequisite_instance.run.return_value = False
|
|
473
479
|
|
|
480
|
+
mock_prompt_test_arguments.return_value = (10, 5, 3)
|
|
481
|
+
mock_prompt_report_arguments.return_value = (
|
|
482
|
+
"2023-01-01",
|
|
483
|
+
"2023-02-01",
|
|
484
|
+
"report.html",
|
|
485
|
+
"html",
|
|
486
|
+
True,
|
|
487
|
+
)
|
|
488
|
+
mock_sleep_validator_instance = mock_sleep_validator.return_value
|
|
489
|
+
mock_sleep_report_instance = mock_sleep_report.return_value
|
|
490
|
+
|
|
474
491
|
result = run_test_cycle(
|
|
475
492
|
duration=None,
|
|
476
493
|
wait=None,
|
|
@@ -492,7 +509,20 @@ class TestTestFunction(unittest.TestCase):
|
|
|
492
509
|
mock_prerequisite_validator.assert_called_once_with(True)
|
|
493
510
|
mock_prerequisite_instance.run.assert_called_once()
|
|
494
511
|
mock_prerequisite_instance.report.assert_called_once()
|
|
495
|
-
|
|
512
|
+
mock_prompt_test_arguments.assert_called_once_with(None, None, None, False)
|
|
513
|
+
mock_prompt_report_arguments.assert_called_once()
|
|
514
|
+
mock_sleep_validator_instance.assert_not_called()
|
|
515
|
+
mock_sleep_report.assert_called_once_with(
|
|
516
|
+
since=None,
|
|
517
|
+
until=None,
|
|
518
|
+
fname="report.html",
|
|
519
|
+
fmt="html",
|
|
520
|
+
tool_debug=True,
|
|
521
|
+
report_debug=True,
|
|
522
|
+
)
|
|
523
|
+
mock_sleep_report_instance.run.assert_called_once()
|
|
524
|
+
mock_display_report_file.assert_called_once_with("report.html", "html")
|
|
525
|
+
self.assertTrue(result)
|
|
496
526
|
|
|
497
527
|
@patch("amd_debug.s2idle.Installer")
|
|
498
528
|
@patch("amd_debug.s2idle.PrerequisiteValidator")
|
|
@@ -533,9 +563,9 @@ class TestTestFunction(unittest.TestCase):
|
|
|
533
563
|
"iasl", "ethtool", "edid-decode"
|
|
534
564
|
)
|
|
535
565
|
mock_installer_instance.install_dependencies.assert_called_once()
|
|
536
|
-
mock_prerequisite_validator.
|
|
537
|
-
mock_prerequisite_instance.run.
|
|
538
|
-
mock_prerequisite_instance.report.
|
|
566
|
+
mock_prerequisite_validator.assert_not_called()
|
|
567
|
+
mock_prerequisite_instance.run.assert_not_called()
|
|
568
|
+
mock_prerequisite_instance.report.assert_not_called()
|
|
539
569
|
mock_prompt_test_arguments.assert_called_once_with(None, None, None, False)
|
|
540
570
|
|
|
541
571
|
|
test_sleep_report.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
This module contains unit tests for the s2idle tool in the amd-debug-tools package.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import math
|
|
8
9
|
import unittest
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from unittest.mock import patch
|
|
@@ -165,3 +166,31 @@ class TestSleepReport(unittest.TestCase):
|
|
|
165
166
|
self.report.build_hw_sleep_chart()
|
|
166
167
|
self.assertIsNotNone(self.report.hwsleep_svg)
|
|
167
168
|
mock_savefig.assert_called_once()
|
|
169
|
+
|
|
170
|
+
def test_pre_process_dataframe_zero_duration(self):
|
|
171
|
+
"""Test the pre_process_dataframe method when t0 and t1 are the same."""
|
|
172
|
+
# Mock the dataframe with t0 and t1 being the same
|
|
173
|
+
self.report.df = pd.DataFrame(
|
|
174
|
+
{
|
|
175
|
+
"t0": [datetime(2023, 10, 10, 12, 0, 0).strftime("%Y%m%d%H%M%S")],
|
|
176
|
+
"t1": [datetime(2023, 10, 10, 12, 0, 0).strftime("%Y%m%d%H%M%S")],
|
|
177
|
+
"hw": [50],
|
|
178
|
+
"requested": [1],
|
|
179
|
+
"gpio": ["1, 2"],
|
|
180
|
+
"wake_irq": ["1"],
|
|
181
|
+
"b0": [90],
|
|
182
|
+
"b1": [85],
|
|
183
|
+
"full": [100],
|
|
184
|
+
}
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Call the method
|
|
188
|
+
self.report.pre_process_dataframe()
|
|
189
|
+
|
|
190
|
+
# Verify the dataframe was processed correctly
|
|
191
|
+
self.assertTrue(
|
|
192
|
+
self.report.df["Duration"].isna().iloc[0]
|
|
193
|
+
) # Duration should be NaN
|
|
194
|
+
self.assertTrue(
|
|
195
|
+
math.isnan(self.report.df["Hardware Sleep"].iloc[0])
|
|
196
|
+
) # Hardware Sleep should be NaN
|
test_validator.py
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
This module contains unit tests for the validator functions in the amd-debug-tools package.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from unittest.mock import patch, mock_open
|
|
8
|
+
from unittest.mock import patch, mock_open, Mock
|
|
9
9
|
|
|
10
|
+
import os
|
|
10
11
|
import logging
|
|
11
12
|
import unittest
|
|
12
13
|
import math
|
|
@@ -157,9 +158,8 @@ class TestValidator(unittest.TestCase):
|
|
|
157
158
|
self.validator.capture_wake_sources()
|
|
158
159
|
|
|
159
160
|
# Validate debug messages
|
|
160
|
-
mock_record_debug.assert_any_call("Possible wakeup sources:")
|
|
161
161
|
mock_record_debug.assert_any_call(
|
|
162
|
-
"
|
|
162
|
+
"Wakeup Source|Linux Device|Status\n|/sys/devices/pci0000:00/0000:00:14.0|enabled\n"
|
|
163
163
|
)
|
|
164
164
|
|
|
165
165
|
# Stop patches
|
|
@@ -694,7 +694,7 @@ class TestValidator(unittest.TestCase):
|
|
|
694
694
|
# Test case 3: Randomized test
|
|
695
695
|
mock_randint.side_effect = [7, 3] # Random duration and wait
|
|
696
696
|
self.validator.run(duration=10, count=1, wait=5, rand=True, logind=False)
|
|
697
|
-
mock_randint.assert_any_call(
|
|
697
|
+
mock_randint.assert_any_call(4, 10)
|
|
698
698
|
mock_randint.assert_any_call(1, 5)
|
|
699
699
|
mock_run_countdown.assert_any_call("Suspending system", math.ceil(3 / 2))
|
|
700
700
|
mock_run_countdown.assert_any_call("Collecting data", math.ceil(3 / 2))
|
|
@@ -705,19 +705,128 @@ class TestValidator(unittest.TestCase):
|
|
|
705
705
|
mock_report_cycle.assert_called()
|
|
706
706
|
mock_unlock_session.assert_called()
|
|
707
707
|
|
|
708
|
-
# Test case 4:
|
|
708
|
+
# Test case 4: Randomized test, but too short of a duration
|
|
709
|
+
result = self.validator.run(
|
|
710
|
+
duration=4, count=1, wait=5, rand=True, logind=False
|
|
711
|
+
)
|
|
712
|
+
self.assertFalse(result)
|
|
713
|
+
mock_report_cycle.assert_called()
|
|
714
|
+
|
|
715
|
+
# Test case 5: Multiple cycles
|
|
709
716
|
self.validator.run(duration=10, count=2, wait=5, rand=False, logind=False)
|
|
710
717
|
self.assertEqual(mock_prep.call_count, 4) # Includes previous calls
|
|
711
718
|
self.assertEqual(mock_program_wakealarm.call_count, 4)
|
|
712
719
|
self.assertEqual(mock_suspend_system.call_count, 4)
|
|
713
720
|
self.assertEqual(mock_post.call_count, 4)
|
|
714
|
-
self.assertEqual(mock_report_cycle.call_count,
|
|
721
|
+
self.assertEqual(mock_report_cycle.call_count, 5)
|
|
715
722
|
self.assertEqual(mock_unlock_session.call_count, 3)
|
|
716
723
|
|
|
717
|
-
# Test case
|
|
724
|
+
# Test case 6: suspend_system fails
|
|
718
725
|
mock_suspend_system.return_value = False
|
|
719
726
|
result = self.validator.run(
|
|
720
727
|
duration=10, count=1, wait=5, rand=False, logind=False
|
|
721
728
|
)
|
|
722
729
|
self.assertFalse(result)
|
|
723
730
|
mock_report_cycle.assert_called()
|
|
731
|
+
|
|
732
|
+
@patch("os.path.exists")
|
|
733
|
+
@patch("builtins.open", new_callable=mock_open, read_data="3")
|
|
734
|
+
@patch("os.write")
|
|
735
|
+
@patch("os.open")
|
|
736
|
+
@patch("os.close")
|
|
737
|
+
def test_suspend_system_sysfs_success(
|
|
738
|
+
self,
|
|
739
|
+
mock_os_close,
|
|
740
|
+
mock_os_open,
|
|
741
|
+
mock_os_write,
|
|
742
|
+
_mock_open_file,
|
|
743
|
+
mock_path_exists,
|
|
744
|
+
):
|
|
745
|
+
"""Test suspend_system method using sysfs interface with success"""
|
|
746
|
+
# Mock wakeup_count file existence
|
|
747
|
+
mock_path_exists.side_effect = lambda path: "wakeup_count" in path
|
|
748
|
+
|
|
749
|
+
# Mock os.open and os.write
|
|
750
|
+
mock_os_open.return_value = 3
|
|
751
|
+
mock_os_write.return_value = None
|
|
752
|
+
|
|
753
|
+
# Call the method
|
|
754
|
+
result = self.validator.suspend_system()
|
|
755
|
+
|
|
756
|
+
# Assert the method returned True
|
|
757
|
+
self.assertTrue(result)
|
|
758
|
+
|
|
759
|
+
# Assert os.open and os.write were called
|
|
760
|
+
mock_os_open.assert_called_once_with(
|
|
761
|
+
"/sys/power/state", os.O_WRONLY | os.O_SYNC
|
|
762
|
+
)
|
|
763
|
+
mock_os_write.assert_called_once_with(3, b"mem")
|
|
764
|
+
mock_os_close.assert_called_once_with(3)
|
|
765
|
+
|
|
766
|
+
@patch("os.path.exists")
|
|
767
|
+
@patch("builtins.open", new_callable=mock_open, read_data="3")
|
|
768
|
+
@patch("os.write")
|
|
769
|
+
@patch("os.open")
|
|
770
|
+
@patch("os.close")
|
|
771
|
+
def test_suspend_system_sysfs_failure(
|
|
772
|
+
self,
|
|
773
|
+
mock_os_close,
|
|
774
|
+
mock_os_open,
|
|
775
|
+
mock_os_write,
|
|
776
|
+
_mock_open_file,
|
|
777
|
+
mock_path_exists,
|
|
778
|
+
):
|
|
779
|
+
"""Test suspend_system method using sysfs interface with failure"""
|
|
780
|
+
# Mock wakeup_count file existence
|
|
781
|
+
mock_path_exists.side_effect = lambda path: "wakeup_count" in path
|
|
782
|
+
|
|
783
|
+
# Mock os.open to raise OSError
|
|
784
|
+
mock_os_open.return_value = 3
|
|
785
|
+
mock_os_write.side_effect = OSError("Failed to write to state")
|
|
786
|
+
|
|
787
|
+
# Call the method
|
|
788
|
+
result = self.validator.suspend_system()
|
|
789
|
+
|
|
790
|
+
# Assert the method returned False
|
|
791
|
+
self.assertFalse(result)
|
|
792
|
+
|
|
793
|
+
# Assert os.open and os.write were called
|
|
794
|
+
mock_os_open.assert_called_once_with(
|
|
795
|
+
"/sys/power/state", os.O_WRONLY | os.O_SYNC
|
|
796
|
+
)
|
|
797
|
+
mock_os_write.assert_called_once_with(3, b"mem")
|
|
798
|
+
mock_os_close.assert_called_once_with(3)
|
|
799
|
+
|
|
800
|
+
@patch("os.path.exists")
|
|
801
|
+
@patch("builtins.open", new_callable=mock_open)
|
|
802
|
+
@patch("os.write")
|
|
803
|
+
@patch("os.open")
|
|
804
|
+
@patch("os.close")
|
|
805
|
+
def test_suspend_system_sysfs_no_wakeup_count(
|
|
806
|
+
self,
|
|
807
|
+
mock_os_close,
|
|
808
|
+
mock_os_open,
|
|
809
|
+
mock_os_write,
|
|
810
|
+
_mock_open_file,
|
|
811
|
+
mock_path_exists,
|
|
812
|
+
):
|
|
813
|
+
"""Test suspend_system method using sysfs interface with no wakeup_count file"""
|
|
814
|
+
# Mock wakeup_count file does not exist
|
|
815
|
+
mock_path_exists.return_value = False
|
|
816
|
+
|
|
817
|
+
# Mock os.open and os.write
|
|
818
|
+
mock_os_open.return_value = 3
|
|
819
|
+
mock_os_write.return_value = None
|
|
820
|
+
|
|
821
|
+
# Call the method
|
|
822
|
+
result = self.validator.suspend_system()
|
|
823
|
+
|
|
824
|
+
# Assert the method returned True
|
|
825
|
+
self.assertTrue(result)
|
|
826
|
+
|
|
827
|
+
# Assert os.open and os.write were called
|
|
828
|
+
mock_os_open.assert_called_once_with(
|
|
829
|
+
"/sys/power/state", os.O_WRONLY | os.O_SYNC
|
|
830
|
+
)
|
|
831
|
+
mock_os_write.assert_called_once_with(3, b"mem")
|
|
832
|
+
mock_os_close.assert_called_once_with(3)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|