Merge 8793250857 into 0eb28ac3d8
This commit is contained in:
commit
8cc88a9c8f
2 changed files with 220 additions and 0 deletions
112
tests/unit/plugins/test_plugin_amd_x3d.py
Normal file
112
tests/unit/plugins/test_plugin_amd_x3d.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import tempfile
|
||||
import unittest
|
||||
|
||||
try:
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import call
|
||||
from unittest.mock import patch
|
||||
except ImportError:
|
||||
from mock import Mock
|
||||
from mock import call
|
||||
from mock import patch
|
||||
|
||||
from tuned.monitors.repository import Repository
|
||||
from tuned.plugins.plugin_amd_x3d import AMDX3DPlugin
|
||||
from tuned.plugins.plugin_amd_x3d import _find_x3d_paths
|
||||
from tuned.utils.commands import commands
|
||||
import tuned.plugins as plugins
|
||||
import tuned.profiles as profiles
|
||||
from tuned import storage
|
||||
|
||||
|
||||
class AMDX3DPluginTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._storage_file = tempfile.NamedTemporaryFile()
|
||||
plugin_instance_factory = plugins.instance.Factory()
|
||||
storage_provider = storage.PickleProvider(self._storage_file.name)
|
||||
storage_factory = storage.Factory(storage_provider)
|
||||
|
||||
self._plugin = AMDX3DPlugin(
|
||||
Repository(),
|
||||
storage_factory,
|
||||
Mock(),
|
||||
Mock(),
|
||||
Mock(),
|
||||
plugin_instance_factory,
|
||||
None,
|
||||
profiles.variables.Variables(),
|
||||
)
|
||||
self._plugin._cmd = commands()
|
||||
self._plugin._cmd.read_file = Mock()
|
||||
self._plugin._cmd.write_to_file = Mock()
|
||||
|
||||
def tearDown(self):
|
||||
self._storage_file.close()
|
||||
|
||||
def _create_instance(self, mode="cache"):
|
||||
instance = self._plugin.create_instance(
|
||||
"amd_x3d",
|
||||
0,
|
||||
"",
|
||||
None,
|
||||
"",
|
||||
"",
|
||||
{"mode": mode},
|
||||
)
|
||||
self._plugin.initialize_instance(instance)
|
||||
return instance
|
||||
|
||||
def test_find_x3d_paths_sorted(self):
|
||||
with patch("tuned.plugins.plugin_amd_x3d.glob.glob",
|
||||
return_value=["/sys/devices/b", "/sys/devices/a"]):
|
||||
self.assertEqual(_find_x3d_paths(),
|
||||
["/sys/devices/a", "/sys/devices/b"])
|
||||
|
||||
def test_apply_verify_and_unapply_mode(self):
|
||||
paths = [
|
||||
"/sys/bus/platform/drivers/amd_x3d_vcache/AMDI0001:00/amd_x3d_mode",
|
||||
"/sys/bus/platform/drivers/amd_x3d_vcache/AMDI0002:00/amd_x3d_mode",
|
||||
]
|
||||
instance = self._create_instance(mode="cache")
|
||||
|
||||
with patch.object(self._plugin, "_x3d_paths", return_value=paths):
|
||||
self._plugin._cmd.read_file.return_value = "frequency\n"
|
||||
instance.apply_tuning()
|
||||
|
||||
self.assertEqual(
|
||||
self._plugin._storage_get(instance, self._plugin._commands["mode"]),
|
||||
"frequency",
|
||||
)
|
||||
self._plugin._cmd.write_to_file.assert_has_calls([
|
||||
call(paths[0], "cache", no_error=False),
|
||||
call(paths[1], "cache", no_error=False),
|
||||
])
|
||||
|
||||
self._plugin._cmd.read_file.reset_mock()
|
||||
self._plugin._cmd.write_to_file.reset_mock()
|
||||
self._plugin._cmd.read_file.return_value = "frequency [cache]\n"
|
||||
self.assertTrue(instance.verify_tuning(False))
|
||||
self._plugin._cmd.write_to_file.assert_not_called()
|
||||
|
||||
self._plugin._cmd.read_file.reset_mock()
|
||||
self._plugin._cmd.write_to_file.reset_mock()
|
||||
instance.unapply_tuning()
|
||||
|
||||
self._plugin._cmd.write_to_file.assert_has_calls([
|
||||
call(paths[0], "frequency", no_error=False),
|
||||
call(paths[1], "frequency", no_error=False),
|
||||
])
|
||||
self.assertIsNone(
|
||||
self._plugin._storage_get(instance, self._plugin._commands["mode"])
|
||||
)
|
||||
|
||||
def test_apply_without_supported_device_is_noop(self):
|
||||
instance = self._create_instance(mode="cache")
|
||||
|
||||
with patch.object(self._plugin, "_x3d_paths", return_value=[]):
|
||||
instance.apply_tuning()
|
||||
|
||||
self._plugin._cmd.write_to_file.assert_not_called()
|
||||
self.assertIsNone(
|
||||
self._plugin._storage_get(instance, self._plugin._commands["mode"])
|
||||
)
|
||||
108
tuned/plugins/plugin_amd_x3d.py
Normal file
108
tuned/plugins/plugin_amd_x3d.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import glob
|
||||
import errno
|
||||
|
||||
from . import base
|
||||
from .decorators import *
|
||||
import tuned.logs
|
||||
|
||||
log = tuned.logs.get()
|
||||
|
||||
_X3D_MODE_GLOB = "/sys/bus/platform/drivers/amd_x3d_vcache/*/amd_x3d_mode"
|
||||
_VALID_MODES = frozenset(["cache", "frequency"])
|
||||
|
||||
|
||||
def _find_x3d_paths():
|
||||
"""Return discovered amd_x3d_mode sysfs paths in a stable order."""
|
||||
return sorted(glob.glob(_X3D_MODE_GLOB))
|
||||
|
||||
|
||||
class AMDX3DPlugin(base.Plugin):
|
||||
"""
|
||||
Controls the AMD 3D V-Cache scheduling mode on dual-CCD processors
|
||||
such as Ryzen 9 7950X3D, 7900X3D, 9950X3D, and 9900X3D processors.
|
||||
|
||||
The [option]`mode` option configures the `amd_x3d_vcache` kernel
|
||||
driver. The driver exposes a sysfs knob that biases the scheduler
|
||||
towards one CCD or the other:
|
||||
|
||||
* `cache`
|
||||
+
|
||||
Prefer the CCD with 3D V-Cache. This is useful for games and other
|
||||
cache-sensitive workloads.
|
||||
|
||||
* `frequency`
|
||||
+
|
||||
Prefer the non-X3D CCD, which can usually boost higher. This is
|
||||
useful for throughput-oriented compute workloads and is the kernel
|
||||
default.
|
||||
|
||||
The plugin discovers the sysfs path using a glob because the ACPI
|
||||
device name can vary across boards. On systems without the
|
||||
`amd_x3d_vcache` driver or without a supported dual-CCD X3D CPU, the
|
||||
plugin does nothing.
|
||||
|
||||
.Prefer the X3D CCD for games
|
||||
====
|
||||
----
|
||||
[amd_x3d]
|
||||
mode=cache
|
||||
----
|
||||
====
|
||||
|
||||
.Prefer the higher-frequency CCD for compute workloads
|
||||
====
|
||||
----
|
||||
[amd_x3d]
|
||||
mode=frequency
|
||||
----
|
||||
====
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _get_config_options(cls):
|
||||
return {
|
||||
"mode": None,
|
||||
}
|
||||
|
||||
def _instance_init(self, instance):
|
||||
instance._has_static_tuning = True
|
||||
instance._has_dynamic_tuning = False
|
||||
|
||||
def _instance_cleanup(self, instance):
|
||||
pass
|
||||
|
||||
def _x3d_paths(self):
|
||||
return _find_x3d_paths()
|
||||
|
||||
@command_set("mode")
|
||||
def _set_mode(self, value, instance, sim, remove):
|
||||
if value not in _VALID_MODES:
|
||||
if not sim:
|
||||
log.warning("amd_x3d: invalid mode '%s', expected one of: %s"
|
||||
% (value, ", ".join(sorted(_VALID_MODES))))
|
||||
return None
|
||||
|
||||
paths = self._x3d_paths()
|
||||
if not paths:
|
||||
if not sim:
|
||||
log.debug("amd_x3d: no AMD 3D V-Cache device found, skipping")
|
||||
return None
|
||||
|
||||
if not sim:
|
||||
for path in paths:
|
||||
log.info("amd_x3d: setting mode to '%s' on %s" % (value, path))
|
||||
self._cmd.write_to_file(path, value,
|
||||
no_error=[errno.ENOENT] if remove else False)
|
||||
return value
|
||||
|
||||
@command_get("mode")
|
||||
def _get_mode(self, instance):
|
||||
paths = self._x3d_paths()
|
||||
if not paths:
|
||||
return None
|
||||
|
||||
# All CCD pairs share the same mode; read from the first found path.
|
||||
data = self._cmd.read_file(paths[0]).strip()
|
||||
if not data:
|
||||
return None
|
||||
return self._cmd.get_active_option(data)
|
||||
Loading…
Reference in a new issue