1
0
Fork 0

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:
Ondřej Lysoněk 2017-08-03 14:58:43 +02:00
parent 4e90f14941
commit bc8ffcfb23
7 changed files with 125 additions and 37 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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