diff --git a/tuned/plugins/base.py b/tuned/plugins/base.py index 64883ea..80ea41e 100644 --- a/tuned/plugins/base.py +++ b/tuned/plugins/base.py @@ -39,6 +39,12 @@ class Plugin(object): if options is not None: self._merge_options(options) + # TODO: cannot be injected now + self._storage = tuned.utils.storage.Storage.get_instance() + + self._autoregister_commands() + assert self._commands_are_valid() + @property def dynamic_tuning(self): return self._options["dynamic_tuning"] in ["1", "true"] @@ -47,33 +53,99 @@ class Plugin(object): def static_tuning(self): return self._options["static_tuning"] in ["1", "true"] - def register_command(self, option, set_fnc, revert_fnc = None, is_per_dev = False): - self._commands[option] = (is_per_dev, set_fnc, revert_fnc) + 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["set"] = member + info["per_device"] = member._command["per_device"] + elif "get" in member._command: + info["get"] = member + + self._commands["command_name"] = info + + def _commands_are_valid(self): + for command in commands: + if "get" not in command or "set" not in command: + return False + return True + + # TODO: should be in storage class + def _storage_key(self, command_name, device): + if device is not None: + return "%s@%s" % [command_name, device] + else + return command_name + + # TODO: should be in storage class + def _storage_get(self, command_name, device = None): + if not self._storage.data.has_key(self.__class__): + return None + key = self._storage_key(command_name, device) + return self._storage.data[self.__class__].get(key, None) + + # TODO: should be in storage class + def _storage_set(self, value, command_name, device = None): + self._storage.data.setdefault(self.__class__, []) + key = self._storage_key(command_name, device) + self._storage.data[self.__class__][key] = value + + # TODO: should be in storage class + def _storage_remove(self, command_name, device = None): + self._storage.data.setdefault(self.__class__, []) + key = self._storage_key(command_name, device) + del self._storage.data[self.__class__][key] def execute_commands(self): - for option, (is_per_dev, set_fnc, revert_fnc) in self._commands.iteritems(): - if not self._options.has_key(option): + for command_name, command in self._commands.iteritems(): + if not self._options.has_key(command_name): continue - if is_per_dev: - for dev in self._devices: - set_fnc(dev, self._options[option]) + new_value = self._options[command_name] + + # FIXME: should we revert old settings before applying the new one? + # can we call cleanup_commands() instead + + # TODO: refactor + if command["per_device"]: + for device in self._devices: + current_value = command["get"](device) + self._storage_set(current_value, command_name, device) + command["set"](new_value, device) else: - set_fnc(self._options[option]) + current_value = command["get"]() + self._storage_set(current_valuie, command_name) + command["set"](new_value) def cleanup_commands(self): - for option, (is_per_dev, set_fnc, revert_fnc) in self._commands.iteritems(): + for command_name, command in self._commands.iteritems(): if not self._options.has_key(option): continue - if revert_fnc: - set_fnc = revert_fnc - - if is_per_dev: - for dev in self._devices: - set_fnc(dev, None) + # TODO: refactor + if command["per_device"]: + for device in self._devices: + old_value = self._storage_get(command_name, device) + if old_value is not None: + command["set"](old_value, device) + self._storage_remove(command_name, device) else: - set_fnc(None) + old_value = self._storage_get(command_name) + if old_value is not None: + commad["set"](old_value) + self._storage_remove(command_name) def cleanup(self): pass diff --git a/tuned/plugins/decorator.py b/tuned/plugins/decorator.py new file mode 100644 index 0000000..e1bb538 --- /dev/null +++ b/tuned/plugins/decorator.py @@ -0,0 +1,52 @@ +# Copyright (C) 2008-2012 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +__all__ = ["command", "command_get"] + +# @command_set("scheduler", per_device=True) +# def set_scheduler(self, device, value): +# set_new_scheduler +# +# @command_get("scheduler") +# def get_scheduler(self, device): +# return current_scheduler +# +# @command_set("foo") +# def set_foo(self, value): +# set_new_foo +# +# @command_get("foo") +# def get_foo(self): +# return current_foo +# + +def command_set(name, per_device=False): + def wrapper(method): + method._command = { + "set": True, + "name": name, + "per_device": per_device, + } + return method + + return wrapper + +def command_get(name): + def wrapper(method): + method._command = { "get": True } + return method + return wrapper diff --git a/tuned/utils/commands.py b/tuned/utils/commands.py index c7ef3c0..29b98ed 100644 --- a/tuned/utils/commands.py +++ b/tuned/utils/commands.py @@ -26,124 +26,6 @@ from functools import wraps log = tuned.logs.get() -def command(plugin, key): - """ - This decorator makes adding new commands easier. The only thing you have to do is - to implement method which handles the value of particular option from config file - and returns previosly set value. - - Here is example of method like that: - @command("disk", "elevator") - def _set_elevator(self, dev, value): - sys_file = os.path.join("/sys/block/", dev, "queue/scheduler") - old_value = tuned.utils.commands.read_file(sys_file) - tuned.utils.commands.write_to_file(sys_file, value) - return old_value - - This decorator works then like this: - 1. Tries to revert to previously stored value in Storage class - 2. Tries to set the new value - 3. Stores old value returned by the original method into Storage - """ - def my_decorator(target): - def wrapper(self, *args, **kwargs): - # Find out if the original method is def method(self, dev, value) - # or just def method(self, value) and set the variables. - dev = "" - value = None - if len(args) == 1: - value = args[0] - else: - dev = "_" + args[0] - value = args[1] - - # Check if this plugin has key in Storage cache - storage = tuned.utils.storage.Storage.get_instance() - if not storage.data.has_key(plugin): - log.error("Storage file does not contain item with key %s" % (plugin)) - return - - # Revert to previous value if it exists - if storage.data[plugin].has_key(key + dev): - old_value = storage.data[plugin][key + dev] - # Plugin could call Plugin.register_command with revert_fnc - # set, so we should try to use specialized method for reverting. - # However, if there's no method like that, use the original method - # this decorator decorates. - revert_fnc = self._commands[key][2] - if not revert_fnc: - if len(dev) == 0: - target(self, old_value) - else: - target(self, dev[1:], old_value) - del storage.data[plugin][key + dev] - else: - if len(dev) == 0: - revert_fnc(old_value) - else: - revert_fnc(dev[1:], old_value) - - # set it to new state and store old_value - if not value or len(value) == 0: - return False - - old_value = target(self, *args, **kwargs) - if old_value and len(old_value) != 0: - storage.data[plugin][key + dev] = old_value - storage.save() - return True - - # Fix the wrapper's call signature - return wraps(target)(wrapper) - - return my_decorator - -def command_revert(plugin, key): - """ - This decorator makes adding new commands easier. Use this decorator - for method which just reverts the particular setting to the previous - value. - - Here is example of method like that: - @command_revert("disk", "elevator") - def _revert_elevator(self, dev, value): - sys_file = os.path.join("/sys/block/", dev, "queue/scheduler") - tuned.utils.commands.write_to_file(sys_file, value) - - This decorator works then like this: - 1. Tries to revert to previously stored value in Storage class if - it's set - """ - def my_decorator(target): - def wrapper(self, *args, **kwargs): - dev = "" - value = None - if len(args) == 1: - value = args[0] - else: - dev = "_" + args[0] - value = args[1] - - # revert to previous state - storage = tuned.utils.storage.Storage.get_instance() - if not storage.data.has_key(plugin): - log.error("Storage file does not contain item with key %s" % (plugin)) - return - - if storage.data[plugin].has_key(key + dev): - old_value = storage.data[plugin][key + dev] - if len(dev) == 0: - target(self, old_value) - else: - target(self, dev[1:], old_value) - del storage.data[plugin][key + dev] - return True - - # Fix the wrapper's call signature - return wraps(target)(wrapper) - - return my_decorator - def write_to_file(f, data): log.debug("Writing to file: %s < %s" % (f, data)) try: