AlkantarClanX12
Current Path : /lib/python3.6/site-packages/tuned/daemon/ |
Current File : //lib/python3.6/site-packages/tuned/daemon/daemon.py |
import os import errno import threading import tuned.logs 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 from tuned.utils.profile_recommender import ProfileRecommender import re log = tuned.logs.get() class Daemon(object): 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) self._update_interval = int(consts.CFG_DEF_UPDATE_INTERVAL) self._dynamic_tuning = consts.CFG_DEF_DYNAMIC_TUNING self._recommend_command = True self._rollback = consts.CFG_DEF_ROLLBACK if config is not None: self._daemon = config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON) self._sleep_interval = int(config.get(consts.CFG_SLEEP_INTERVAL, consts.CFG_DEF_SLEEP_INTERVAL)) 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._rollback = config.get(consts.CFG_ROLLBACK, consts.CFG_DEF_ROLLBACK) self._application = application if self._sleep_interval <= 0: self._sleep_interval = int(consts.CFG_DEF_SLEEP_INTERVAL) if self._update_interval == 0: self._dynamic_tuning = False elif self._update_interval < self._sleep_interval: self._update_interval = self._sleep_interval self._sleep_cycles = self._update_interval // self._sleep_interval log.info("using sleep interval of %d second(s)" % self._sleep_interval) if self._dynamic_tuning: log.info("dynamic tuning is enabled (can be overridden by plugins)") log.info("using update interval of %d second(s) (%d times of the sleep interval)" % (self._sleep_cycles * self._sleep_interval, self._sleep_cycles)) self._profile_recommender = ProfileRecommender(is_hardcoded = not self._recommend_command) self._unit_manager = unit_manager self._profile_loader = profile_loader self._init_threads() self._cmd = commands() try: self._init_profile(profile_names) except TunedException as e: log.error("Cannot set initial profile. No tunings will be enabled: %s" % e) def _init_threads(self): self._thread = None self._terminate = threading.Event() # Flag which is set if terminating due to profile_switch self._terminate_profile_switch = threading.Event() # Flag which is set if there is no operation in progress self._not_used = threading.Event() # Flag which is set if SIGHUP is being processed self._sighup_processing = threading.Event() self._not_used.set() self._profile_applied = threading.Event() def reload_profile_config(self): """Read configuration files again and load profile according to them""" self._init_profile(None) def _init_profile(self, profile_names): manual = True post_loaded_profile = self._cmd.get_post_loaded_profile() if profile_names is None: (profile_names, manual) = self._get_startup_profile() if profile_names is None: msg = "No profile is preset, running in manual mode. " if post_loaded_profile: msg += "Only post-loaded profile will be enabled" else: msg += "No profile will be enabled." log.info(msg) # Passed through '-p' cmdline option elif profile_names == "": if post_loaded_profile: log.info("Only post-loaded profile will be enabled") else: log.info("No profile will be enabled.") self._profile = None self._manual = None self._active_profiles = [] self._post_loaded_profile = None self.set_all_profiles(profile_names, manual, post_loaded_profile) def _load_profiles(self, profile_names, manual): profile_names = profile_names or "" profile_list = profile_names.split() if self._post_loaded_profile: log.info("Using post-loaded profile '%s'" % self._post_loaded_profile) profile_list = profile_list + [self._post_loaded_profile] for profile in profile_list: if profile not in self.profile_loader.profile_locator.get_known_names(): errstr = "Requested profile '%s' doesn't exist." % profile self._notify_profile_changed(profile_names, False, errstr) raise TunedException(errstr) try: if profile_list: self._profile = self._profile_loader.load(profile_list) else: self._profile = None self._manual = manual self._active_profiles = profile_names.split() except InvalidProfileException as e: errstr = "Cannot load profile(s) '%s': %s" % (" ".join(profile_list), e) self._notify_profile_changed(profile_names, False, errstr) raise TunedException(errstr) def set_profile(self, profile_names, manual): if self.is_running(): errstr = "Cannot set profile while the daemon is running." self._notify_profile_changed(profile_names, False, errstr) raise TunedException(errstr) self._load_profiles(profile_names, manual) def _set_post_loaded_profile(self, profile_name): if not profile_name: self._post_loaded_profile = None elif len(profile_name.split()) > 1: errstr = "Whitespace is not allowed in profile names; only a single post-loaded profile is allowed." raise TunedException(errstr) else: self._post_loaded_profile = profile_name def set_all_profiles(self, active_profiles, manual, post_loaded_profile, save_instantly=False): if self.is_running(): errstr = "Cannot set profile while the daemon is running." self._notify_profile_changed(active_profiles, False, errstr) raise TunedException(errstr) self._set_post_loaded_profile(post_loaded_profile) self._load_profiles(active_profiles, manual) if save_instantly: self._save_active_profile(active_profiles, manual) self._save_post_loaded_profile(post_loaded_profile) @property def profile(self): return self._profile @property def manual(self): return self._manual @property def post_loaded_profile(self): # Return the profile name only if the profile is active. If # the profile is not active, then the value is meaningless. return self._post_loaded_profile if self._profile else None @property def profile_recommender(self): return self._profile_recommender @property 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_names, result, errstr): if self._application is not None: exports.send_signal(consts.SIGNAL_PROFILE_CHANGED, profile_names, result, errstr) return errstr 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 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 None and not retcode def _thread_code(self): if self._profile is None: raise TunedException("Cannot start the daemon without setting a profile.") self._unit_manager.create(self._profile.units) self._save_active_profile(" ".join(self._active_profiles), self._manual) self._save_post_loaded_profile(self._post_loaded_profile) 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() profile_names = " ".join(self._active_profiles) self._notify_profile_changed(profile_names, True, "OK") self._sighup_processing.clear() if self._daemon: # In python 2 interpreter with applied patch for rhbz#917709 we need to periodically # poll, otherwise the python will not have chance to update events / locks (due to GIL) # and e.g. DBus control will not work. The polling interval of 1 seconds (which is # the default) is still much better than 50 ms polling with unpatched interpreter. # For more details see TuneD rhbz#917587. _sleep_cnt = self._sleep_cycles while not self._cmd.wait(self._terminate, self._sleep_interval): if self._dynamic_tuning: _sleep_cnt -= 1 if _sleep_cnt <= 0: _sleep_cnt = self._sleep_cycles log.debug("updating monitors") self._unit_manager.update_monitors() log.debug("performing tunings") self._unit_manager.update_tuning() self._profile_applied.clear() # wait for others to complete their tasks, use timeout 3 x sleep_interval to prevent # deadlocks i = 0 while not self._cmd.wait(self._not_used, self._sleep_interval) and i < 3: i += 1 # if terminating due to profile switch if self._terminate_profile_switch.is_set(): rollback = consts.ROLLBACK_FULL else: # Assume only soft rollback is needed. Soft rollback means reverting all # non-persistent tunings applied by a plugin instance. In contrast to full # rollback, information about what to revert is kept in RAM (volatile # memory) -- TuneD data structures. # With systemd TuneD detects system shutdown and in such a case it doesn't # perform full cleanup. If the system is not shutting down, it means that TuneD # was explicitly stopped by the user and in such case do the full cleanup. On # systems without systemd, full cleanup is never performed. rollback = consts.ROLLBACK_SOFT if not self._full_rollback_required(): log.info("terminating TuneD due to system shutdown / reboot") elif self._rollback == "not_on_exit": # no rollback on TuneD exit whatsoever rollback = consts.ROLLBACK_NONE log.info("terminating TuneD and not rolling back any changes due to '%s' option in '%s'" % (consts.CFG_ROLLBACK, consts.GLOBAL_CONFIG_FILE)) else: if self._daemon: log.info("terminating TuneD, rolling back all changes") rollback = consts.ROLLBACK_FULL else: log.info("terminating TuneD in one-shot mode") if self._daemon: self._unit_manager.stop_tuning(rollback) self._unit_manager.destroy_all() def _save_active_profile(self, profile_names, manual): try: self._cmd.save_active_profile(profile_names, manual) except TunedException as e: log.error(str(e)) def _save_post_loaded_profile(self, profile_name): try: self._cmd.save_post_loaded_profile(profile_name) except TunedException as e: log.error(str(e)) def _get_recommended_profile(self): log.info("Running in automatic mode, checking what profile is recommended for your configuration.") profile = self._profile_recommender.recommend() log.info("Using '%s' profile" % profile) return profile def _get_startup_profile(self): profile, manual = self._cmd.get_active_profile() if manual is None: manual = profile is not None if not manual: profile = self._get_recommended_profile() return profile, manual def get_all_plugins(self): """Return all accessible plugin classes""" return self._unit_manager.plugins_repository.load_all_plugins() def get_plugin_documentation(self, plugin_name): """Return plugin class docstring""" try: plugin_class = self._unit_manager.plugins_repository.load_plugin( plugin_name ) except ImportError: return "" return plugin_class.__doc__ def get_plugin_hints(self, plugin_name): """Return plugin's parameters and their hints Parameters: plugin_name -- plugins name Return: dictionary -- {parameter_name: hint} """ try: plugin_class = self._unit_manager.plugins_repository.load_plugin( plugin_name ) except ImportError: return {} return plugin_class.get_config_options_hints() def is_enabled(self): return self._profile is not None def is_running(self): return self._thread is not None and self._thread.is_alive() def start(self): if self.is_running(): return False if self._profile is None: return False log.info("starting tuning") self._not_used.set() self._thread = threading.Thread(target=self._thread_code) self._terminate_profile_switch.clear() self._terminate.clear() self._thread.start() return True def verify_profile(self, ignore_missing): if not self.is_running(): log.error("TuneD is not running") return False if self._profile is None: log.error("no profile is set") return False if not self._profile_applied.is_set(): log.error("profile is not applied") return False # using daemon, the main loop mustn't exit before our completion self._not_used.clear() log.info("verifying profile(s): %s" % self._profile.name) ret = self._unit_manager.verify_tuning(ignore_missing) # main loop is allowed to exit self._not_used.set() return ret # profile_switch is helper telling plugins whether the stop is due to profile switch def stop(self, profile_switch = False): if not self.is_running(): return False log.info("stopping tuning") if profile_switch: self._terminate_profile_switch.set() self._terminate.set() self._thread.join() self._thread = None return True