Introduce the concept of manually vs automatically set profile
A profile can now be set in two modes: automatic and manual. When the automatic mode is active, Tuned always activates the recommended profile upon startup. This mode can be set using the following command: tuned-adm auto_profile When the manual mode is active, the previously applied profile is activated upon startup. This mode can be set by switching to the desired profile, e.g. tuned-adm profile throughput-performance The mode is saved on the second line of /etc/tuned/active_profile. When the file /etc/tuned/active_profile does not exist, or is empty, automatic mode is enabled. When the file has only one line, i.e. it was generated by a previous Tuned version, manual mode is enabled so that the upgrade doesn't cause unexpected active profile changes. Related: rhbz#1459146 Signed-off-by: Ondřej Lysoněk <olysonek@redhat.com>
This commit is contained in:
parent
4e90f14941
commit
bc8ffcfb23
7 changed files with 125 additions and 37 deletions
|
|
@ -117,6 +117,16 @@
|
|||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.auto_profile">
|
||||
<description>Enable automatic profile selection mode</description>
|
||||
<message>Authentication is required to change profile selection mode</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="com.redhat.tuned.verify_profile">
|
||||
<description>Verify Tuned profile</description>
|
||||
<message>Authentication is required to verify Tuned profile</message>
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ if __name__ == "__main__":
|
|||
parser_verify.set_defaults(action="verify_profile")
|
||||
parser_verify.add_argument("--ignore-missing", "-i", action="store_true", help="do not treat missing/non-supported tunings as errors")
|
||||
|
||||
parser_auto_profile = subparsers.add_parser("auto_profile", help="enable automatic profile selection mode, switch to the recommended profile")
|
||||
parser_auto_profile.set_defaults(action="auto_profile")
|
||||
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
options = vars(args)
|
||||
|
|
|
|||
|
|
@ -112,7 +112,12 @@ class Admin(object):
|
|||
|
||||
def _get_active_profile(self):
|
||||
profile_name = None
|
||||
profile_name = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE, None))
|
||||
contents = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE))
|
||||
if contents == '':
|
||||
profile_name = ''
|
||||
else:
|
||||
arr = contents.split('\n')
|
||||
profile_name = arr[0]
|
||||
if profile_name == "":
|
||||
profile_name = None
|
||||
return profile_name
|
||||
|
|
@ -161,6 +166,8 @@ class Admin(object):
|
|||
return True
|
||||
return self._print_profile_name(profile_name)
|
||||
|
||||
# TODO action profile mode - auto/manual
|
||||
|
||||
def _profile_print_status(self, ret, msg):
|
||||
if ret:
|
||||
if not self._controller.is_running() and not self._controller.start():
|
||||
|
|
@ -197,20 +204,23 @@ class Admin(object):
|
|||
self._controller.set_action(self._action_dbus_wait_profile, profile_name)
|
||||
return self._profile_print_status(ret, msg)
|
||||
|
||||
def _action_profile(self, profiles):
|
||||
if len(profiles) == 0:
|
||||
return self._action_list()
|
||||
profile_name = " ".join(profiles)
|
||||
if profile_name == "":
|
||||
return False
|
||||
def _restart_tuned(self):
|
||||
print("Trying to (re)start tuned...")
|
||||
(ret, msg) = self._cmd.execute(["service", "tuned", "restart"])
|
||||
if ret == 0:
|
||||
print("Tuned (re)started, changes applied.")
|
||||
else:
|
||||
print("Tuned (re)start failed, you need to (re)start tuned by hand for changes to apply.")
|
||||
|
||||
def _set_profile(self, profile_name, manual):
|
||||
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...")
|
||||
(ret, msg) = self._cmd.execute(["service", "tuned", "restart"])
|
||||
if ret == 0:
|
||||
print("Tuned (re)started, changes applied.")
|
||||
else:
|
||||
print("Tuned (re)start failed, you need to (re)start tuned by hand for changes to apply.")
|
||||
s = profile_name + '\n'
|
||||
if manual:
|
||||
s += consts.ACTIVE_PROFILE_MANUAL + '\n'
|
||||
else:
|
||||
s += consts.ACTIVE_PROFILE_AUTO + '\n'
|
||||
if self._cmd.write_to_file(consts.ACTIVE_PROFILE_FILE, s):
|
||||
self._restart_tuned()
|
||||
return True
|
||||
else:
|
||||
self._error("Unable to switch profile, do you have enough permissions?")
|
||||
|
|
@ -219,6 +229,29 @@ class Admin(object):
|
|||
self._error("Requested profile '%s' doesn't exist." % profile_name)
|
||||
return False
|
||||
|
||||
def _action_profile(self, profiles):
|
||||
if len(profiles) == 0:
|
||||
return self._action_list()
|
||||
profile_name = " ".join(profiles)
|
||||
if profile_name == "":
|
||||
return False
|
||||
return self._set_profile(profile_name, True)
|
||||
|
||||
def _action_dbus_auto_profile(self):
|
||||
profile_name = self._controller.recommend_profile()
|
||||
self._daemon_action_finished.clear()
|
||||
(ret, msg) = self._controller.auto_profile()
|
||||
if self._async or not ret:
|
||||
return self._controller.exit(self._profile_print_status(ret, msg))
|
||||
else:
|
||||
self._timestamp = time.time()
|
||||
self._controller.set_action(self._action_dbus_wait_profile, profile_name)
|
||||
return self._profile_print_status(ret, msg)
|
||||
|
||||
def _action_auto_profile(self):
|
||||
profile_name = self._cmd.recommend_profile()
|
||||
return self._set_profile(profile_name, False)
|
||||
|
||||
def _action_dbus_recommend_profile(self):
|
||||
print(self._controller.recommend_profile())
|
||||
return self._controller.exit(True)
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ class DBusController(object):
|
|||
return (False, "No profile specified")
|
||||
return self._call("switch_profile", new_profile)
|
||||
|
||||
def auto_profile(self):
|
||||
return self._call("auto_profile")
|
||||
|
||||
def recommend_profile(self):
|
||||
return self._call("recommend_profile")
|
||||
|
||||
|
|
|
|||
|
|
@ -99,3 +99,8 @@ STR_VERIFY_PROFILE_FAIL = "verify: failed: '%s'"
|
|||
|
||||
# timout for tuned-adm operations in seconds
|
||||
ADMIN_TIMEOUT = 600
|
||||
|
||||
# Strings for /etc/tuned/active_profile specifying if the active profile
|
||||
# was set automatically or manually
|
||||
ACTIVE_PROFILE_AUTO = "auto"
|
||||
ACTIVE_PROFILE_MANUAL = "manual"
|
||||
|
|
|
|||
|
|
@ -79,19 +79,15 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
else:
|
||||
return self.stop() and self.start()
|
||||
|
||||
@exports.export("s", "(bs)")
|
||||
def switch_profile(self, profile_name, caller = None):
|
||||
if caller == "":
|
||||
return (False, "Unauthorized")
|
||||
def _switch_profile(self, profile_name, manual):
|
||||
was_running = self._daemon.is_running()
|
||||
msg = "OK"
|
||||
success = True
|
||||
reapply = False
|
||||
try:
|
||||
if was_running:
|
||||
# stop(switch_profile = True), due to profile switch
|
||||
self._daemon.stop(True)
|
||||
self._daemon.set_profile(profile_name)
|
||||
self._daemon.stop(profile_switch = True)
|
||||
self._daemon.set_profile(profile_name, manual)
|
||||
except tuned.exceptions.TunedException as e:
|
||||
success = False
|
||||
msg = str(e)
|
||||
|
|
@ -110,6 +106,19 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
|
||||
return (success, msg)
|
||||
|
||||
@exports.export("s", "(bs)")
|
||||
def switch_profile(self, profile_name, caller = None):
|
||||
if caller == "":
|
||||
return (False, "Unauthorized")
|
||||
return self._switch_profile(profile_name, True)
|
||||
|
||||
@exports.export("", "(bs)")
|
||||
def auto_profile(self, caller = None):
|
||||
if caller == "":
|
||||
return (False, "Unauthorized")
|
||||
profile_name = self.recommend_profile()
|
||||
return self._switch_profile(profile_name, False)
|
||||
|
||||
@exports.export("", "s")
|
||||
def active_profile(self, caller = None):
|
||||
if caller == "":
|
||||
|
|
@ -126,7 +135,7 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
|
|||
if self._daemon.is_running():
|
||||
self._daemon.stop()
|
||||
if self._daemon.is_enabled():
|
||||
self._daemon.set_profile(None, save_instantly=True)
|
||||
self._daemon.set_profile(None, True, save_instantly=True)
|
||||
return True
|
||||
|
||||
@exports.export("", "b")
|
||||
|
|
|
|||
|
|
@ -57,31 +57,34 @@ class Daemon(object):
|
|||
self._profile_applied = threading.Event()
|
||||
|
||||
def _init_profile(self, profile_name):
|
||||
manual = True
|
||||
if profile_name is None:
|
||||
profile_name = self._get_active_profile()
|
||||
(profile_name, manual) = self._get_startup_profile()
|
||||
|
||||
self._profile = None
|
||||
self.set_profile(profile_name)
|
||||
self._manual = None
|
||||
self.set_profile(profile_name, manual)
|
||||
|
||||
def set_profile(self, profile_name, save_instantly=False):
|
||||
def set_profile(self, profile_name, 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."))
|
||||
|
||||
if profile_name == "" or profile_name is None:
|
||||
self._profile = None
|
||||
self._manual = None
|
||||
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:
|
||||
try:
|
||||
self._profile = self._profile_loader.load(profile_name)
|
||||
self._manual = manual
|
||||
except InvalidProfileException as e:
|
||||
raise TunedException(self._notify_profile_changed(profile_name, False, "Cannot load profile '%s': %s" % (profile_name, e)))
|
||||
|
||||
if save_instantly:
|
||||
if profile_name is None:
|
||||
profile_name = ""
|
||||
self._save_active_profile(profile_name)
|
||||
self._save_active_profile(profile_name, manual)
|
||||
|
||||
@property
|
||||
def profile(self):
|
||||
|
|
@ -103,7 +106,7 @@ class Daemon(object):
|
|||
raise TunedException("Cannot start the daemon without setting a profile.")
|
||||
|
||||
self._unit_manager.create(self._profile.units)
|
||||
self._save_active_profile(self._profile.name)
|
||||
self._save_active_profile(self._profile.name, self._manual)
|
||||
self._unit_manager.start_tuning()
|
||||
self._profile_applied.set()
|
||||
log.info("static tuning from profile '%s' applied" % self._profile.name)
|
||||
|
|
@ -154,10 +157,15 @@ class Daemon(object):
|
|||
self._unit_manager.stop_tuning(full_rollback)
|
||||
self._unit_manager.destroy_all()
|
||||
|
||||
def _save_active_profile(self, profile_name):
|
||||
def _save_active_profile(self, profile_name, manual):
|
||||
try:
|
||||
with open(consts.ACTIVE_PROFILE_FILE, "w") as f:
|
||||
f.write(profile_name + "\n")
|
||||
if len(profile_name) > 0:
|
||||
f.write(profile_name + "\n")
|
||||
if manual:
|
||||
f.write(consts.ACTIVE_PROFILE_MANUAL + "\n")
|
||||
else:
|
||||
f.write(consts.ACTIVE_PROFILE_AUTO + "\n")
|
||||
except (OSError,IOError) as e:
|
||||
log.error("Cannot write active profile into %s: %s" % (consts.ACTIVE_PROFILE_FILE, str(e)))
|
||||
|
||||
|
|
@ -165,16 +173,33 @@ class Daemon(object):
|
|||
log.info("no profile preset, checking what is recommended for your configuration")
|
||||
profile = self._cmd.recommend_profile(hardcoded = not self._recommend_command)
|
||||
log.info("using '%s' profile and setting it as active" % profile)
|
||||
self._save_active_profile(profile)
|
||||
self._save_active_profile(profile, False)
|
||||
return profile
|
||||
|
||||
def _get_active_profile(self):
|
||||
def _get_startup_profile(self):
|
||||
manual = False
|
||||
try:
|
||||
with open(consts.ACTIVE_PROFILE_FILE, "r") as f:
|
||||
profile = f.read().strip()
|
||||
if profile == "":
|
||||
content = f.read().strip()
|
||||
if content == "":
|
||||
profile = self._set_recommended_profile()
|
||||
return profile
|
||||
else:
|
||||
arr = content.split('\n')
|
||||
if len(arr) > 2 or (len(arr) == 2 and arr[1] != consts.ACTIVE_PROFILE_AUTO and arr[1] != consts.ACTIVE_PROFILE_MANUAL):
|
||||
profile = self._set_recommended_profile()
|
||||
log.error("cannot read active profile from '%s': bad format. Falling back to '%s' profile."
|
||||
% consts.ACTIVE_PROFILE_FILE, profile)
|
||||
else:
|
||||
profile = arr[0]
|
||||
if len(arr) == 2:
|
||||
manual = arr[1] == consts.ACTIVE_PROFILE_MANUAL
|
||||
if not manual:
|
||||
profile = self._set_recommended_profile()
|
||||
else:
|
||||
# The file has only one line - generated by previous Tuned version.
|
||||
# Treat the profile as manually set.
|
||||
manual = True
|
||||
return (profile, manual)
|
||||
except IOError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
# No such file or directory
|
||||
|
|
@ -182,10 +207,10 @@ class Daemon(object):
|
|||
else:
|
||||
profile = consts.DEFAULT_PROFILE
|
||||
log.error("error reading active profile from '%s', falling back to '%s' profile" % (consts.ACTIVE_PROFILE_FILE, profile))
|
||||
return profile
|
||||
return (profile, manual)
|
||||
except (OSError, EOFError) as e:
|
||||
log.error("cannot read active profile, falling back to '%s' profile" % consts.DEFAULT_PROFILE)
|
||||
return consts.DEFAULT_PROFILE
|
||||
return (consts.DEFAULT_PROFILE, manual)
|
||||
|
||||
def is_enabled(self):
|
||||
return self._profile is not None
|
||||
|
|
|
|||
Loading…
Reference in a new issue