plugins: tuning units are handled within plugin classes
This change moves a lot of logic into the plugins, namely the enumeration, seizing, and releasing of the devices. However it allows the plugins to control the process in more detail. This will be also utilised when tuning the devices, which were dynamically added into the system. WATCH OUT: only cpu plugin has been updated
This commit is contained in:
parent
68992725f2
commit
f4cc64a3a9
18 changed files with 535 additions and 390 deletions
|
|
@ -21,12 +21,13 @@ class Application(object):
|
|||
storage_provider = storage.PickleProvider()
|
||||
storage_factory = storage.Factory(storage_provider)
|
||||
|
||||
unit_factory = units.Factory()
|
||||
device_matcher = units.DeviceMatcher()
|
||||
hardware_enumerator = hardware.Enumerator()
|
||||
monitors_repository = monitors.Repository()
|
||||
plugins_repository = plugins.Repository(storage_factory, monitors_repository, hardware_enumerator)
|
||||
unit_manager = units.Manager(plugins_repository, monitors_repository, unit_factory, device_matcher)
|
||||
hardware_inventory = hardware.Inventory()
|
||||
device_matcher = hardware.DeviceMatcher()
|
||||
plugin_instance_factory = plugins.instance.Factory()
|
||||
|
||||
plugins_repository = plugins.Repository(monitors_repository, storage_factory, hardware_inventory, device_matcher, plugin_instance_factory)
|
||||
unit_manager = units.Manager(plugins_repository, monitors_repository)
|
||||
|
||||
profile_factory = profiles.Factory()
|
||||
profile_merger = profiles.Merger()
|
||||
|
|
|
|||
|
|
@ -58,16 +58,17 @@ class Daemon(object):
|
|||
|
||||
self._unit_manager.create(self._profile.units)
|
||||
self._save_active_profile(self._profile.name)
|
||||
self._unit_manager.plugins_repository.do_static_tuning()
|
||||
self._unit_manager.start_tuning()
|
||||
|
||||
self._terminate.clear()
|
||||
while not self._terminate.wait(10):
|
||||
log.debug("updating monitors")
|
||||
self._unit_manager.monitors_repository.update()
|
||||
self._unit_manager.update_monitors()
|
||||
log.debug("performing tunings")
|
||||
self._unit_manager.plugins_repository.update()
|
||||
self._unit_manager.update_tuning()
|
||||
|
||||
self._unit_manager.delete_all()
|
||||
self._unit_manager.stop_tuning()
|
||||
self._unit_manager.destroy_all()
|
||||
|
||||
def _save_active_profile(self, profile_name):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
from enumerator import *
|
||||
from inventory import *
|
||||
from device_matcher import *
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import fnmatch
|
||||
import re
|
||||
|
||||
__all__ = ["DeviceMatcher"]
|
||||
|
||||
class DeviceMatcher(object):
|
||||
"""
|
||||
Device name matching against the devices specification in tuning profiles.
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import pyudev
|
||||
import re
|
||||
|
||||
__all__ = ["Enumerator"]
|
||||
|
||||
class Enumerator(object):
|
||||
"""
|
||||
Class for system devices enumeration.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._context = pyudev.Context()
|
||||
|
||||
def _device_fulfills_requirements(self, device, requirements):
|
||||
for attribute, requirement in requirements.items():
|
||||
|
||||
if hasattr(device, attribute):
|
||||
value = getattr(device, attribute)
|
||||
|
||||
if requirement is None:
|
||||
pass
|
||||
elif isinstance(requirement, str):
|
||||
if value != requirement:
|
||||
return False
|
||||
elif isinstance(requirement, re._pattern_type):
|
||||
if requirement.match(value) is None:
|
||||
return False
|
||||
else:
|
||||
raise TypeError("Invalid type of a requirement for a device.")
|
||||
|
||||
else:
|
||||
# custom callbacks with non-existing attributes
|
||||
if callable(requirement):
|
||||
if not requirement(device):
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_devices(self, **requirements):
|
||||
subsystem = requirements.get("subsystem", None)
|
||||
if subsystem is not None:
|
||||
del requirements["subsystem"]
|
||||
|
||||
devices = self._context.list_devices(subsystem = subsystem)
|
||||
return [dev for dev in devices if self._device_fulfills_requirements(dev, requirements)]
|
||||
22
tuned/hardware/inventory.py
Normal file
22
tuned/hardware/inventory.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import pyudev
|
||||
|
||||
class Inventory(object):
|
||||
"""
|
||||
Inventory object can handle information about available hardware devices. It also informs the plugins
|
||||
about related hardware events.
|
||||
"""
|
||||
|
||||
def __init__(self, udev_context=None):
|
||||
if udev_context is not None:
|
||||
self._udev_context = udev_context
|
||||
else:
|
||||
self._udev_context = pyudev.Context()
|
||||
|
||||
def get_devices(self, subsystem):
|
||||
return self._udev_context.list_devices(subsystem=subsystem)
|
||||
|
||||
def subscribe(self, plugin, subsystem, callback):
|
||||
pass
|
||||
|
||||
def unsubscribe(self, plugin, subsystem=None):
|
||||
pass
|
||||
|
|
@ -12,6 +12,10 @@ class Repository(PluginLoader):
|
|||
super(self.__class__, self).__init__()
|
||||
self._monitors = set()
|
||||
|
||||
@property
|
||||
def monitors(self):
|
||||
return self._monitors
|
||||
|
||||
def _set_loader_parameters(self):
|
||||
self._namespace = "tuned.monitors"
|
||||
self._prefix = "monitor_"
|
||||
|
|
@ -28,8 +32,3 @@ class Repository(PluginLoader):
|
|||
assert isinstance(monitor, self._interface)
|
||||
monitor.cleanup()
|
||||
self._monitors.remove(monitor)
|
||||
|
||||
def update(self):
|
||||
for monitor in self._monitors:
|
||||
log.debug("updating %s" % monitor)
|
||||
monitor.update()
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
from repository import *
|
||||
import instance
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import tuned.logs
|
||||
import collections
|
||||
|
||||
log = tuned.logs.get()
|
||||
|
||||
|
|
@ -9,182 +10,47 @@ class Plugin(object):
|
|||
Plugins change various system settings in order to get desired performance or power
|
||||
saving. Plugins use Monitor objects to get information from the running system.
|
||||
|
||||
Methods requiring reimplementation:
|
||||
- device_requirements(cls)
|
||||
- update_tuning(self)
|
||||
Intentionally a lot of logic is included in the plugin to increase plugin flexibility.
|
||||
"""
|
||||
|
||||
_has_dynamic_options = False
|
||||
def __init__(self, monitors_repository, storage_factory, hardware_inventory, device_matcher, instance_factory):
|
||||
"""Plugin constructor."""
|
||||
|
||||
# class methods
|
||||
|
||||
@classmethod
|
||||
def _get_default_options(cls):
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def device_requirements(cls):
|
||||
raise Exception("device_requirements not implemented")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def is_supported(cls):
|
||||
return True
|
||||
|
||||
# instance methods
|
||||
|
||||
def __init__(self, monitors_repository, storage_factory, devices=None, options=None):
|
||||
"""
|
||||
Plugin instance constructor. Plugins should not override this function in general,
|
||||
_post_init method should be used instead.
|
||||
"""
|
||||
self._monitors_repository = monitors_repository
|
||||
self._storage = storage_factory.create(self.__class__.__name__)
|
||||
self._devices = devices
|
||||
self._options = self._get_default_options()
|
||||
if options is not None:
|
||||
self._merge_options(options)
|
||||
self._dynamic_tuning = True
|
||||
self._monitors_repository = monitors_repository
|
||||
self._hardware_inventory = hardware_inventory
|
||||
self._device_matcher = device_matcher
|
||||
self._instance_factory = instance_factory
|
||||
|
||||
self._instances = collections.OrderedDict()
|
||||
self._init_commands()
|
||||
self._post_init()
|
||||
|
||||
def _init_commands(self):
|
||||
self._commands = {}
|
||||
self._autoregister_commands()
|
||||
|
||||
for command_name, command in self._commands.iteritems():
|
||||
if command.get("custom", False):
|
||||
continue
|
||||
|
||||
if "get" not in command or "set" not in command:
|
||||
raise TypeError("Plugin command '%s' is not defined correctly" % command_name)
|
||||
|
||||
def _post_init(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def dynamic_tuning(self):
|
||||
return self._dynamic_tuning
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
return self._devices
|
||||
|
||||
def _autoregister_commands(self):
|
||||
"""
|
||||
Register all commands marked using @command_set and @command_get decorators.
|
||||
"""
|
||||
|
||||
for member_name in self.__class__.__dict__:
|
||||
if member_name.startswith("__"):
|
||||
continue
|
||||
member = getattr(self, member_name)
|
||||
if not hasattr(member, "_command"):
|
||||
continue
|
||||
|
||||
command_name = member._command["name"]
|
||||
info = self._commands.get(command_name, {})
|
||||
|
||||
if "set" in member._command:
|
||||
info["custom"] = None
|
||||
info["set"] = member
|
||||
info["per_device"] = member._command["per_device"]
|
||||
elif "get" in member._command:
|
||||
info["get"] = member
|
||||
elif "custom" in member._command:
|
||||
info["custom"] = member
|
||||
info["per_device"] = member._command["per_device"]
|
||||
|
||||
self._commands[command_name] = info
|
||||
|
||||
def _storage_key(self, command_name, device=None):
|
||||
if device is not None:
|
||||
return "%s@%s" % (command_name, device)
|
||||
else:
|
||||
return command_name
|
||||
|
||||
def _execute_command(self, command_name, command):
|
||||
if not self._options.has_key(command_name):
|
||||
raise ValueError("Command is not supported.")
|
||||
|
||||
new_value = self._options[command_name]
|
||||
if new_value is None:
|
||||
return
|
||||
|
||||
if not command["per_device"]:
|
||||
if command["custom"] is not None:
|
||||
command["custom"](True, new_value)
|
||||
else:
|
||||
current_value = command["get"]()
|
||||
storage_key = self._storage_key(command_name)
|
||||
self._storage.set(command_name, current_value)
|
||||
command["set"](new_value)
|
||||
return
|
||||
|
||||
if self._devices is None:
|
||||
raise TypeError("No devices were specified.")
|
||||
|
||||
for device in self._devices:
|
||||
if command["custom"] is not None:
|
||||
command["custom"](True, new_value, device)
|
||||
else:
|
||||
current_value = command["get"](device)
|
||||
storage_key = self._storage_key(command_name, device)
|
||||
self._storage.set(storage_key, current_value)
|
||||
command["set"](new_value, device)
|
||||
|
||||
def _cleanup_command(self, command_name, command):
|
||||
if not self._options.has_key(command_name):
|
||||
raise ValueError("Command is not supported.")
|
||||
|
||||
if self._options[command_name] is None:
|
||||
return
|
||||
|
||||
if not command["per_device"]:
|
||||
if command["custom"] is not None:
|
||||
command["custom"](False, None)
|
||||
else:
|
||||
storage_key = self._storage_key(command_name)
|
||||
old_value = self._storage.get(storage_key)
|
||||
if old_value is not None:
|
||||
command["set"](old_value)
|
||||
self._storage.unset(storage_key)
|
||||
return
|
||||
|
||||
if self._devices is None:
|
||||
raise TypeError("No devices were specified.")
|
||||
|
||||
for device in self._devices:
|
||||
if command["custom"] is not None:
|
||||
command["custom"](False, None, device)
|
||||
else:
|
||||
storage_key = self._storage_key(command_name, device)
|
||||
old_value = self._storage.get(storage_key)
|
||||
if old_value is not None:
|
||||
command["set"](old_value, device)
|
||||
self._storage.unset(storage_key)
|
||||
|
||||
def execute_commands(self):
|
||||
for command_name, command in self._commands.iteritems():
|
||||
self._execute_command(command_name, command)
|
||||
|
||||
def cleanup_commands(self):
|
||||
for command_name, command in self._commands.iteritems():
|
||||
self._cleanup_command(command_name, command)
|
||||
self._init_devices()
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
self._destroy_all_instances()
|
||||
|
||||
def _merge_options(self, options):
|
||||
@property
|
||||
def name(self):
|
||||
return self.__class__.__module__.split(".")[-1].lstrip("plugin_")
|
||||
|
||||
#
|
||||
# Plugin configuration manipulation and helpers.
|
||||
#
|
||||
|
||||
def _get_config_options(self):
|
||||
"""Default configuration options for the plugin."""
|
||||
return {}
|
||||
|
||||
def _get_effective_options(self, options):
|
||||
"""Merge provided options with plugin default options."""
|
||||
# TODO: _has_dynamic_options is a hack
|
||||
effective = self._get_config_options().copy()
|
||||
for key in options:
|
||||
if key in self._options or self.__class__._has_dynamic_options:
|
||||
self._options[key] = options[key]
|
||||
if key in effective or self._has_dynamic_options:
|
||||
effective[key] = options[key]
|
||||
else:
|
||||
log.warn("Unknown option '%s' for plugin '%s'." % (key, self.__class__.__name__))
|
||||
|
||||
def update_tuning(self):
|
||||
raise NotImplementedError()
|
||||
return effective
|
||||
|
||||
def _option_bool(self, value):
|
||||
if type(value) is bool:
|
||||
|
|
@ -199,3 +65,269 @@ class Plugin(object):
|
|||
return false_value
|
||||
else:
|
||||
return None
|
||||
|
||||
#
|
||||
# Interface for manipulation with instances of the plugin.
|
||||
#
|
||||
|
||||
def create_instance(self, name, devices_expression, options):
|
||||
"""Create new instance of the plugin and seize the devices."""
|
||||
if name in self._instances:
|
||||
raise Exception("Plugin instance with name '%s' already exists." % name)
|
||||
|
||||
effective_options = self._get_effective_options(options)
|
||||
instance = self._instance_factory.create(self, name, devices_expression, effective_options)
|
||||
self._instances[name] = instance
|
||||
self._instance_init(instance)
|
||||
|
||||
return instance
|
||||
|
||||
def destroy_instance(self, instance):
|
||||
"""Destroy existing instance."""
|
||||
if instance._plugin != self:
|
||||
raise Exception("Plugin instance '%s' does not belong to this plugin '%s'." % (instance, self))
|
||||
if instance.name not in self._instances:
|
||||
raise Exception("Plugin instance '%s' was already destroyed." % instance)
|
||||
|
||||
instance = self._instances[instance.name]
|
||||
self._destroy_instance(instance)
|
||||
del self._instances[instance.name]
|
||||
|
||||
def _destroy_instance(self, instance):
|
||||
log.debug("destroying instance %s (%s)" % (instance.name, self.name))
|
||||
self.release_devices(instance)
|
||||
self._instance_cleanup(instance)
|
||||
|
||||
def _destroy_all_instances(self):
|
||||
for instance in self._instances.values():
|
||||
self._destroy_instance(instance)
|
||||
self._instances.clear()
|
||||
|
||||
def _instance_init(self, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _instance_cleanup(self, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
#
|
||||
# Devices handling
|
||||
#
|
||||
|
||||
def _init_devices(self):
|
||||
self._devices = None
|
||||
self._assigned_devices = set()
|
||||
self._free_devices = set()
|
||||
|
||||
def assign_free_devices(self, instance):
|
||||
# devices are not supported
|
||||
if self._devices is None:
|
||||
return
|
||||
|
||||
assert(isinstance(self._devices, set))
|
||||
assert(isinstance(self._assigned_devices, set))
|
||||
assert(isinstance(self._free_devices, set))
|
||||
|
||||
to_assign = set(self._device_matcher.match_list(instance.devices_expression, self._free_devices))
|
||||
|
||||
if len(to_assign) == 0:
|
||||
log.warn("instance '%s': no matching devices available" % instance.name)
|
||||
instance.active = False
|
||||
return
|
||||
|
||||
log.info("instance '%s': assigning devices %s" % (instance.name, ", ".join(to_assign)))
|
||||
|
||||
instance.active = True
|
||||
instance.devices.update(to_assign) # cannot use |=
|
||||
self._assigned_devices |= to_assign
|
||||
self._free_devices -= to_assign
|
||||
|
||||
def release_devices(self, instance):
|
||||
# devices are not supported
|
||||
if self._devices is None:
|
||||
return
|
||||
|
||||
to_release = instance.devices & self._devices
|
||||
|
||||
instance.active = False
|
||||
instance.devices.clear()
|
||||
self._assigned_devices -= to_release
|
||||
self._free_devices |= to_release
|
||||
|
||||
#
|
||||
# Tuning activation and deactivation.
|
||||
#
|
||||
|
||||
def instance_apply_tuning(self, instance):
|
||||
"""
|
||||
Apply static and dynamic tuning if the plugin instance is active.
|
||||
"""
|
||||
if not instance.active:
|
||||
return
|
||||
if instance.has_static_tuning:
|
||||
self._instance_apply_static(instance)
|
||||
if instance.has_dynamic_tuning:
|
||||
self._instance_update_dynamic(instance)
|
||||
|
||||
def instance_update_tuning(self, instance):
|
||||
"""
|
||||
Apply dynamic tuning if the plugin instance is active.
|
||||
"""
|
||||
if not instance.active:
|
||||
return
|
||||
if instance.has_dynamic_tuning:
|
||||
self._instance_update_dynamic(instance)
|
||||
|
||||
def instance_unapply_tuning(self, instance):
|
||||
"""
|
||||
Remove all tunings applied by the plugin instance.
|
||||
"""
|
||||
if instance.has_dynamic_tuning:
|
||||
self._instance_unapply_dynamic(instance)
|
||||
if instance.has_static_tuning:
|
||||
self._instance_unapply_static(instance)
|
||||
|
||||
|
||||
def _instance_apply_static(self, instance):
|
||||
for command in self._commands.values():
|
||||
self._execute_command(instance, command)
|
||||
|
||||
def _instance_unapply_static(self, instance):
|
||||
for command in self._commands.values():
|
||||
self._cleanup_command(instance, command)
|
||||
|
||||
def _instance_apply_dynamic(self, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _instance_unapply_dynamic(self, instance):
|
||||
raise NotImplementedError()
|
||||
|
||||
#
|
||||
# Registration of commands for static plugins.
|
||||
#
|
||||
|
||||
def _init_commands(self):
|
||||
"""
|
||||
Initialize commands.
|
||||
"""
|
||||
self._commands = {}
|
||||
self._autoregister_commands()
|
||||
self._check_commands()
|
||||
|
||||
def _autoregister_commands(self):
|
||||
"""
|
||||
Register all commands marked using @command_set, @command_get, and @command_custom decorators.
|
||||
"""
|
||||
for member_name in self.__class__.__dict__:
|
||||
if member_name.startswith("__"):
|
||||
continue
|
||||
member = getattr(self, member_name)
|
||||
if not hasattr(member, "_command"):
|
||||
continue
|
||||
|
||||
command_name = member._command["name"]
|
||||
info = self._commands.get(command_name, {"name": command_name})
|
||||
|
||||
if "set" in member._command:
|
||||
info["custom"] = None
|
||||
info["set"] = member
|
||||
info["per_device"] = member._command["per_device"]
|
||||
elif "get" in member._command:
|
||||
info["get"] = member
|
||||
elif "custom" in member._command:
|
||||
info["custom"] = member
|
||||
info["per_device"] = member._command["per_device"]
|
||||
|
||||
self._commands[command_name] = info
|
||||
|
||||
def _check_commands(self):
|
||||
"""
|
||||
Check if all commands are defined correctly.
|
||||
"""
|
||||
for command_name, command in self._commands.iteritems():
|
||||
# do not check custom commands
|
||||
if command.get("custom", False):
|
||||
continue
|
||||
# automatic commands should have 'get' and 'set' functions
|
||||
if "get" not in command or "set" not in command:
|
||||
raise TypeError("Plugin command '%s' is not defined correctly" % command_name)
|
||||
|
||||
#
|
||||
# Operations with persistent storage for status data.
|
||||
#
|
||||
|
||||
def _storage_key(self, instance_name, command_name, device_name=None):
|
||||
if device_name is not None:
|
||||
return "%s/%s/%s" % (command_name, instance_name, device_name)
|
||||
else:
|
||||
return "%s/%s" % (command_name, instance_name)
|
||||
|
||||
def _storage_set(self, instance, command, value, device_name=None):
|
||||
key = self._storage_key(instance.name, command["name"], device_name)
|
||||
self._storage.set(key, value)
|
||||
|
||||
def _storage_get(self, instance, command, device_name=None):
|
||||
key = self._storage_key(instance.name, command["name"], device_name)
|
||||
return self._storage.get(key)
|
||||
|
||||
def _storage_unset(self, instance, command, device_name=None):
|
||||
key = self._storage_key(instance.name, command["name"], device_name)
|
||||
return self._storage.unset(key)
|
||||
|
||||
#
|
||||
# Command execution and cleanup.
|
||||
#
|
||||
|
||||
def _execute_command(self, instance, command):
|
||||
new_value = instance.options.get(command["name"], None)
|
||||
if new_value is None:
|
||||
return
|
||||
|
||||
if command["per_device"]:
|
||||
for device in instance.devices:
|
||||
self._execute_device_command(instance, command, device, new_value)
|
||||
else:
|
||||
self._execute_non_device_command(instance, command, new_value)
|
||||
|
||||
def _execute_device_command(self, instance, command, device, new_value):
|
||||
if command["custom"] is not None:
|
||||
command["custom"](True, new_value, device)
|
||||
else:
|
||||
current_value = command["get"](device)
|
||||
self._storage_set(instance, command, current_value, device)
|
||||
command["set"](new_value, device)
|
||||
|
||||
def _execute_non_device_command(self, instance, command, new_value):
|
||||
if command["custom"] is not None:
|
||||
command["custom"](True, new_value)
|
||||
else:
|
||||
current_value = command["get"]()
|
||||
self._storage_set(instance, command_name, current_value)
|
||||
command["set"](new_value)
|
||||
|
||||
def _cleanup_command(self, instance, command):
|
||||
if instance.options.get(command["name"], None) is None:
|
||||
return
|
||||
|
||||
if command["per_device"]:
|
||||
for device in instance.devices:
|
||||
self._cleanup_device_command(instance, command, device)
|
||||
else:
|
||||
self._cleanup_non_device_command(instance, command)
|
||||
|
||||
def _cleanup_device_command(self, instance, command, device):
|
||||
if command["custom"] is not None:
|
||||
command["custom"](False, None, device)
|
||||
else:
|
||||
old_value = self._storage_get(instance, command, device)
|
||||
if old_value is not None:
|
||||
command["set"](old_value, device)
|
||||
self._storage_unset(instance, command, device)
|
||||
|
||||
def _cleanup_non_device_command(self, instance, command):
|
||||
if command["custom"] is not None:
|
||||
command["custom"](False, None)
|
||||
else:
|
||||
old_value = self._storage_get(instance, command)
|
||||
if old_value is not None:
|
||||
command["set"](old_value)
|
||||
self._storage_unset(instance, command)
|
||||
|
|
|
|||
2
tuned/plugins/instance/__init__.py
Normal file
2
tuned/plugins/instance/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from instance import Instance
|
||||
from factory import Factory
|
||||
6
tuned/plugins/instance/factory.py
Normal file
6
tuned/plugins/instance/factory.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from instance import Instance
|
||||
|
||||
class Factory(object):
|
||||
def create(self, *args, **kwargs):
|
||||
instance = Instance(*args, **kwargs)
|
||||
return instance
|
||||
64
tuned/plugins/instance/instance.py
Normal file
64
tuned/plugins/instance/instance.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
class Instance(object):
|
||||
"""
|
||||
"""
|
||||
|
||||
def __init__(self, plugin, name, devices_expression, options):
|
||||
self._plugin = plugin
|
||||
self._name = name
|
||||
self._devices_expression = devices_expression
|
||||
self._options = options
|
||||
|
||||
self._active = True
|
||||
self._has_static_tuning = False
|
||||
self._has_dynamic_tuning = False
|
||||
self._devices = set()
|
||||
|
||||
# properties
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
"""The instance performs some tuning (otherwise it is suspended)."""
|
||||
return self._active
|
||||
|
||||
@active.setter
|
||||
def active(self, value):
|
||||
self._active = value
|
||||
|
||||
@property
|
||||
def devices_expression(self):
|
||||
return self._devices_expression
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
return self._devices
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return self._options
|
||||
|
||||
@property
|
||||
def has_static_tuning(self):
|
||||
return self._has_static_tuning
|
||||
|
||||
@property
|
||||
def has_dynamic_tuning(self):
|
||||
return self._has_dynamic_tuning
|
||||
|
||||
# methods
|
||||
|
||||
def apply_tuning(self):
|
||||
self._plugin.instance_apply_tuning(self)
|
||||
|
||||
def update_tuning(self):
|
||||
self._plugin.instance_update_tuning(self)
|
||||
|
||||
def unapply_tuning(self):
|
||||
self._plugin.instance_unapply_tuning(self)
|
||||
|
||||
def destroy(self):
|
||||
self.unapply_tuning()
|
||||
self._plugin.destroy_instance(self)
|
||||
|
|
@ -3,34 +3,32 @@ from decorators import *
|
|||
import tuned.logs
|
||||
import tuned.utils.commands
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import struct
|
||||
|
||||
log = tuned.logs.get()
|
||||
|
||||
# TODO: force_latency -> command
|
||||
|
||||
class CPULatencyPlugin(base.Plugin):
|
||||
"""
|
||||
Plugin for tuning CPU options. Powersaving, governor, required latency, etc.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def device_requirements(self):
|
||||
return {"subsystem": "cpu"}
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
self._init_devices()
|
||||
|
||||
def _post_init(self):
|
||||
self._latency = None
|
||||
self._load_monitor = None
|
||||
self._cpu_latency_fd = os.open("/dev/cpu_dma_latency", os.O_WRONLY)
|
||||
def _init_devices(self):
|
||||
self._devices = set()
|
||||
# current list of devices
|
||||
for device in self._hardware_inventory.get_devices("cpu"):
|
||||
self._devices.add(device.sys_name)
|
||||
|
||||
if self._options["force_latency"] is None:
|
||||
self._load_monitor = self._monitors_repository.create("load", None)
|
||||
else:
|
||||
self._dynamic_tuning = False
|
||||
self._set_latency(self._options["force_latency"])
|
||||
self._assigned_devices = set()
|
||||
self._free_devices = self._devices.copy()
|
||||
|
||||
@classmethod
|
||||
def _get_default_options(cls):
|
||||
def _get_config_options(self):
|
||||
return {
|
||||
"load_threshold" : 0.2,
|
||||
"latency_low" : 100,
|
||||
|
|
@ -40,13 +38,44 @@ class CPULatencyPlugin(base.Plugin):
|
|||
"multicore_powersave" : None,
|
||||
}
|
||||
|
||||
def cleanup(self):
|
||||
if self._load_monitor is not None:
|
||||
self._monitors_repository.delete(self._load_monitor)
|
||||
def _instance_init(self, instance):
|
||||
instance._has_static_tuning = True
|
||||
instance._has_dynamic_tuning = False
|
||||
|
||||
os.close(self._cpu_latency_fd)
|
||||
# only the first instance of the plugin can control the latency
|
||||
if self._instances.values()[0] == instance:
|
||||
instance._controls_latency = True
|
||||
self._cpu_latency_fd = os.open("/dev/cpu_dma_latency", os.O_WRONLY)
|
||||
self._latency = None
|
||||
|
||||
def update_tuning(self):
|
||||
if instance.options["force_latency"] is None:
|
||||
instance._load_monitor = self._monitors_repository.create("load", None)
|
||||
instance._dynamic_tuning = True
|
||||
else:
|
||||
instance._load_monitor = None
|
||||
|
||||
else:
|
||||
instance._controls_latency = False
|
||||
log.info("Latency settings from non-first CPU plugin instance '%s' will be ignored." % instance.name)
|
||||
|
||||
def _instance_cleanup(self, instance):
|
||||
if instance._controls_latency:
|
||||
os.close(self._cpu_latency_fd)
|
||||
if instance._load_monitor is not None:
|
||||
self._monitors_repository.delete(instance._load_monitor)
|
||||
|
||||
def _instance_apply_static(self, instance):
|
||||
super(self.__class__, self)._instance_apply_static(instance)
|
||||
|
||||
if not instance._controls_latency:
|
||||
return
|
||||
|
||||
force_latency_value = instance.options["force_latency"]
|
||||
if force_latency_value is not None:
|
||||
self._set_latency(force_latency_value)
|
||||
|
||||
def _instance_apply_dynamic(self, instance):
|
||||
assert(instance._controls_latency)
|
||||
load = self._load_monitor.get_load()["system"]
|
||||
if load < self._options["load_threshold"]:
|
||||
self._set_latency(self._options["latency_high"])
|
||||
|
|
@ -56,7 +85,7 @@ class CPULatencyPlugin(base.Plugin):
|
|||
def _set_latency(self, latency):
|
||||
latency = int(latency)
|
||||
if self._latency != latency:
|
||||
log.info("new cpu latency %d" % latency)
|
||||
log.info("setting new cpu latency %d" % latency)
|
||||
latency_bin = struct.pack("i", latency)
|
||||
os.write(self._cpu_latency_fd, latency_bin)
|
||||
self._latency = latency
|
||||
|
|
@ -64,13 +93,15 @@ class CPULatencyPlugin(base.Plugin):
|
|||
@command_set("governor", per_device=True)
|
||||
def _set_governor(self, governor, device):
|
||||
log.info("setting governor '%s' on cpu '%s'" % (governor, device))
|
||||
tuned.utils.commands.execute(["cpupower", "-c", str(device), "frequency-set", "-g", str(governor)])
|
||||
cpu_id = device.lstrip("cpu")
|
||||
tuned.utils.commands.execute(["cpupower", "-c", cpu_id, "frequency-set", "-g", str(governor)])
|
||||
|
||||
@command_get("governor")
|
||||
def _get_governor(self, device):
|
||||
governor = None
|
||||
try:
|
||||
lines = tuned.utils.commands.execute(["cpupower", "-c", str(device), "frequency-info", "-p"]).splitlines()
|
||||
cpu_id = device.lstrip("cpu")
|
||||
lines = tuned.utils.commands.execute(["cpupower", "-c", cpu_id, "frequency-info", "-p"]).splitlines()
|
||||
for line in lines:
|
||||
if line.startswith("analyzing"):
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -8,53 +8,33 @@ __all__ = ["Repository"]
|
|||
|
||||
class Repository(PluginLoader):
|
||||
|
||||
def __init__(self, storage_factory, monitor_repository, hardware_enumerator):
|
||||
def __init__(self, monitor_repository, storage_factory, hardware_inventory, device_matcher, plugin_instance_factory):
|
||||
super(self.__class__, self).__init__()
|
||||
self._plugins = set()
|
||||
self._storage_factory = storage_factory
|
||||
self._monitor_repository = monitor_repository
|
||||
self._hardware_enumerator = hardware_enumerator
|
||||
self._storage_factory = storage_factory
|
||||
self._hardware_inventory = hardware_inventory
|
||||
self._device_matcher = device_matcher
|
||||
self._plugin_instance_factory = plugin_instance_factory
|
||||
|
||||
@property
|
||||
def plugins(self):
|
||||
return self._plugins
|
||||
|
||||
def _set_loader_parameters(self):
|
||||
self._namespace = "tuned.plugins"
|
||||
self._prefix = "plugin_"
|
||||
self._interface = tuned.plugins.base.Plugin
|
||||
|
||||
def create(self, plugin_name, devices, options):
|
||||
def create(self, plugin_name):
|
||||
log.debug("creating plugin %s" % plugin_name)
|
||||
plugin_cls = self.load_plugin(plugin_name)
|
||||
plugin_instance = plugin_cls(self._monitor_repository, self._storage_factory, devices, options)
|
||||
plugin_instance = plugin_cls(self._monitor_repository, self._storage_factory, self._hardware_inventory, self._device_matcher, self._plugin_instance_factory)
|
||||
self._plugins.add(plugin_instance)
|
||||
return plugin_instance
|
||||
|
||||
def tunable_devices(self, plugin_name):
|
||||
plugin_cls = self.load_plugin(plugin_name)
|
||||
device_requirements = plugin_cls.device_requirements()
|
||||
devices = self._hardware_enumerator.get_devices(**device_requirements)
|
||||
|
||||
return map(lambda device: device.sys_name, devices)
|
||||
|
||||
def is_supported(self, plugin_name):
|
||||
plugin_cls = self.load_plugin(plugin_name)
|
||||
return plugin_cls.is_supported()
|
||||
|
||||
def do_static_tuning(self):
|
||||
for plugin in self._plugins:
|
||||
# TODO: plugin to str conversion, not ideal now
|
||||
log.debug("running static tuning for plugin '%s'" % plugin)
|
||||
plugin.cleanup_commands()
|
||||
plugin.execute_commands()
|
||||
|
||||
def delete(self, plugin):
|
||||
assert isinstance(plugin, self._interface)
|
||||
log.debug("removing plugin %s" % plugin)
|
||||
plugin.cleanup_commands()
|
||||
plugin.cleanup()
|
||||
self._plugins.remove(plugin)
|
||||
|
||||
def update(self):
|
||||
for plugin in self._plugins:
|
||||
if not plugin.dynamic_tuning:
|
||||
continue
|
||||
log.debug("updating %s" % plugin)
|
||||
plugin.update_tuning()
|
||||
|
|
|
|||
|
|
@ -1,4 +1 @@
|
|||
from manager import *
|
||||
from device_matcher import *
|
||||
from factory import *
|
||||
from unit import *
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import tuned.units.unit
|
||||
|
||||
class Factory(object):
|
||||
def create(self, name, type, plugin):
|
||||
return tuned.units.unit.Unit(name, type, plugin)
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
import tuned.exceptions
|
||||
import tuned.logs
|
||||
import tuned.patterns
|
||||
import tuned.units.unit
|
||||
|
||||
log = tuned.logs.get()
|
||||
|
||||
|
|
@ -9,91 +7,85 @@ __all__ = ["Manager"]
|
|||
|
||||
class Manager(object):
|
||||
"""
|
||||
Manager instantiates Unit objects, and keeps track of them.
|
||||
Manager creates plugin instances and keeps a track of them.
|
||||
"""
|
||||
|
||||
__slots__ = ["_units", "_plugins_repository", "_monitors_repository", "_unit_factory", "_device_matcher"]
|
||||
|
||||
def __init__(self, plugins_repository, monitors_repository, unit_factory, device_matcher):
|
||||
def __init__(self, plugins_repository, monitors_repository):
|
||||
super(self.__class__, self).__init__()
|
||||
self._units = set()
|
||||
self._monitors_repository = monitors_repository
|
||||
self._plugins_repository = plugins_repository
|
||||
self._unit_factory = unit_factory
|
||||
self._device_matcher = device_matcher
|
||||
self._monitors_repository = monitors_repository
|
||||
self._instances = []
|
||||
self._plugins = []
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
return self._units
|
||||
def plugins(self):
|
||||
return self._plugins
|
||||
|
||||
@property
|
||||
def plugins_repository(self):
|
||||
return self._plugins_repository
|
||||
def instances(self):
|
||||
return self._instances
|
||||
|
||||
@property
|
||||
def monitors_repository(self):
|
||||
return self._monitors_repository
|
||||
def create(self, instances_config):
|
||||
|
||||
def create(self, units):
|
||||
# reverse order, newer units have priority to claim a device
|
||||
for unit_name in reversed(units):
|
||||
unit_info = units[unit_name]
|
||||
if not unit_info.enabled:
|
||||
log.debug("skipping disabled unit '%s'" % unit_info.name)
|
||||
# group instances by plugin
|
||||
|
||||
instances_by_plugin = {}
|
||||
for instance_name, instance_info in instances_config.items():
|
||||
if not instance_info.enabled:
|
||||
log.debug("skipping disabled instance '%s'" % instance_name)
|
||||
continue
|
||||
instances_by_plugin.setdefault(instance_info.type, [])
|
||||
instances_by_plugin[instance_info.type].append(instance_info)
|
||||
|
||||
if not self._plugins_repository.is_supported(unit_info.type):
|
||||
log.info("skipping unit '%s', plugin not supported on your system" % unit_info.type)
|
||||
continue
|
||||
# create all plugin instances at once
|
||||
|
||||
devices = self._possible_devices(unit_info)
|
||||
if devices is None:
|
||||
continue
|
||||
|
||||
log.info("creating unit '%s' (devices: %s)" % (unit_info.name, ", ".join(devices)))
|
||||
for plugin_name, instances_info in instances_by_plugin.items():
|
||||
try:
|
||||
self._create_unit(unit_info, devices)
|
||||
except tuned.exceptions.TunedException as e:
|
||||
log.error("unable to create unit '%s' (trace follows)" % unit_info.name)
|
||||
e.log()
|
||||
except Exception as E:
|
||||
log.error("unable to create unit '%s' (%s)" % (unit_info.name, str(e)))
|
||||
plugin = self._plugins_repository.create(plugin_name)
|
||||
self._plugins.append(plugin)
|
||||
#except NotSupportedPlugin:
|
||||
# log.info("skipping plugin '%s', not supported on your system" % plugin_name)
|
||||
# continue
|
||||
except Exception as e:
|
||||
log.error("failed to initialize plugin %s (%s)" % (plugin_name, e))
|
||||
continue
|
||||
|
||||
def _possible_devices(self, unit_info):
|
||||
tunable_devices = self._plugins_repository.tunable_devices(unit_info.type)
|
||||
if not tunable_devices:
|
||||
log.info("skipping unit '%s', no devices available" % unit_info.name)
|
||||
return None
|
||||
# create instances, seize devices in reverse order
|
||||
# FIXME: hmm, is this rational?
|
||||
|
||||
available_devices = [dev for dev in tunable_devices if dev not in self._seized_devices(unit_info.type)]
|
||||
if not available_devices:
|
||||
log.info("skipping unit '%s', all devices are already claimed by another unit" % unit_info.name)
|
||||
return None
|
||||
created_instances = []
|
||||
for instance_info in instances_info:
|
||||
log.debug("creating '%s' instance '%s'" % (instance_info.type, instance_info.name))
|
||||
new_instance = plugin.create_instance(instance_info.name, instance_info.devices, instance_info.options)
|
||||
created_instances.append(new_instance)
|
||||
|
||||
devices = self._device_matcher.match_list(unit_info.devices, available_devices)
|
||||
if not devices:
|
||||
log.info("skipping unit '%s', no matching devices available" % unit_info.name)
|
||||
return None
|
||||
for instance in reversed(created_instances):
|
||||
log.debug("assigning devices to '%s'" % instance.name)
|
||||
plugin.assign_free_devices(instance)
|
||||
|
||||
return devices
|
||||
self._instances.extend(created_instances)
|
||||
|
||||
def _seized_devices(self, type):
|
||||
devices = []
|
||||
for unit in self._units:
|
||||
if unit.type == type:
|
||||
devices.extend(unit.devices)
|
||||
return set(devices)
|
||||
def destroy_all(self):
|
||||
for plugin in self._plugins:
|
||||
log.debug("cleaning plugin '%s'" % plugin.name)
|
||||
plugin.cleanup()
|
||||
|
||||
def _create_unit(self, unit_info, devices):
|
||||
plugin = self._plugins_repository.create(unit_info.type, devices, unit_info.options)
|
||||
unit = self._unit_factory.create(unit_info.name, unit_info.type, plugin)
|
||||
self._units.add(unit)
|
||||
del self._plugins[:]
|
||||
del self._instances[:]
|
||||
|
||||
def delete(self, unit):
|
||||
self._plugins_repository.delete(unit.plugin)
|
||||
self._units.delete(unit)
|
||||
def update_monitors(self):
|
||||
for monitor in self._monitors_repository.monitors:
|
||||
log.debug("updating monitor %s" % monitor)
|
||||
monitor.update()
|
||||
|
||||
def delete_all(self):
|
||||
for unit in self._units:
|
||||
self._plugins_repository.delete(unit.plugin)
|
||||
self._units.clear()
|
||||
def start_tuning(self):
|
||||
for instance in self._instances:
|
||||
instance.apply_tuning()
|
||||
|
||||
def update_tuning(self):
|
||||
for instance in self._instances:
|
||||
instance.update_tuning()
|
||||
|
||||
def stop_tuning(self):
|
||||
for instance in self._instances:
|
||||
instance.unapply_tuning()
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
import tuned.plugins
|
||||
|
||||
class Unit(object):
|
||||
"""
|
||||
Unit is the smallest tuning component. Units have to be instantiated using UnitManager.
|
||||
|
||||
One unit utilizes one plugin. The tuning can be limited to certain devices with specific
|
||||
options. Multiple units can utilize one plugin, but they should not control the same
|
||||
device.
|
||||
"""
|
||||
|
||||
__slots__ = ["_name", "_type", "_plugin"]
|
||||
|
||||
def __init__(self, name, type, plugin):
|
||||
self._name = name
|
||||
self._type = type
|
||||
self._plugin = plugin
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def devices(self):
|
||||
return self._plugin.devices
|
||||
|
||||
@property
|
||||
def plugin(self):
|
||||
return self._plugin
|
||||
Loading…
Reference in a new issue