diff --git a/doc/manual/modules/performance/con_the-location-of-tuned-profiles.adoc b/doc/manual/modules/performance/con_the-location-of-tuned-profiles.adoc index e428861..678ff6a 100644 --- a/doc/manual/modules/performance/con_the-location-of-tuned-profiles.adoc +++ b/doc/manual/modules/performance/con_the-location-of-tuned-profiles.adoc @@ -6,10 +6,19 @@ *TuneD* stores profiles in the following directories: [filename]`/usr/lib/tuned/`:: -Distribution-specific profiles are stored in the directory. Each profile has its own directory. The profile consists of the main configuration file called `tuned.conf`, and optionally other files, for example helper scripts. +Distribution-specific profiles are stored in the [filename]`/usr/lib/tuned/` directory. Each profile has its own directory. The profile consists of the main configuration file called `tuned.conf`, and optionally other files, for example helper scripts. [filename]`/etc/tuned/`:: -If you need to customize a profile, copy the profile directory into the directory, which is used for custom profiles. If there are two profiles of the same name, the custom profile located in [filename]`/etc/tuned/` is used. +If you need to customize a profile, copy the profile directory into the [filename]`/etc/tuned/` directory, which is used for custom profiles, and then adjust it. If there is a system profile and a custom profile of the same name, the custom profile located in [filename]`/etc/tuned/` is used. + +.User-defined profile directories +==== +If you want to make TuneD load profiles from a directory other than [filename]`/usr/lib/tuned/` and [filename]`/etc/tuned/`, you can list it in [filename]`/etc/tuned/tuned-main.conf` as follows: +---- +profile_dirs=/usr/lib/tuned,/etc/tuned,/my/custom/profiles +---- +In this example, profiles are loaded also from [filename]`/my/custom/profiles/`. If two directories contain profiles with the same names, the one that is listed later takes precedence. +==== [role="_additional-resources"] .Additional resources diff --git a/tuned-adm.py b/tuned-adm.py index 712e7da..83ed569 100755 --- a/tuned-adm.py +++ b/tuned-adm.py @@ -124,10 +124,11 @@ if __name__ == "__main__": log_level = options.pop("loglevel") result = False + profile_dirs = config.get_list(consts.CFG_PROFILE_DIRS, consts.CFG_DEF_PROFILE_DIRS) dbus = config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON) try: - admin = tuned.admin.Admin(dbus, debug, asynco, timeout, log_level) + admin = tuned.admin.Admin(profile_dirs, dbus, debug, asynco, timeout, log_level) result = admin.action(action_name, **options) except: diff --git a/tuned-gui.py b/tuned-gui.py index da591e7..5a2b866 100755 --- a/tuned-gui.py +++ b/tuned-gui.py @@ -130,7 +130,7 @@ class Base(object): return self.manager = tuned.gtk.gui_profile_loader.GuiProfileLoader( - tuned.consts.LOAD_DIRECTORIES) + self.config.get_list(consts.CFG_PROFILE_DIRS, consts.CFG_DEF_PROFILE_DIRS)) self.plugin_loader = tuned.gtk.gui_plugin_loader.GuiPluginLoader() @@ -373,8 +373,8 @@ class Base(object): try: self.manager.remove_profile(profile, is_admin=self.is_admin) - except ManagerException: - self.error_dialog('failed to authorize', '') + except ManagerException as ex: + self.error_dialog('Removing profile failed', ex.__str__()) return for item in self.treestore_profiles: @@ -591,8 +591,8 @@ class Base(object): copied_profile.name = self.editing_profile_name + '-modified' try: self.manager.save_profile(copied_profile) - except ManagerException: - self.error_dialog('failed to authorize', '') + except ManagerException as ex: + self.error_dialog('Error saving profile', ex.__str__()) return else: if not TunedDialog('System profile can not be modified ' diff --git a/tuned-main.conf b/tuned-main.conf index 9cec833..86bca4d 100644 --- a/tuned-main.conf +++ b/tuned-main.conf @@ -82,3 +82,8 @@ log_file_max_size = 1MB # - not_on_exit: rollbacks are always performed on a profile # switch, but not on any kind of TuneD process exit # rollback = auto + +# Directories to search for profiles separated by , or ; +# In case of conflicts in profile names, the later directory +# takes precedence +# profile_dirs = /usr/lib/tuned,/etc/tuned diff --git a/tuned/admin/admin.py b/tuned/admin/admin.py index b9ef94b..8f8682f 100644 --- a/tuned/admin/admin.py +++ b/tuned/admin/admin.py @@ -15,15 +15,15 @@ import threading import logging class Admin(object): - def __init__(self, dbus = True, debug = False, asynco = False, - timeout = consts.ADMIN_TIMEOUT, + def __init__(self, profile_dirs, dbus = True, debug = False, + asynco = False, timeout = consts.ADMIN_TIMEOUT, log_level = logging.ERROR): self._dbus = dbus self._debug = debug self._async = asynco self._timeout = timeout self._cmd = commands(debug) - self._profiles_locator = profiles_locator(consts.LOAD_DIRECTORIES) + self._profiles_locator = profiles_locator(profile_dirs) self._daemon_action_finished = threading.Event() self._daemon_action_profile = "" self._daemon_action_result = True diff --git a/tuned/consts.py b/tuned/consts.py index 3749363..965068a 100644 --- a/tuned/consts.py +++ b/tuned/consts.py @@ -13,7 +13,7 @@ DBUS_INTERFACE = "com.redhat.tuned.control" DBUS_OBJECT = "/Tuned" DEFAULT_PROFILE = "balanced" DEFAULT_STORAGE_FILE = "/run/tuned/save.pickle" -LOAD_DIRECTORIES = ["/usr/lib/tuned", "/etc/tuned"] +SYSTEM_PROFILE_DIR = "/usr/lib/tuned" PERSISTENT_STORAGE_DIR = "/var/lib/tuned" PLUGIN_MAIN_UNIT_NAME = "main" # Magic section header because ConfigParser does not support "headerless" config @@ -122,6 +122,7 @@ CFG_UNIX_SOCKET_PERMISIONS = "unix_socket_permissions" CFG_UNIX_SOCKET_CONNECTIONS_BACKLOG = "connections_backlog" CFG_CPU_EPP_FLAG = "hwp_epp" CFG_ROLLBACK = "rollback" +CFG_PROFILE_DIRS = "profile_dirs" # no_daemon mode CFG_DEF_DAEMON = True @@ -171,6 +172,8 @@ CFG_DEF_UNIX_SOCKET_CONNECTIONS_BACKLOG = "1024" CFG_FUNC_UNIX_SOCKET_CONNECTIONS_BACKLOG = "getint" # default rollback strategy CFG_DEF_ROLLBACK = "auto" +# default profile directories +CFG_DEF_PROFILE_DIRS = [SYSTEM_PROFILE_DIR, "/etc/tuned"] PATH_CPU_DMA_LATENCY = "/dev/cpu_dma_latency" diff --git a/tuned/daemon/application.py b/tuned/daemon/application.py index 607f349..a7400cf 100644 --- a/tuned/daemon/application.py +++ b/tuned/daemon/application.py @@ -50,7 +50,7 @@ class Application(object): profile_factory = profiles.Factory() profile_merger = profiles.Merger() - profile_locator = profiles.Locator(consts.LOAD_DIRECTORIES) + profile_locator = profiles.Locator(self.config.get_list(consts.CFG_PROFILE_DIRS, consts.CFG_DEF_PROFILE_DIRS)) profile_loader = profiles.Loader(profile_locator, profile_factory, profile_merger, self.config, self.variables) self._daemon = daemon.Daemon(unit_manager, profile_loader, profile_name, self.config, self) diff --git a/tuned/gtk/gui_profile_loader.py b/tuned/gtk/gui_profile_loader.py index 00df17e..9f65ae0 100644 --- a/tuned/gtk/gui_profile_loader.py +++ b/tuned/gtk/gui_profile_loader.py @@ -60,7 +60,7 @@ class GuiProfileLoader(object): profilePath = self._locate_profile_path(profile_name) - if profilePath == tuned.consts.LOAD_DIRECTORIES[1]: + if profilePath != tuned.consts.SYSTEM_PROFILE_DIR: file_path = profilePath + '/' + profile_name + '/' + tuned.consts.PROFILE_FILE config_parser = ConfigParser(delimiters=('='), inline_comment_prefixes=('#'), strict=False) config_parser.optionxform = str @@ -127,7 +127,15 @@ class GuiProfileLoader(object): self._load_all_profiles() def save_profile(self, profile): - path = tuned.consts.LOAD_DIRECTORIES[1] + '/' + profile.name + # save the new profile to a non-system directory with the highest priority + path = None + for d in reversed(self.directories): + if d != tuned.consts.SYSTEM_PROFILE_DIR: + path = os.path.join(d, profile.name) + break + if path is None: + raise managerException.ManagerException('Cannot save profile to a system directory') + config = { 'main': collections.OrderedDict(), 'filename': path + '/' + tuned.consts.PROFILE_FILE, @@ -160,7 +168,7 @@ class GuiProfileLoader(object): raise managerException.ManagerException('Profile: ' + old_profile_name + ' is not in profiles') - path = tuned.consts.LOAD_DIRECTORIES[1] + '/' + profile.name + path = os.path.join(self._locate_profile_path(old_profile_name), profile.name) if old_profile_name != profile.name: self.remove_profile(old_profile_name, is_admin=is_admin) @@ -207,20 +215,11 @@ class GuiProfileLoader(object): + ' profile is stored in ' + profile_path) def is_profile_removable(self, profile_name): - - # profile is in /etc/profile - - profile_path = self._locate_profile_path(profile_name) - if profile_path == tuned.consts.LOAD_DIRECTORIES[1]: - return True - else: - return False + return not self.is_profile_factory(profile_name) def is_profile_factory(self, profile_name): - - # profile is in /usr/lib/tuned - - return not self.is_profile_removable(profile_name) + profile_path = self._locate_profile_path(profile_name) + return profile_path == tuned.consts.SYSTEM_PROFILE_DIR def _save_profile(self, config): ec = subprocess.call(['pkexec', sys.executable, tuned.gtk.gui_profile_saver.__file__ , json.dumps(config)])