From 2a3422fcd11cfc73743c10e739cb5d5a640e053b Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Wed, 25 Apr 2012 08:15:18 +0200 Subject: [PATCH] @command and @command_revert decorator --- tuned/plugins/base.py | 39 +++++++-- tuned/plugins/plugin_cpu.py | 75 +++++++---------- tuned/plugins/plugin_disk.py | 135 +++++++++++++++---------------- tuned/plugins/plugin_wireless.py | 33 ++------ tuned/plugins/repository.py | 1 + tuned/utils/commands.py | 107 +++++++++++++++++++++++- 6 files changed, 239 insertions(+), 151 deletions(-) diff --git a/tuned/plugins/base.py b/tuned/plugins/base.py index 6c8ec6c..fe7abbb 100644 --- a/tuned/plugins/base.py +++ b/tuned/plugins/base.py @@ -27,16 +27,45 @@ class Plugin(object): def __init__(self, devices = None, options = None): self._devices = devices + self._commands = {} self._options = self._get_default_options() self._options["_load_path"] = "" if options is not None: self._merge_options(options) - def __del__(self): - try: - self.cleanup() - except: - pass + #def __del__(self): + #try: + #self.cleanup() + #except: + #pass + + 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 execute_commands(self, devices = []): + for option, (is_per_dev, set_fnc, revert_fnc) in self._commands.iteritems(): + if not self._options.has_key(option): + continue + + if is_per_dev: + for dev in devices: + set_fnc(dev, self._options[option]) + else: + set_fnc(self._options[option]) + + def cleanup_commands(self, devices = []): + for option, (is_per_dev, set_fnc, revert_fnc) 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 devices: + set_fnc(dev, None) + else: + set_fnc(None) def cleanup(self): pass diff --git a/tuned/plugins/plugin_cpu.py b/tuned/plugins/plugin_cpu.py index a7108ed..587c5fa 100644 --- a/tuned/plugins/plugin_cpu.py +++ b/tuned/plugins/plugin_cpu.py @@ -1,7 +1,7 @@ import tuned.plugins import tuned.logs import tuned.monitors -import tuned.utils.commands +from tuned.utils.commands import * import os import struct @@ -24,6 +24,13 @@ class CPULatencyPlugin(tuned.plugins.Plugin): if not tuned.utils.storage.Storage.get_instance().data.has_key("cpu"): tuned.utils.storage.Storage.get_instance().data["cpu"] = {} + self.register_command("cpu_governor", + self._set_cpu_governor, + self._revert_cpu_governor) + self.register_command("cpu_multicore_powersave", + self._set_cpu_multicore_powersave, + self._revert_cpu_multicore_powersave) + @classmethod def _get_default_options(cls): return { @@ -35,16 +42,14 @@ class CPULatencyPlugin(tuned.plugins.Plugin): } def cleanup(self): - self._revert_cpu_governor() - self._revert_cpu_multicore_powersave() + self.cleanup_commands() tuned.monitors.get_repository().delete(self._load_monitor) os.close(self._cpu_latency_fd) def update_tuning(self): if not self._commands_run: - self._apply_cpu_governor() - self._apply_cpu_multicore_powersave() + self.execute_commands() self._commands_run = True load = self._load_monitor.get_load()["system"] @@ -61,65 +66,39 @@ class CPULatencyPlugin(tuned.plugins.Plugin): os.write(self._cpu_latency_fd, latency_bin) self._latency = latency -# COMMANDS: - - def _apply_cpu_governor(self): - self._revert_cpu_governor() - - if len(self._options["cpu_governor"]) == 0: - return False - - storage = tuned.utils.storage.Storage.get_instance() + @command("cpu", "cpu_governor") + def _set_cpu_governor(self, value): old_value = tuned.utils.commands.execute(["cpupower", "frequency-info", "-p"]) if old_value.startswith("analyzing CPU"): try: old_value = old_value.split('\n')[1].split(' ')[2] - storage.data["cpu"]["cpu_governor"] = old_value - storage.save() except IndexError: + old_value = "" pass - - tuned.utils.commands.execute(["cpupower", "frequency-set", "-g", self._options["cpu_governor"]]) - return True + tuned.utils.commands.execute(["cpupower", "frequency-set", "-g", value]) + return old_value - def _revert_cpu_governor(self): - storage = tuned.utils.storage.Storage.get_instance() - if storage.data["cpu"].has_key("cpu_governor"): - tuned.utils.commands.execute(["cpupower", "frequency-set", "-g", storage.data["cpu"]["cpu_governor"]]) - del storage.data["cpu"]["cpu_governor"] + @command_revert("cpu", "cpu_governor") + def _revert_cpu_governor(self, value): + tuned.utils.commands.execute(["cpupower", "frequency-set", "-g", value]) - def _apply_cpu_multicore_powersave(self): - self._revert_cpu_multicore_powersave() - - if len(self._options["cpu_multicore_powersave"]) == 0: - return False - - storage = tuned.utils.storage.Storage.get_instance() + @command("cpu", "cpu_multicore_powersave") + def _set_cpu_multicore_powersave(self, value): old_value = tuned.utils.commands.execute(["cpupower", "info", "-m"]) if old_value.find("not supported") != -1: log.info("cpu_multicore_powersave is not supported by this system") - return False + return "" if old_value.startswith("System's multi core scheduler setting"): try: old_value = old_value.split(' ')[:-1][:-1] # get "2\n" and remove '\n' - storage.data["cpu"]["cpu_multicore_powersave"] = old_value - storage.save() except IndexError: - pass - + old_value = "" - tuned.utils.commands.execute(["cpupower", "set", "-m", storage.data["cpu"]["cpu_multicore_powersave"]]) - return True + tuned.utils.commands.execute(["cpupower", "set", "-m", value]) + return old_value - def _revert_cpu_multicore_powersave(self): - storage = tuned.utils.storage.Storage.get_instance() - if storage.data["cpu"].has_key("cpu_multicore_powersave"): - tuned.utils.commands.execute(["cpupower", "set", "-m", storage.data["cpu"]["cpu_multicore_powersave"]]) - del storage.data["cpu"]["cpu_multicore_powersave"] - - - - - \ No newline at end of file + @command_revert("cpu", "cpu_multicore_powersave") + def _revert_cpu_multicore_powersave(self, value): + tuned.utils.commands.execute(["cpupower", "set", "-m", value]) diff --git a/tuned/plugins/plugin_disk.py b/tuned/plugins/plugin_disk.py index 0f53458..dc0774a 100644 --- a/tuned/plugins/plugin_disk.py +++ b/tuned/plugins/plugin_disk.py @@ -2,7 +2,7 @@ import os, copy import tuned.plugins import tuned.logs import tuned.monitors -import tuned.utils.commands +from tuned.utils.commands import * import tuned.utils.storage import struct @@ -30,6 +30,25 @@ class DiskPlugin(tuned.plugins.Plugin): if not tuned.utils.storage.Storage.get_instance().data.has_key("disk"): tuned.utils.storage.Storage.get_instance().data["disk"] = {} + self.register_command("elevator", + self._set_elevator, + self._revert_elevator, + is_per_dev = True) + self.register_command("disk_readahead_multiplier", + self._set_disk_readahead_multiplier, + self._revert_disk_readahead_multiplier, + is_per_dev = True) + self.register_command("disk_alpm", + self._set_disk_alpm, + self._revert_disk_alpm) + self.register_command("disk_apm", + self._set_disk_apm, + self._revert_disk_apm, + is_per_dev = True) + self.register_command("disk_spindown", + self._set_disk_spindown, + is_per_dev = True) + @classmethod def tunable_devices(cls): block_devices = os.listdir("/sys/block") @@ -101,21 +120,12 @@ class DiskPlugin(tuned.plugins.Plugin): for dev in self.devidle.keys(): if self.devidle[dev]["LEVEL"] > 0: os.system("hdparm -S0 -B255 /dev/"+dev+" > /dev/null 2>&1") - self._revert_elevator(dev) - self._revert_disk_apm(dev) - self._revert_disk_readahead_multiplier(dev) - self._revert_disk_alpm() + self.cleanup_commands(self.devidle.keys()) def update_tuning(self): load = self._load_monitor.get_load() for dev, devload in load.iteritems(): - if not self._elevator_set: - self._apply_elevator(dev) - self._apply_disk_apm(dev) - self._apply_disk_spindown(dev) - self._apply_disk_readahead_multiplier(dev) - self._init_stats(dev) self._update_stats(dev, devload) self._update_idle(dev) @@ -142,32 +152,24 @@ class DiskPlugin(tuned.plugins.Plugin): log.debug("%s idle: read %d, write %d, level %d" % (dev, self.devidle[dev]["read"], self.devidle[dev]["write"], self.devidle[dev]["LEVEL"])) if not self._elevator_set: - self._apply_disk_alpm() - - self._elevator_set = True - - # Commands: - - def _apply_elevator(self, dev): - self._revert_elevator(dev) - - if len(self._options["elevator"]) == 0: - return False + self.execute_commands(load.keys()) + self._elevator_set = True + @command("disk", "elevator") + def _set_elevator(self, dev, value): sys_file = os.path.join("/sys/block/", dev, "queue/scheduler") - tuned.utils.commands.set_file("disk", "elevator_" + dev, sys_file, self._options["elevator"]) - return True + old_value = tuned.utils.commands.read_file(sys_file) + tuned.utils.commands.write_to_file(sys_file, value) + return old_value - def _revert_elevator(self, dev): + @command_revert("disk", "elevator") + def _revert_elevator(self, dev, value): sys_file = os.path.join("/sys/block/", dev, "queue/scheduler") - tuned.utils.commands.revert_file("disk", "elevator_" + dev, sys_file) - - def _apply_disk_alpm(self): - self._revert_disk_alpm() - - if len(self._options["disk_alpm"]) == 0: - return False + tuned.utils.commands.write_to_file(sys_file, value) + @command("disk", "disk_alpm") + def _set_disk_alpm(self, value): + old_value = "" for host in os.listdir("/sys/class/scsi_host/"): port_cmd_path = os.path.join("/sys/class/scsi_host/", host, "ahci_port_cmd") try: @@ -186,57 +188,46 @@ class DiskPlugin(tuned.plugins.Plugin): policy = "max_performance" sys_file = os.path.join("/sys/class/scsi_host/", host, "link_power_management_policy") - tuned.utils.commands.set_file("disk", "disk_alpm", sys_file, policy) + if len(old_value) == 0: + old_value = tuned.utils.commands.read_file(sys_file) + tuned.utils.commands.write_to_file(sys_file, policy) - return True + return old_value - def _revert_disk_alpm(self): + @command_revert("disk", "disk_alpm") + def _revert_disk_alpm(self, value): for host in os.listdir("/sys/class/scsi_host/"): - sys_file = os.path.join("/sys/class/scsi_host/", host, "link_power_management_policy") - tuned.utils.commands.revert_file("disk", "disk_alpm", sys_file) - - def _apply_disk_apm(self, dev): - self._revert_disk_apm(dev) - - if len(self._options["disk_apm"]) == 0: - return False + tuned.utils.commands.write_to_file(sys_file, value) + @command("disk", "disk_apm") + def _set_disk_apm(self, dev, value): #TODO: get current value using hdparm -B. My disk does not support it... - tuned.utils.commands.execute(["hdparm", "-B", self._options["disk_apm"], "/dev/" + dev]) - return True + tuned.utils.commands.execute(["hdparm", "-B", value, "/dev/" + dev]) + return "" - def _revert_disk_apm(self, dev): - storage = tuned.utils.storage.Storage.get_instance() - if storage.data["disk"].has_key("disk_apm_" + dev): - tuned.utils.commands.execute(["hdparm", "-B", storage.data["disk"]["disk_apm_" + dev], "/dev/" + dev]) - del storage.data["disk"]["disk_apm_" + dev] + @command_revert("disk", "disk_apm") + def _revert_disk_apm(self, dev, value): + tuned.utils.commands.execute(["hdparm", "-B", value, "/dev/" + dev]) - def _apply_disk_spindown(self, dev): - if len(self._options["disk_spindown"]) == 0: - return False - - tuned.utils.commands.execute(["hdparm", "-S", self._options["disk_spindown"], "/dev/" + dev]) - return True - - def _apply_disk_readahead_multiplier(self, dev): - self._revert_elevator(dev) - - if len(self._options["elevator"]) == 0: - return False + @command("disk", "disk_spindown") + def _set_disk_spindown(self, dev, value): + # There's no way how to get current/old spindown value... + tuned.utils.commands.execute(["hdparm", "-S", value, "/dev/" + dev]) + @command("disk", "disk_readahead_multiplier") + def _set_disk_readahead_multiplier(self, dev, value): sys_file = os.path.join("/sys/block/", dev, "queue/read_ahead_kb") - try: - value = open(sys_file).read().strip() - except (OSError,IOError) as e: - log.error("Reading %s error: %s" % (sys_file, e)) - return - new_value = int(int(value) * float(self._options["disk_readahead_multiplier"])) + old_value = tuned.utils.commands.read_file(sys_file).strip() + if len(old_value) == 0: + return "" + new_value = int(int(old_value) * float(value)) - tuned.utils.commands.set_file("disk", "disk_readahead_multiplier_" + dev, sys_file, str(new_value)) - return True + tuned.utils.commands.write_to_file(sys_file, new_value) + return old_value - def _revert_disk_readahead_multiplier(self, dev): + @command_revert("disk", "disk_readahead_multiplier") + def _revert_disk_readahead_multiplier(self, dev, value): sys_file = os.path.join("/sys/block/", dev, "queue/read_ahead_kb") - tuned.utils.commands.revert_file("disk", "disk_readahead_multiplier_" + dev, sys_file) + tuned.utils.commands.write_to_file(sys_file, value) diff --git a/tuned/plugins/plugin_wireless.py b/tuned/plugins/plugin_wireless.py index 618b5c2..c83e85c 100644 --- a/tuned/plugins/plugin_wireless.py +++ b/tuned/plugins/plugin_wireless.py @@ -1,7 +1,7 @@ import tuned.plugins import tuned.logs import tuned.monitors -import tuned.utils.commands +from tuned.utils.commands import * import os import struct import glob @@ -22,6 +22,8 @@ class WirelessPlugin(tuned.plugins.Plugin): if not tuned.utils.storage.Storage.get_instance().data.has_key("wireless"): tuned.utils.storage.Storage.get_instance().data["wireless"] = {} + self.register_command("wifi_power_level", self._set_wifi_power_level) + @classmethod def _get_default_options(cls): return { @@ -29,21 +31,22 @@ class WirelessPlugin(tuned.plugins.Plugin): } def cleanup(self): - self._revert_wifi_power_level() + self.cleanup_commands() def update_tuning(self): if not self._commands_run: - self._apply_wifi_power_level() + self.execute_commands() self._commands_run = True # COMMANDS: + @command("wireless", "wifi_power_level") def _set_wifi_power_level(self, power_level): try: lines = open("/proc/net/wireless").readlines() except (OSError,IOError) as e: log.error("Error reading wifi devices from /proc/net/wireless: %s" % (e)) - return None + return "" ifaces = [] for line in lines: @@ -56,7 +59,7 @@ class WirelessPlugin(tuned.plugins.Plugin): if len(ifaces) == 0: log.info("No wifi interfaces found") - return None + return "" # TODO: set old_value properly. Is there "get_power"? I don't have wifi here old_value = "5" @@ -68,23 +71,3 @@ class WirelessPlugin(tuned.plugins.Plugin): tuned.utils.commands.write_to_file(sys_file, power_level) return old_value - - def _apply_wifi_power_level(self): - self._revert_wifi_power_level() - - if len(self._options["wifi_power_level"]) == 0: - return False - - old_value = self._set_wifi_power_level(self._options["wifi_power_level"]) - if old_value: - storage = tuned.utils.storage.Storage.get_instance() - storage.data["wireless"]["wifi_power_level"] = old_value - storage.save() - - return True - - def _revert_wifi_power_level(self): - storage = tuned.utils.storage.Storage.get_instance() - if storage.data["wireless"].has_key("wifi_power_level"): - self._set_wifi_power_level(storage.data["wireless"]["wifi_power_level"]) - del storage.data["wireless"]["wifi_power_level"] diff --git a/tuned/plugins/repository.py b/tuned/plugins/repository.py index 8c49041..5396b51 100644 --- a/tuned/plugins/repository.py +++ b/tuned/plugins/repository.py @@ -42,6 +42,7 @@ class PluginRepository(tuned.patterns.Singleton): def delete(self, plugin): assert isinstance(plugin, self._loader.interface) log.debug("removing plugin %s" % plugin) + plugin.cleanup() self._plugins.remove(plugin) def update(self): diff --git a/tuned/utils/commands.py b/tuned/utils/commands.py index 6434fb2..508ebc6 100644 --- a/tuned/utils/commands.py +++ b/tuned/utils/commands.py @@ -22,15 +22,120 @@ import tuned.monitors import tuned.utils.storage import struct from subprocess import * +from functools import wraps log = tuned.logs.get() +# command decorator +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 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 + +# command_revert decorator +def command_revert(plugin, key): + 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: fd = open(f, "w") - fd.write(data) + fd.write(str(data)) fd.close() except (OSError,IOError) as e: log.error("Writing to file %s error: %s" % (f, e))