From d78565a20506ff0f7113e0ef3c661d7e8d59626d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Mon, 25 Sep 2017 16:03:52 +0200 Subject: [PATCH 1/5] Start dbus exports after a profile is applied MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We should register to dbus after a profile is fully applied when starting up. This makes systemd services depending on some tunings performed by tuned (with After=tuned.service in their service file) to start only after all tunings were applied. Resolves: https://github.com/redhat-performance/tuned/issues/23 Resolves: rhbz#1443142 Signed-off-by: Ondřej Lysoněk --- tuned/daemon/application.py | 4 +--- tuned/daemon/controller.py | 7 +++++-- tuned/daemon/daemon.py | 3 +++ tuned/exports/dbus_exporter.py | 5 +++++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/tuned/daemon/application.py b/tuned/daemon/application.py index db2ba34..618d1ce 100644 --- a/tuned/daemon/application.py +++ b/tuned/daemon/application.py @@ -186,9 +186,7 @@ class Application(object): # override global config if ran from command line with daemon option (-d) if daemon: self.config.set(consts.CFG_DAEMON, True) - if self.config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON): - exports.start() - else: + if not self.config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON): log.warn("Using one shot no deamon mode, most of the functionality will be not available, it can be changed in global config") result = self._controller.run() if self.config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON): diff --git a/tuned/daemon/controller.py b/tuned/daemon/controller.py index 59a55b1..c0adb75 100644 --- a/tuned/daemon/controller.py +++ b/tuned/daemon/controller.py @@ -28,9 +28,12 @@ class Controller(tuned.exports.interfaces.ExportableInterface): Controller main loop. The call is blocking. """ log.info("starting controller") - self.start() + res = self.start() + daemon = self._global_config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON) + if not res and daemon: + exports.start() - if self._global_config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON): + if daemon: self._terminate.clear() # we have to pass some timeout, otherwise signals will not work while not self._cmd.wait(self._terminate, 3600): diff --git a/tuned/daemon/daemon.py b/tuned/daemon/daemon.py index 58d1340..f3a175e 100644 --- a/tuned/daemon/daemon.py +++ b/tuned/daemon/daemon.py @@ -6,6 +6,7 @@ from tuned.exceptions import TunedException from tuned.profiles.exceptions import InvalidProfileException import tuned.consts as consts from tuned.utils.commands import commands +from tuned import exports import re log = tuned.logs.get() @@ -127,6 +128,8 @@ class Daemon(object): self._unit_manager.start_tuning() self._profile_applied.set() log.info("static tuning from profile '%s' applied" % self._profile.name) + if self._daemon: + exports.start() self._notify_profile_changed(self._profile.name, True, "OK") if self._daemon: diff --git a/tuned/exports/dbus_exporter.py b/tuned/exports/dbus_exporter.py index 65ee969..959275e 100644 --- a/tuned/exports/dbus_exporter.py +++ b/tuned/exports/dbus_exporter.py @@ -56,6 +56,9 @@ class DBusExporter(interfaces.ExporterInterface): def object_name(self): return self._object_name + def running(self): + return self._thread is not None + def export(self, method, in_signature, out_signature): if not inspect.ismethod(method): raise Exception("Only bound methods can be exported.") @@ -129,6 +132,8 @@ class DBusExporter(interfaces.ExporterInterface): self._dbus_object_cls = cls def start(self): + if self.running(): + return if self._dbus_object_cls is None: self._construct_dbus_object_class() From ed8bf19ec2827b0c647bfde7043a0cf75488ae15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Mon, 25 Sep 2017 12:16:48 +0200 Subject: [PATCH 2/5] Fix hang when 'tuned-adm profile' is given empty profile name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ondřej Lysoněk --- tuned/admin/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tuned/admin/admin.py b/tuned/admin/admin.py index 8d1b107..faf215f 100644 --- a/tuned/admin/admin.py +++ b/tuned/admin/admin.py @@ -222,7 +222,7 @@ class Admin(object): return self._action_dbus_list() profile_name = " ".join(profiles) if profile_name == "": - return False + return self._controller.exit(False) self._daemon_action_finished.clear() (ret, msg) = self._controller.switch_profile(profile_name) if self._async or not ret: From cf87220216c1d624c99d0912d8366bec6491d679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Mon, 25 Sep 2017 17:38:10 +0200 Subject: [PATCH 3/5] Allow applying multiple profiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'tuned-adm profile' command can now (once again) be given multiple profile names. In case of conflicting settings in the profiles, the setting from the last profile specified is used. E.g.: tuned-adm profile powersave balanced Signed-off-by: Ondřej Lysoněk --- tuned/daemon/daemon.py | 48 +++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/tuned/daemon/daemon.py b/tuned/daemon/daemon.py index f3a175e..bbfcfc5 100644 --- a/tuned/daemon/daemon.py +++ b/tuned/daemon/daemon.py @@ -13,7 +13,7 @@ log = tuned.logs.get() class Daemon(object): - def __init__(self, unit_manager, profile_loader, profile_name=None, config=None, application=None): + def __init__(self, unit_manager, profile_loader, profile_names=None, config=None, application=None): log.debug("initializing daemon") self._daemon = consts.CFG_DEF_DAEMON self._sleep_interval = int(consts.CFG_DEF_SLEEP_INTERVAL) @@ -44,7 +44,7 @@ class Daemon(object): self._init_threads() self._cmd = commands() try: - self._init_profile(profile_name) + self._init_profile(profile_names) except TunedException as e: log.error("Cannot set initial profile. No tunings will be enabled: %s" % e) @@ -58,40 +58,44 @@ class Daemon(object): self._not_used.set() self._profile_applied = threading.Event() - def _init_profile(self, profile_name): + def _init_profile(self, profile_names): manual = True - if profile_name is None: - (profile_name, manual) = self._get_startup_profile() - if profile_name is None: + if profile_names is None: + (profile_names, manual) = self._get_startup_profile() + if profile_names is None: log.info("No profile is preset, running in manual mode. No profile will be enabled.") # Passed through '-p' cmdline option - elif profile_name == "": + elif profile_names == "": log.info("No profile will be enabled.") self._profile = None self._manual = None - self.set_profile(profile_name, manual) + self.set_profile(profile_names, manual) - def set_profile(self, profile_name, manual, save_instantly=False): + def set_profile(self, profile_names, manual, save_instantly=False): if self.is_running(): - raise TunedException(self._notify_profile_changed(profile_name, False, "Cannot set profile while the daemon is running.")) + raise TunedException(self._notify_profile_changed(profile_names, False, "Cannot set profile while the daemon is running.")) - if profile_name == "" or profile_name is None: + if profile_names == "" or profile_names is None: self._profile = None self._manual = manual - elif profile_name not in self.profile_loader.profile_locator.get_known_names(): - raise TunedException(self._notify_profile_changed(profile_name, False, "Requested profile '%s' doesn't exist." % profile_name)) else: + profile_list = profile_names.split() + for profile in profile_list: + if profile not in self.profile_loader.profile_locator.get_known_names(): + raise TunedException(self._notify_profile_changed(\ + profile_names, False,\ + "Requested profile '%s' doesn't exist." % profile)) try: - self._profile = self._profile_loader.load(profile_name) + self._profile = self._profile_loader.load(profile_names) self._manual = manual except InvalidProfileException as e: - raise TunedException(self._notify_profile_changed(profile_name, False, "Cannot load profile '%s': %s" % (profile_name, e))) + raise TunedException(self._notify_profile_changed(profile_names, False, "Cannot load profile(s) '%s': %s" % (profile_names, e))) if save_instantly: - if profile_name is None: - profile_name = "" - self._save_active_profile(profile_name, manual) + if profile_names is None: + profile_names = "" + self._save_active_profile(profile_names, manual) @property def profile(self): @@ -107,9 +111,9 @@ class Daemon(object): # 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): + def _notify_profile_changed(self, profile_names, 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) + self._application._dbus_exporter.send_signal(consts.DBUS_SIGNAL_PROFILE_CHANGED, profile_names, result, errstr) return errstr def _system_shutting_down(self): @@ -175,9 +179,9 @@ class Daemon(object): self._unit_manager.stop_tuning(full_rollback) self._unit_manager.destroy_all() - def _save_active_profile(self, profile_name, manual): + def _save_active_profile(self, profile_names, manual): try: - self._cmd.save_active_profile(profile_name, manual) + self._cmd.save_active_profile(profile_names, manual) except TunedException as e: log.error(str(e)) From 7e7b1ee878bb8084fb98bf537fb6d951c479ee18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Mon, 25 Sep 2017 17:53:01 +0200 Subject: [PATCH 4/5] plugin_net: Allow disabling dynamic tuning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The net plugin can now be given the boolean 'dynamic' option, which is used to determine, whether dynamic tuning should be enabled. Signed-off-by: Ondřej Lysoněk --- tuned/plugins/plugin_net.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tuned/plugins/plugin_net.py b/tuned/plugins/plugin_net.py index c597000..d37ef3c 100644 --- a/tuned/plugins/plugin_net.py +++ b/tuned/plugins/plugin_net.py @@ -38,11 +38,16 @@ class NetTuningPlugin(base.Plugin): def _instance_init(self, instance): instance._has_static_tuning = True - instance._has_dynamic_tuning = True - - instance._load_monitor = self._monitors_repository.create("net", instance.devices) - instance._idle = {} - instance._stats = {} + if self._option_bool(instance.options["dynamic"]): + instance._has_dynamic_tuning = True + instance._load_monitor = self._monitors_repository.create("net", instance.devices) + instance._idle = {} + instance._stats = {} + else: + instance._has_dynamic_tuning = False + instance._load_monitor = None + instance._idle = None + instance._stats = None def _instance_cleanup(self, instance): if instance._load_monitor is not None: @@ -120,6 +125,7 @@ class NetTuningPlugin(base.Plugin): @classmethod def _get_config_options(cls): return { + "dynamic": True, "wake_on_lan": None, "nf_conntrack_hashsize": None, "features": None, From a4346d2b484b98db7382161f9f209e187b22dd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Tue, 26 Sep 2017 13:03:02 +0200 Subject: [PATCH 5/5] Don't do full rollback on systems without systemd when tuned is exiting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes commit 701f09a8. Signed-off-by: Ondřej Lysoněk --- tuned/daemon/daemon.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tuned/daemon/daemon.py b/tuned/daemon/daemon.py index bbfcfc5..52f6725 100644 --- a/tuned/daemon/daemon.py +++ b/tuned/daemon/daemon.py @@ -116,12 +116,14 @@ class Daemon(object): self._application._dbus_exporter.send_signal(consts.DBUS_SIGNAL_PROFILE_CHANGED, profile_names, result, errstr) return errstr - def _system_shutting_down(self): + def _full_rollback_required(self): retcode, out = self._cmd.execute(["systemctl", "is-system-running"], no_errors = [0]) + if retcode < 0: + return False if out[:8] == "stopping": - return True + return False retcode, out = self._cmd.execute(["systemctl", "list-jobs"], no_errors = [0]) - return re.search(r"\b(shutdown|reboot|halt|poweroff)\.target.*start", out) is not None + return re.search(r"\b(shutdown|reboot|halt|poweroff)\.target.*start", out) is None def _thread_code(self): if self._profile is None: @@ -170,11 +172,11 @@ class Daemon(object): # stopped by user and in such case do full cleanup, without systemd never # do full cleanup full_rollback = False - if self._system_shutting_down(): - log.info("terminating Tuned due to system shutdown / reboot") - else: + if self._full_rollback_required(): log.info("terminating Tuned, rolling back all changes") full_rollback = True + else: + log.info("terminating Tuned due to system shutdown / reboot") if self._daemon: self._unit_manager.stop_tuning(full_rollback) self._unit_manager.destroy_all()