tuned-adm: synchronous profile switching
By default tuned-adm now synchronously switch profiles, i.e it waits untill
the profile is applied and then returns to the shell. It returns with
exitcode 0 if profile is correctly applied. If there is an error it uses
exitcode > 0 (currently only 1 is used) and displays error. For reverting
to old behaviour (i.e. asynchronous profile switching) when the tuned-adm
returns immediately there is an tuned-adm command line option -a (or --async).
D-Bus API was extended to allow synchronous profile switching. Now the
"profile_changed" D-Bus signal is sent when the profile is applied. The
signal contains the following data:
profile_name:string - the name of the profile which was applied
result:boolean - status of the operation, true if OK, false on error
errstr:string - string containing description of the error (if result is
false)
Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
This commit is contained in:
parent
32ef5d8db0
commit
67f78d632e
11 changed files with 168 additions and 37 deletions
|
|
@ -32,6 +32,7 @@ if __name__ == "__main__":
|
|||
parser = argparse.ArgumentParser(description="Manage tuned daemon.")
|
||||
parser.add_argument('--version', "-v", action = "version", version = "%%(prog)s %s.%s.%s" % (ver.TUNED_VERSION_MAJOR, ver.TUNED_VERSION_MINOR, ver.TUNED_VERSION_PATCH))
|
||||
parser.add_argument("--debug", "-d", action="store_true", help="show debug messages")
|
||||
parser.add_argument("--async", "-a", action="store_true", help="with dbus do not wait on commands completion and return immediately")
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
parser_list = subparsers.add_parser("list", help="list available profiles")
|
||||
|
|
@ -62,6 +63,7 @@ if __name__ == "__main__":
|
|||
|
||||
options = vars(args)
|
||||
debug = options.pop("debug")
|
||||
async = options.pop("async")
|
||||
action_name = options.pop("action")
|
||||
result = False
|
||||
|
||||
|
|
@ -70,10 +72,12 @@ if __name__ == "__main__":
|
|||
controller = tuned.admin.DBusController(consts.DBUS_BUS, consts.DBUS_OBJECT, consts.DBUS_INTERFACE, debug)
|
||||
else:
|
||||
controller = None
|
||||
admin = tuned.admin.Admin(controller, debug)
|
||||
admin = tuned.admin.Admin(controller, debug, async)
|
||||
|
||||
action = getattr(admin, action_name)
|
||||
result = action(**options)
|
||||
if controller is not None:
|
||||
controller.exit()
|
||||
except tuned.admin.TunedAdminException as e:
|
||||
if not debug:
|
||||
print >>sys.stderr, e
|
||||
|
|
|
|||
|
|
@ -5,17 +5,34 @@ import tuned.consts as consts
|
|||
import os
|
||||
import sys
|
||||
import errno
|
||||
import threading
|
||||
|
||||
class Admin(object):
|
||||
def __init__(self, controller, debug = False):
|
||||
def __init__(self, controller, debug = False, async = False):
|
||||
self._controller = controller
|
||||
self._debug = debug
|
||||
self._async = async
|
||||
self._cmd = commands(debug)
|
||||
self._profiles_locator = profiles_locator(consts.LOAD_DIRECTORIES)
|
||||
self._daemon_action_finished = threading.Event()
|
||||
self._daemon_action_profile = ""
|
||||
self._daemon_action_result = True
|
||||
self._daemon_action_errstr = ""
|
||||
if self._controller is not None:
|
||||
self._dbus = True
|
||||
self._controller.set_signal_handler(consts.DBUS_SIGNAL_PROFILE_CHANGED, self._signal_profile_changed_cb)
|
||||
else:
|
||||
self._dbus = False
|
||||
|
||||
def _error(self, message):
|
||||
print >>sys.stderr, message
|
||||
|
||||
def _signal_profile_changed_cb(self, profile_name, result, errstr):
|
||||
self._daemon_action_profile = profile_name
|
||||
self._daemon_action_result = result
|
||||
self._daemon_action_errstr = errstr
|
||||
self._daemon_action_finished.set()
|
||||
|
||||
def _tuned_is_running(self):
|
||||
try:
|
||||
os.kill(int(self._cmd.read_file(consts.PID_FILE)), 0)
|
||||
|
|
@ -26,8 +43,7 @@ class Admin(object):
|
|||
return True
|
||||
|
||||
def list(self):
|
||||
no_dbus = self._controller is None
|
||||
if not no_dbus:
|
||||
if self._dbus:
|
||||
try:
|
||||
profile_names = self._controller.profiles2()
|
||||
except TunedAdminDBusException as e:
|
||||
|
|
@ -36,9 +52,9 @@ class Admin(object):
|
|||
profile_names = self._controller.profiles()
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
no_dbus = True
|
||||
self._dbus = False
|
||||
profile_names = map(lambda profile:(profile, ""), profile_names)
|
||||
if no_dbus:
|
||||
if not self._dbus:
|
||||
profile_names = self._profiles_locator.get_known_names_summary()
|
||||
print "Available profiles:"
|
||||
for profile in profile_names:
|
||||
|
|
@ -50,30 +66,28 @@ class Admin(object):
|
|||
|
||||
def _get_active_profile(self):
|
||||
profile_name = None
|
||||
no_dbus = self._controller is None
|
||||
if not no_dbus:
|
||||
if self._dbus:
|
||||
try:
|
||||
profile_name = self._controller.active_profile()
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
no_dbus = True
|
||||
if no_dbus:
|
||||
self._dbus = False
|
||||
if not self._dbus:
|
||||
profile_name = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE, None))
|
||||
if profile_name == "":
|
||||
profile_name = None
|
||||
return profile_name
|
||||
|
||||
def profile_info(self, profile = ""):
|
||||
no_dbus = self._controller is None
|
||||
if profile == "":
|
||||
profile = self._get_active_profile()
|
||||
if not no_dbus:
|
||||
if self._dbus:
|
||||
try:
|
||||
ret = self._controller.profile_info(profile)
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
no_dbus = True
|
||||
if no_dbus:
|
||||
self._dbus = False
|
||||
if not self._dbus:
|
||||
ret = self._profiles_locator.get_profile_attrs(profile, [consts.PROFILE_ATTR_SUMMARY, consts.PROFILE_ATTR_DESCRIPTION], ["", ""])
|
||||
if ret[0] == True:
|
||||
print "Profile name:"
|
||||
|
|
@ -104,17 +118,29 @@ class Admin(object):
|
|||
return False
|
||||
|
||||
def profile(self, profiles):
|
||||
no_dbus = self._controller is None
|
||||
profile_name = " ".join(profiles)
|
||||
if profile_name == "":
|
||||
return False
|
||||
if not no_dbus:
|
||||
if self._dbus:
|
||||
self._daemon_action_finished.clear()
|
||||
try:
|
||||
(ret, msg) = self._controller.switch_profile(profile_name)
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
no_dbus = True
|
||||
if no_dbus:
|
||||
self._dbus = False
|
||||
if not self._async:
|
||||
waiting = True
|
||||
while waiting:
|
||||
if self._daemon_action_finished.wait(consts.ADMIN_TIMEOUT):
|
||||
if self._daemon_action_profile == profile_name:
|
||||
waiting = False
|
||||
if not self._daemon_action_result:
|
||||
print "Error changing profile: %s" % self._daemon_action_errstr
|
||||
return False
|
||||
else:
|
||||
print "Operation timed out"
|
||||
return False
|
||||
if not self._dbus:
|
||||
if profile_name in self._profiles_locator.get_known_names():
|
||||
if self._cmd.write_to_file(consts.ACTIVE_PROFILE_FILE, profile_name):
|
||||
print "Trying to (re)start tuned..."
|
||||
|
|
@ -140,14 +166,13 @@ class Admin(object):
|
|||
return ret
|
||||
|
||||
def recommend_profile(self):
|
||||
no_dbus = self._controller is None
|
||||
if not no_dbus:
|
||||
if self._dbus:
|
||||
try:
|
||||
profile = self._controller.recommend_profile()
|
||||
except TunedAdminDBusException as e:
|
||||
self._error(e)
|
||||
no_dbus = True
|
||||
if no_dbus:
|
||||
self._dbus = False
|
||||
if not self._dbus:
|
||||
profile = self._cmd.recommend_profile()
|
||||
print profile
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import threading
|
||||
import dbus
|
||||
import dbus.exceptions
|
||||
from dbus.mainloop.glib import DBusGMainLoop
|
||||
from gi.repository import GLib
|
||||
from exceptions import TunedAdminDBusException
|
||||
|
||||
__all__ = ["DBusController"]
|
||||
|
|
@ -11,18 +14,27 @@ class DBusController(object):
|
|||
self._object_name = object_name
|
||||
self._proxy = None
|
||||
self._debug = debug
|
||||
self._main_loop = None
|
||||
self._thread = None
|
||||
|
||||
def _init_proxy(self):
|
||||
if self._proxy is None:
|
||||
bus = dbus.SystemBus()
|
||||
self._proxy = bus.get_object(self._bus_name, self._interface_name, self._object_name)
|
||||
|
||||
def _call(self, method_name, *args, **kwargs):
|
||||
try:
|
||||
self._init_proxy()
|
||||
if self._proxy is None:
|
||||
DBusGMainLoop(set_as_default=True)
|
||||
self._main_loop = GLib.MainLoop()
|
||||
self._thread = threading.Thread(target=self._thread_code)
|
||||
self._thread.start()
|
||||
bus = dbus.SystemBus()
|
||||
self._proxy = bus.get_object(self._bus_name, self._interface_name, self._object_name)
|
||||
except dbus.exceptions.DBusException:
|
||||
raise TunedAdminDBusException("Cannot talk to Tuned daemon via DBus. Is Tuned daemon running?")
|
||||
|
||||
def _thread_code(self):
|
||||
self._main_loop.run()
|
||||
|
||||
def _call(self, method_name, *args, **kwargs):
|
||||
self._init_proxy()
|
||||
|
||||
try:
|
||||
method = self._proxy.get_dbus_method(method_name)
|
||||
return method(*args, **kwargs)
|
||||
|
|
@ -32,6 +44,10 @@ class DBusController(object):
|
|||
err_str += " (%s)" % str(dbus_exception)
|
||||
raise TunedAdminDBusException(err_str)
|
||||
|
||||
def set_signal_handler(self, signal, cb):
|
||||
self._init_proxy()
|
||||
self._proxy.connect_to_signal(signal, cb)
|
||||
|
||||
def is_running(self):
|
||||
return self._call("is_running")
|
||||
|
||||
|
|
@ -66,3 +82,9 @@ class DBusController(object):
|
|||
|
||||
def off(self):
|
||||
return self._call("disable")
|
||||
|
||||
def exit(self):
|
||||
if self._thread is not None and self._thread.is_alive():
|
||||
self._main_loop.quit()
|
||||
self._thread.join()
|
||||
self._thread = None
|
||||
|
|
|
|||
|
|
@ -63,9 +63,14 @@ PATH_CPU_DMA_LATENCY = "/dev/cpu_dma_latency"
|
|||
PROFILE_ATTR_SUMMARY = "summary"
|
||||
PROFILE_ATTR_DESCRIPTION = "description"
|
||||
|
||||
DBUS_SIGNAL_PROFILE_CHANGED = "profile_changed"
|
||||
|
||||
STR_VERIFY_PROFILE_DEVICE_VALUE_OK = "verify: passed: device %s: %s = %s"
|
||||
STR_VERIFY_PROFILE_VALUE_OK = "verify: passed: %s = %s"
|
||||
STR_VERIFY_PROFILE_OK = "verify: passed: %s"
|
||||
STR_VERIFY_PROFILE_DEVICE_VALUE_FAIL = "verify: failed: device %s: %s = %s, expected %s"
|
||||
STR_VERIFY_PROFILE_VALUE_FAIL = "verify: failed: %s = %s, expected %s"
|
||||
STR_VERIFY_PROFILE_FAIL = "verify: failed: %s"
|
||||
|
||||
# timout for tuned-adm operations in seconds
|
||||
ADMIN_TIMEOUT = 60
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class Application(object):
|
|||
profile_locator = profiles.Locator(consts.LOAD_DIRECTORIES)
|
||||
profile_loader = profiles.Loader(profile_locator, profile_factory, profile_merger, self.variables)
|
||||
|
||||
self._daemon = daemon.Daemon(unit_manager, profile_loader, profile_name, self.config)
|
||||
self._daemon = daemon.Daemon(unit_manager, profile_loader, profile_name, self.config, self)
|
||||
self._controller = controller.Controller(self._daemon, self.config)
|
||||
|
||||
self._dbus_exporter = None
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
def terminate(self):
|
||||
self._terminate.set()
|
||||
|
||||
@exports.signal("sbs")
|
||||
def profile_changed(self, profile_name, result, errstr):
|
||||
pass
|
||||
|
||||
@exports.export("", "b")
|
||||
def start(self):
|
||||
if self._global_config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON):
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ log = tuned.logs.get()
|
|||
|
||||
|
||||
class Daemon(object):
|
||||
def __init__(self, unit_manager, profile_loader, profile_name=None, config=None):
|
||||
def __init__(self, unit_manager, profile_loader, profile_name=None, config=None, application=None):
|
||||
log.debug("initializing daemon")
|
||||
self._daemon = consts.CFG_DEF_DAEMON
|
||||
self._sleep_interval = int(consts.CFG_DEF_SLEEP_INTERVAL)
|
||||
|
|
@ -24,6 +24,7 @@ class Daemon(object):
|
|||
self._update_interval = int(config.get(consts.CFG_UPDATE_INTERVAL, consts.CFG_DEF_UPDATE_INTERVAL))
|
||||
self._dynamic_tuning = config.get_bool(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING)
|
||||
self._recommend_command = config.get_bool(consts.CFG_RECOMMEND_COMMAND, consts.CFG_DEF_RECOMMEND_COMMAND)
|
||||
self._application = application
|
||||
if self._sleep_interval <= 0:
|
||||
self._sleep_interval = int(consts.CFG_DEF_SLEEP_INTERVAL)
|
||||
if self._update_interval == 0:
|
||||
|
|
@ -64,17 +65,18 @@ class Daemon(object):
|
|||
|
||||
def set_profile(self, profile_name, save_instantly=False):
|
||||
if self.is_running():
|
||||
raise TunedException("Cannot set profile while the daemon is running.")
|
||||
raise TunedException(self._notify_profile_changed(profile_name, False, "Cannot set profile while the daemon is running."))
|
||||
|
||||
if profile_name == "" or profile_name is None:
|
||||
self._profile = None
|
||||
elif profile_name not in self.profile_loader.profile_locator.get_known_names():
|
||||
raise TunedException("Requested profile '%s' doesn't exist." % profile_name)
|
||||
|
||||
raise TunedException(self._notify_profile_changed(profile_name, False, "Requested profile '%s' doesn't exist." % profile_name))
|
||||
else:
|
||||
try:
|
||||
self._profile = self._profile_loader.load(profile_name)
|
||||
except InvalidProfileException:
|
||||
raise TunedException("Cannot load profile '%s'." % profile_name)
|
||||
raise TunedException(self._notify_profile_changed(profile_name, False, "Cannot load profile '%s'." % profile_name))
|
||||
|
||||
if save_instantly:
|
||||
if profile_name is None:
|
||||
|
|
@ -89,6 +91,13 @@ class Daemon(object):
|
|||
def profile_loader(self):
|
||||
return self._profile_loader
|
||||
|
||||
# send notification when profile is changed (everything is setup) or if error occured
|
||||
# result: True - OK, False - error occured
|
||||
def _notify_profile_changed(self, profile_name, result, errstr):
|
||||
if self._application is not None and self._application._dbus_exporter is not None:
|
||||
self._application._dbus_exporter.send_signal(consts.DBUS_SIGNAL_PROFILE_CHANGED, profile_name, result, errstr)
|
||||
return errstr
|
||||
|
||||
def _thread_code(self):
|
||||
if self._profile is None:
|
||||
raise TunedException("Cannot start the daemon without setting a profile.")
|
||||
|
|
@ -98,6 +107,7 @@ class Daemon(object):
|
|||
self._unit_manager.start_tuning()
|
||||
self._profile_applied.set()
|
||||
log.info("static tuning from profile '%s' applied" % self._profile.name)
|
||||
self._notify_profile_changed(self._profile.name, True, "OK")
|
||||
|
||||
if self._daemon:
|
||||
# In python 2 interpreter with applied patch for rhbz#917709 we need to periodically
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ def export(*args, **kwargs):
|
|||
return method
|
||||
return wrapper
|
||||
|
||||
def signal(*args, **kwargs):
|
||||
"""Decorator, use to mark exportable signals."""
|
||||
def wrapper(method):
|
||||
method.signal_params = [ args, kwargs ]
|
||||
return method
|
||||
return wrapper
|
||||
|
||||
def register_exporter(instance):
|
||||
if not isinstance(instance, interfaces.ExporterInterface):
|
||||
raise Exception()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ class ExportsController(tuned.patterns.Singleton):
|
|||
"""Check if method was marked with @exports.export wrapper."""
|
||||
return inspect.ismethod(method) and hasattr(method, "export_params")
|
||||
|
||||
def _is_exportable_signal(self, method):
|
||||
"""Check if method was marked with @exports.signal wrapper."""
|
||||
return inspect.ismethod(method) and hasattr(method, "signal_params")
|
||||
|
||||
def _export_method(self, method):
|
||||
"""Register method to all exporters."""
|
||||
for exporter in self._exporters:
|
||||
|
|
@ -32,14 +36,22 @@ class ExportsController(tuned.patterns.Singleton):
|
|||
kwargs = method.export_params[1]
|
||||
exporter.export(method, *args, **kwargs)
|
||||
|
||||
def _export_signal(self, method):
|
||||
"""Register signal to all exporters."""
|
||||
for exporter in self._exporters:
|
||||
args = method.signal_params[0]
|
||||
kwargs = method.signal_params[1]
|
||||
exporter.signal(method, *args, **kwargs)
|
||||
|
||||
def _initialize_exports(self):
|
||||
if self._exports_initialized:
|
||||
return
|
||||
|
||||
for instance in self._objects:
|
||||
exportable = inspect.getmembers(instance, self._is_exportable_method)
|
||||
for name, method in exportable:
|
||||
for name, method in inspect.getmembers(instance, self._is_exportable_method):
|
||||
self._export_method(method)
|
||||
for name, method in inspect.getmembers(instance, self._is_exportable_signal):
|
||||
self._export_signal(method)
|
||||
|
||||
self._exports_initialized = True
|
||||
|
||||
|
|
|
|||
|
|
@ -23,11 +23,13 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
self._dbus_object_cls = None
|
||||
self._dbus_object = None
|
||||
self._dbus_methods = {}
|
||||
self._signals = set()
|
||||
|
||||
self._bus_name = bus_name
|
||||
self._interface_name = interface_name
|
||||
self._object_name = object_name
|
||||
self._thread = None
|
||||
self._bus_object = None
|
||||
|
||||
# dirty hack that fixes KeyboardInterrupt handling
|
||||
# the hack is needed because PyGObject / GTK+-3 developers are morons
|
||||
|
|
@ -63,6 +65,36 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
|
||||
self._dbus_methods[method_name] = wrapper
|
||||
|
||||
def signal(self, method, out_signature):
|
||||
if not inspect.ismethod(method):
|
||||
raise Exception("Only bound methods can be exported.")
|
||||
|
||||
method_name = method.__name__
|
||||
if method_name in self._dbus_methods:
|
||||
raise Exception("Method with this name is already exported.")
|
||||
|
||||
def wrapper(wrapped, owner, *args, **kwargs):
|
||||
return method(*args, **kwargs)
|
||||
|
||||
wrapper = decorator.decorator(wrapper, method.im_func)
|
||||
wrapper = dbus.service.signal(self._interface_name, out_signature)(wrapper)
|
||||
|
||||
self._dbus_methods[method_name] = wrapper
|
||||
self._signals.add(method_name)
|
||||
|
||||
def send_signal(self, signal, *args, **kwargs):
|
||||
err = False
|
||||
if not signal in self._signals or self._bus_object is None:
|
||||
err = True
|
||||
try:
|
||||
method = getattr(self._bus_object, signal)
|
||||
except AttributeError:
|
||||
err = True
|
||||
if err:
|
||||
raise Exception("Signal '%s' doesn't exist." % signal)
|
||||
else:
|
||||
method(*args, **kwargs)
|
||||
|
||||
def _construct_dbus_object_class(self):
|
||||
if self._dbus_object_cls is not None:
|
||||
raise Exception("The exporter class was already build.")
|
||||
|
|
@ -91,7 +123,9 @@ class DBusExporter(interfaces.ExporterInterface):
|
|||
|
||||
bus = dbus.SystemBus()
|
||||
bus_name = dbus.service.BusName(self._bus_name, bus)
|
||||
bus_object = self._dbus_object_cls(bus, self._object_name, bus_name)
|
||||
self._bus_object = self._dbus_object_cls(bus, self._object_name, bus_name)
|
||||
|
||||
self._main_loop.run()
|
||||
del bus_object
|
||||
del self._bus_object
|
||||
self._bus_object = None
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ class ExporterInterface(object):
|
|||
# to be overriden by concrete implementation
|
||||
raise NotImplemented()
|
||||
|
||||
def signal(self, method, out_signature):
|
||||
# to be overriden by concrete implementation
|
||||
raise NotImplemented()
|
||||
|
||||
def send_signal(self, signal, *args, **kwargs):
|
||||
# to be overriden by concrete implementation
|
||||
raise NotImplemented()
|
||||
|
||||
def start(self):
|
||||
raise NotImplemented()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue