1
0
Fork 0

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:
Jaroslav Škarvada 2016-04-21 14:55:19 +02:00
parent 32ef5d8db0
commit 67f78d632e
11 changed files with 168 additions and 37 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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()