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