Skip to content

Commit

Permalink
Merge pull request #609 from zacikpa/custom-profile-dir
Browse files Browse the repository at this point in the history
Add the option to specify custom directories with user-defined profiles
  • Loading branch information
yarda authored Apr 3, 2024
2 parents fcb2975 + 90ea23b commit 1fb7c80
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion tuned-adm.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,11 @@ def check_log_level(value):
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:
Expand Down
10 changes: 5 additions & 5 deletions tuned-gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def __init__(self):
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()

Expand Down Expand Up @@ -373,8 +373,8 @@ def execute_remove_profile(self, button):

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:
Expand Down Expand Up @@ -591,8 +591,8 @@ def execute_update_profile(self, data):
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 '
Expand Down
5 changes: 5 additions & 0 deletions tuned-main.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions tuned/admin/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
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
Expand Down
5 changes: 4 additions & 1 deletion tuned/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
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
Expand Down Expand Up @@ -122,6 +122,7 @@
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
Expand Down Expand Up @@ -171,6 +172,8 @@
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"

Expand Down
4 changes: 2 additions & 2 deletions tuned/daemon/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def __init__(self, profile_name = None, config = None):

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)
Expand Down Expand Up @@ -83,7 +83,7 @@ def attach_to_unix_socket(self):
raise TunedException("Unix socket interface is already initialized.")

self._unix_socket_exporter = exports.unix_socket.UnixSocketExporter(self.config.get(consts.CFG_UNIX_SOCKET_PATH),
self.config.get(consts.CFG_UNIX_SOCKET_SIGNAL_PATHS),
self.config.get_list(consts.CFG_UNIX_SOCKET_SIGNAL_PATHS),
self.config.get(consts.CFG_UNIX_SOCKET_OWNERSHIP),
self.config.get_int(consts.CFG_UNIX_SOCKET_PERMISIONS),
self.config.get_int(consts.CFG_UNIX_SOCKET_CONNECTIONS_BACKLOG))
Expand Down
2 changes: 1 addition & 1 deletion tuned/exports/unix_socket_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__(self, socket_path=consts.CFG_DEF_UNIX_SOCKET_PATH,

self._socket_path = socket_path
self._socket_object = None
self._socket_signal_paths = re.split(r",;", signal_paths) if signal_paths else []
self._socket_signal_paths = signal_paths
self._socket_signal_objects = []
self._ownership = [-1, -1]
if ownership:
Expand Down
29 changes: 14 additions & 15 deletions tuned/gtk/gui_profile_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def set_raw_profile(self, profile_name, config):

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
Expand Down Expand Up @@ -127,7 +127,15 @@ def _refresh_profiles(self):
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,
Expand Down Expand Up @@ -160,7 +168,7 @@ def update_profile(
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)
Expand Down Expand Up @@ -207,20 +215,11 @@ def remove_profile(self, profile_name, is_admin):
+ ' 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)])
Expand Down
9 changes: 9 additions & 0 deletions tuned/utils/global_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
import tuned.logs
from tuned.utils.config_parser import ConfigParser, Error
from tuned.exceptions import TunedException
Expand Down Expand Up @@ -77,6 +78,14 @@ def get_int(self, key, default = 0):
return int(i, 0)
return default

def get_list(self, key, default = []):
value = self._cfg.get(key, default)
if isinstance(value, list):
return value
if value.strip() == "":
return []
return [x.strip() for x in re.split(r",|;", value)]

def set(self, key, value):
self._cfg[key] = value

Expand Down

0 comments on commit 1fb7c80

Please sign in to comment.