diff --git a/tuned/daemon/application.py b/tuned/daemon/application.py index 39990fe..a65e043 100644 --- a/tuned/daemon/application.py +++ b/tuned/daemon/application.py @@ -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() diff --git a/tuned/daemon/daemon.py b/tuned/daemon/daemon.py index cbcd2a0..162603b 100644 --- a/tuned/daemon/daemon.py +++ b/tuned/daemon/daemon.py @@ -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: diff --git a/tuned/hardware/__init__.py b/tuned/hardware/__init__.py index 8085cb8..b3c9af9 100644 --- a/tuned/hardware/__init__.py +++ b/tuned/hardware/__init__.py @@ -1 +1,2 @@ -from enumerator import * +from inventory import * +from device_matcher import * diff --git a/tuned/units/device_matcher.py b/tuned/hardware/device_matcher.py similarity index 98% rename from tuned/units/device_matcher.py rename to tuned/hardware/device_matcher.py index 03f3468..873d6ed 100644 --- a/tuned/units/device_matcher.py +++ b/tuned/hardware/device_matcher.py @@ -1,6 +1,8 @@ import fnmatch import re +__all__ = ["DeviceMatcher"] + class DeviceMatcher(object): """ Device name matching against the devices specification in tuning profiles. diff --git a/tuned/hardware/enumerator.py b/tuned/hardware/enumerator.py deleted file mode 100644 index 36b2b46..0000000 --- a/tuned/hardware/enumerator.py +++ /dev/null @@ -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)] diff --git a/tuned/hardware/inventory.py b/tuned/hardware/inventory.py new file mode 100644 index 0000000..8e6b008 --- /dev/null +++ b/tuned/hardware/inventory.py @@ -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 diff --git a/tuned/monitors/repository.py b/tuned/monitors/repository.py index 5bcbb75..445da16 100644 --- a/tuned/monitors/repository.py +++ b/tuned/monitors/repository.py @@ -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() diff --git a/tuned/plugins/__init__.py b/tuned/plugins/__init__.py index cb96099..dcd514c 100644 --- a/tuned/plugins/__init__.py +++ b/tuned/plugins/__init__.py @@ -1 +1,2 @@ from repository import * +import instance diff --git a/tuned/plugins/base.py b/tuned/plugins/base.py index 36e1959..3a51ea2 100644 --- a/tuned/plugins/base.py +++ b/tuned/plugins/base.py @@ -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) diff --git a/tuned/plugins/instance/__init__.py b/tuned/plugins/instance/__init__.py new file mode 100644 index 0000000..cdd3de5 --- /dev/null +++ b/tuned/plugins/instance/__init__.py @@ -0,0 +1,2 @@ +from instance import Instance +from factory import Factory diff --git a/tuned/plugins/instance/factory.py b/tuned/plugins/instance/factory.py new file mode 100644 index 0000000..9574bd9 --- /dev/null +++ b/tuned/plugins/instance/factory.py @@ -0,0 +1,6 @@ +from instance import Instance + +class Factory(object): + def create(self, *args, **kwargs): + instance = Instance(*args, **kwargs) + return instance diff --git a/tuned/plugins/instance/instance.py b/tuned/plugins/instance/instance.py new file mode 100644 index 0000000..a40f12f --- /dev/null +++ b/tuned/plugins/instance/instance.py @@ -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) diff --git a/tuned/plugins/plugin_cpu.py b/tuned/plugins/plugin_cpu.py index 837b15f..7bf62cd 100644 --- a/tuned/plugins/plugin_cpu.py +++ b/tuned/plugins/plugin_cpu.py @@ -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 diff --git a/tuned/plugins/repository.py b/tuned/plugins/repository.py index d0f5307..818d9b2 100644 --- a/tuned/plugins/repository.py +++ b/tuned/plugins/repository.py @@ -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() diff --git a/tuned/units/__init__.py b/tuned/units/__init__.py index 99df76b..fa54824 100644 --- a/tuned/units/__init__.py +++ b/tuned/units/__init__.py @@ -1,4 +1 @@ from manager import * -from device_matcher import * -from factory import * -from unit import * diff --git a/tuned/units/factory.py b/tuned/units/factory.py deleted file mode 100644 index b251866..0000000 --- a/tuned/units/factory.py +++ /dev/null @@ -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) diff --git a/tuned/units/manager.py b/tuned/units/manager.py index 78fe390..f4520d1 100644 --- a/tuned/units/manager.py +++ b/tuned/units/manager.py @@ -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() diff --git a/tuned/units/unit.py b/tuned/units/unit.py deleted file mode 100644 index 0142289..0000000 --- a/tuned/units/unit.py +++ /dev/null @@ -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