1
0
Fork 0

Merge pull request #60 from olysonek/recommend.d_rebase

Recommend.d functionality
This commit is contained in:
Jaroslav Škarvada 2017-08-09 16:08:44 +02:00 committed by GitHub
commit 0a2c210db3
9 changed files with 237 additions and 68 deletions

View file

@ -31,6 +31,7 @@ DATADIR = /usr/share
DOCDIR = $(DATADIR)/doc/$(NAME)
PYTHON_SITELIB = $(shell python -c 'from distutils.sysconfig import get_python_lib; print get_python_lib();' || echo /usr/lib/python2.7/site-packages)
TUNED_PROFILESDIR = /usr/lib/tuned
TUNED_RECOMMEND_DIR = $(TUNED_PROFILESDIR)/recommend.d
BASH_COMPLETIONS = $(DATADIR)/bash-completion/completions
release-dir:
@ -100,6 +101,7 @@ install-dirs:
mkdir -p $(DESTDIR)/var/log/tuned
mkdir -p $(DESTDIR)/run/tuned
mkdir -p $(DESTDIR)$(DOCDIR)
mkdir -p $(DESTDIR)$(TUNED_RECOMMEND_DIR)
install: install-dirs
# library
@ -135,7 +137,7 @@ install: install-dirs
$(DESTDIR)/etc/tuned/realtime-virtual-host-variables.conf
mv $(DESTDIR)$(TUNED_PROFILESDIR)/cpu-partitioning/cpu-partitioning-variables.conf \
$(DESTDIR)/etc/tuned/cpu-partitioning-variables.conf
install -pm 0644 recommend.conf $(DESTDIR)$(TUNED_PROFILESDIR)/recommend.conf
install -pm 0644 recommend.conf $(DESTDIR)$(TUNED_RECOMMEND_DIR)/50-tuned-recommend.conf
# bash completion
install -Dpm 0644 tuned-adm.bash $(DESTDIR)$(BASH_COMPLETIONS)/tuned-adm

View file

@ -17,6 +17,16 @@
</defaults>
</action>
<action id="com.redhat.tuned.profile_mode">
<description>Show current profile selection mode</description>
<message>Authentication is required to show current profile selection mode</message>
<defaults>
<allow_any>yes</allow_any>
<allow_inactive>yes</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
</action>
<action id="com.redhat.tuned.disable">
<description>Disable Tuned</description>
<message>Authentication is required to disable Tuned</message>
@ -117,6 +127,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,12 @@ 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")
parser_profile_mode = subparsers.add_parser("profile_mode", help="show current profile selection mode")
parser_profile_mode.set_defaults(action="profile_mode")
args = parser.parse_args(sys.argv[1:])
options = vars(args)

View file

@ -112,11 +112,29 @@ 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
def _get_profile_mode(self):
contents = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE))
if contents == '':
mode = consts.ACTIVE_PROFILE_AUTO
else:
arr = contents.split('\n')
if len(arr) == 1:
# The file was generated by old Tuned -> manual mode
mode = consts.ACTIVE_PROFILE_MANUAL
else:
mode = arr[1]
return mode
def _print_profile_info(self, profile, profile_info):
if profile_info[0] == True:
print("Profile name:")
@ -161,6 +179,19 @@ class Admin(object):
return True
return self._print_profile_name(profile_name)
def _print_profile_mode(self, mode):
print("Profile selection mode: " + mode)
def _action_dbus_profile_mode(self):
mode = self._controller.profile_mode()
self._print_profile_mode(mode)
return self._controller.exit(True)
def _action_profile_mode(self):
mode = self._get_profile_mode()
self._print_profile_mode(mode)
return True
def _profile_print_status(self, ret, msg):
if ret:
if not self._controller.is_running() and not self._controller.start():
@ -197,20 +228,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 +253,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

@ -99,11 +99,17 @@ class DBusController(object):
def active_profile(self):
return self._call("active_profile")
def profile_mode(self):
return self._call("profile_mode")
def switch_profile(self, new_profile):
if new_profile == "":
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

@ -1,7 +1,7 @@
GLOBAL_CONFIG_FILE = "/etc/tuned/tuned-main.conf"
ACTIVE_PROFILE_FILE = "/etc/tuned/active_profile"
PROFILE_FILE = "tuned.conf"
AUTODETECT_FILE = "recommend.conf"
RECOMMEND_CONF_FILE = "/etc/tuned/recommend.conf"
DAEMONIZE_PARENT_TIMEOUT = 5
NAMESPACE = "com.redhat.tuned"
DBUS_BUS = NAMESPACE
@ -12,6 +12,7 @@ DEFAULT_STORAGE_FILE = "/run/tuned/save.pickle"
LOAD_DIRECTORIES = ["/usr/lib/tuned", "/etc/tuned"]
PERSISTENT_STORAGE_DIR = "/var/lib/tuned"
PLUGIN_MAIN_UNIT_NAME = "main"
RECOMMEND_DIRECTORIES = ["/usr/lib/tuned/recommend.d", "/etc/tuned/recommend.d"]
TMP_FILE_SUFFIX = ".tmp"
# max. number of consecutive errors to give up
@ -99,3 +100,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 == "":
@ -119,6 +128,15 @@ class Controller(tuned.exports.interfaces.ExportableInterface):
else:
return ""
@exports.export("", "s")
def profile_mode(self, caller = None):
if caller == "":
return ""
if self._daemon.manual:
return consts.ACTIVE_PROFILE_MANUAL
else:
return consts.ACTIVE_PROFILE_AUTO
@exports.export("", "b")
def disable(self, caller = None):
if caller == "":
@ -126,7 +144,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, None, save_instantly=True)
return True
@exports.export("", "b")

View file

@ -57,36 +57,44 @@ 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):
return self._profile
@property
def manual(self):
# manual == None means /etc/tuned/active_profile is empty -> automatic mode
return self._manual == True or self._manual is None
@property
def profile_loader(self):
return self._profile_loader
@ -103,7 +111,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 +162,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 +178,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 +212,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

View file

@ -364,40 +364,64 @@ class commands:
s = s.zfill(ls)
return ",".join(s[i:i + 8] for i in range(0, len(s), 8))
def process_recommend_file(self, fname):
matching_profile = None
try:
if not os.path.isfile(fname):
return None
config = ConfigObj(fname, list_values = False, interpolation = False)
for section in config.keys():
match = True
for option in config[section].keys():
value = config[section][option]
if value == "":
value = r"^$"
if option == "virt":
if not re.match(value, self.execute("virt-what")[1], re.S):
match = False
elif option == "system":
if not re.match(value, self.read_file(consts.SYSTEM_RELEASE_FILE), re.S):
match = False
elif option[0] == "/":
if not os.path.exists(option) or not re.match(value, self.read_file(option), re.S):
match = False
elif option[0:7] == "process":
ps = procfs.pidstats()
ps.reload_threads()
if len(ps.find_by_regex(re.compile(value))) == 0:
match = False
if match:
# remove the ",.*" suffix
r = re.compile(r",[^,]*$")
matching_profile = r.sub("", section)
break
except (IOError, OSError, ConfigObjError) as e:
log.error("error processing '%s', %s" % (fname, e))
return matching_profile
def recommend_profile(self, hardcoded = False):
profile = consts.DEFAULT_PROFILE
if hardcoded:
return profile
r = re.compile(r",[^,]*$")
for f in consts.LOAD_DIRECTORIES:
matching = self.process_recommend_file(consts.RECOMMEND_CONF_FILE)
if matching is not None:
return matching
files = {}
for directory in consts.RECOMMEND_DIRECTORIES:
contents = []
try:
fname = os.path.join(f, consts.AUTODETECT_FILE)
config = ConfigObj(fname, list_values = False, interpolation = False)
for section in reversed(config.keys()):
match = True
for option in config[section].keys():
value = config[section][option]
if value == "":
value = r"^$"
if option == "virt":
if not re.match(value, self.execute("virt-what")[1], re.S):
match = False
elif option == "system":
if not re.match(value, self.read_file(consts.SYSTEM_RELEASE_FILE), re.S):
match = False
elif option[0] == "/":
if not os.path.exists(option) or not re.match(value, self.read_file(option), re.S):
match = False
elif option[0:7] == "process":
ps = procfs.pidstats()
ps.reload_threads()
if len(ps.find_by_regex(re.compile(value))) == 0:
match = False
if match:
# remove the ",.*" suffix
profile = r.sub("", section)
except (IOError, OSError, ConfigObjError) as e:
log.error("error parsing '%s', %s" % (fname, e))
contents = os.listdir(directory)
except OSError as e:
if e.errno != errno.ENOENT:
log.error("error accessing %s: %s" % (directory, e))
for name in contents:
path = os.path.join(directory, name)
files[name] = path
for name in sorted(files.keys()):
path = files[name]
matching = self.process_recommend_file(path)
if matching is not None:
return matching
return profile
# Do not make balancing on patched Python 2 interpreter (rhbz#1028122).