Your IP : 3.149.249.84
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