command decorators: descriptive, auto register function, remove dark magic
This commit is contained in:
parent
cfed1abaa8
commit
ec6fe3a918
3 changed files with 140 additions and 134 deletions
|
|
@ -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
|
||||
|
|
|
|||
52
tuned/plugins/decorator.py
Normal file
52
tuned/plugins/decorator.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue