Skip to content

Commit

Permalink
Expose TuneD API to the Unix Domain Socket.
Browse files Browse the repository at this point in the history
TuneD is listening on paths specified in config in option unix_socket_paths and send signals to paths in option unix_socket_signal_paths.
Example call:

printf '[{"jsonrpc": "2.0", "method": "active_profile", "id": 1}, 1]' | sudo nc -U /run/tuned/tuned.sock
printf '{"jsonrpc": "2.0", "method": "switch_profile", "params": {"profile_name": "balanced"}, "id": 1}' | sudo nc -U /run/tuned/tuned.sock

This PR also introduce possibility to disable dbus API in main TuneD config.

Resolves: rhbz#2113900

Signed-off-by: Jan Zerdik <jzerdik@redhat.com>
  • Loading branch information
Jan Zerdik committed Feb 1, 2023
1 parent 420267f commit 4265813
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 10 deletions.
24 changes: 24 additions & 0 deletions tuned-main.conf
Expand Up @@ -49,3 +49,27 @@ log_file_max_size = 1MB
# It can be used to force tuning for specific architecture.
# If commented, "/proc/cpuinfo" will be read to fill its content.
# cpuinfo_string = Intel

# Enable TuneD listening on dbus
# enable_dbus = 1

# Enable TuneD listening on unix domain socket
# enable_unix_socket = 1

# Path to socket for TuneD to listen
# Existing files on given path will be removed
# unix_socket_path = /run/tuned/tuned.sock

# Paths to sockets for TuneD to send signals to separated by , or ;
# unix_socket_signal_paths =

# Default unix socket ownership
# Can be set as id or name, -1 or non-existing name leaves unchanged
# unix_socket_ownership = -1 -1

# Permissions for listening sockets
# unix_socket_permissions = 0o600

# Size of connections backlog for listen function on socket
# Higher value allows to process requests from more clients
# connections_backlog = 1024
17 changes: 15 additions & 2 deletions tuned.py
Expand Up @@ -41,6 +41,7 @@ def error(message):
parser.add_argument("--log", "-l", nargs = "?", const = consts.LOG_FILE, help = "log to file, default file: " + consts.LOG_FILE)
parser.add_argument("--pid", "-P", nargs = "?", const = consts.PID_FILE, help = "write PID file, default file: " + consts.PID_FILE)
parser.add_argument("--no-dbus", action = "store_true", help = "do not attach to DBus")
parser.add_argument("--no-socket", action = "store_true", help = "do not attach to socket")
parser.add_argument("--profile", "-p", action = "store", type=str, metavar = "name", help = "tuning profile to be activated")
parser.add_argument('--version', "-v", action = "version", version = "%%(prog)s %s.%s.%s" % (ver.TUNED_VERSION_MAJOR, ver.TUNED_VERSION_MINOR, ver.TUNED_VERSION_PATCH))
args = parser.parse_args(sys.argv[1:])
Expand All @@ -67,13 +68,25 @@ def error(message):

app = tuned.daemon.Application(args.profile, config)

# no daemon mode doesn't need DBus
if not config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON):
# no daemon mode doesn't need DBus or if disabled in config
if not config.get(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON) \
or not config.get_bool(consts.CFG_ENABLE_DBUS, consts.CFG_DEF_ENABLE_DBUS):
args.no_dbus = True

# no daemon mode doesn't need sockets or if disabled in config
if not config.get(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON) \
or not config.get_bool(consts.CFG_ENABLE_UNIX_SOCKET, consts.CFG_DEF_ENABLE_UNIX_SOCKET):
args.no_socket = True

if not args.no_dbus:
app.attach_to_dbus(consts.DBUS_BUS, consts.DBUS_OBJECT, consts.DBUS_INTERFACE)

if not args.no_socket:
app.attach_to_unix_socket()

if not args.no_dbus or not args.no_socket:
app.register_controller()

# always write PID file
if args.pid is None:
args.pid = consts.PID_FILE
Expand Down
2 changes: 1 addition & 1 deletion tuned/admin/admin.py
Expand Up @@ -35,7 +35,7 @@ def __init__(self, dbus = True, debug = False, asynco = False,
if self._dbus:
self._controller = tuned.admin.DBusController(consts.DBUS_BUS, consts.DBUS_INTERFACE, consts.DBUS_OBJECT, debug)
try:
self._controller.set_signal_handler(consts.DBUS_SIGNAL_PROFILE_CHANGED, self._signal_profile_changed_cb)
self._controller.set_signal_handler(consts.SIGNAL_PROFILE_CHANGED, self._signal_profile_changed_cb)
except TunedAdminDBusException as e:
self._error(e)
self._dbus = False
Expand Down
27 changes: 25 additions & 2 deletions tuned/consts.py
Expand Up @@ -100,6 +100,13 @@
CFG_LOG_FILE_MAX_SIZE = "log_file_max_size"
CFG_UNAME_STRING = "uname_string"
CFG_CPUINFO_STRING = "cpuinfo_string"
CFG_ENABLE_DBUS = "enable_dbus"
CFG_ENABLE_UNIX_SOCKET = "enable_unix_socket"
CFG_UNIX_SOCKET_PATH = "unix_socket_path"
CFG_UNIX_SOCKET_SIGNAL_PATHS = "unix_socket_signal_paths"
CFG_UNIX_SOCKET_OWNERSHIP = "unix_socket_ownership"
CFG_UNIX_SOCKET_PERMISIONS = "unix_socket_permissions"
CFG_UNIX_SOCKET_CONNECTIONS_BACKLOG = "connections_backlog"

# no_daemon mode
CFG_DEF_DAEMON = True
Expand Down Expand Up @@ -129,15 +136,31 @@
CFG_FUNC_LOG_FILE_COUNT = "getint"
# default log file max size
CFG_DEF_LOG_FILE_MAX_SIZE = 1024 * 1024

# default listening on dbus
CFG_DEF_ENABLE_DBUS = True
CFG_FUNC_ENABLE_DBUS = "getboolean"
# default listening on unix socket
CFG_DEF_ENABLE_UNIX_SOCKET = True
CFG_FUNC_ENABLE_UNIX_SOCKET = "getboolean"
# default unix socket path
CFG_DEF_UNIX_SOCKET_PATH = "/run/tuned/tuned.sock"
CFG_DEF_UNIX_SOCKET_SIGNAL_PATHS = ""
# default unix socket ownership
# (uid and gid, python2 does not support names out of box, -1 leaves default)
CFG_DEF_UNIX_SOCKET_OWNERSHIP = "-1 -1"
# default unix socket permissions
CFG_DEF_UNIX_SOCKET_PERMISIONS = "0o600"
# default unix socket conections backlog
CFG_DEF_UNIX_SOCKET_CONNECTIONS_BACKLOG = "1024"
CFG_FUNC_UNIX_SOCKET_CONNECTIONS_BACKLOG = "getint"

PATH_CPU_DMA_LATENCY = "/dev/cpu_dma_latency"

# profile attributes which can be specified in the main section
PROFILE_ATTR_SUMMARY = "summary"
PROFILE_ATTR_DESCRIPTION = "description"

DBUS_SIGNAL_PROFILE_CHANGED = "profile_changed"
SIGNAL_PROFILE_CHANGED = "profile_changed"

STR_HINT_REBOOT = "you need to reboot for changes to take effect"

Expand Down
14 changes: 14 additions & 0 deletions tuned/daemon/application.py
Expand Up @@ -22,6 +22,7 @@ def __init__(self, profile_name = None, config = None):
# like e.g. '5.15.13-100.fc34.x86_64'
log.info("TuneD: %s, kernel: %s" % (tuned.version.TUNED_VERSION_STR, os.uname()[2]))
self._dbus_exporter = None
self._unix_socket_exporter = None

storage_provider = storage.PickleProvider()
storage_factory = storage.Factory(storage_provider)
Expand Down Expand Up @@ -76,6 +77,19 @@ def attach_to_dbus(self, bus_name, object_name, interface_name):

self._dbus_exporter = exports.dbus.DBusExporter(bus_name, interface_name, object_name)
exports.register_exporter(self._dbus_exporter)

def attach_to_unix_socket(self):
if self._unix_socket_exporter is not None:
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(consts.CFG_UNIX_SOCKET_OWNERSHIP),
self.config.get_int(consts.CFG_UNIX_SOCKET_PERMISIONS),
self.config.get_int(consts.CFG_UNIX_SOCKET_CONNECTIONS_BACKLOG))
exports.register_exporter(self._unix_socket_exporter)

def register_controller(self):
exports.register_object(self._controller)

def _daemonize_parent(self, parent_in_fd, child_out_fd):
Expand Down
23 changes: 20 additions & 3 deletions tuned/daemon/controller.py
Expand Up @@ -61,8 +61,8 @@ def run(self):
if daemon:
self._terminate.clear()
# we have to pass some timeout, otherwise signals will not work
while not self._cmd.wait(self._terminate, 10):
pass
while not self._cmd.wait(self._terminate, 1):
exports.period_check()

log.info("terminating controller")
self.stop()
Expand Down Expand Up @@ -142,7 +142,7 @@ def reload(self, caller = None):
return False
return self.start()

def _switch_profile(self, profile_name, manual):
def _switch_profile(self, profile_name, manual):
was_running = self._daemon.is_running()
msg = "OK"
success = True
Expand Down Expand Up @@ -308,3 +308,20 @@ def get_plugin_hints(self, plugin_name, caller = None):
if caller == "":
return False
return self._daemon.get_plugin_hints(str(plugin_name))

@exports.export("s", "b")
def register_socket_signal_path(self, path, caller = None):
"""Allows to dynamically add sockets to send signals to
Parameters:
path -- path to socket to register for sending signals
Return:
bool -- True on success
"""
if caller == "":
return False
if self._daemon._application and self._daemon._application._unix_socket_exporter:
self._daemon._application._unix_socket_exporter.register_signal_path(path)
return True
return False
4 changes: 2 additions & 2 deletions tuned/daemon/daemon.py
Expand Up @@ -173,8 +173,8 @@ def profile_loader(self):
# send notification when profile is changed (everything is setup) or if error occured
# result: True - OK, False - error occured
def _notify_profile_changed(self, profile_names, result, errstr):
if self._application is not None and self._application._dbus_exporter is not None:
self._application._dbus_exporter.send_signal(consts.DBUS_SIGNAL_PROFILE_CHANGED, profile_names, result, errstr)
if self._application is not None:
exports.send_signal(consts.SIGNAL_PROFILE_CHANGED, profile_names, result, errstr)
return errstr

def _full_rollback_required(self):
Expand Down
9 changes: 9 additions & 0 deletions tuned/exports/__init__.py
@@ -1,6 +1,7 @@
from . import interfaces
from . import controller
from . import dbus_exporter as dbus
from . import unix_socket_exporter as unix_socket

def export(*args, **kwargs):
"""Decorator, use to mark exportable methods."""
Expand Down Expand Up @@ -28,10 +29,18 @@ def register_object(instance):
ctl = controller.ExportsController.get_instance()
return ctl.register_object(instance)

def send_signal(*args, **kwargs):
ctl = controller.ExportsController.get_instance()
return ctl.send_signal(*args, **kwargs)

def start():
ctl = controller.ExportsController.get_instance()
return ctl.start()

def stop():
ctl = controller.ExportsController.get_instance()
return ctl.stop()

def period_check():
ctl = controller.ExportsController.get_instance()
return ctl.period_check()
10 changes: 10 additions & 0 deletions tuned/exports/controller.py
Expand Up @@ -43,6 +43,16 @@ def _export_signal(self, method):
kwargs = method.signal_params[1]
exporter.signal(method, *args, **kwargs)

def send_signal(self, signal, *args, **kwargs):
"""Register signal to all exporters."""
for exporter in self._exporters:
exporter.send_signal(signal, *args, **kwargs)

def period_check(self):
"""Allows to perform checks on exporters without special thread."""
for exporter in self._exporters:
exporter.period_check()

def _initialize_exports(self):
if self._exports_initialized:
return
Expand Down
3 changes: 3 additions & 0 deletions tuned/exports/interfaces.py
Expand Up @@ -19,3 +19,6 @@ def start(self):

def stop(self):
raise NotImplementedError()

def period_check(self):
pass

0 comments on commit 4265813

Please sign in to comment.