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.
- amd_debug/__init__.py +10 -3
- amd_debug/acpi.py +0 -1
- amd_debug/battery.py +0 -1
- amd_debug/bios.py +8 -5
- amd_debug/common.py +70 -3
- amd_debug/database.py +22 -5
- amd_debug/display.py +2 -3
- amd_debug/failures.py +44 -3
- amd_debug/installer.py +7 -6
- amd_debug/kernel.py +20 -17
- amd_debug/prerequisites.py +166 -61
- amd_debug/pstate.py +9 -6
- amd_debug/s2idle.py +53 -44
- amd_debug/sleep_report.py +129 -87
- amd_debug/templates/html +4 -2
- amd_debug/ttm.py +157 -0
- amd_debug/validator.py +61 -34
- amd_debug/wake.py +0 -1
- amd_debug_tools-0.2.12.dist-info/METADATA +75 -0
- amd_debug_tools-0.2.12.dist-info/RECORD +47 -0
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.12.dist-info}/entry_points.txt +1 -0
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.12.dist-info}/top_level.txt +1 -0
- launcher.py +0 -1
- test_acpi.py +1 -1
- test_bios.py +30 -12
- test_common.py +117 -1
- test_database.py +1 -1
- test_display.py +6 -6
- test_installer.py +68 -1
- test_kernel.py +7 -6
- test_launcher.py +9 -1
- test_prerequisites.py +629 -26
- test_s2idle.py +66 -19
- test_sleep_report.py +29 -0
- test_ttm.py +276 -0
- test_validator.py +187 -16
- amd_debug_tools-0.2.2.dist-info/METADATA +0 -181
- amd_debug_tools-0.2.2.dist-info/RECORD +0 -45
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.12.dist-info}/WHEEL +0 -0
- {amd_debug_tools-0.2.2.dist-info → amd_debug_tools-0.2.12.dist-info}/licenses/LICENSE +0 -0
amd_debug/sleep_report.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
1
|
# SPDX-License-Identifier: MIT
|
|
3
2
|
|
|
4
3
|
import os
|
|
5
4
|
import re
|
|
6
5
|
import math
|
|
7
6
|
from datetime import datetime, timedelta
|
|
7
|
+
import numpy as np
|
|
8
8
|
from tabulate import tabulate
|
|
9
9
|
from jinja2 import Environment, FileSystemLoader
|
|
10
10
|
import pandas as pd
|
|
@@ -73,6 +73,8 @@ def format_percent(val):
|
|
|
73
73
|
|
|
74
74
|
def format_timedelta(val):
|
|
75
75
|
"""Format seconds as a nicer format"""
|
|
76
|
+
if math.isnan(val):
|
|
77
|
+
val = 0
|
|
76
78
|
return str(timedelta(seconds=val))
|
|
77
79
|
|
|
78
80
|
|
|
@@ -97,8 +99,23 @@ class SleepReport(AmdTool):
|
|
|
97
99
|
self.debug = report_debug
|
|
98
100
|
self.format = fmt
|
|
99
101
|
self.failures = []
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
if since and until:
|
|
103
|
+
self.df = self.db.report_summary_dataframe(self.since, self.until)
|
|
104
|
+
self.pre_process_dataframe()
|
|
105
|
+
else:
|
|
106
|
+
self.df = pd.DataFrame(
|
|
107
|
+
columns=[
|
|
108
|
+
"t0",
|
|
109
|
+
"t1",
|
|
110
|
+
"requested",
|
|
111
|
+
"hw",
|
|
112
|
+
"b0",
|
|
113
|
+
"b1",
|
|
114
|
+
"full",
|
|
115
|
+
"wake_irq",
|
|
116
|
+
"gpio",
|
|
117
|
+
]
|
|
118
|
+
)
|
|
102
119
|
self.battery_svg = None
|
|
103
120
|
self.hwsleep_svg = None
|
|
104
121
|
|
|
@@ -136,6 +153,7 @@ class SleepReport(AmdTool):
|
|
|
136
153
|
self.df["Duration"] = self.df["t1"].apply(format_as_seconds) - self.df[
|
|
137
154
|
"t0"
|
|
138
155
|
].apply(format_as_seconds)
|
|
156
|
+
self.df["Duration"] = self.df["Duration"].replace(0, np.nan)
|
|
139
157
|
self.df["Hardware Sleep"] = (self.df["hw"] / self.df["Duration"]).apply(
|
|
140
158
|
parse_hw_sleep
|
|
141
159
|
)
|
|
@@ -187,7 +205,8 @@ class SleepReport(AmdTool):
|
|
|
187
205
|
format_watts
|
|
188
206
|
)
|
|
189
207
|
|
|
190
|
-
def
|
|
208
|
+
def convert_table_dataframe(self, content):
|
|
209
|
+
"""Convert a table like dataframe to an HTML table"""
|
|
191
210
|
header = False
|
|
192
211
|
rows = []
|
|
193
212
|
for line in content.split("\n"):
|
|
@@ -196,17 +215,25 @@ class SleepReport(AmdTool):
|
|
|
196
215
|
if header:
|
|
197
216
|
continue
|
|
198
217
|
header = True
|
|
218
|
+
line = line.strip("│")
|
|
219
|
+
line = line.replace("├─", "└─")
|
|
199
220
|
if "|" in line:
|
|
200
221
|
# first column missing '|'
|
|
201
222
|
rows.append(line.replace("\t", "|"))
|
|
202
223
|
columns = [row.split("|") for row in rows]
|
|
203
224
|
df = pd.DataFrame(columns[1:], columns=columns[0])
|
|
204
|
-
return df.to_html(index=False,
|
|
225
|
+
return df.to_html(index=False, justify="center", col_space=30)
|
|
205
226
|
|
|
206
227
|
def get_prereq_data(self):
|
|
207
228
|
"""Get the prereq data"""
|
|
208
229
|
prereq = []
|
|
209
230
|
prereq_debug = []
|
|
231
|
+
tables = [
|
|
232
|
+
"int|active",
|
|
233
|
+
"ACPI name",
|
|
234
|
+
"PCI Slot",
|
|
235
|
+
"DMI|value",
|
|
236
|
+
]
|
|
210
237
|
ts = self.db.get_last_prereq_ts()
|
|
211
238
|
if not ts:
|
|
212
239
|
return [], "", []
|
|
@@ -216,23 +243,24 @@ class SleepReport(AmdTool):
|
|
|
216
243
|
if self.debug:
|
|
217
244
|
for row in self.db.report_debug(t0):
|
|
218
245
|
content = row[0]
|
|
219
|
-
if self.format == "html" and
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)
|
|
246
|
+
if self.format == "html" and [
|
|
247
|
+
table for table in tables if table in content
|
|
248
|
+
]:
|
|
249
|
+
content = self.convert_table_dataframe(content)
|
|
250
|
+
prereq_debug.append({"data": f"{content.strip()}"})
|
|
224
251
|
return prereq, t0, prereq_debug
|
|
225
252
|
|
|
226
253
|
def get_cycle_data(self):
|
|
227
254
|
"""Get the cycle data"""
|
|
228
255
|
cycles = []
|
|
229
256
|
debug = []
|
|
257
|
+
tables = ["Wakeup Source"]
|
|
230
258
|
num = 0
|
|
231
259
|
for cycle in self.df["Start Time"]:
|
|
232
260
|
if self.format == "html":
|
|
233
261
|
data = ""
|
|
234
262
|
for line in self.db.report_cycle_data(cycle).split("\n"):
|
|
235
|
-
data += "<p>{line}</p>"
|
|
263
|
+
data += f"<p>{line}</p>"
|
|
236
264
|
cycles.append({"cycle_num": num, "data": data})
|
|
237
265
|
else:
|
|
238
266
|
cycles.append([num, self.db.report_cycle_data(cycle)])
|
|
@@ -240,7 +268,12 @@ class SleepReport(AmdTool):
|
|
|
240
268
|
messages = []
|
|
241
269
|
priorities = []
|
|
242
270
|
for row in self.db.report_debug(cycle):
|
|
243
|
-
|
|
271
|
+
content = row[0]
|
|
272
|
+
if self.format == "html" and [
|
|
273
|
+
table for table in tables if table in content
|
|
274
|
+
]:
|
|
275
|
+
content = self.convert_table_dataframe(content)
|
|
276
|
+
messages.append(content)
|
|
244
277
|
priorities.append(get_log_priority(row[1]))
|
|
245
278
|
debug.append(
|
|
246
279
|
{"cycle_num": num, "messages": messages, "priorities": priorities}
|
|
@@ -265,58 +298,69 @@ class SleepReport(AmdTool):
|
|
|
265
298
|
prereq, prereq_date, prereq_debug = self.get_prereq_data()
|
|
266
299
|
|
|
267
300
|
# 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",
|
|
301
|
+
if not self.df.empty:
|
|
302
|
+
cycles, debug = self.get_cycle_data()
|
|
303
|
+
|
|
304
|
+
self.post_process_dataframe()
|
|
305
|
+
failures = None
|
|
306
|
+
if self.format == "md":
|
|
307
|
+
summary = self.df.to_markdown(floatfmt=".02f")
|
|
308
|
+
cycle_data = tabulate(
|
|
309
|
+
cycles, headers=["Cycle", "data"], tablefmt="pipe"
|
|
280
310
|
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
headers=
|
|
290
|
-
|
|
311
|
+
if self.failures:
|
|
312
|
+
failures = tabulate(
|
|
313
|
+
self.failures,
|
|
314
|
+
headers=["Cycle", "Problem", "Explanation"],
|
|
315
|
+
tablefmt="pipe",
|
|
316
|
+
)
|
|
317
|
+
elif self.format == "txt":
|
|
318
|
+
summary = tabulate(
|
|
319
|
+
self.df, headers=self.df.columns, tablefmt="fancy_grid"
|
|
320
|
+
)
|
|
321
|
+
cycle_data = tabulate(
|
|
322
|
+
cycles, headers=["Cycle", "data"], tablefmt="fancy_grid"
|
|
291
323
|
)
|
|
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,
|
|
324
|
+
if self.failures:
|
|
325
|
+
failures = tabulate(
|
|
326
|
+
self.failures,
|
|
327
|
+
headers=["Cycle", "Problem", "Explanation"],
|
|
328
|
+
tablefmt="fancy_grid",
|
|
303
329
|
)
|
|
304
|
-
|
|
305
|
-
summary
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
330
|
+
elif self.format == "html":
|
|
331
|
+
summary = ""
|
|
332
|
+
row = 0
|
|
333
|
+
# we will use javascript to highlight the high values
|
|
334
|
+
for line in self.df.to_html(
|
|
335
|
+
table_id="summary", render_links=True
|
|
336
|
+
).split("\n"):
|
|
337
|
+
if "<tr>" in line:
|
|
338
|
+
line = line.replace(
|
|
339
|
+
"<tr>",
|
|
340
|
+
f'<tr class="row-low" onclick="pick_summary_cycle({row})">',
|
|
341
|
+
)
|
|
342
|
+
row = row + 1
|
|
343
|
+
summary += line
|
|
344
|
+
cycle_data = cycles
|
|
345
|
+
failures = self.failures
|
|
346
|
+
# only show one cycle in stdout output even if we found more
|
|
316
347
|
else:
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
348
|
+
df = self.df.tail(1)
|
|
349
|
+
summary = tabulate(
|
|
350
|
+
df, headers=self.df.columns, tablefmt="fancy_grid", showindex=False
|
|
351
|
+
)
|
|
352
|
+
if cycles[-1][0] == df.index.start:
|
|
353
|
+
cycle_data = cycles[-1][-1]
|
|
354
|
+
else:
|
|
355
|
+
cycle_data = None
|
|
356
|
+
if self.failures and self.failures[-1][0] == df.index.start:
|
|
357
|
+
failures = self.failures[-1][-1]
|
|
358
|
+
else:
|
|
359
|
+
cycles = []
|
|
360
|
+
debug = []
|
|
361
|
+
cycle_data = []
|
|
362
|
+
summary = "No sleep cycles found in the database."
|
|
363
|
+
failures = None
|
|
320
364
|
|
|
321
365
|
# let it burn
|
|
322
366
|
context = {
|
|
@@ -333,7 +377,7 @@ class SleepReport(AmdTool):
|
|
|
333
377
|
"failures": failures,
|
|
334
378
|
}
|
|
335
379
|
if self.fname:
|
|
336
|
-
with open(self.fname, "w") as f:
|
|
380
|
+
with open(self.fname, "w", encoding="utf-8") as f:
|
|
337
381
|
f.write(template.render(context))
|
|
338
382
|
if "SUDO_UID" in os.environ:
|
|
339
383
|
os.chown(
|
|
@@ -345,30 +389,30 @@ class SleepReport(AmdTool):
|
|
|
345
389
|
|
|
346
390
|
def build_battery_chart(self):
|
|
347
391
|
"""Build a battery chart using matplotlib and seaborn"""
|
|
348
|
-
import matplotlib.pyplot as plt
|
|
349
|
-
import seaborn as sns
|
|
350
|
-
import io
|
|
392
|
+
import matplotlib.pyplot as plt # pylint: disable=import-outside-toplevel
|
|
393
|
+
import seaborn as sns # pylint: disable=import-outside-toplevel
|
|
394
|
+
import io # pylint: disable=import-outside-toplevel
|
|
351
395
|
|
|
352
396
|
if "Battery Ave Rate" not in self.df.columns:
|
|
353
397
|
return
|
|
354
398
|
|
|
355
399
|
plt.set_loglevel("warning")
|
|
356
|
-
|
|
357
|
-
|
|
400
|
+
_fig, ax1 = plt.subplots()
|
|
401
|
+
ax1.plot(
|
|
358
402
|
self.df["Battery Ave Rate"], color="green", label="Charge/Discharge Rate"
|
|
359
403
|
)
|
|
360
404
|
|
|
361
405
|
ax2 = ax1.twinx()
|
|
362
|
-
|
|
406
|
+
sns.barplot(
|
|
363
407
|
x=self.df.index,
|
|
364
408
|
y=self.df["Battery Delta"],
|
|
365
409
|
color="grey",
|
|
366
410
|
label="Battery Change",
|
|
367
411
|
alpha=0.3,
|
|
368
412
|
)
|
|
369
|
-
|
|
370
|
-
if
|
|
371
|
-
ax1.set_xticks(range(0, len(self.df.index),
|
|
413
|
+
max_range = int(len(self.df.index) / 10)
|
|
414
|
+
if max_range:
|
|
415
|
+
ax1.set_xticks(range(0, len(self.df.index), max_range))
|
|
372
416
|
ax1.set_xlabel("Cycle")
|
|
373
417
|
ax1.set_ylabel("Rate (Watts)")
|
|
374
418
|
ax2.set_ylabel("Battery Change (%)")
|
|
@@ -385,20 +429,20 @@ class SleepReport(AmdTool):
|
|
|
385
429
|
|
|
386
430
|
def build_hw_sleep_chart(self):
|
|
387
431
|
"""Build the hardware sleep chart using matplotlib and seaborn"""
|
|
388
|
-
import matplotlib.pyplot as plt
|
|
389
|
-
import seaborn as sns
|
|
390
|
-
import io
|
|
432
|
+
import matplotlib.pyplot as plt # pylint: disable=import-outside-toplevel
|
|
433
|
+
import seaborn as sns # pylint: disable=import-outside-toplevel
|
|
434
|
+
import io # pylint: disable=import-outside-toplevel
|
|
391
435
|
|
|
392
436
|
plt.set_loglevel("warning")
|
|
393
|
-
|
|
394
|
-
|
|
437
|
+
_fig, ax1 = plt.subplots()
|
|
438
|
+
ax1.plot(
|
|
395
439
|
self.df["Hardware Sleep"],
|
|
396
440
|
color="red",
|
|
397
441
|
label="Hardware Sleep",
|
|
398
442
|
)
|
|
399
443
|
|
|
400
444
|
ax2 = ax1.twinx()
|
|
401
|
-
|
|
445
|
+
sns.barplot(
|
|
402
446
|
x=self.df.index,
|
|
403
447
|
y=self.df["Duration"] / 60,
|
|
404
448
|
color="grey",
|
|
@@ -406,9 +450,9 @@ class SleepReport(AmdTool):
|
|
|
406
450
|
alpha=0.3,
|
|
407
451
|
)
|
|
408
452
|
|
|
409
|
-
|
|
410
|
-
if
|
|
411
|
-
ax1.set_xticks(range(0, len(self.df.index),
|
|
453
|
+
max_range = int(len(self.df.index) / 10)
|
|
454
|
+
if max_range:
|
|
455
|
+
ax1.set_xticks(range(0, len(self.df.index), max_range))
|
|
412
456
|
ax1.set_xlabel("Cycle")
|
|
413
457
|
ax1.set_ylabel("Percent")
|
|
414
458
|
ax2.set_yscale("log")
|
|
@@ -427,15 +471,13 @@ class SleepReport(AmdTool):
|
|
|
427
471
|
def run(self, inc_prereq=True):
|
|
428
472
|
"""Run the report"""
|
|
429
473
|
|
|
430
|
-
if self.df.empty:
|
|
431
|
-
raise ValueError(f"No data found between {self.since} and {self.until}")
|
|
432
|
-
|
|
433
474
|
characters = print_temporary_message("Building report, please wait...")
|
|
434
475
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
self.
|
|
438
|
-
|
|
476
|
+
if not self.df.empty:
|
|
477
|
+
# Build charts in the page for html format
|
|
478
|
+
if len(self.df.index) > 1 and self.format == "html":
|
|
479
|
+
self.build_battery_chart()
|
|
480
|
+
self.build_hw_sleep_chart()
|
|
439
481
|
|
|
440
482
|
# Render the template using jinja
|
|
441
483
|
msg = self.build_template(inc_prereq)
|
|
@@ -445,7 +487,7 @@ class SleepReport(AmdTool):
|
|
|
445
487
|
text = line.strip()
|
|
446
488
|
if not text:
|
|
447
489
|
continue
|
|
448
|
-
for group in ["🗣️", "❌", "🚦", "🦟", "
|
|
490
|
+
for group in ["🗣️", "❌", "🚦", "🦟", "🚫", "○"]:
|
|
449
491
|
if line.startswith(group):
|
|
450
492
|
text = line.split(group)[-1]
|
|
451
493
|
color = get_group_color(group)
|
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/ttm.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""TTM configuration tool"""
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
import argparse
|
|
7
|
+
from amd_debug.common import (
|
|
8
|
+
AmdTool,
|
|
9
|
+
bytes_to_gb,
|
|
10
|
+
gb_to_pages,
|
|
11
|
+
get_system_mem,
|
|
12
|
+
relaunch_sudo,
|
|
13
|
+
print_color,
|
|
14
|
+
reboot,
|
|
15
|
+
version,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
TTM_PARAM_PATH = "/sys/module/ttm/parameters/pages_limit"
|
|
19
|
+
MODPROBE_CONF_PATH = "/etc/modprobe.d/ttm.conf"
|
|
20
|
+
# Maximum percentage of total system memory to allow for TTM
|
|
21
|
+
MAX_MEMORY_PERCENTAGE = 90
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def maybe_reboot() -> bool:
|
|
25
|
+
"""Prompt to reboot system"""
|
|
26
|
+
response = input("Would you like to reboot the system now? (y/n): ").strip().lower()
|
|
27
|
+
if response in ("y", "yes"):
|
|
28
|
+
return reboot()
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AmdTtmTool(AmdTool):
|
|
33
|
+
"""Class for handling TTM page configuration"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, logging):
|
|
36
|
+
log_prefix = "ttm" if logging else None
|
|
37
|
+
super().__init__(log_prefix)
|
|
38
|
+
|
|
39
|
+
def get(self) -> bool:
|
|
40
|
+
"""Read current page limit"""
|
|
41
|
+
try:
|
|
42
|
+
with open(TTM_PARAM_PATH, "r", encoding="utf-8") as f:
|
|
43
|
+
pages = int(f.read().strip())
|
|
44
|
+
gb_value = bytes_to_gb(pages)
|
|
45
|
+
print_color(
|
|
46
|
+
f"Current TTM pages limit: {pages} pages ({gb_value:.2f} GB)", "💻"
|
|
47
|
+
)
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
print_color(f"Error: Could not find {TTM_PARAM_PATH}", "❌")
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
total = get_system_mem()
|
|
53
|
+
if total > 0:
|
|
54
|
+
print_color(f"Total system memory: {total:.2f} GB", "💻")
|
|
55
|
+
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
def set(self, gb_value) -> bool:
|
|
59
|
+
"""Set a new page limit"""
|
|
60
|
+
relaunch_sudo()
|
|
61
|
+
|
|
62
|
+
# Check against system memory
|
|
63
|
+
total = get_system_mem()
|
|
64
|
+
if total > 0:
|
|
65
|
+
max_recommended_gb = total * MAX_MEMORY_PERCENTAGE / 100
|
|
66
|
+
|
|
67
|
+
if gb_value > total:
|
|
68
|
+
print_color(
|
|
69
|
+
f"{gb_value:.2f} GB is greater than total system memory ({total:.2f} GB)",
|
|
70
|
+
"❌",
|
|
71
|
+
)
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
if gb_value > max_recommended_gb:
|
|
75
|
+
print_color(
|
|
76
|
+
f"Warning: The requested value ({gb_value:.2f} GB) exceeds {MAX_MEMORY_PERCENTAGE}% of your system memory ({max_recommended_gb:.2f} GB).",
|
|
77
|
+
"🚦",
|
|
78
|
+
)
|
|
79
|
+
response = (
|
|
80
|
+
input(
|
|
81
|
+
"This could cause system instability. Continue anyway? (y/n): "
|
|
82
|
+
)
|
|
83
|
+
.strip()
|
|
84
|
+
.lower()
|
|
85
|
+
)
|
|
86
|
+
if response not in ("y", "yes"):
|
|
87
|
+
print_color("Operation cancelled.", "🚦")
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
pages = gb_to_pages(gb_value)
|
|
91
|
+
|
|
92
|
+
with open(MODPROBE_CONF_PATH, "w", encoding="utf-8") as f:
|
|
93
|
+
f.write(f"options ttm pages_limit={pages}\n")
|
|
94
|
+
print_color(
|
|
95
|
+
f"Successfully set TTM pages limit to {pages} pages ({gb_value:.2f} GB)",
|
|
96
|
+
"🐧",
|
|
97
|
+
)
|
|
98
|
+
print_color(f"Configuration written to {MODPROBE_CONF_PATH}", "🐧")
|
|
99
|
+
print_color("NOTE: You need to reboot for changes to take effect.", "○")
|
|
100
|
+
|
|
101
|
+
return maybe_reboot()
|
|
102
|
+
|
|
103
|
+
def clear(self) -> bool:
|
|
104
|
+
"""Clears the page limit"""
|
|
105
|
+
if not os.path.exists(MODPROBE_CONF_PATH):
|
|
106
|
+
print_color(f"{MODPROBE_CONF_PATH} doesn't exist", "❌")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
relaunch_sudo()
|
|
110
|
+
|
|
111
|
+
os.remove(MODPROBE_CONF_PATH)
|
|
112
|
+
print_color(f"Configuration {MODPROBE_CONF_PATH} removed", "🐧")
|
|
113
|
+
|
|
114
|
+
return maybe_reboot()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def parse_args():
|
|
118
|
+
"""Parse command line arguments."""
|
|
119
|
+
parser = argparse.ArgumentParser(description="Manage TTM pages limit")
|
|
120
|
+
parser.add_argument("--set", type=float, help="Set pages limit in GB")
|
|
121
|
+
parser.add_argument(
|
|
122
|
+
"--clear", action="store_true", help="Clear a previously set page limit"
|
|
123
|
+
)
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--version", action="store_true", help="Show version information"
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--tool-debug",
|
|
129
|
+
action="store_true",
|
|
130
|
+
help="Enable tool debug logging",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return parser.parse_args()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def main() -> None | int:
|
|
137
|
+
"""Main function"""
|
|
138
|
+
|
|
139
|
+
args = parse_args()
|
|
140
|
+
tool = AmdTtmTool(args.tool_debug)
|
|
141
|
+
ret = False
|
|
142
|
+
|
|
143
|
+
if args.version:
|
|
144
|
+
print(version())
|
|
145
|
+
return
|
|
146
|
+
elif args.set is not None:
|
|
147
|
+
if args.set <= 0:
|
|
148
|
+
print("Error: GB value must be greater than 0")
|
|
149
|
+
return 1
|
|
150
|
+
ret = tool.set(args.set)
|
|
151
|
+
elif args.clear:
|
|
152
|
+
ret = tool.clear()
|
|
153
|
+
else:
|
|
154
|
+
ret = tool.get()
|
|
155
|
+
if ret is False:
|
|
156
|
+
return 1
|
|
157
|
+
return
|