From bc8ffcfb2396fae8f015e263d0158bbc65e58e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Thu, 3 Aug 2017 14:58:43 +0200 Subject: [PATCH 1/3] Introduce the concept of manually vs automatically set profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- com.redhat.tuned.policy | 10 ++++++ tuned-adm.py | 3 ++ tuned/admin/admin.py | 61 ++++++++++++++++++++++++++-------- tuned/admin/dbus_controller.py | 3 ++ tuned/consts.py | 5 +++ tuned/daemon/controller.py | 25 +++++++++----- tuned/daemon/daemon.py | 55 +++++++++++++++++++++--------- 7 files changed, 125 insertions(+), 37 deletions(-) diff --git a/com.redhat.tuned.policy b/com.redhat.tuned.policy index 1f9e6a7..1db1bf2 100644 --- a/com.redhat.tuned.policy +++ b/com.redhat.tuned.policy @@ -117,6 +117,16 @@ + + Enable automatic profile selection mode + Authentication is required to change profile selection mode + + auth_admin + auth_admin + yes + + + Verify Tuned profile Authentication is required to verify Tuned profile diff --git a/tuned-adm.py b/tuned-adm.py index 9dc6f42..c56dc68 100755 --- a/tuned-adm.py +++ b/tuned-adm.py @@ -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) diff --git a/tuned/admin/admin.py b/tuned/admin/admin.py index 040f5cd..b751ff0 100644 --- a/tuned/admin/admin.py +++ b/tuned/admin/admin.py @@ -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) diff --git a/tuned/admin/dbus_controller.py b/tuned/admin/dbus_controller.py index 2721ecb..322b8c0 100644 --- a/tuned/admin/dbus_controller.py +++ b/tuned/admin/dbus_controller.py @@ -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") diff --git a/tuned/consts.py b/tuned/consts.py index d557608..467ddfa 100644 --- a/tuned/consts.py +++ b/tuned/consts.py @@ -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" diff --git a/tuned/daemon/controller.py b/tuned/daemon/controller.py index 5addcee..d626bf5 100644 --- a/tuned/daemon/controller.py +++ b/tuned/daemon/controller.py @@ -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") diff --git a/tuned/daemon/daemon.py b/tuned/daemon/daemon.py index 8432294..55f66d8 100644 --- a/tuned/daemon/daemon.py +++ b/tuned/daemon/daemon.py @@ -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 From cd655df597d7c10171036f4788530cd86821b766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Thu, 3 Aug 2017 15:18:21 +0200 Subject: [PATCH 2/3] Add 'tuned-adm profile_mode' command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement a new 'tuned-adm profile_mode' command, which prints the current profile mode - automatic vs. manual. Related: rhbz#1459146 Signed-off-by: Ondřej Lysoněk --- com.redhat.tuned.policy | 10 ++++++++++ tuned-adm.py | 3 +++ tuned/admin/admin.py | 26 +++++++++++++++++++++++++- tuned/admin/dbus_controller.py | 3 +++ tuned/daemon/controller.py | 11 ++++++++++- tuned/daemon/daemon.py | 5 +++++ 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/com.redhat.tuned.policy b/com.redhat.tuned.policy index 1db1bf2..b34fdab 100644 --- a/com.redhat.tuned.policy +++ b/com.redhat.tuned.policy @@ -17,6 +17,16 @@ + + Show current profile selection mode + Authentication is required to show current profile selection mode + + yes + yes + yes + + + Disable Tuned Authentication is required to disable Tuned diff --git a/tuned-adm.py b/tuned-adm.py index c56dc68..a5b6b39 100755 --- a/tuned-adm.py +++ b/tuned-adm.py @@ -74,6 +74,9 @@ if __name__ == "__main__": 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) diff --git a/tuned/admin/admin.py b/tuned/admin/admin.py index b751ff0..fd96b0a 100644 --- a/tuned/admin/admin.py +++ b/tuned/admin/admin.py @@ -122,6 +122,19 @@ class Admin(object): 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:") @@ -166,7 +179,18 @@ class Admin(object): return True return self._print_profile_name(profile_name) - # TODO action profile mode - auto/manual + 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: diff --git a/tuned/admin/dbus_controller.py b/tuned/admin/dbus_controller.py index 322b8c0..fa46a0d 100644 --- a/tuned/admin/dbus_controller.py +++ b/tuned/admin/dbus_controller.py @@ -99,6 +99,9 @@ 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") diff --git a/tuned/daemon/controller.py b/tuned/daemon/controller.py index d626bf5..087380b 100644 --- a/tuned/daemon/controller.py +++ b/tuned/daemon/controller.py @@ -128,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 == "": @@ -135,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, True, save_instantly=True) + self._daemon.set_profile(None, None, save_instantly=True) return True @exports.export("", "b") diff --git a/tuned/daemon/daemon.py b/tuned/daemon/daemon.py index 55f66d8..4292575 100644 --- a/tuned/daemon/daemon.py +++ b/tuned/daemon/daemon.py @@ -90,6 +90,11 @@ class Daemon(object): 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 From fde8e806a04ccb00edb97ab97321a3777b5ea094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Lyson=C4=9Bk?= Date: Wed, 9 Aug 2017 14:53:55 +0200 Subject: [PATCH 3/3] Implement recommend.d functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Configuration files for the recommend functionality can now be added to the /etc/tuned/recommend.d and /usr/lib/tuned/recommend.d directories. The format is the same as in /etc/tuned/recommend.conf. The file /etc/tuned/recommend.conf takes precedence over the files in the recommend.d directories. The files in the /etc/tuned/recommend.d directory are merged with those in /usr/lib/tuned/recommend.d (in case of name conflict the file from /etc/tuned/recommend.d is used) and are evaluated in alphabetical order. The first file containing a matching entry is used. The /etc/tuned/recommend.d directory is meant mainly for use by administrators. Third-party packages should install recommend configuration files in /usr/lib/tuned/recommend.d. Also, the file /usr/lib/tuned/recommend.conf was moved to /usr/lib/tuned/recommend.d/50-tuned-recommend.conf. Resolves: rhbz#1459146 Signed-off-by: Ondřej Lysoněk --- Makefile | 4 +- tuned/consts.py | 3 +- tuned/utils/commands.py | 82 ++++++++++++++++++++++++++--------------- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index e3da864..ee63e91 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/tuned/consts.py b/tuned/consts.py index 467ddfa..aa3f38a 100644 --- a/tuned/consts.py +++ b/tuned/consts.py @@ -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 diff --git a/tuned/utils/commands.py b/tuned/utils/commands.py index d2d2951..a876a86 100644 --- a/tuned/utils/commands.py +++ b/tuned/utils/commands.py @@ -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).