Skip to content

Commit

Permalink
Refactor dynamic tuning functionality
Browse files Browse the repository at this point in the history
- Add a new global option "dynamic_plugins" which can be
  used to globally enable dynamic tuning only for specific
  plugins.

- Let plugins choose between the default (periodic)
  dynamic tuning and their own implementation.

- Remove the "runtime" option from the scheduler plugin
  and integrate its functionality into dynamic tuning.
  Currently, the scheduler plugin is the only dynamic
  plugin with its own implementation of dynamic tuning
  (not initiated periodically by the main daemon thread).

- Adjust default global configuration so that dynamic tuning
  is enabled by default, but only for the scheduler plugin.
  • Loading branch information
zacikpa committed Apr 15, 2024
1 parent fea5934 commit 3e17127
Show file tree
Hide file tree
Showing 34 changed files with 346 additions and 213 deletions.
2 changes: 1 addition & 1 deletion profiles/openshift/tuned.conf
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ vm.max_map_count=262144
# see rhbz#1979352; exclude containers from aligning to house keeping CPUs
cgroup_ps_blacklist=/kubepods\.slice/
# workaround for rhbz#1921738
runtime=0
dynamic=0
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ rlJournalStart
rlRun "pushd /etc/tuned/test-profile"
cat << EOF > tuned.conf
[scheduler]
runtime=0
dynamic=0
EOF
rlRun "popd"

Expand Down
7 changes: 6 additions & 1 deletion tuned-main.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
daemon = 1

# Dynamically tune devices, if disabled only static tuning will be used.
dynamic_tuning = 0
dynamic_tuning = 1

# A list of plugins to allow dynamic tuning for. The "dynamic_tuning"
# option must be enabled for this to have any effect. If not set, all
# plugins are allowed to perform dynamic tuning.
dynamic_plugins = scheduler

# How long to sleep before checking for events (in seconds)
# higher number means lower overhead but longer response time.
Expand Down
1 change: 1 addition & 0 deletions tuned/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
# (see configobj for methods, default is get for string)
CFG_DAEMON = "daemon"
CFG_DYNAMIC_TUNING = "dynamic_tuning"
CFG_DYNAMIC_PLUGINS = "dynamic_plugins"
CFG_SLEEP_INTERVAL = "sleep_interval"
CFG_UPDATE_INTERVAL = "update_interval"
CFG_RECOMMEND_COMMAND = "recommend_command"
Expand Down
28 changes: 24 additions & 4 deletions tuned/daemon/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ def __init__(self, profile_name = None, config = None):
storage_factory = storage.Factory(storage_provider)

self.config = GlobalConfig() if config is None else config
if self.config.get_bool(consts.CFG_DYNAMIC_TUNING):
log.info("dynamic tuning is enabled (can be overridden in plugins)")
else:
log.info("dynamic tuning is globally disabled")

monitors_repository = monitors.Repository()
udev_buffer_size = self.config.get_size("udev_buffer_size", consts.CFG_DEF_UDEV_BUFFER_SIZE)
Expand All @@ -53,6 +49,30 @@ def __init__(self, profile_name = None, config = None):
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)

if not self.config.get(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING):
log.info("dynamic tuning is globally disabled")
self.config.set(consts.CFG_DYNAMIC_PLUGINS, [])
else:
all_plugins = {p.name(): p for p in plugins_repository.load_all_plugins()}
all_dynamic_plugins = {name: p for name, p in all_plugins.items() if p.supports_dynamic_tuning()}
enabled_dynamic_plugins = {}
for name in self.config.get_list(consts.CFG_DYNAMIC_PLUGINS, all_dynamic_plugins.keys()):
if name not in all_plugins:
log.warn("Configuring dynamic tuning: Plugin '%s' does not exist" % name)
continue
if name not in all_dynamic_plugins:
log.warn("Configuring dynamic tuning: Plugin '%s' does not support dynamic tuning" % name)
continue
enabled_dynamic_plugins[name] = all_plugins[name]
self.config.set(consts.CFG_DYNAMIC_PLUGINS, enabled_dynamic_plugins.values())
if set(enabled_dynamic_plugins.keys()) == set(all_dynamic_plugins.keys()):
log.info("dynamic tuning is enabled for all plugins which support it")
elif len(enabled_dynamic_plugins) == 0:
log.info("dynamic tuning is disabled for all plugins")
self.config.set(consts.CFG_DYNAMIC_TUNING, False)
else:
log.info("dynamic tuning is enabled for plugins: %s" % ", ".join(enabled_dynamic_plugins.keys()))

self._daemon = daemon.Daemon(unit_manager, profile_loader, profile_name, self.config, self)
self._controller = controller.Controller(self._daemon, self.config)

Expand Down
10 changes: 5 additions & 5 deletions tuned/daemon/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ def get_all_plugins(self, caller = None):
return False
plugins = {}
for plugin_class in self._daemon.get_all_plugins():
plugin_name = plugin_class.__module__.split(".")[-1].split("_", 1)[1]
plugin_name = plugin_class.name()
conf_options = plugin_class._get_config_options()
plugins[plugin_name] = {}
for key, val in conf_options.items():
Expand Down Expand Up @@ -360,10 +360,10 @@ def instance_acquire_devices(self, devices, instance_name, caller = None):
devs -= devs_moving
log.info("Moving devices '%s' from instance '%s' to instance '%s'." % (str(devs_moving),
instance.name, instance_target.name))
if (instance.plugin.name != instance_target.plugin.name):
if (instance.plugin.name() != instance_target.plugin.name()):
rets = "Target instance '%s' is of type '%s', but devices '%s' are currently handled by " \
"instance '%s' which is of type '%s'." % (instance_target.name,
instance_target.plugin.name, str(devs_moving), instance.name, instance.plugin.name)
instance_target.plugin.name(), str(devs_moving), instance.name, instance.plugin.name())
log.error(rets)
return (False, rets)
instance.plugin._remove_devices_nocheck(instance, devs_moving)
Expand Down Expand Up @@ -394,8 +394,8 @@ def get_instances(self, plugin_name, caller = None):
return (False, rets, [])
instances = filter(lambda instance: instance.active, self._daemon._unit_manager.instances)
if plugin_name != "":
instances = filter(lambda instance: instance.plugin.name == plugin_name, instances)
return (True, "OK", list(map(lambda instance: (instance.name, instance.plugin.name), instances)))
instances = filter(lambda instance: instance.plugin.name() == plugin_name, instances)
return (True, "OK", list(map(lambda instance: (instance.name, instance.plugin.name()), instances)))

@exports.export("s", "(bsas)")
def instance_get_devices(self, instance_name, caller = None):
Expand Down
15 changes: 8 additions & 7 deletions tuned/daemon/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,28 @@ def __init__(self, unit_manager, profile_loader, profile_names=None, config=None
self._daemon = consts.CFG_DEF_DAEMON
self._sleep_interval = int(consts.CFG_DEF_SLEEP_INTERVAL)
self._update_interval = int(consts.CFG_DEF_UPDATE_INTERVAL)
self._dynamic_tuning = consts.CFG_DEF_DYNAMIC_TUNING
self._recommend_command = True
self._rollback = consts.CFG_DEF_ROLLBACK
dynamic_plugins = []
if config is not None:
self._daemon = config.get_bool(consts.CFG_DAEMON, consts.CFG_DEF_DAEMON)
self._sleep_interval = int(config.get(consts.CFG_SLEEP_INTERVAL, consts.CFG_DEF_SLEEP_INTERVAL))
self._update_interval = int(config.get(consts.CFG_UPDATE_INTERVAL, consts.CFG_DEF_UPDATE_INTERVAL))
self._dynamic_tuning = config.get_bool(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING)
self._recommend_command = config.get_bool(consts.CFG_RECOMMEND_COMMAND, consts.CFG_DEF_RECOMMEND_COMMAND)
self._rollback = config.get(consts.CFG_ROLLBACK, consts.CFG_DEF_ROLLBACK)
dynamic_plugins = config.get(consts.CFG_DYNAMIC_PLUGINS, [])
self._application = application
self._periodic_tuning = any(plugin.uses_periodic_tuning() for plugin in dynamic_plugins)
if self._sleep_interval <= 0:
self._sleep_interval = int(consts.CFG_DEF_SLEEP_INTERVAL)
if self._update_interval == 0:
self._dynamic_tuning = False
self._periodic_tuning = False
elif self._update_interval < self._sleep_interval:
self._update_interval = self._sleep_interval
self._sleep_cycles = self._update_interval // self._sleep_interval
log.info("using sleep interval of %d second(s)" % self._sleep_interval)
if self._dynamic_tuning:
log.info("dynamic tuning is enabled (can be overridden by plugins)")
if self._periodic_tuning:
log.info("using sleep interval of %d second(s)" % self._sleep_interval)
log.info("periodic tuning is enabled")
log.info("using update interval of %d second(s) (%d times of the sleep interval)" % (self._sleep_cycles * self._sleep_interval, self._sleep_cycles))

self._profile_recommender = ProfileRecommender(is_hardcoded = not self._recommend_command)
Expand Down Expand Up @@ -215,7 +216,7 @@ def _thread_code(self):
# For more details see TuneD rhbz#917587.
_sleep_cnt = self._sleep_cycles
while not self._cmd.wait(self._terminate, self._sleep_interval):
if self._dynamic_tuning:
if self._periodic_tuning:
_sleep_cnt -= 1
if _sleep_cnt <= 0:
_sleep_cnt = self._sleep_cycles
Expand Down
61 changes: 41 additions & 20 deletions tuned/plugins/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,21 @@ def init_devices(self):
self._init_devices()
self._devices_inited = True

@property
def name(self):
return self.__class__.__module__.split(".")[-1].split("_", 1)[1]
@classmethod
def name(cls):
return cls.__module__.split(".")[-1].split("_", 1)[1]

@classmethod
def supports_static_tuning(cls):
raise NotImplementedError()

@classmethod
def supports_dynamic_tuning(cls):
raise NotImplementedError()

@classmethod
def uses_periodic_tuning(cls):
raise NotImplementedError()

#
# Plugin configuration manipulation and helpers.
Expand All @@ -74,8 +86,10 @@ def _get_config_options_used_by_dynamic(self):

def _get_effective_options(self, options):
"""Merge provided options with plugin default options."""
# TODO: _has_dynamic_options is a hack
effective = self._get_config_options().copy()
if self.supports_dynamic_tuning():
effective["dynamic"] = True
# TODO: _has_dynamic_options is a hack
for key in options:
if key in effective or self._has_dynamic_options:
effective[key] = options[key]
Expand Down Expand Up @@ -118,13 +132,13 @@ def destroy_instance(self, instance):

def initialize_instance(self, instance):
"""Initialize an instance."""
log.debug("initializing instance %s (%s)" % (instance.name, self.name))
log.debug("initializing instance %s (%s)" % (instance.name, self.name()))
self._instance_init(instance)

def destroy_instances(self):
"""Destroy all instances."""
for instance in list(self._instances.values()):
log.debug("destroying instance %s (%s)" % (instance.name, self.name))
log.debug("destroying instance %s (%s)" % (instance.name, self.name()))
self._destroy_instance(instance)
self._instances.clear()

Expand All @@ -133,10 +147,13 @@ def _destroy_instance(self, instance):
self._instance_cleanup(instance)

def _instance_init(self, instance):
raise NotImplementedError()
instance._static_tuning_enabled = self.supports_static_tuning()
instance._dynamic_tuning_enabled = self.supports_dynamic_tuning() \
and self.__class__ in self._global_cfg.get(consts.CFG_DYNAMIC_PLUGINS) \
and instance.options["dynamic"]

def _instance_cleanup(self, instance):
raise NotImplementedError()
pass

#
# Devices handling
Expand All @@ -158,7 +175,7 @@ def _get_matching_devices(self, instance, devices):
else:
udev_devices = self._get_device_objects(devices)
if udev_devices is None:
log.error("Plugin '%s' does not support the 'devices_udev_regex' option", self.name)
log.error("Plugin '%s' does not support the 'devices_udev_regex' option", self.name())
return set()
udev_devices = self._device_matcher_udev.match_list(instance.devices_udev_regex, udev_devices)
return set([x.sys_name for x in udev_devices])
Expand All @@ -174,8 +191,8 @@ def assign_free_devices(self, instance):
log.warn("instance %s: no matching devices available" % instance.name)
else:
name = instance.name
if instance.name != self.name:
name += " (%s)" % self.name
if instance.name != self.name():
name += " (%s)" % self.name()
log.info("instance %s: assigning devices %s" % (name, ", ".join(to_assign)))
instance.assigned_devices.update(to_assign) # cannot use |=
self._assigned_devices |= to_assign
Expand Down Expand Up @@ -254,15 +271,15 @@ def instance_apply_tuning(self, instance):
if not instance.active:
return

if instance.has_static_tuning:
if instance.static_tuning_enabled:
self._call_device_script(instance, instance.script_pre,
"apply", instance.assigned_devices)
self._instance_pre_static(instance, True)
self._instance_apply_static(instance)
self._instance_post_static(instance, True)
self._call_device_script(instance, instance.script_post,
"apply", instance.assigned_devices)
if instance.has_dynamic_tuning and self._global_cfg.get(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING):
if instance.dynamic_tuning_enabled:
self._instance_init_dynamic(instance)
self._run_for_each_device(instance, self._instance_apply_dynamic, instance.assigned_devices)
instance.processed_devices.update(instance.assigned_devices)
Expand All @@ -279,7 +296,7 @@ def instance_verify_tuning(self, instance, ignore_missing):
log.error("BUG: Some devices have not been tuned: %s"
% ", ".join(instance.assigned_devices))
devices = instance.processed_devices.copy()
if instance.has_static_tuning:
if instance.static_tuning_enabled:
if self._call_device_script(instance, instance.script_pre, "verify", devices) == False:
return False
if self._instance_verify_static(instance, ignore_missing, devices) == False:
Expand All @@ -296,7 +313,7 @@ def instance_update_tuning(self, instance):
"""
if not instance.active:
return
if instance.has_dynamic_tuning and self._global_cfg.get(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING):
if instance.dynamic_tuning_enabled and self.uses_periodic_tuning():
self._run_for_each_device(instance, self._instance_update_dynamic, instance.processed_devices.copy())

def instance_unapply_tuning(self, instance, rollback = consts.ROLLBACK_SOFT):
Expand All @@ -306,9 +323,10 @@ def instance_unapply_tuning(self, instance, rollback = consts.ROLLBACK_SOFT):
if rollback == consts.ROLLBACK_NONE:
return

if instance.has_dynamic_tuning and self._global_cfg.get(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING):
if instance.dynamic_tuning_enabled:
self._run_for_each_device(instance, self._instance_unapply_dynamic, instance.processed_devices)
if instance.has_static_tuning:
self._instance_deinit_dynamic(instance)
if instance.static_tuning_enabled:
self._call_device_script(instance, instance.script_post,
"unapply", instance.processed_devices,
rollback = rollback)
Expand Down Expand Up @@ -337,14 +355,17 @@ def _instance_unapply_static(self, instance, rollback = consts.ROLLBACK_SOFT):
def _instance_init_dynamic(self, instance):
pass

def _instance_deinit_dynamic(self, instance):
pass

def _instance_apply_dynamic(self, instance, device):
for option in [opt for opt in self._options_used_by_dynamic if self._storage_get(instance, self._commands[opt], device) is None]:
self._check_and_save_value(instance, self._commands[option], device)

self._instance_update_dynamic(instance, device)
if self.uses_periodic_tuning():
self._instance_update_dynamic(instance, device)

def _instance_unapply_dynamic(self, instance, device):
raise NotImplementedError()
pass

def _instance_update_dynamic(self, instance, device):
raise NotImplementedError()
Expand Down
4 changes: 2 additions & 2 deletions tuned/plugins/hotplug.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ def _remove_devices_nocheck(self, instance, device_names):

def _added_device_apply_tuning(self, instance, device_name):
self._execute_all_device_commands(instance, [device_name])
if instance.has_dynamic_tuning and self._global_cfg.get(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING):
if instance.dynamic_tuning_enabled:
self._instance_apply_dynamic(instance, device_name)

def _removed_device_unapply_tuning(self, instance, device_name):
if instance.has_dynamic_tuning and self._global_cfg.get(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING):
if instance.dynamic_tuning_enabled:
self._instance_unapply_dynamic(instance, device_name)
self._cleanup_all_device_commands(instance, [device_name], remove = True)
12 changes: 6 additions & 6 deletions tuned/plugins/instance/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def __init__(self, plugin, name, devices_expression, devices_udev_regex, script_
self._options = options

self._active = True
self._has_static_tuning = False
self._has_dynamic_tuning = False
self._static_tuning_enabled = False
self._dynamic_tuning_enabled = False
self._assigned_devices = set()
self._processed_devices = set()

Expand Down Expand Up @@ -67,12 +67,12 @@ def options(self):
return self._options

@property
def has_static_tuning(self):
return self._has_static_tuning
def static_tuning_enabled(self):
return self._static_tuning_enabled

@property
def has_dynamic_tuning(self):
return self._has_dynamic_tuning
def dynamic_tuning_enabled(self):
return self._dynamic_tuning_enabled

# methods

Expand Down
15 changes: 8 additions & 7 deletions tuned/plugins/plugin_acpi.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ def __init__(self, *args, **kwargs):
super(ACPIPlugin, self).__init__(*args, **kwargs)

@classmethod
def _get_config_options(cls):
return {"platform_profile": None}
def supports_static_tuning(cls):
return True

def _instance_init(self, instance):
instance._has_static_tuning = True
instance._has_dynamic_tuning = False
@classmethod
def supports_dynamic_tuning(cls):
return False

def _instance_cleanup(self, instance):
pass
@classmethod
def _get_config_options(cls):
return {"platform_profile": None}

@classmethod
def _platform_profile_choices_path(cls):
Expand Down
Loading

0 comments on commit 3e17127

Please sign in to comment.