amd-debug-tools 0.2.6__py3-none-any.whl → 0.2.8__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/common.py CHANGED
@@ -5,6 +5,7 @@
5
5
  This module contains common utility functions and classes for various amd-debug-tools.
6
6
  """
7
7
 
8
+ import asyncio
8
9
  import importlib.metadata
9
10
  import logging
10
11
  import os
@@ -47,7 +48,7 @@ def get_group_color(group) -> str:
47
48
  color = Colors.WARNING
48
49
  elif group == "🗣️":
49
50
  color = Colors.HEADER
50
- elif group == "💯":
51
+ elif any(mk in group for mk in ["💯", "🚫"]):
51
52
  color = Colors.UNDERLINE
52
53
  elif any(mk in group for mk in ["🦟", "🖴"]):
53
54
  color = Colors.DEBUG
@@ -238,18 +239,47 @@ def gb_to_pages(gb_value):
238
239
 
239
240
  def reboot():
240
241
  """Reboot the system"""
241
- try:
242
- import dbus # pylint: disable=import-outside-toplevel
243
242
 
244
- bus = dbus.SystemBus()
245
- obj = bus.get_object("org.freedesktop.login1", "/org/freedesktop/login1")
246
- intf = dbus.Interface(obj, "org.freedesktop.login1.Manager")
247
- intf.Reboot(True)
243
+ async def reboot_dbus_fast():
244
+ """Reboot using dbus-fast"""
245
+ try:
246
+ from dbus_fast.aio import ( # pylint: disable=import-outside-toplevel
247
+ MessageBus,
248
+ )
249
+ from dbus_fast import BusType # pylint: disable=import-outside-toplevel
250
+
251
+ bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
252
+ introspection = await bus.introspect(
253
+ "org.freedesktop.login1", "/org/freedesktop/login1"
254
+ )
255
+ proxy_obj = bus.get_proxy_object(
256
+ "org.freedesktop.login1", "/org/freedesktop/login1", introspection
257
+ )
258
+ interface = proxy_obj.get_interface("org.freedesktop.login1.Manager")
259
+ await interface.call_reboot(True)
260
+
261
+ except ImportError:
262
+ return False
263
+ return True
264
+
265
+ def reboot_dbus():
266
+ """Reboot using python-dbus"""
267
+ try:
268
+ import dbus # pylint: disable=import-outside-toplevel
269
+
270
+ bus = dbus.SystemBus()
271
+ obj = bus.get_object("org.freedesktop.login1", "/org/freedesktop/login1")
272
+ intf = dbus.Interface(obj, "org.freedesktop.login1.Manager")
273
+ intf.Reboot(True)
274
+ except ImportError:
275
+ return False
248
276
  return True
249
- except ImportError:
250
- fatal_error("Missing dbus")
251
- except dbus.exceptions.DBusException as e:
252
- fatal_error({e})
277
+
278
+ loop = asyncio.get_event_loop()
279
+ result = loop.run_until_complete(reboot_dbus_fast())
280
+ if not result:
281
+ return reboot_dbus()
282
+
253
283
  return True
254
284
 
255
285
 
@@ -21,7 +21,7 @@ import pyudev
21
21
 
22
22
  from amd_debug.wake import WakeIRQ
23
23
  from amd_debug.display import Display
24
- from amd_debug.kernel import get_kernel_log, SystemdLogger, DmesgLogger
24
+ from amd_debug.kernel import get_kernel_log, SystemdLogger, CySystemdLogger, DmesgLogger
25
25
  from amd_debug.common import (
26
26
  apply_prefix_wrapper,
27
27
  BIT,
@@ -672,7 +672,9 @@ class PrerequisiteValidator(AmdTool):
672
672
  """Check the source for kernel logs"""
673
673
  if isinstance(self.kernel_log, SystemdLogger):
674
674
  self.db.record_prereq("Logs are provided via systemd", "✅")
675
- if isinstance(self.kernel_log, DmesgLogger):
675
+ elif isinstance(self.kernel_log, CySystemdLogger):
676
+ self.db.record_prereq("Logs are provided via cysystemd", "✅")
677
+ elif isinstance(self.kernel_log, DmesgLogger):
676
678
  self.db.record_prereq(
677
679
  "Logs are provided via dmesg, timestamps may not be accurate over multiple cycles",
678
680
  "🚦",
@@ -1029,6 +1031,21 @@ class PrerequisiteValidator(AmdTool):
1029
1031
  shutil.rmtree(tmpd)
1030
1032
  return True
1031
1033
 
1034
+ def capture_cstates(self):
1035
+ """Capture ACPI C state information for the first CPU (assumes the same for all CPUs)"""
1036
+ base = os.path.join("/", "sys", "bus", "cpu", "devices", "cpu0", "cpuidle")
1037
+ paths = {}
1038
+ for root, _dirs, files in os.walk(base, topdown=False):
1039
+ for fname in files:
1040
+ target = os.path.join(root, fname)
1041
+ with open(target, "rb") as f:
1042
+ paths[target] = f.read()
1043
+ debug_str = "ACPI C-state information\n"
1044
+ for path, data in paths.items():
1045
+ prefix = "│ " if path != list(paths.keys())[-1] else "└─"
1046
+ debug_str += f"{prefix}{path}: {data.decode('utf-8', 'ignore')}"
1047
+ self.db.record_debug(debug_str)
1048
+
1032
1049
  def capture_battery(self):
1033
1050
  """Capture battery information"""
1034
1051
  obj = Batteries()
@@ -1266,6 +1283,7 @@ class PrerequisiteValidator(AmdTool):
1266
1283
  self.capture_pci_acpi,
1267
1284
  self.capture_edid,
1268
1285
  self.capture_nvidia,
1286
+ self.capture_cstates,
1269
1287
  ]
1270
1288
  checks = []
1271
1289
 
@@ -1318,7 +1336,7 @@ class PrerequisiteValidator(AmdTool):
1318
1336
  if not check():
1319
1337
  result = False
1320
1338
  if not result:
1321
- self.db.record_prereq(Headers.BrokenPrerequisites, "💯")
1339
+ self.db.record_prereq(Headers.BrokenPrerequisites, "🚫")
1322
1340
  self.db.sync()
1323
1341
  clear_temporary_message(len(msg))
1324
1342
  return result
amd_debug/sleep_report.py CHANGED
@@ -488,7 +488,7 @@ class SleepReport(AmdTool):
488
488
  text = line.strip()
489
489
  if not text:
490
490
  continue
491
- for group in ["🗣️", "❌", "🚦", "🦟", "💯", "○"]:
491
+ for group in ["🗣️", "❌", "🚦", "🦟", "🚫", "○"]:
492
492
  if line.startswith(group):
493
493
  text = line.split(group)[-1]
494
494
  color = get_group_color(group)
amd_debug/ttm.py CHANGED
@@ -2,6 +2,7 @@
2
2
  # SPDX-License-Identifier: MIT
3
3
  """TTM configuration tool"""
4
4
 
5
+ import asyncio
5
6
  import os
6
7
  import argparse
7
8
  from amd_debug.common import (
@@ -9,7 +10,7 @@ from amd_debug.common import (
9
10
  bytes_to_gb,
10
11
  gb_to_pages,
11
12
  get_system_mem,
12
- is_root,
13
+ relaunch_sudo,
13
14
  print_color,
14
15
  reboot,
15
16
  version,
@@ -57,9 +58,7 @@ class AmdTtmTool(AmdTool):
57
58
 
58
59
  def set(self, gb_value) -> bool:
59
60
  """Set a new page limit"""
60
- if not is_root():
61
- print_color("Root privileges required", "❌")
62
- return False
61
+ relaunch_sudo()
63
62
 
64
63
  # Check against system memory
65
64
  total = get_system_mem()
@@ -108,9 +107,7 @@ class AmdTtmTool(AmdTool):
108
107
  print_color(f"{MODPROBE_CONF_PATH} doesn't exist", "❌")
109
108
  return False
110
109
 
111
- if not is_root():
112
- print_color("Root privileges required", "❌")
113
- return False
110
+ relaunch_sudo()
114
111
 
115
112
  os.remove(MODPROBE_CONF_PATH)
116
113
  print_color(f"Configuration {MODPROBE_CONF_PATH} removed", "🐧")
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: amd-debug-tools
3
+ Version: 0.2.8
4
+ Summary: debug tools for AMD systems
5
+ Author-email: Mario Limonciello <superm1@kernel.org>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://web.git.kernel.org/pub/scm/linux/kernel/git/superm1/amd-debug-tools.git/
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: dbus-fast
14
+ Requires-Dist: pyudev
15
+ Requires-Dist: packaging
16
+ Requires-Dist: pandas
17
+ Requires-Dist: jinja2
18
+ Requires-Dist: tabulate
19
+ Requires-Dist: seaborn
20
+ Requires-Dist: cysystemd
21
+ Requires-Dist: Jinja2
22
+ Requires-Dist: matplotlib
23
+ Requires-Dist: seaborn
24
+ Dynamic: license-file
25
+
26
+ # Helpful tools for debugging AMD Zen systems
27
+ [![codecov](https://codecov.io/github/superm1/amd-debug-tools/graph/badge.svg?token=Z9WTBZADGT)](https://codecov.io/github/superm1/amd-debug-tools)
28
+ [![PyPI](https://img.shields.io/pypi/v/amd-debug-tools.svg)](https://pypi.org/project/amd-debug-tools/)
29
+
30
+ This repository hosts open tools that are useful for debugging issues on AMD systems.
31
+
32
+ ## Installation
33
+ ### Distro (Arch)
34
+ `amd-debug-tools` has been [packaged for Arch Linux](https://archlinux.org/packages/extra/any/amd-debug-tools/) (and derivatives). You can install it using:
35
+
36
+ pacman -Sy amd-debug-tools
37
+
38
+ ### Using a python wheel (Generic)
39
+ It is suggested to install tools in a virtual environment either using
40
+ `pipx` or `python3 -m venv`.
41
+
42
+ #### From PyPI
43
+ `amd-debug-tools` is distributed as a python wheel, which is a
44
+ binary package format for Python. To install from PyPI, run the following
45
+ command:
46
+
47
+ pipx install amd-debug-tools
48
+
49
+ ### From source
50
+ To build the package from source, you will need to the `python3-build`
51
+ package natively installed by your distribution package manager. Then you
52
+ can generate and install a wheel by running the following commands:
53
+
54
+ python3 -m build
55
+ pipx install dist/amd-debug-tools-*.whl
56
+
57
+ ### Ensuring path
58
+ If you have not used a `pipx` environment before, you may need to run the following command
59
+ to set up the environment:
60
+
61
+ pipx ensurepath
62
+
63
+ This will add the `pipx` environment to your path.
64
+
65
+ ## Running in-tree
66
+ Documentation about running directly from a git checkout is available [here](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-s2idle.md).
67
+
68
+ ## Tools
69
+
70
+ Each tool has its own individual documentation page:
71
+ * [amd-s2idle](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-s2idle.md)
72
+ * [amd-bios](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-bios.md)
73
+ * [amd-pstate](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-pstate.md)
74
+ * [amd-ttm](https://github.com/superm1/amd-debug-tools/blob/master/docs/amd-ttm.md)
75
+
@@ -2,36 +2,36 @@ launcher.py,sha256=M8kT9DtyZoQgZaKWDbSBu4jsS6tZF1gWko3sovNVyag,947
2
2
  test_acpi.py,sha256=wtS43Rz95h7YEEJBeFa6Mswaeo4syBZrw4hY8i0YbJY,3117
3
3
  test_batteries.py,sha256=nN5pfP5El7Whypq3HHEpW8bufdf5EWSTVGbayfNQYP4,3360
4
4
  test_bios.py,sha256=x_KLmQqGEbQhTugyWCHGXjGp2H1dCdhRz0kgw2Big8w,9276
5
- test_common.py,sha256=EYbyObC9vIXquT3EbgQ_98V4Zw2ebUCY9cfS9VOoywE,19722
5
+ test_common.py,sha256=qOCouXyO-dhY_x_L8kyNyuP_c0bhHhlqPoc084_F6Xg,20757
6
6
  test_database.py,sha256=q5ZjI5u20f7ki6iCY5o1iPi0YOvPz1_W0LTDraU8mN4,10040
7
7
  test_display.py,sha256=hHggv-zBthF1BlwWWSjzAm7BBw1DWcElwil5xAuz87g,5822
8
8
  test_failures.py,sha256=H1UxXeVjhJs9-j9yas4vwAha676GX1Es7Kz8RN2B590,6845
9
9
  test_installer.py,sha256=oDMCvaKqqAWjTggltacnasQ-s1gyUvXPDcNrCUGnux4,10216
10
10
  test_kernel.py,sha256=2EXrLht5ZWdT4N5pb_F3zqZl9NEghjnDpcMGCMw3obI,7917
11
11
  test_launcher.py,sha256=8g8CBTvLX64Us4RmHtRPSdpV5E2kQFaudBl7VIsxLhE,1733
12
- test_prerequisites.py,sha256=-q6v80QXDMB_Mdek2KZTmKfKcRVZnHf8SBEhcT0RyIY,88498
12
+ test_prerequisites.py,sha256=ig0ENpnR-wRMNlxLQ1FghivQsOu4yx5XP4HsJlptyQA,91791
13
13
  test_pstate.py,sha256=a9oAJ9-LANX32XNQhplz6Y75VNYc__QqoSBKIrwvANg,6058
14
14
  test_s2idle.py,sha256=FxsyujgX9Px3m56VzHNeA8yMTHmJiRLWxYt-fh1m5gw,33585
15
15
  test_sleep_report.py,sha256=ANuxYi_C1oSKAi4xUU2wBu4SwJtcZA7VPpazBe3_WUQ,6922
16
+ test_ttm.py,sha256=QrCdRodQ_CD3nyDqKodb6yR158mgE0iIM5f1fV1Axu8,10515
16
17
  test_validator.py,sha256=RpjyzxDpExhLcSJfQ0UDuonr4sTFAfa7sTtY5g7tc_Q,36410
17
18
  test_wake.py,sha256=6zi5GVFHQKU1sTWw3O5-aGriB9uu5713QLn4l2wjhpM,7152
18
19
  amd_debug/__init__.py,sha256=66Ya61av8RCws6bEY_vdujGmjBIZ6_UqfuWHgMNNOJY,1271
19
20
  amd_debug/acpi.py,sha256=fkD3Sov8cRT5ryPlakRlT7Z9jiCLT9x_MPWxt3xU_tc,3161
20
21
  amd_debug/battery.py,sha256=WN-6ys9PHCZIwg7PdwyBOa62GjBp8WKG0v1YZt5_W5s,3122
21
22
  amd_debug/bios.py,sha256=y1iwDqX-mXCkoUtHSi-XO9pN-oLfaqbAMzANGI12zHs,4041
22
- amd_debug/common.py,sha256=fHrmSEVerVAE7KXjspf60eOAetUO7K9s1yhNyE2xi94,11598
23
+ amd_debug/common.py,sha256=JclB-X3xwz2Ovvm0nsejE3W9FirP-inTgvHYx7MPNZo,12635
23
24
  amd_debug/database.py,sha256=GkRg3cmaNceyQ2_hy0MBAlMbnTDPHo2co2o4ObWpnQg,10621
24
25
  amd_debug/display.py,sha256=5L9x9tI_UoulHpIvuxuVASRtdXta7UCW_JjTb5StEB0,953
25
26
  amd_debug/failures.py,sha256=z4O4Q-akv3xYGssSZFCqE0cDE4P9F_aw1hxil3McoD4,22910
26
27
  amd_debug/installer.py,sha256=6_Y0oHypW-oh_P8N9JW7fzbqidpsi5jphw9_8s5Qvso,14288
27
28
  amd_debug/kernel.py,sha256=HpX-QRh8tgkvqKnExfo2JrYqfcbMY8GNgDrC2VVV0Oc,11638
28
- amd_debug/prerequisites.py,sha256=zK-IXL52p_jomA1SsS-6btIHuaapNKW4sD-Fy0wBr68,51399
29
+ amd_debug/prerequisites.py,sha256=-awyFaG2KCX3GSvVyzH1-o3UPhkMNm7Wf3F5AwYWH5U,52340
29
30
  amd_debug/pstate.py,sha256=AOKCvUb0ngwHU2C59uSKrFwdLzEyn8r1w2DgWhZAMKM,9583
30
31
  amd_debug/s2idle-hook,sha256=LLiaqPtGd0qetu9n6EYxKHZaIdHpVQDONdOuSc0pfFg,1695
31
32
  amd_debug/s2idle.py,sha256=4cxHNfmvq11BE-AtkEthIqR-xrWmxW2LTa6oVDtGztY,13284
32
- amd_debug/sleep_report.py,sha256=hhqu711AKtjeYF2xmGcejyCyyPtmq4-gC_hROUCrC0g,17317
33
- amd_debug/test_ttm.py,sha256=McNdEJZ14AeFw8fIxzul9ff8kr67dz1dPvK2fqlM4IA,11219
34
- amd_debug/ttm.py,sha256=U7beRffgoXIPgUXet4ZMabEGo7b0qqKMluhBVMD7O04,4830
33
+ amd_debug/sleep_report.py,sha256=EzzvOHe0XxZcr8ZurxQXFjKs21gJPBj-qksj4a2WLuM,17317
34
+ amd_debug/ttm.py,sha256=p9zV7OAOrcbI8apOkRMnsI8g1GDvOJfNEAklFuFGdRQ,4679
35
35
  amd_debug/validator.py,sha256=X-cNFVvHWKzFgT4aR0Td3I2kwQRBOK4vQUk6L276VCQ,34153
36
36
  amd_debug/wake.py,sha256=xT8WrFrN6voCmXWo5dsn4mQ7iR2QJxHrrYBd3EREG-Q,3936
37
37
  amd_debug/bash/amd-s2idle,sha256=g_cle1ElCJpwE4wcLezL6y-BdasDKTnNMhrtzKLE9ks,1142
@@ -39,9 +39,9 @@ amd_debug/templates/html,sha256=JfGhpmHIB2C2GItdGI1kuC8uayqEVgrpQvAWAj35eZ4,1458
39
39
  amd_debug/templates/md,sha256=r8X2aehnH2gzj0WHYTZ5K9wAqC5y39i_3nkDORSC0uM,787
40
40
  amd_debug/templates/stdout,sha256=hyoOJ96K2dJfnWRWhyCuariLKbEHXvs9mstV_g5aMdI,469
41
41
  amd_debug/templates/txt,sha256=nNdsvbPFOhGdL7VA-_4k5aN3nB-6ouGQt6AsWst7T3w,649
42
- amd_debug_tools-0.2.6.dist-info/licenses/LICENSE,sha256=RBlZI6r3MRGzymI2VDX2iW__D2APDbMhu_Xg5t6BWeo,1066
43
- amd_debug_tools-0.2.6.dist-info/METADATA,sha256=pMVZTIUIqoKEksnbkS2UCFJPnDRJ1k6sf3Jwj74mNxk,6971
44
- amd_debug_tools-0.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
- amd_debug_tools-0.2.6.dist-info/entry_points.txt,sha256=hIskDz6k0_6q1qpqWCpVFsca_djxAqkLrUAwzAyEGuE,144
46
- amd_debug_tools-0.2.6.dist-info/top_level.txt,sha256=XYjxExbUTEtiIlag_5iQvZSVOC1EIxhKM4NLklReQ0k,234
47
- amd_debug_tools-0.2.6.dist-info/RECORD,,
42
+ amd_debug_tools-0.2.8.dist-info/licenses/LICENSE,sha256=RBlZI6r3MRGzymI2VDX2iW__D2APDbMhu_Xg5t6BWeo,1066
43
+ amd_debug_tools-0.2.8.dist-info/METADATA,sha256=rd1U-CfX7hGfsubYk0mvuR3Ec92dAZrquTKGKRdOFEk,2775
44
+ amd_debug_tools-0.2.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
+ amd_debug_tools-0.2.8.dist-info/entry_points.txt,sha256=hIskDz6k0_6q1qpqWCpVFsca_djxAqkLrUAwzAyEGuE,144
46
+ amd_debug_tools-0.2.8.dist-info/top_level.txt,sha256=VvGkkY5I7O3HoLNrc2VfgjCA-to3PUjnnKd7juONaFw,243
47
+ amd_debug_tools-0.2.8.dist-info/RECORD,,
@@ -14,5 +14,6 @@ test_prerequisites
14
14
  test_pstate
15
15
  test_s2idle
16
16
  test_sleep_report
17
+ test_ttm
17
18
  test_validator
18
19
  test_wake
test_common.py CHANGED
@@ -4,13 +4,16 @@
4
4
  """
5
5
  This module contains unit tests for the common functions in the amd-debug-tools package.
6
6
  """
7
- from unittest.mock import patch, mock_open, call
7
+ from unittest.mock import patch, mock_open, call, Mock
8
8
 
9
+ import asyncio
10
+ import builtins
9
11
  import logging
10
12
  import tempfile
11
13
  import unittest
12
14
  import os
13
15
  from platform import uname_result
16
+ import sys
14
17
 
15
18
 
16
19
  from amd_debug.common import (
@@ -49,6 +52,7 @@ color_dict = {
49
52
  "○": Colors.OK,
50
53
  "💤": Colors.OK,
51
54
  "💯": Colors.UNDERLINE,
55
+ "🚫": Colors.UNDERLINE,
52
56
  "🗣️": Colors.HEADER,
53
57
  }
54
58
 
@@ -473,63 +477,84 @@ class TestCommon(unittest.TestCase):
473
477
  self.assertAlmostEqual(get_system_mem(), expected_gb)
474
478
  mock_file.assert_called_once_with("/proc/meminfo", "r", encoding="utf-8")
475
479
 
476
- @patch("builtins.open", new_callable=mock_open, read_data="NoMemHere: 1234\n")
477
- @patch("os.path.join", return_value="/proc/meminfo")
478
- def test_get_system_mem_missing(self, _mock_join, _mock_file):
479
- """Test get_system_mem raises ValueError if MemTotal is missing"""
480
- with self.assertRaises(ValueError):
481
- get_system_mem()
482
-
483
- @patch("amd_debug.common.fatal_error")
484
- def test_reboot_importerror(self, mock_fatal_error):
485
- """Test reboot handles ImportError"""
486
- with patch.dict("sys.modules", {"dbus": None}):
487
- reboot()
488
- mock_fatal_error.assert_called_once_with("Missing dbus")
489
-
490
- @patch("amd_debug.common.fatal_error")
491
- def test_reboot_dbus_exception(self, mock_fatal_error):
492
- """Test reboot handles dbus.exceptions.DBusException"""
493
-
494
- class DummyDBusException(Exception):
495
- """Dummy exception"""
496
-
497
- class DummyIntf:
498
- """Dummy interface"""
480
+ def test_reboot_dbus_fast_success(self):
481
+ """Test reboot returns True when reboot_dbus_fast succeeds"""
499
482
 
500
- def Reboot(self, _arg): # pylint: disable=invalid-name
501
- """Dummy Reboot method"""
502
- raise DummyDBusException("fail")
503
-
504
- class DummyObj: # pylint: disable=too-few-public-methods
505
- """Dummy object"""
506
-
507
- def __init__(self):
483
+ # Create a mock loop that properly handles coroutines
484
+ def mock_run_until_complete(coro):
485
+ # Consume the coroutine to prevent the warning
486
+ try:
487
+ # Close the coroutine to prevent the warning
488
+ coro.close()
489
+ except (AttributeError, RuntimeError):
508
490
  pass
491
+ return True
509
492
 
510
- class DummyBus: # pylint: disable=too-few-public-methods
511
- """Dummy bus"""
493
+ mock_loop = Mock()
494
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
512
495
 
513
- def get_object(self, *args, **kwargs):
514
- """Dummy get_object method"""
515
- return DummyObj()
496
+ with patch("amd_debug.common.asyncio.get_event_loop", return_value=mock_loop):
497
+ result = reboot()
498
+ self.assertTrue(result)
499
+ mock_loop.run_until_complete.assert_called_once()
516
500
 
517
- class DummyDBus:
518
- """Dummy dbus"""
501
+ @patch("asyncio.get_event_loop")
502
+ def test_reboot_dbus_fast_failure_and_dbus_success(self, mock_get_event_loop):
503
+ """Test reboot falls back to reboot_dbus when reboot_dbus_fast fails"""
519
504
 
520
- class exceptions: # pylint: disable=invalid-name
521
- """Dummy exceptions"""
505
+ # Create a mock loop that properly handles coroutines
506
+ def mock_run_until_complete(coro):
507
+ # Consume the coroutine to prevent the warning
508
+ try:
509
+ coro.close()
510
+ except (AttributeError, RuntimeError):
511
+ pass
512
+ return False
513
+
514
+ mock_loop = Mock()
515
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
516
+ mock_get_event_loop.return_value = mock_loop
517
+
518
+ # Mock the dbus module to avoid ImportError in CI
519
+ mock_dbus = Mock()
520
+ mock_bus = Mock()
521
+ mock_obj = Mock()
522
+ mock_intf = Mock()
523
+
524
+ mock_dbus.SystemBus.return_value = mock_bus
525
+ mock_bus.get_object.return_value = mock_obj
526
+ mock_obj.get_interface.return_value = mock_intf
527
+ mock_dbus.Interface = Mock(return_value=mock_intf)
528
+
529
+ with patch.dict("sys.modules", {"dbus": mock_dbus}):
530
+ result = reboot()
531
+ self.assertTrue(result)
532
+
533
+ @patch("asyncio.get_event_loop")
534
+ def test_reboot_dbus_fast_failure_and_dbus_failure(self, mock_get_event_loop):
535
+ """Test reboot returns False when both reboot_dbus_fast and reboot_dbus fail"""
536
+
537
+ # Create a mock loop that properly handles coroutines
538
+ def mock_run_until_complete(coro):
539
+ # Consume the coroutine to prevent the warning
540
+ try:
541
+ coro.close()
542
+ except (AttributeError, RuntimeError):
543
+ pass
544
+ return False
522
545
 
523
- DBusException = DummyDBusException
546
+ mock_loop = Mock()
547
+ mock_loop.run_until_complete.side_effect = mock_run_until_complete
548
+ mock_get_event_loop.return_value = mock_loop
524
549
 
525
- def SystemBus(self): # pylint: disable=invalid-name
526
- """Dummy SystemBus method"""
527
- return DummyBus()
550
+ # Mock the import to raise ImportError when dbus is imported
551
+ original_import = builtins.__import__
528
552
 
529
- def Interface(self, _obj, _name): # pylint: disable=invalid-name
530
- """Dummy Interface method"""
531
- return DummyIntf()
553
+ def mock_import(name, *args, **kwargs):
554
+ if name == "dbus":
555
+ raise ImportError("No module named 'dbus'")
556
+ return original_import(name, *args, **kwargs)
532
557
 
533
- with patch.dict("sys.modules", {"dbus": DummyDBus()}):
534
- reboot()
535
- self.assertTrue(mock_fatal_error.called)
558
+ with patch("builtins.__import__", side_effect=mock_import):
559
+ result = reboot()
560
+ self.assertFalse(result)
test_prerequisites.py CHANGED
@@ -2035,3 +2035,76 @@ class TestPrerequisiteValidator(unittest.TestCase):
2035
2035
  self.mock_db.record_prereq.assert_called_with(
2036
2036
  "NVIDIA GPU {f} not readable", "👀"
2037
2037
  )
2038
+
2039
+ @patch("amd_debug.prerequisites.os.walk")
2040
+ @patch(
2041
+ "builtins.open",
2042
+ new_callable=unittest.mock.mock_open,
2043
+ read_data=b"C1 state info",
2044
+ )
2045
+ def test_capture_cstates_single_file(self, mock_open, mock_walk):
2046
+ """Test capture_cstates with a single cpuidle file"""
2047
+ mock_walk.return_value = [
2048
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2049
+ ]
2050
+ self.validator.capture_cstates()
2051
+ self.mock_db.record_debug.assert_called_with(
2052
+ "ACPI C-state information\n└─/sys/bus/cpu/devices/cpu0/cpuidle/state1: C1 state info"
2053
+ )
2054
+
2055
+ @patch("amd_debug.prerequisites.os.walk")
2056
+ @patch("builtins.open", new_callable=mock_open)
2057
+ def test_capture_cstates_multiple_files(self, mock_open_func, mock_walk):
2058
+ """Test capture_cstates with multiple cpuidle files"""
2059
+ # Setup mock file reads for two files
2060
+ file_contents = {
2061
+ "/sys/bus/cpu/devices/cpu0/cpuidle/state1": b"C1 info",
2062
+ "/sys/bus/cpu/devices/cpu0/cpuidle/state2": b"C2 info",
2063
+ }
2064
+
2065
+ def side_effect(path, mode="rb"):
2066
+ mock_file = mock_open(read_data=file_contents[path])()
2067
+ return mock_file
2068
+
2069
+ mock_open_func.side_effect = side_effect
2070
+ mock_walk.return_value = [
2071
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1", "state2"]),
2072
+ ]
2073
+ self.validator.capture_cstates()
2074
+ # The prefix logic is based on order, so check for both lines
2075
+ debug_call = self.mock_db.record_debug.call_args[0][0]
2076
+ self.assertIn("/sys/bus/cpu/devices/cpu0/cpuidle/state1: C1 info", debug_call)
2077
+ self.assertIn("/sys/bus/cpu/devices/cpu0/cpuidle/state2: C2 info", debug_call)
2078
+ self.assertTrue(debug_call.startswith("ACPI C-state information\n"))
2079
+
2080
+ @patch("amd_debug.prerequisites.os.walk")
2081
+ @patch("builtins.open", new_callable=mock_open, read_data=b"")
2082
+ def test_capture_cstates_empty_files(self, _mock_open, mock_walk):
2083
+ """Test capture_cstates with empty cpuidle files"""
2084
+ mock_walk.return_value = [
2085
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2086
+ ]
2087
+ self.validator.capture_cstates()
2088
+ self.mock_db.record_debug.assert_called_with(
2089
+ "ACPI C-state information\n└─/sys/bus/cpu/devices/cpu0/cpuidle/state1: "
2090
+ )
2091
+
2092
+ @patch("amd_debug.prerequisites.os.walk")
2093
+ @patch("builtins.open", side_effect=PermissionError)
2094
+ def test_capture_cstates_permission_error(self, _mock_open, mock_walk):
2095
+ """Test capture_cstates when reading cpuidle files raises PermissionError"""
2096
+ mock_walk.return_value = [
2097
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], ["state1"]),
2098
+ ]
2099
+ with self.assertRaises(PermissionError):
2100
+ self.validator.capture_cstates()
2101
+ self.mock_db.record_debug.assert_not_called()
2102
+
2103
+ @patch("amd_debug.prerequisites.os.walk")
2104
+ def test_capture_cstates_no_files(self, mock_walk):
2105
+ """Test capture_cstates when no cpuidle files are present"""
2106
+ mock_walk.return_value = [
2107
+ ("/sys/bus/cpu/devices/cpu0/cpuidle", [], []),
2108
+ ]
2109
+ self.validator.capture_cstates()
2110
+ self.mock_db.record_debug.assert_called_with("ACPI C-state information\n")
@@ -199,18 +199,12 @@ class TestAmdTtmTool(unittest.TestCase):
199
199
  )
200
200
  self.assertFalse(result)
201
201
 
202
- @mock.patch("amd_debug.ttm.is_root", return_value=False)
203
- @mock.patch("amd_debug.ttm.print_color")
204
- def test_set_not_root(self, mock_print, _mock_is_root):
205
- """Test set() when not root"""
206
- result = self.tool.set(2)
207
- mock_print.assert_called_with("Root privileges required", "❌")
208
- self.assertFalse(result)
209
-
210
- @mock.patch("amd_debug.ttm.is_root", return_value=True)
202
+ @mock.patch("amd_debug.ttm.relaunch_sudo", return_value=True)
211
203
  @mock.patch("amd_debug.ttm.get_system_mem", return_value=8.0)
212
204
  @mock.patch("amd_debug.ttm.print_color")
213
- def test_set_gb_greater_than_total(self, mock_print, _mock_mem, _mock_is_root):
205
+ def test_set_gb_greater_than_total(
206
+ self, mock_print, _mock_mem, _mock_relaunch_sudo
207
+ ):
214
208
  """Test set() when gb_value > total system memory"""
215
209
  result = self.tool.set(16)
216
210
  mock_print.assert_any_call(
@@ -218,19 +212,19 @@ class TestAmdTtmTool(unittest.TestCase):
218
212
  )
219
213
  self.assertFalse(result)
220
214
 
221
- @mock.patch("amd_debug.ttm.is_root", return_value=True)
215
+ @mock.patch("amd_debug.ttm.relaunch_sudo", return_value=True)
222
216
  @mock.patch("amd_debug.ttm.get_system_mem", return_value=10.0)
223
217
  @mock.patch("amd_debug.ttm.print_color")
224
218
  @mock.patch("builtins.input", return_value="n")
225
219
  def test_set_gb_exceeds_max_percentage_cancel(
226
- self, _mock_input, mock_print, _mock_mem, mock_is_root
220
+ self, _mock_input, mock_print, _mock_mem, mock_relaunch_sudo
227
221
  ):
228
222
  """Test set() when gb_value exceeds max percentage and user cancels"""
229
223
  result = self.tool.set(9.5)
230
224
  self.assertFalse(result)
231
225
  mock_print.assert_any_call("Operation cancelled.", "🚦")
232
226
 
233
- @mock.patch("amd_debug.ttm.is_root", return_value=True)
227
+ @mock.patch("amd_debug.ttm.relaunch_sudo", return_value=True)
234
228
  @mock.patch("amd_debug.ttm.get_system_mem", return_value=10.0)
235
229
  @mock.patch("amd_debug.ttm.gb_to_pages", return_value=20480)
236
230
  @mock.patch("amd_debug.ttm.print_color")
@@ -244,8 +238,8 @@ class TestAmdTtmTool(unittest.TestCase):
244
238
  mock_open,
245
239
  mock_print,
246
240
  _mock_gb_to_pages,
247
- mock_mem,
248
- mock_is_root,
241
+ _mock_mem,
242
+ _relaunch_sudo,
249
243
  ):
250
244
  """Test set() success path"""
251
245
  result = self.tool.set(5)
@@ -266,21 +260,12 @@ class TestAmdTtmTool(unittest.TestCase):
266
260
  self.assertFalse(result)
267
261
 
268
262
  @mock.patch("os.path.exists", return_value=True)
269
- @mock.patch("amd_debug.ttm.is_root", return_value=False)
270
- @mock.patch("amd_debug.ttm.print_color")
271
- def test_clear_not_root(self, mock_print, _mock_is_root, _mock_exists):
272
- """Test clear() when not root"""
273
- result = self.tool.clear()
274
- mock_print.assert_called_with("Root privileges required", "❌")
275
- self.assertFalse(result)
276
-
277
- @mock.patch("os.path.exists", return_value=True)
278
- @mock.patch("amd_debug.ttm.is_root", return_value=True)
263
+ @mock.patch("amd_debug.ttm.relaunch_sudo", return_value=True)
279
264
  @mock.patch("os.remove")
280
265
  @mock.patch("amd_debug.ttm.print_color")
281
266
  @mock.patch("amd_debug.ttm.maybe_reboot", return_value=True)
282
267
  def test_clear_success(
283
- self, _mock_reboot, mock_print, mock_remove, _mock_is_root, mock_exists
268
+ self, _mock_reboot, mock_print, mock_remove, _mock_relaunch_sudo, _mock_exists
284
269
  ):
285
270
  """Test clear() success path"""
286
271
  result = self.tool.clear()
@@ -1,184 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: amd-debug-tools
3
- Version: 0.2.6
4
- Summary: debug tools for AMD systems
5
- Author-email: Mario Limonciello <superm1@kernel.org>
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://web.git.kernel.org/pub/scm/linux/kernel/git/superm1/amd-debug-tools.git/
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Operating System :: POSIX :: Linux
10
- Requires-Python: >=3.7
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Requires-Dist: pyudev
14
- Requires-Dist: packaging
15
- Requires-Dist: pandas
16
- Requires-Dist: jinja2
17
- Requires-Dist: tabulate
18
- Requires-Dist: seaborn
19
- Requires-Dist: cysystemd
20
- Requires-Dist: Jinja2
21
- Requires-Dist: matplotlib
22
- Requires-Dist: seaborn
23
- Dynamic: license-file
24
-
25
- # Helpful tools for debugging AMD Zen systems
26
- [![codecov](https://codecov.io/github/superm1/amd-debug-tools/graph/badge.svg?token=Z9WTBZADGT)](https://codecov.io/github/superm1/amd-debug-tools)
27
- [![PyPI](https://img.shields.io/pypi/v/amd-debug-tools.svg)](https://pypi.org/project/amd-debug-tools/)
28
-
29
- This repository hosts open tools that are useful for debugging issues on AMD systems.
30
-
31
- ## Installation
32
- It is suggested to install tools in a virtual environment either using
33
- `pipx` or `python3 -m venv`.
34
-
35
- ### From PyPI
36
- `amd-debug-tools` is distributed as a python wheel, which is a
37
- binary package format for Python. To install from PyPI, run the following
38
- command:
39
-
40
- pipx install amd-debug-tools
41
-
42
- ### From source
43
- To build the package from source, you will need to the `python3-build`
44
- package natively installed by your distribution package manager. Then you
45
- can generate and install a wheel by running the following commands:
46
-
47
- python3 -m build
48
- pipx install dist/amd-debug-tools-*.whl
49
-
50
- ### Ensuring path
51
- If you have not used a `pipx` environment before, you may need to run the following command
52
- to set up the environment:
53
-
54
- pipx ensurepath
55
-
56
- This will add the `pipx` environment to your path.
57
-
58
- ### Running in tree
59
- If you want to run the tools in tree, you need to make sure that distro dependencies
60
- that would normally install into a venv are installed. This can be done by running:
61
-
62
- ./install_deps.py
63
-
64
- After dependencies are installed, you can run the tools by running:
65
-
66
- ./amd_s2idle.py
67
- ./amd_bios.py
68
- ./amd_pstate.py
69
-
70
- ## amd-s2idle
71
- `amd-s2idle` is a tool used for analyzing the entry and exit of the s2idle
72
- state of a Linux system.
73
-
74
- It is intended to use with Linux kernel 6.1 or later and works by hooking
75
- into dynamic debugging messages and events that are generated by the kernel.
76
-
77
- For analysis of power consumption issues it can be hooked into `systemd` to
78
- run a command to capture data right before and after the system enters and
79
- exits the s2idle state.
80
-
81
- 4 high level commands are supported.
82
-
83
- ### `amd-s2idle install`
84
- This will install the systemd hook so that data will be captured before and
85
- after the system enters and exits the s2idle state.
86
-
87
- This will also install a bash completion script that can be used for other
88
- commands.
89
-
90
- **NOTE:** This command is only supported when run from a venv.
91
-
92
- ### `amd-s2idle uninstall`
93
- This will uninstall the systemd hook and remove the bash completion script.
94
-
95
- **NOTE:** This command is only supported when run from a venv.
96
-
97
- ### `amd-s2idle test`
98
- This will run a suspend cycle with a timer based wakeup and capture relevant
99
- data into a database and produce a report. This can also be used to run multiple cycles.
100
-
101
- The following optional arguments are supported for this command:
102
-
103
- --count COUNT Number of cycles to run
104
- --duration DURATION Duration of the cycle in seconds
105
- --wait WAIT Time to wait before starting the cycle in seconds
106
- --format FORMAT Format of the report to produce (html, txt or md)
107
- --report-file File to write the report to
108
- --force Run a test cycle even if the system fails to pass prerequisite checks
109
- --random Run sleep cycles for random durations and waits, using the --duration and --wait arguments as an upper bound
110
- --logind Use logind to suspend the system
111
- --tool-debug Enable debug logging
112
- --bios-debug Enable BIOS debug logging instead of notify logging
113
-
114
- If the tool is launched with an environment that can call `xdg-open`, the report
115
- will be opened in a browser.
116
-
117
- ### `amd-s2idle report`
118
- This will produce a report from the data captured by the `test` command
119
- and/or from the systemd hook. The report will default to 60 days of data.
120
-
121
- The following optional arguments are supported for this command:
122
-
123
- --since SINCE Date to start the report from
124
- --until UNTIL Date to end the report at
125
- --format FORMAT Format of the report to produce (html, txt or md)
126
- --report-file File to write the report to
127
- --tool-debug Enable tool debug logging
128
- --report-debug
129
- --no-report-debug
130
- Include debug messages in report (WARNING: can significantly increase report size)
131
- If the tool is launched with an environment that can call `xdg-open`, the report
132
- will be opened in a browser.
133
-
134
- ### `amd-s2idle --version`
135
- This will print the version of the tool and exit.
136
-
137
- ### Debug output
138
- All commands support the `--tool-debug` argument which will enable extra debug output. This is often needed for debugging issues with a particular cycle.
139
-
140
- **NOTE:** enabling debug output significantly increases the size of the report.
141
- It's suggested that you use `--since` and `--until` to focus on the cycles that you are interested in.
142
-
143
- ## amd-bios
144
- `amd-bios` is a a tool that can be used to enable or disable BIOS AML debug logging
145
- -and to parse a kernel log that contains BIOS logs.
146
-
147
- ### `amd-bios trace`
148
- Modify BIOS AML trace debug logging.
149
-
150
- One of the following arguments must be set for this command:
151
-
152
- --enable Enable BIOS AML tracing
153
- --disable Disable BIOS AML tracing
154
-
155
- The following optional arguments are supported for this command:
156
-
157
- --tool-debug Enable tool debug logging
158
-
159
- ### `amd-bios parse`
160
- Parses a kernel log that contains BIOS AML debug logging and produces a report.
161
-
162
- The following optional arguments are supported for this command:
163
-
164
- --input INPUT Optional input file to parse
165
- --tool-debug Enable tool debug logging
166
-
167
- ### `amd-bios --version`
168
- This will print the version of the tool and exit.
169
-
170
- ## amd-pstate
171
- `amd-pstate` is a tool used for identification of issues with amd-pstate.
172
- It will capture some state from the system as well as from the machine specific registers that
173
- amd-pstate uses.
174
-
175
- ## amd-ttm
176
- `amd-ttm` is a tool used for managing the TTM memory settings on AMD systems.
177
-
178
- ## Compatibility scripts
179
-
180
- Compatibility scripts are provided for the previous names the tools went by:
181
- `amd_s2idle.py`, `amd_bios.py` and `amd_pstate.py`.
182
- These allow cloning the repository and running the scripts without installing
183
- the package.
184
-