Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor dynamic tuning and add per-plugin global configuration #626

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -43,7 +43,7 @@ rlJournalStart
rlRun "pushd $PROFILE_DIR/test-profile"
cat << EOF > tuned.conf
[scheduler]
runtime=0
dynamic=0
EOF
rlRun "popd"

Expand Down
16 changes: 16 additions & 0 deletions tests/unit/plugins/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ def __init__(self,*args,**kwargs):
super(DummyPlugin,self).__init__(*args,**kwargs)
self.cleaned_instances = []

@classmethod
def supports_static_tuning(cls):
return True

@classmethod
def supports_dynamic_tuning(cls):
return False

@classmethod
def _get_config_options(self):
return {'default_option1':'default_value1',\
Expand Down Expand Up @@ -205,6 +213,14 @@ def __init__(self,*args,**kwargs):
super(CommandsPlugin,self).__init__(*args,**kwargs)
self._size = 'S'

@classmethod
def supports_static_tuning(cls):
return True

@classmethod
def supports_dynamic_tuning(cls):
return False

@classmethod
def _get_config_options(self):
"""Default configuration options for the plugin."""
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
35 changes: 31 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,8 @@ 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)

self._configure_dynamic_tuning(plugins_repository)

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

Expand All @@ -71,6 +69,35 @@ def _init_signals(self):
self._handle_signal(signal.SIGINT, self._controller.terminate)
self._handle_signal(signal.SIGTERM, self._controller.terminate)

def _configure_dynamic_tuning(self, plugins_repository):
# First check if the global `dynamic_tuning` option is set to False,
# disabling dynamic tuning for all plugins
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, [])
return
# If the global `dynamic_tuning` is True, check the `dynamic_plugins` option, which
# restricts the dynamic tuning to selected plugins only (if present)
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()}
# If it's not present, we enable dynamic tuning where possible
if self.config.get(consts.CFG_DYNAMIC_PLUGINS) is None:
self.config.set(consts.CFG_DYNAMIC_PLUGINS, all_dynamic_plugins.values())
log.info("dynamic tuning is enabled for all plugins which support it")
return
# Otherwise only to the specified plugins
enabled_dynamic_plugins = {}
for name in self.config.get_list(consts.CFG_DYNAMIC_PLUGINS):
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())
log.info("dynamic tuning is enabled for plugins: %s" % ", ".join(enabled_dynamic_plugins.keys()))

def attach_to_dbus(self, bus_name, object_name, interface_name, namespace):
if self._dbus_exporter is not None:
raise TunedException("DBus interface is already initialized.")
Expand Down
10 changes: 5 additions & 5 deletions tuned/daemon/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,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 @@ -371,10 +371,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 @@ -405,8 +405,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 @@ -217,7 +218,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 @@ -119,13 +133,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 @@ -134,10 +148,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 self._option_bool(instance.options["dynamic"])

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

#
# Devices handling
Expand All @@ -159,7 +176,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 @@ -175,8 +192,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 @@ -255,15 +272,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 @@ -280,7 +297,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 @@ -297,7 +314,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 @@ -307,9 +324,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 @@ -338,14 +356,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 @@ -15,8 +15,8 @@ def __init__(self, plugin, name, priority, devices_expression, devices_udev_rege

self._active = True
self._priority = priority
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 @@ -72,12 +72,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
Loading
Loading