Skip to content

Commit 1fb7c80

Browse files
authored
Merge pull request #609 from zacikpa/custom-profile-dir
Add the option to specify custom directories with user-defined profiles
2 parents fcb2975 + 90ea23b commit 1fb7c80

File tree

10 files changed

+56
-30
lines changed

10 files changed

+56
-30
lines changed

doc/manual/modules/performance/con_the-location-of-tuned-profiles.adoc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,19 @@
66
*TuneD* stores profiles in the following directories:
77

88
[filename]`/usr/lib/tuned/`::
9-
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.
9+
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.
1010

1111
[filename]`/etc/tuned/`::
12-
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.
12+
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.
13+
14+
.User-defined profile directories
15+
====
16+
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:
17+
----
18+
profile_dirs=/usr/lib/tuned,/etc/tuned,/my/custom/profiles
19+
----
20+
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.
21+
====
1322

1423
[role="_additional-resources"]
1524
.Additional resources

tuned-adm.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,11 @@ def check_log_level(value):
124124
log_level = options.pop("loglevel")
125125
result = False
126126

127+
profile_dirs = config.get_list(consts.CFG_PROFILE_DIRS, consts.CFG_DEF_PROFILE_DIRS)
127128
dbus = config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON)
128129

129130
try:
130-
admin = tuned.admin.Admin(dbus, debug, asynco, timeout, log_level)
131+
admin = tuned.admin.Admin(profile_dirs, dbus, debug, asynco, timeout, log_level)
131132

132133
result = admin.action(action_name, **options)
133134
except:

tuned-gui.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def __init__(self):
130130
return
131131

132132
self.manager = tuned.gtk.gui_profile_loader.GuiProfileLoader(
133-
tuned.consts.LOAD_DIRECTORIES)
133+
self.config.get_list(consts.CFG_PROFILE_DIRS, consts.CFG_DEF_PROFILE_DIRS))
134134

135135
self.plugin_loader = tuned.gtk.gui_plugin_loader.GuiPluginLoader()
136136

@@ -373,8 +373,8 @@ def execute_remove_profile(self, button):
373373

374374
try:
375375
self.manager.remove_profile(profile, is_admin=self.is_admin)
376-
except ManagerException:
377-
self.error_dialog('failed to authorize', '')
376+
except ManagerException as ex:
377+
self.error_dialog('Removing profile failed', ex.__str__())
378378
return
379379

380380
for item in self.treestore_profiles:
@@ -591,8 +591,8 @@ def execute_update_profile(self, data):
591591
copied_profile.name = self.editing_profile_name + '-modified'
592592
try:
593593
self.manager.save_profile(copied_profile)
594-
except ManagerException:
595-
self.error_dialog('failed to authorize', '')
594+
except ManagerException as ex:
595+
self.error_dialog('Error saving profile', ex.__str__())
596596
return
597597
else:
598598
if not TunedDialog('System profile can not be modified '

tuned-main.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,8 @@ log_file_max_size = 1MB
8282
# - not_on_exit: rollbacks are always performed on a profile
8383
# switch, but not on any kind of TuneD process exit
8484
# rollback = auto
85+
86+
# Directories to search for profiles separated by , or ;
87+
# In case of conflicts in profile names, the later directory
88+
# takes precedence
89+
# profile_dirs = /usr/lib/tuned,/etc/tuned

tuned/admin/admin.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
import logging
1616

1717
class Admin(object):
18-
def __init__(self, dbus = True, debug = False, asynco = False,
19-
timeout = consts.ADMIN_TIMEOUT,
18+
def __init__(self, profile_dirs, dbus = True, debug = False,
19+
asynco = False, timeout = consts.ADMIN_TIMEOUT,
2020
log_level = logging.ERROR):
2121
self._dbus = dbus
2222
self._debug = debug
2323
self._async = asynco
2424
self._timeout = timeout
2525
self._cmd = commands(debug)
26-
self._profiles_locator = profiles_locator(consts.LOAD_DIRECTORIES)
26+
self._profiles_locator = profiles_locator(profile_dirs)
2727
self._daemon_action_finished = threading.Event()
2828
self._daemon_action_profile = ""
2929
self._daemon_action_result = True

tuned/consts.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
DBUS_OBJECT = "/Tuned"
1414
DEFAULT_PROFILE = "balanced"
1515
DEFAULT_STORAGE_FILE = "/run/tuned/save.pickle"
16-
LOAD_DIRECTORIES = ["/usr/lib/tuned", "/etc/tuned"]
16+
SYSTEM_PROFILE_DIR = "/usr/lib/tuned"
1717
PERSISTENT_STORAGE_DIR = "/var/lib/tuned"
1818
PLUGIN_MAIN_UNIT_NAME = "main"
1919
# Magic section header because ConfigParser does not support "headerless" config
@@ -122,6 +122,7 @@
122122
CFG_UNIX_SOCKET_CONNECTIONS_BACKLOG = "connections_backlog"
123123
CFG_CPU_EPP_FLAG = "hwp_epp"
124124
CFG_ROLLBACK = "rollback"
125+
CFG_PROFILE_DIRS = "profile_dirs"
125126

126127
# no_daemon mode
127128
CFG_DEF_DAEMON = True
@@ -171,6 +172,8 @@
171172
CFG_FUNC_UNIX_SOCKET_CONNECTIONS_BACKLOG = "getint"
172173
# default rollback strategy
173174
CFG_DEF_ROLLBACK = "auto"
175+
# default profile directories
176+
CFG_DEF_PROFILE_DIRS = [SYSTEM_PROFILE_DIR, "/etc/tuned"]
174177

175178
PATH_CPU_DMA_LATENCY = "/dev/cpu_dma_latency"
176179

tuned/daemon/application.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def __init__(self, profile_name = None, config = None):
5050

5151
profile_factory = profiles.Factory()
5252
profile_merger = profiles.Merger()
53-
profile_locator = profiles.Locator(consts.LOAD_DIRECTORIES)
53+
profile_locator = profiles.Locator(self.config.get_list(consts.CFG_PROFILE_DIRS, consts.CFG_DEF_PROFILE_DIRS))
5454
profile_loader = profiles.Loader(profile_locator, profile_factory, profile_merger, self.config, self.variables)
5555

5656
self._daemon = daemon.Daemon(unit_manager, profile_loader, profile_name, self.config, self)
@@ -83,7 +83,7 @@ def attach_to_unix_socket(self):
8383
raise TunedException("Unix socket interface is already initialized.")
8484

8585
self._unix_socket_exporter = exports.unix_socket.UnixSocketExporter(self.config.get(consts.CFG_UNIX_SOCKET_PATH),
86-
self.config.get(consts.CFG_UNIX_SOCKET_SIGNAL_PATHS),
86+
self.config.get_list(consts.CFG_UNIX_SOCKET_SIGNAL_PATHS),
8787
self.config.get(consts.CFG_UNIX_SOCKET_OWNERSHIP),
8888
self.config.get_int(consts.CFG_UNIX_SOCKET_PERMISIONS),
8989
self.config.get_int(consts.CFG_UNIX_SOCKET_CONNECTIONS_BACKLOG))

tuned/exports/unix_socket_exporter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __init__(self, socket_path=consts.CFG_DEF_UNIX_SOCKET_PATH,
3030

3131
self._socket_path = socket_path
3232
self._socket_object = None
33-
self._socket_signal_paths = re.split(r",;", signal_paths) if signal_paths else []
33+
self._socket_signal_paths = signal_paths
3434
self._socket_signal_objects = []
3535
self._ownership = [-1, -1]
3636
if ownership:

tuned/gtk/gui_profile_loader.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def set_raw_profile(self, profile_name, config):
6060

6161
profilePath = self._locate_profile_path(profile_name)
6262

63-
if profilePath == tuned.consts.LOAD_DIRECTORIES[1]:
63+
if profilePath != tuned.consts.SYSTEM_PROFILE_DIR:
6464
file_path = profilePath + '/' + profile_name + '/' + tuned.consts.PROFILE_FILE
6565
config_parser = ConfigParser(delimiters=('='), inline_comment_prefixes=('#'), strict=False)
6666
config_parser.optionxform = str
@@ -127,7 +127,15 @@ def _refresh_profiles(self):
127127
self._load_all_profiles()
128128

129129
def save_profile(self, profile):
130-
path = tuned.consts.LOAD_DIRECTORIES[1] + '/' + profile.name
130+
# save the new profile to a non-system directory with the highest priority
131+
path = None
132+
for d in reversed(self.directories):
133+
if d != tuned.consts.SYSTEM_PROFILE_DIR:
134+
path = os.path.join(d, profile.name)
135+
break
136+
if path is None:
137+
raise managerException.ManagerException('Cannot save profile to a system directory')
138+
131139
config = {
132140
'main': collections.OrderedDict(),
133141
'filename': path + '/' + tuned.consts.PROFILE_FILE,
@@ -160,7 +168,7 @@ def update_profile(
160168
raise managerException.ManagerException('Profile: '
161169
+ old_profile_name + ' is not in profiles')
162170

163-
path = tuned.consts.LOAD_DIRECTORIES[1] + '/' + profile.name
171+
path = os.path.join(self._locate_profile_path(old_profile_name), profile.name)
164172

165173
if old_profile_name != profile.name:
166174
self.remove_profile(old_profile_name, is_admin=is_admin)
@@ -207,20 +215,11 @@ def remove_profile(self, profile_name, is_admin):
207215
+ ' profile is stored in ' + profile_path)
208216

209217
def is_profile_removable(self, profile_name):
210-
211-
# profile is in /etc/profile
212-
213-
profile_path = self._locate_profile_path(profile_name)
214-
if profile_path == tuned.consts.LOAD_DIRECTORIES[1]:
215-
return True
216-
else:
217-
return False
218+
return not self.is_profile_factory(profile_name)
218219

219220
def is_profile_factory(self, profile_name):
220-
221-
# profile is in /usr/lib/tuned
222-
223-
return not self.is_profile_removable(profile_name)
221+
profile_path = self._locate_profile_path(profile_name)
222+
return profile_path == tuned.consts.SYSTEM_PROFILE_DIR
224223

225224
def _save_profile(self, config):
226225
ec = subprocess.call(['pkexec', sys.executable, tuned.gtk.gui_profile_saver.__file__ , json.dumps(config)])

tuned/utils/global_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
import tuned.logs
23
from tuned.utils.config_parser import ConfigParser, Error
34
from tuned.exceptions import TunedException
@@ -77,6 +78,14 @@ def get_int(self, key, default = 0):
7778
return int(i, 0)
7879
return default
7980

81+
def get_list(self, key, default = []):
82+
value = self._cfg.get(key, default)
83+
if isinstance(value, list):
84+
return value
85+
if value.strip() == "":
86+
return []
87+
return [x.strip() for x in re.split(r",|;", value)]
88+
8089
def set(self, key, value):
8190
self._cfg[key] = value
8291

0 commit comments

Comments
 (0)