diff --git a/profiles/atomic-guest/tuned.conf b/profiles/atomic-guest/tuned.conf index aff05f2..6691f1d 100644 --- a/profiles/atomic-guest/tuned.conf +++ b/profiles/atomic-guest/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize virtual guests based on the Atomic variant include=virtual-guest [selinux] diff --git a/profiles/atomic-host/tuned.conf b/profiles/atomic-host/tuned.conf index ad223bd..0010077 100644 --- a/profiles/atomic-host/tuned.conf +++ b/profiles/atomic-host/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize bare metal systems running the Atomic variant include=throughput-performance [selinux] diff --git a/profiles/balanced/tuned.conf b/profiles/balanced/tuned.conf index a53ef55..2d61775 100644 --- a/profiles/balanced/tuned.conf +++ b/profiles/balanced/tuned.conf @@ -2,6 +2,9 @@ # tuned configuration # +[main] +summary=General non-specialized tuned profile + [cpu] governor=conservative energy_perf_bias=normal diff --git a/profiles/default/tuned.conf b/profiles/default/tuned.conf index 3e6e3bb..ebdd412 100644 --- a/profiles/default/tuned.conf +++ b/profiles/default/tuned.conf @@ -2,6 +2,9 @@ # tuned configuration # +[main] +summary=Legacy default tuned profile + [cpu] [disk] diff --git a/profiles/desktop-powersave/tuned.conf b/profiles/desktop-powersave/tuned.conf index 7e80a4d..d0171d9 100644 --- a/profiles/desktop-powersave/tuned.conf +++ b/profiles/desktop-powersave/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optmize for the desktop use-case with power saving. include=server-powersave [video] diff --git a/profiles/desktop/tuned.conf b/profiles/desktop/tuned.conf index 95de98b..4b47ce6 100644 --- a/profiles/desktop/tuned.conf +++ b/profiles/desktop/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optmize for the desktop use-case include=balanced [sysctl] diff --git a/profiles/enterprise-storage/tuned.conf b/profiles/enterprise-storage/tuned.conf index 0863334..9f0d143 100644 --- a/profiles/enterprise-storage/tuned.conf +++ b/profiles/enterprise-storage/tuned.conf @@ -3,4 +3,5 @@ # [main] +summary=Legacy profile for RHEL6. For RHEL7, please use throughput-performance profile. include=throughput-performance diff --git a/profiles/laptop-ac-powersave/tuned.conf b/profiles/laptop-ac-powersave/tuned.conf index 691047c..8088760 100644 --- a/profiles/laptop-ac-powersave/tuned.conf +++ b/profiles/laptop-ac-powersave/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for laptop with power savings include=desktop-powersave [script] diff --git a/profiles/laptop-battery-powersave/tuned.conf b/profiles/laptop-battery-powersave/tuned.conf index 0fd2542..a43f089 100644 --- a/profiles/laptop-battery-powersave/tuned.conf +++ b/profiles/laptop-battery-powersave/tuned.conf @@ -3,4 +3,5 @@ # [main] +summary=Optimize laptop profile with more aggressive power saving include=powersave diff --git a/profiles/latency-performance/tuned.conf b/profiles/latency-performance/tuned.conf index 04abfbe..054c554 100644 --- a/profiles/latency-performance/tuned.conf +++ b/profiles/latency-performance/tuned.conf @@ -2,6 +2,9 @@ # tuned configuration # +[main] +summary=Optimize for deterministic performance at the cost of increased power consumption + [cpu] force_latency=1 governor=performance diff --git a/profiles/network-latency/tuned.conf b/profiles/network-latency/tuned.conf index f4ec2de..a2ca91a 100644 --- a/profiles/network-latency/tuned.conf +++ b/profiles/network-latency/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for deterministic performance at the cost of increased power consumption, focused on low latency network performance include=latency-performance [vm] diff --git a/profiles/network-throughput/tuned.conf b/profiles/network-throughput/tuned.conf index fbc2b72..e2703ee 100644 --- a/profiles/network-throughput/tuned.conf +++ b/profiles/network-throughput/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for streaming network throughput. Generally only necessary on older CPUs or 40G+ networks. include=throughput-performance [sysctl] diff --git a/profiles/oracle/tuned.conf b/profiles/oracle/tuned.conf index e047fa1..0e4cdfe 100644 --- a/profiles/oracle/tuned.conf +++ b/profiles/oracle/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for Oracle RDBMS include=throughput-performance [sysctl] diff --git a/profiles/powersave/tuned.conf b/profiles/powersave/tuned.conf index 589f194..d74200b 100644 --- a/profiles/powersave/tuned.conf +++ b/profiles/powersave/tuned.conf @@ -2,6 +2,9 @@ # tuned configuration # +[main] +summary=Optimize for low power consumption + [cpu] governor=ondemand energy_perf_bias=powersave diff --git a/profiles/realtime-virtual-guest/tuned.conf b/profiles/realtime-virtual-guest/tuned.conf index eaaf4ab..4ccfc6f 100644 --- a/profiles/realtime-virtual-guest/tuned.conf +++ b/profiles/realtime-virtual-guest/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for realtime workloads running within a KVM guest include=realtime [variables] diff --git a/profiles/realtime-virtual-host/tuned.conf b/profiles/realtime-virtual-host/tuned.conf index 51c182e..0106590 100644 --- a/profiles/realtime-virtual-host/tuned.conf +++ b/profiles/realtime-virtual-host/tuned.conf @@ -20,6 +20,7 @@ [main] +summary=Optimize for KVM guests running realtime workloads include=realtime [variables] diff --git a/profiles/realtime/tuned.conf b/profiles/realtime/tuned.conf index 4273ad2..ed50213 100644 --- a/profiles/realtime/tuned.conf +++ b/profiles/realtime/tuned.conf @@ -4,6 +4,7 @@ # https://docs.redhat.com [main] +summary=Optimize for realtime workloads include = network-latency [variables] diff --git a/profiles/sap-hana-vmware/tuned.conf b/profiles/sap-hana-vmware/tuned.conf index 71fbccf..1ec933b 100644 --- a/profiles/sap-hana-vmware/tuned.conf +++ b/profiles/sap-hana-vmware/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for SAP running inside a VMware guest include=throughput-performance [cpu] diff --git a/profiles/sap-hana/tuned.conf b/profiles/sap-hana/tuned.conf index 403a6a4..9b94c54 100644 --- a/profiles/sap-hana/tuned.conf +++ b/profiles/sap-hana/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for SAP include=throughput-performance [cpu] diff --git a/profiles/sap-netweaver/tuned.conf b/profiles/sap-netweaver/tuned.conf index 95326f3..6190051 100644 --- a/profiles/sap-netweaver/tuned.conf +++ b/profiles/sap-netweaver/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for SAP NetWeaver include=throughput-performance [sysctl] diff --git a/profiles/server-powersave/tuned.conf b/profiles/server-powersave/tuned.conf index 47b60a9..d67774d 100644 --- a/profiles/server-powersave/tuned.conf +++ b/profiles/server-powersave/tuned.conf @@ -2,6 +2,9 @@ # tuned configuration # +[main] +summary=Optimize for server power savings + [cpu] [disk] diff --git a/profiles/spindown-disk/tuned.conf b/profiles/spindown-disk/tuned.conf index ec9f2dd..6b54e53 100644 --- a/profiles/spindown-disk/tuned.conf +++ b/profiles/spindown-disk/tuned.conf @@ -16,6 +16,9 @@ # Bluetooth will be switch off. # Wifi will be switch into power safe mode. +[main] +summary=Optimize for power saving by spinning-down rotational disks + [disk] apm=128 spindown=6 diff --git a/profiles/throughput-performance/tuned.conf b/profiles/throughput-performance/tuned.conf index 4d6c607..62776c1 100644 --- a/profiles/throughput-performance/tuned.conf +++ b/profiles/throughput-performance/tuned.conf @@ -2,6 +2,9 @@ # tuned configuration # +[main] +summary=Broadly applicable tuning that provides excellent performance across a variety of common server workloads. This is the default profile for RHEL7. + [cpu] governor=performance energy_perf_bias=performance diff --git a/profiles/virtual-guest/tuned.conf b/profiles/virtual-guest/tuned.conf index 0d802ef..89fe021 100644 --- a/profiles/virtual-guest/tuned.conf +++ b/profiles/virtual-guest/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for running inside a virtual guest. include=throughput-performance [sysctl] diff --git a/profiles/virtual-host/tuned.conf b/profiles/virtual-host/tuned.conf index c508cdd..2ef5031 100644 --- a/profiles/virtual-host/tuned.conf +++ b/profiles/virtual-host/tuned.conf @@ -3,6 +3,7 @@ # [main] +summary=Optimize for running KVM guests include=throughput-performance [sysctl] diff --git a/tuned-adm.py b/tuned-adm.py index 74042db..029a076 100755 --- a/tuned-adm.py +++ b/tuned-adm.py @@ -47,6 +47,10 @@ if __name__ == "__main__": parser_profile.set_defaults(action="profile") parser_profile.add_argument("profiles", metavar="profile", type=str, nargs="+", help="profile name") + parser_profile_info = subparsers.add_parser("profile_info", help="show information/description of given profile or current profile if no profile is specified") + parser_profile_info.set_defaults(action="profile_info") + parser_profile_info.add_argument("profile", metavar="profile", type=str, nargs="?", default="", help="profile name, current profile if not specified") + if config.get(consts.CFG_RECOMMEND_COMMAND, consts.CFG_DEF_RECOMMEND_COMMAND): parser_off = subparsers.add_parser("recommend", help="recommend profile") parser_off.set_defaults(action="recommend_profile") diff --git a/tuned/admin/admin.py b/tuned/admin/admin.py index 1180785..c8af967 100644 --- a/tuned/admin/admin.py +++ b/tuned/admin/admin.py @@ -11,6 +11,7 @@ class Admin(object): self._controller = controller self._debug = debug self._cmd = commands(debug) + self._profiles_locator = profiles_locator(consts.LOAD_DIRECTORIES) def _error(self, message): print >>sys.stderr, message @@ -28,18 +29,27 @@ class Admin(object): no_dbus = self._controller is None if not no_dbus: try: - profile_names = self._controller.profiles() + profile_names = self._controller.profiles2() except TunedAdminDBusException as e: - self._error(e) - no_dbus = True + # fallback to older API + try: + profile_names = self._controller.profiles() + except TunedAdminDBusException as e: + self._error(e) + no_dbus = True + profile_names = map(lambda profile:(profile, ""), profile_names) if no_dbus: - profile_names = profiles_locator(consts.LOAD_DIRECTORIES).get_known_names() + profile_names = self._profiles_locator.get_known_names_summary() print "Available profiles:" for profile in profile_names: - print "- %s" % profile + if profile[1] is not None and profile[1] != "": + print self._cmd.align_str("- %s" % profile[0], 30, "- %s" % profile[1]) + else: + print "- %s" % profile[0] self.active() - def active(self): + def _get_active_profile(self): + profile_name = None no_dbus = self._controller is None if not no_dbus: try: @@ -49,7 +59,39 @@ class Admin(object): no_dbus = True if no_dbus: profile_name = str.strip(self._cmd.read_file(consts.ACTIVE_PROFILE_FILE, None)) - if profile_name is not None and profile_name != "": + if profile_name == "": + profile_name = None + return profile_name + + def profile_info(self, profile = ""): + no_dbus = self._controller is None + if profile == "": + profile = self._get_active_profile() + if not no_dbus: + try: + ret = self._controller.profile_info(profile) + except TunedAdminDBusException as e: + self._error(e) + no_dbus = True + if no_dbus: + ret = self._profiles_locator.get_profile_attrs(profile, [consts.PROFILE_ATTR_SUMMARY, consts.PROFILE_ATTR_DESCRIPTION], ["", ""]) + if ret[0] == True: + print "Profile name:" + print ret[1] + print + print "Profile summary:" + print ret[2] + print + print "Profile description:" + print ret[3] + return True + else: + print "Unable to get information about profile '%s'" % profile + return False + + def active(self): + profile_name = self._get_active_profile() + if profile_name is not None: if self._controller is not None and self._tuned_is_running(): print "Current active profile: %s" % profile_name else: @@ -73,7 +115,7 @@ class Admin(object): self._error(e) no_dbus = True if no_dbus: - if profile_name in profiles_locator(consts.LOAD_DIRECTORIES).get_known_names(): + 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, out) = self._cmd.execute(["service", "tuned", "restart"]) diff --git a/tuned/admin/dbus_controller.py b/tuned/admin/dbus_controller.py index 7592fc2..8385b58 100644 --- a/tuned/admin/dbus_controller.py +++ b/tuned/admin/dbus_controller.py @@ -44,6 +44,12 @@ class DBusController(object): def profiles(self): return self._call("profiles") + def profiles2(self): + return self._call("profiles2") + + def profile_info(self, profile_name): + return self._call("profile_info", profile_name) + def active_profile(self): return self._call("active_profile") diff --git a/tuned/consts.py b/tuned/consts.py index fe3b588..aaf3d32 100644 --- a/tuned/consts.py +++ b/tuned/consts.py @@ -57,6 +57,10 @@ CFG_DEF_UPDATE_INTERVAL = 10 # recommend command availability CFG_DEF_RECOMMEND_COMMAND = True +# profile attributes which can be specified in the main section +PROFILE_ATTR_SUMMARY = "summary" +PROFILE_ATTR_DESCRIPTION = "description" + STR_VERIFY_PROFILE_DEVICE_VALUE_OK = "verify: passed: device %s: %s = %s" STR_VERIFY_PROFILE_VALUE_OK = "verify: passed: %s = %s" STR_VERIFY_PROFILE_OK = "verify: passed: %s" diff --git a/tuned/daemon/controller.py b/tuned/daemon/controller.py index a7b657a..7508a53 100644 --- a/tuned/daemon/controller.py +++ b/tuned/daemon/controller.py @@ -106,6 +106,16 @@ class Controller(tuned.exports.interfaces.ExportableInterface): def profiles(self): return self._daemon.profile_loader.profile_locator.get_known_names() + @exports.export("", "a(ss)") + def profiles2(self): + return self._daemon.profile_loader.profile_locator.get_known_names_summary() + + @exports.export("s", "(bsss)") + def profile_info(self, profile_name): + if profile_name is None or profile_name == "": + profile_name = self.active_profile() + return tuple(self._daemon.profile_loader.profile_locator.get_profile_attrs(profile_name, [consts.PROFILE_ATTR_SUMMARY, consts.PROFILE_ATTR_DESCRIPTION], [""])) + @exports.export("", "s") def recommend_profile(self): return self._cmd.recommend_profile(hardcoded = not self._global_config.get_bool(consts.CFG_RECOMMEND_COMMAND, consts.CFG_DEF_RECOMMEND_COMMAND)) diff --git a/tuned/profiles/locator.py b/tuned/profiles/locator.py index 1bfa99d..c04153d 100644 --- a/tuned/profiles/locator.py +++ b/tuned/profiles/locator.py @@ -1,4 +1,6 @@ import os +import tuned.consts as consts +from configobj import ConfigObj, ConfigObjError class Locator(object): """ @@ -23,7 +25,8 @@ class Locator(object): def get_config(self, profile_name, skip_files=None): for dir_name in reversed(self._load_directories): - config_file = self._get_config_filename(dir_name, profile_name) + # basename is protection not to get out of the path + config_file = self._get_config_filename(dir_name, os.path.basename(profile_name)) if skip_files is not None and config_file in skip_files: continue @@ -33,7 +36,57 @@ class Locator(object): return None - def get_known_names(self): + def check_profile_name_format(self, profile_name): + return profile_name is not None and profile_name != "" and "/" not in profile_name + + def parse_config(self, profile_name): + if not self.check_profile_name_format(profile_name): + return None + config_file = self.get_config(profile_name) + if config_file is None: + return None + try: + return ConfigObj(config_file, list_values = False, interpolation = False) + except (IOError, OSError, ConfigObjError) as e: + return None + + # Get profile attributes (e.g. summary, description), attrs is list of requested attributes, + # if it is not list it is converted to list, defvals is list of default values to return if + # attribute is not found, it is also converted to list if it is not list. + # Returns list of the following format [status, profile_name, attr1_val, attr2_val, ...], + # status is boolean. + def get_profile_attrs(self, profile_name, attrs, defvals = None): + # check types + try: + attrs_len = len(attrs) + except TypeError: + attrs = [attrs] + attrs_len = 1 + try: + defvals_len = len(defvals) + except TypeError: + defvals = [defvals] + defvals_len = 1 + # Extend defvals if needed, last value is used for extension + if defvals_len < attrs_len: + defvals = defvals + ([defvals[-1]] * (attrs_len - defvals_len)) + config = self.parse_config(profile_name) + if config is None: + return [False, "", "", ""] + if config.has_key("main"): + d = config["main"] + else: + d = dict() + vals = [True, profile_name] + for (attr, defval) in zip(attrs, defvals): + if attr == "" or attr is None: + vals[0] = False + vals = vals + [""] + else: + vals = vals + [d.get(attr, defval)] + return vals + + def list_profiles(self): profiles = set() for dir_name in self._load_directories: try: @@ -43,5 +96,10 @@ class Locator(object): profiles.add(profile_name) except OSError: pass + return profiles - return sorted(list(profiles)) + def get_known_names(self): + return sorted(self.list_profiles()) + + def get_known_names_summary(self): + return map(lambda profile: (profile, self.get_profile_attrs(profile, [consts.PROFILE_ATTR_SUMMARY], [""])[2]), sorted(self.list_profiles())) diff --git a/tuned/utils/commands.py b/tuned/utils/commands.py index 2392a89..17375cf 100644 --- a/tuned/utils/commands.py +++ b/tuned/utils/commands.py @@ -33,6 +33,10 @@ class commands: def unquote(self, v): return re.sub("^\"(.*)\"$", r"\1", v) + # add spaces to align s2 to pos, returns resulting string: s1 + spaces + s2 + def align_str(self, s1, pos, s2): + return s1 + " " * (pos - len(s1)) + s2 + # convert dictionary 'd' to flat list and return it # it uses sort on the dictionary items to return consistent results # for directories with different inserte/delete history