From b17db758813efcdd5c34e28b50782ee6ba638cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= Date: Tue, 16 Apr 2024 10:32:08 +0200 Subject: [PATCH 1/3] Refactor per-plugin dynamic tuning configuration Add a new global option "dynamic_plugins" which can be used to globally enable dynamic tuning only for specific plugins. Also unify the behavior of the "dynamic" profile option for plugins with dynamic capabilities. All such plugins now have this option and it is set to True by default. Dynamic tuning is then enabled for a specific plugin if it is allowed both globally and locally. --- tests/unit/plugins/test_base.py | 16 ++++++++++ tuned/consts.py | 1 + tuned/daemon/application.py | 35 ++++++++++++++++++--- tuned/daemon/controller.py | 10 +++--- tuned/plugins/base.py | 49 +++++++++++++++++++----------- tuned/plugins/hotplug.py | 4 +-- tuned/plugins/instance/instance.py | 12 ++++---- tuned/plugins/plugin_acpi.py | 17 +++++------ tuned/plugins/plugin_audio.py | 15 ++++----- tuned/plugins/plugin_bootloader.py | 14 ++++++--- tuned/plugins/plugin_cpu.py | 19 +++++++----- tuned/plugins/plugin_disk.py | 17 ++++++----- tuned/plugins/plugin_eeepc_she.py | 11 +++++-- tuned/plugins/plugin_irq.py | 13 +++++--- tuned/plugins/plugin_irqbalance.py | 11 ++++--- tuned/plugins/plugin_modules.py | 14 ++++++--- tuned/plugins/plugin_mounts.py | 15 ++++----- tuned/plugins/plugin_net.py | 12 ++++++-- tuned/plugins/plugin_rtentsk.py | 12 ++++++-- tuned/plugins/plugin_scheduler.py | 11 +++++-- tuned/plugins/plugin_script.py | 14 ++++++--- tuned/plugins/plugin_scsi_host.py | 15 ++++----- tuned/plugins/plugin_selinux.py | 15 ++++----- tuned/plugins/plugin_service.py | 14 ++++++--- tuned/plugins/plugin_sysctl.py | 12 ++++++-- tuned/plugins/plugin_sysfs.py | 14 ++++++--- tuned/plugins/plugin_systemd.py | 11 ++++--- tuned/plugins/plugin_uncore.py | 15 ++++----- tuned/plugins/plugin_usb.py | 15 ++++----- tuned/plugins/plugin_video.py | 16 +++++----- tuned/plugins/plugin_vm.py | 15 ++++----- tuned/units/manager.py | 2 +- 32 files changed, 295 insertions(+), 171 deletions(-) diff --git a/tests/unit/plugins/test_base.py b/tests/unit/plugins/test_base.py index 7f59624c..ca4791de 100644 --- a/tests/unit/plugins/test_base.py +++ b/tests/unit/plugins/test_base.py @@ -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',\ @@ -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.""" diff --git a/tuned/consts.py b/tuned/consts.py index 48c80f0d..debaa080 100644 --- a/tuned/consts.py +++ b/tuned/consts.py @@ -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" diff --git a/tuned/daemon/application.py b/tuned/daemon/application.py index a7400cfe..d6050cc3 100644 --- a/tuned/daemon/application.py +++ b/tuned/daemon/application.py @@ -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) @@ -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) @@ -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.") diff --git a/tuned/daemon/controller.py b/tuned/daemon/controller.py index 7ddcd2d6..6f5e5636 100644 --- a/tuned/daemon/controller.py +++ b/tuned/daemon/controller.py @@ -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(): @@ -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) @@ -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): diff --git a/tuned/plugins/base.py b/tuned/plugins/base.py index afeb37f8..9c053eb5 100644 --- a/tuned/plugins/base.py +++ b/tuned/plugins/base.py @@ -49,9 +49,17 @@ 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() # # Plugin configuration manipulation and helpers. @@ -74,8 +82,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] @@ -119,13 +129,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() @@ -134,10 +144,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 @@ -159,7 +172,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]) @@ -175,8 +188,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 @@ -255,7 +268,7 @@ 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) @@ -263,7 +276,7 @@ def instance_apply_tuning(self, 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) @@ -280,7 +293,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: @@ -297,7 +310,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: self._run_for_each_device(instance, self._instance_update_dynamic, instance.processed_devices.copy()) def instance_unapply_tuning(self, instance, rollback = consts.ROLLBACK_SOFT): @@ -307,9 +320,9 @@ 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: + if instance.static_tuning_enabled: self._call_device_script(instance, instance.script_post, "unapply", instance.processed_devices, rollback = rollback) @@ -345,7 +358,7 @@ def _instance_apply_dynamic(self, instance, device): 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() diff --git a/tuned/plugins/hotplug.py b/tuned/plugins/hotplug.py index 9e392a70..e44de4b3 100644 --- a/tuned/plugins/hotplug.py +++ b/tuned/plugins/hotplug.py @@ -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) diff --git a/tuned/plugins/instance/instance.py b/tuned/plugins/instance/instance.py index d789d353..b27dfd95 100644 --- a/tuned/plugins/instance/instance.py +++ b/tuned/plugins/instance/instance.py @@ -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() @@ -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 diff --git a/tuned/plugins/plugin_acpi.py b/tuned/plugins/plugin_acpi.py index 38eca682..c2c5942e 100644 --- a/tuned/plugins/plugin_acpi.py +++ b/tuned/plugins/plugin_acpi.py @@ -33,19 +33,18 @@ class ACPIPlugin(base.Plugin): ==== -- """ - 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): diff --git a/tuned/plugins/plugin_audio.py b/tuned/plugins/plugin_audio.py index eee932cb..e25f0952 100644 --- a/tuned/plugins/plugin_audio.py +++ b/tuned/plugins/plugin_audio.py @@ -37,6 +37,14 @@ class AudioPlugin(hotplug.Plugin): ==== """ + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _init_devices(self): self._devices_supported = True self._assigned_devices = set() @@ -47,13 +55,6 @@ def _init_devices(self): if module_name in ["snd_hda_intel", "snd_ac97_codec"]: self._free_devices.add(module_name) - def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False - - def _instance_cleanup(self, instance): - pass - def _device_module_name(self, device): try: return device.parent.driver diff --git a/tuned/plugins/plugin_bootloader.py b/tuned/plugins/plugin_bootloader.py index 1b79c381..3a16341a 100644 --- a/tuned/plugins/plugin_bootloader.py +++ b/tuned/plugins/plugin_bootloader.py @@ -193,9 +193,16 @@ def __init__(self, *args, **kwargs): super(BootloaderPlugin, self).__init__(*args, **kwargs) self._cmd = commands() + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True + super(BootloaderPlugin, self)._instance_init(instance) # controls grub2_cfg rewrites in _instance_post_static self.update_grub2_cfg = False self._skip_grub_config_val = False @@ -208,9 +215,6 @@ def _instance_init(self, instance): self._rpm_ostree = self._rpm_ostree_status() is not None - def _instance_cleanup(self, instance): - pass - @classmethod def _get_config_options(cls): return { diff --git a/tuned/plugins/plugin_cpu.py b/tuned/plugins/plugin_cpu.py index 648c5e4d..2204eecb 100644 --- a/tuned/plugins/plugin_cpu.py +++ b/tuned/plugins/plugin_cpu.py @@ -209,6 +209,14 @@ def __init__(self, *args, **kwargs): self._flags = None + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return True + def _init_devices(self): self._devices_supported = True self._free_devices = set() @@ -315,8 +323,7 @@ def _check_cpu_can_change_governor(self, device): return True def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False + super(CPULatencyPlugin, self)._instance_init(instance) instance._load_monitor = None # only the first instance of the plugin can control the latency @@ -329,12 +336,13 @@ def _instance_init(self, instance): self._has_pm_qos = False self._latency = None - if instance.options["force_latency"] is None and instance.options["pm_qos_resume_latency_us"] is None: - instance._has_dynamic_tuning = True + if not instance.options["force_latency"] is None or not instance.options["pm_qos_resume_latency_us"] is None: + instance._dynamic_tuning_enabled = False self._check_arch() else: instance._first_instance = False + instance._dynamic_tuning_enabled = False log.info("Latency settings from non-first CPU plugin instance '%s' will be ignored." % instance.name) try: @@ -414,9 +422,6 @@ def _instance_update_dynamic(self, instance, device): else: self._set_latency(instance.options["latency_low"]) - def _instance_unapply_dynamic(self, instance, device): - pass - def _str2int(self, s): try: return int(s) diff --git a/tuned/plugins/plugin_disk.py b/tuned/plugins/plugin_disk.py index 1438e354..7b133929 100644 --- a/tuned/plugins/plugin_disk.py +++ b/tuned/plugins/plugin_disk.py @@ -95,6 +95,14 @@ def __init__(self, *args, **kwargs): self._load_smallest = 0.01 self._cmd = commands() + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return True + def _init_devices(self): super(DiskPlugin, self)._init_devices() self._devices_supported = True @@ -158,7 +166,6 @@ def _removed_device_unapply_tuning(self, instance, device_name): @classmethod def _get_config_options(cls): return { - "dynamic" : True, # FIXME: do we want this default? "elevator" : None, "apm" : None, "spindown" : None, @@ -175,13 +182,10 @@ def _get_config_options_used_by_dynamic(cls): ] def _instance_init(self, instance): - instance._has_static_tuning = True - + super(DiskPlugin, self)._instance_init(instance) self._apm_errcnt = 0 self._spindown_errcnt = 0 - instance._load_monitor = None - instance._has_dynamic_tuning = self._option_bool(instance.options["dynamic"]) def _instance_cleanup(self, instance): if instance._load_monitor is not None: @@ -320,9 +324,6 @@ def _instance_apply_dynamic(self, instance, device): else: super(DiskPlugin, self)._instance_apply_dynamic(instance, device) - def _instance_unapply_dynamic(self, instance, device): - pass - def _sysfs_path(self, device, suffix, prefix = "/sys/block/"): if "/" in device: dev = os.path.join(prefix, device.replace("/", "!"), suffix) diff --git a/tuned/plugins/plugin_eeepc_she.py b/tuned/plugins/plugin_eeepc_she.py index d292e225..7c605494 100644 --- a/tuned/plugins/plugin_eeepc_she.py +++ b/tuned/plugins/plugin_eeepc_she.py @@ -36,6 +36,14 @@ def __init__(self, *args, **kwargs): raise exceptions.NotSupportedPluginException("Plugin is not supported on your hardware.") super(EeePCSHEPlugin, self).__init__(*args, **kwargs) + @classmethod + def supports_static_tuning(cls): + return False + + @classmethod + def supports_dynamic_tuning(cls): + return True + @classmethod def _get_config_options(self): return { @@ -46,8 +54,7 @@ def _get_config_options(self): } def _instance_init(self, instance): - instance._has_static_tuning = False - instance._has_dynamic_tuning = True + super(EeePCSHEPlugin, self)._instance_init(instance) instance._she_mode = None instance._load_monitor = None diff --git a/tuned/plugins/plugin_irq.py b/tuned/plugins/plugin_irq.py index 09684383..236fb378 100644 --- a/tuned/plugins/plugin_irq.py +++ b/tuned/plugins/plugin_irq.py @@ -59,6 +59,14 @@ def __init__(self, monitor_repository, storage_factory, hardware_inventory, devi super(IrqPlugin, self).__init__(monitor_repository, storage_factory, hardware_inventory, device_matcher, device_matcher_udev, plugin_instance_factory, global_cfg, variables) self._irqs = {} + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + # # plugin-level methods: devices and plugin options # @@ -91,9 +99,6 @@ def _get_config_options(cls): # instance-level methods: implement the Instance interface # def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False - affinity = instance.options.get("affinity") affinity_list = self._cmd.cpulist_unpack(affinity) if len(affinity.strip()) == 0: @@ -111,8 +116,6 @@ def _instance_init(self, instance): % (mode, instance.name)) instance.options["mode"] = "set" - def _instance_cleanup(self, instance): - pass def _instance_apply_static(self, instance): log.debug("Applying IRQ affinities (%s)" % instance.name) diff --git a/tuned/plugins/plugin_irqbalance.py b/tuned/plugins/plugin_irqbalance.py index d8e9e59f..4f0e934c 100644 --- a/tuned/plugins/plugin_irqbalance.py +++ b/tuned/plugins/plugin_irqbalance.py @@ -33,12 +33,13 @@ def __init__(self, *args, **kwargs): super(IrqbalancePlugin, self).__init__(*args, **kwargs) self._cpus = perf.cpu_map() - def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True + @classmethod + def supports_static_tuning(cls): + return True - def _instance_cleanup(self, instance): - pass + @classmethod + def supports_dynamic_tuning(cls): + return False @classmethod def _get_config_options(cls): diff --git a/tuned/plugins/plugin_modules.py b/tuned/plugins/plugin_modules.py index fa2d04b1..d53d1449 100644 --- a/tuned/plugins/plugin_modules.py +++ b/tuned/plugins/plugin_modules.py @@ -53,14 +53,18 @@ def __init__(self, *args, **kwargs): self._has_dynamic_options = True self._cmd = commands() + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True + super(ModulesPlugin, self)._instance_init(instance) instance._modules = instance.options - def _instance_cleanup(self, instance): - pass - def _reload_modules(self, modules): for module in modules: retcode, out = self._cmd.execute(["modprobe", "-r", module]) diff --git a/tuned/plugins/plugin_mounts.py b/tuned/plugins/plugin_mounts.py index e64111cd..81b93379 100644 --- a/tuned/plugins/plugin_mounts.py +++ b/tuned/plugins/plugin_mounts.py @@ -20,6 +20,14 @@ class MountsPlugin(base.Plugin): systems (ext) are supported by this plug-in. """ + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + @classmethod def _generate_mountpoint_topology(cls): """ @@ -68,13 +76,6 @@ def _get_config_options(self): "disable_barriers": None, } - def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True - - def _instance_cleanup(self, instance): - pass - def _get_device_cache_type(self, device): """ Get device cache type. This will work only for devices on SCSI kernel subsystem. diff --git a/tuned/plugins/plugin_net.py b/tuned/plugins/plugin_net.py index 521198a1..9e11cd82 100644 --- a/tuned/plugins/plugin_net.py +++ b/tuned/plugins/plugin_net.py @@ -159,6 +159,14 @@ def __init__(self, *args, **kwargs): self._re_ip_link_show = {} self._use_ip = True + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return True + def _init_devices(self): self._devices_supported = True self._free_devices = set() @@ -175,11 +183,10 @@ def _get_device_objects(self, devices): return [self._hardware_inventory.get_device("net", x) for x in devices] def _instance_init(self, instance): - instance._has_static_tuning = True + super(NetTuningPlugin, self)._instance_init(instance) instance._load_monitor = None instance._idle = None instance._stats = None - instance._has_dynamic_tuning = self._option_bool(instance.options["dynamic"]) def _instance_cleanup(self, instance): if instance._load_monitor is not None: @@ -270,7 +277,6 @@ def _get_config_options_channels(cls): @classmethod def _get_config_options(cls): return { - "dynamic": True, "wake_on_lan": None, "nf_conntrack_hashsize": None, "features": None, diff --git a/tuned/plugins/plugin_rtentsk.py b/tuned/plugins/plugin_rtentsk.py index 31e21677..870c5dd0 100644 --- a/tuned/plugins/plugin_rtentsk.py +++ b/tuned/plugins/plugin_rtentsk.py @@ -17,13 +17,19 @@ class RTENTSKPlugin(base.Plugin): socket ourselves the static key is kept enabled). """ + @classmethod + def supports_dynamic_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False + super(RTENTSKPlugin, self)._instance_init(instance) # SO_TIMESTAMP nor SOF_TIMESTAMPING_OPT_TX_SWHW is defined by # the socket class - SO_TIMESTAMP = 29 # see include/uapi/asm-generic/socket.h #define SO_TIMESTAMP 0x4012 # parisc! SOF_TIMESTAMPING_OPT_TX_SWHW = (1<<14) # see include/uapi/linux/net_tstamp.h diff --git a/tuned/plugins/plugin_scheduler.py b/tuned/plugins/plugin_scheduler.py index 85d61515..171faa98 100644 --- a/tuned/plugins/plugin_scheduler.py +++ b/tuned/plugins/plugin_scheduler.py @@ -459,6 +459,14 @@ def __init__(self, monitor_repository, storage_factory, hardware_inventory, devi except AttributeError: self._scheduler_utils = SchedulerUtilsSchedutils() + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _calc_mmap_pages(self, mmap_pages): if mmap_pages is None: return None @@ -472,9 +480,8 @@ def _calc_mmap_pages(self, mmap_pages): return int(2 ** math.ceil(math.log(mp, 2))) def _instance_init(self, instance): + super(SchedulerPlugin, self)._instance_init(instance) instance._evlist = None - instance._has_dynamic_tuning = False - instance._has_static_tuning = True # this is hack, runtime_tuning should be covered by dynamic_tuning configuration # TODO: add per plugin dynamic tuning configuration and use dynamic_tuning configuration # instead of runtime_tuning diff --git a/tuned/plugins/plugin_script.py b/tuned/plugins/plugin_script.py index ab605e42..243f67fa 100644 --- a/tuned/plugins/plugin_script.py +++ b/tuned/plugins/plugin_script.py @@ -57,9 +57,16 @@ def _get_config_options(self): "script" : None, } + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False + super(ScriptPlugin, self)._instance_init(instance) if instance.options["script"] is not None: # FIXME: this hack originated from profiles merger assert isinstance(instance.options["script"], list) @@ -67,9 +74,6 @@ def _instance_init(self, instance): else: instance._scripts = [] - def _instance_cleanup(self, instance): - pass - def _call_scripts(self, scripts, arguments): ret = True for script in scripts: diff --git a/tuned/plugins/plugin_scsi_host.py b/tuned/plugins/plugin_scsi_host.py index a230a128..2baff6c6 100644 --- a/tuned/plugins/plugin_scsi_host.py +++ b/tuned/plugins/plugin_scsi_host.py @@ -36,6 +36,14 @@ def __init__(self, *args, **kwargs): self._cmd = commands() + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _init_devices(self): super(SCSIHostPlugin, self)._init_devices() self._devices_supported = True @@ -75,13 +83,6 @@ def _get_config_options(cls): "alpm" : None, } - def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False - - def _instance_cleanup(self, instance): - pass - def _get_alpm_policy_file(self, device): return os.path.join("/sys/class/scsi_host/", str(device), "link_power_management_policy") diff --git a/tuned/plugins/plugin_selinux.py b/tuned/plugins/plugin_selinux.py index 313b9c4a..84a34f9b 100644 --- a/tuned/plugins/plugin_selinux.py +++ b/tuned/plugins/plugin_selinux.py @@ -33,6 +33,14 @@ class SelinuxPlugin(base.Plugin): ==== """ + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + @classmethod def _get_selinux_path(self): path = "/sys/fs/selinux" @@ -56,13 +64,6 @@ def _get_config_options(self): "avc_cache_threshold" : None, } - def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False - - def _instance_cleanup(self, instance): - pass - @command_set("avc_cache_threshold") def _set_avc_cache_threshold(self, value, sim, remove): if value is None: diff --git a/tuned/plugins/plugin_service.py b/tuned/plugins/plugin_service.py index 2f2f20ee..9a8a29ca 100644 --- a/tuned/plugins/plugin_service.py +++ b/tuned/plugins/plugin_service.py @@ -215,6 +215,14 @@ def __init__(self, *args, **kwargs): self._has_dynamic_options = True self._init_handler = self._detect_init_system() + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _check_cmd(self, command): (retcode, out) = cmd.execute(command, no_errors = [0]) return retcode == 0 @@ -254,17 +262,13 @@ def _parse_service_options(self, name, val): return service def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True + super(ServicePlugin, self)._instance_init(instance) self._services = collections.OrderedDict([(option[8:], self._parse_service_options(option[8:], self._variables.expand(value))) for option, value in instance.options.items() if option[:8] == "service." and len(option) > 8]) instance._services_original = {} - def _instance_cleanup(self, instance): - pass - def _process_service(self, name, start, enable, runlevel): if start: self._init_handler.start(name) diff --git a/tuned/plugins/plugin_sysctl.py b/tuned/plugins/plugin_sysctl.py index 96854fd0..d0d045d0 100644 --- a/tuned/plugins/plugin_sysctl.py +++ b/tuned/plugins/plugin_sysctl.py @@ -44,10 +44,16 @@ def __init__(self, *args, **kwargs): self._has_dynamic_options = True self._cmd = commands() - def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _instance_init(self, instance): + super(SysctlPlugin, self)._instance_init(instance) # FIXME: do we want to do this here? # recover original values in case of crash storage_key = self._storage_key(instance.name) diff --git a/tuned/plugins/plugin_sysfs.py b/tuned/plugins/plugin_sysfs.py index 4f14e2ad..1a751dd7 100644 --- a/tuned/plugins/plugin_sysfs.py +++ b/tuned/plugins/plugin_sysfs.py @@ -41,16 +41,20 @@ def __init__(self, *args, **kwargs): self._has_dynamic_options = True self._cmd = commands() + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True + super(SysfsPlugin, self)._instance_init(instance) instance._sysfs = dict([(os.path.normpath(key_value[0]), key_value[1]) for key_value in list(instance.options.items())]) instance._sysfs_original = {} - def _instance_cleanup(self, instance): - pass - def _instance_apply_static(self, instance): for key, value in list(instance._sysfs.items()): v = self._variables.expand(value) diff --git a/tuned/plugins/plugin_systemd.py b/tuned/plugins/plugin_systemd.py index 49304ae4..1f913574 100644 --- a/tuned/plugins/plugin_systemd.py +++ b/tuned/plugins/plugin_systemd.py @@ -39,12 +39,13 @@ def __init__(self, *args, **kwargs): super(SystemdPlugin, self).__init__(*args, **kwargs) self._cmd = commands() - def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True + @classmethod + def supports_static_tuning(cls): + return True - def _instance_cleanup(self, instance): - pass + @classmethod + def supports_dynamic_tuning(cls): + return False @classmethod def _get_config_options(cls): diff --git a/tuned/plugins/plugin_uncore.py b/tuned/plugins/plugin_uncore.py index 76af767d..e19af4d9 100644 --- a/tuned/plugins/plugin_uncore.py +++ b/tuned/plugins/plugin_uncore.py @@ -33,6 +33,14 @@ class UncorePlugin(hotplug.Plugin): ==== """ + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _init_devices(self): self._devices_supported = True self._assigned_devices = set() @@ -55,13 +63,6 @@ def _init_devices(self): log.debug("devices: %s", str(self._free_devices)) - def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False - - def _instance_cleanup(self, instance): - pass - def _get(self, dev_dir, file): sysfs_file = SYSFS_DIR + dev_dir + "/" + file value = cmd.read_file(sysfs_file) diff --git a/tuned/plugins/plugin_usb.py b/tuned/plugins/plugin_usb.py index 159d336f..30e5eb26 100644 --- a/tuned/plugins/plugin_usb.py +++ b/tuned/plugins/plugin_usb.py @@ -28,6 +28,14 @@ class USBPlugin(base.Plugin): ==== """ + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + def _init_devices(self): self._devices_supported = True self._free_devices = set() @@ -47,13 +55,6 @@ def _get_config_options(self): "autosuspend" : None, } - def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False - - def _instance_cleanup(self, instance): - pass - def _autosuspend_sysfile(self, device): return "/sys/bus/usb/devices/%s/power/autosuspend" % device diff --git a/tuned/plugins/plugin_video.py b/tuned/plugins/plugin_video.py index 4bd76ddd..df5a6587 100644 --- a/tuned/plugins/plugin_video.py +++ b/tuned/plugins/plugin_video.py @@ -48,8 +48,13 @@ class VideoPlugin(base.Plugin): but will trade off color accuracy. """ - def __init__(self, *args, **kwargs): - super(VideoPlugin, self).__init__(*args, **kwargs) + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False def _init_devices(self): self._devices_supported = True @@ -74,13 +79,6 @@ def _get_config_options(self): "panel_power_savings": None, } - def _instance_init(self, instance): - instance._has_dynamic_tuning = False - instance._has_static_tuning = True - - def _instance_cleanup(self, instance): - pass - def _files(self, device): return { "method" : "/sys/class/drm/%s/device/power_method" % device, diff --git a/tuned/plugins/plugin_vm.py b/tuned/plugins/plugin_vm.py index e86230b6..048f9a95 100644 --- a/tuned/plugins/plugin_vm.py +++ b/tuned/plugins/plugin_vm.py @@ -34,6 +34,14 @@ class VMPlugin(base.Plugin): link:https://www.kernel.org/doc/Documentation/vm/transhuge.txt[Transparent Hugepage Support]. """ + @classmethod + def supports_static_tuning(cls): + return True + + @classmethod + def supports_dynamic_tuning(cls): + return False + @classmethod def _get_config_options(self): return { @@ -42,13 +50,6 @@ def _get_config_options(self): "transparent_hugepage.defrag" : None, } - def _instance_init(self, instance): - instance._has_static_tuning = True - instance._has_dynamic_tuning = False - - def _instance_cleanup(self, instance): - pass - @classmethod def _thp_path(self): path = "/sys/kernel/mm/transparent_hugepage" diff --git a/tuned/units/manager.py b/tuned/units/manager.py index 51f3490c..2bb44313 100644 --- a/tuned/units/manager.py +++ b/tuned/units/manager.py @@ -133,7 +133,7 @@ def destroy_all(self): instance.plugin.destroy_instance, instance) for plugin in self._plugins: - log.debug("cleaning plugin '%s'" % plugin.name) + log.debug("cleaning plugin '%s'" % plugin.name()) self._try_call("destroy_all", None, plugin.cleanup) del self._plugins[:] From 854cd9e1d8c86390cc4dc44c0e9425c4ddb43418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= Date: Tue, 16 Apr 2024 10:43:13 +0200 Subject: [PATCH 2/3] Allow non-default dynamic tuning implementations Let plugins choose between the default (periodic) dynamic tuning and their own implementation. If a plugin uses the default periodic tuning, it must implement the update_tuning method which is then periodically called by the main daemon thread. Non-periodic implementations may, e.g., start their own threads. --- tuned/daemon/daemon.py | 15 ++++++++------- tuned/plugins/base.py | 14 +++++++++++--- tuned/plugins/plugin_cpu.py | 4 ++++ tuned/plugins/plugin_disk.py | 4 ++++ tuned/plugins/plugin_eeepc_she.py | 4 ++++ tuned/plugins/plugin_net.py | 4 ++++ 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/tuned/daemon/daemon.py b/tuned/daemon/daemon.py index d87d8cd0..b1ab159d 100644 --- a/tuned/daemon/daemon.py +++ b/tuned/daemon/daemon.py @@ -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) @@ -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 diff --git a/tuned/plugins/base.py b/tuned/plugins/base.py index 9c053eb5..c7292d6f 100644 --- a/tuned/plugins/base.py +++ b/tuned/plugins/base.py @@ -61,6 +61,10 @@ def supports_static_tuning(cls): def supports_dynamic_tuning(cls): raise NotImplementedError() + @classmethod + def uses_periodic_tuning(cls): + raise NotImplementedError() + # # Plugin configuration manipulation and helpers. # @@ -310,7 +314,7 @@ def instance_update_tuning(self, instance): """ if not instance.active: return - if instance.dynamic_tuning_enabled: + 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): @@ -322,6 +326,7 @@ def instance_unapply_tuning(self, instance, rollback = consts.ROLLBACK_SOFT): if instance.dynamic_tuning_enabled: self._run_for_each_device(instance, self._instance_unapply_dynamic, instance.processed_devices) + self._instance_deinit_dynamic(instance) if instance.static_tuning_enabled: self._call_device_script(instance, instance.script_post, "unapply", instance.processed_devices, @@ -351,11 +356,14 @@ 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): pass diff --git a/tuned/plugins/plugin_cpu.py b/tuned/plugins/plugin_cpu.py index 2204eecb..998896fe 100644 --- a/tuned/plugins/plugin_cpu.py +++ b/tuned/plugins/plugin_cpu.py @@ -217,6 +217,10 @@ def supports_static_tuning(cls): def supports_dynamic_tuning(cls): return True + @classmethod + def uses_periodic_tuning(cls): + return True + def _init_devices(self): self._devices_supported = True self._free_devices = set() diff --git a/tuned/plugins/plugin_disk.py b/tuned/plugins/plugin_disk.py index 7b133929..b95c37ba 100644 --- a/tuned/plugins/plugin_disk.py +++ b/tuned/plugins/plugin_disk.py @@ -103,6 +103,10 @@ def supports_static_tuning(cls): def supports_dynamic_tuning(cls): return True + @classmethod + def uses_periodic_tuning(cls): + return True + def _init_devices(self): super(DiskPlugin, self)._init_devices() self._devices_supported = True diff --git a/tuned/plugins/plugin_eeepc_she.py b/tuned/plugins/plugin_eeepc_she.py index 7c605494..cd192701 100644 --- a/tuned/plugins/plugin_eeepc_she.py +++ b/tuned/plugins/plugin_eeepc_she.py @@ -44,6 +44,10 @@ def supports_static_tuning(cls): def supports_dynamic_tuning(cls): return True + @classmethod + def uses_periodic_tuning(cls): + return True + @classmethod def _get_config_options(self): return { diff --git a/tuned/plugins/plugin_net.py b/tuned/plugins/plugin_net.py index 9e11cd82..44ec1bc9 100644 --- a/tuned/plugins/plugin_net.py +++ b/tuned/plugins/plugin_net.py @@ -167,6 +167,10 @@ def supports_static_tuning(cls): def supports_dynamic_tuning(cls): return True + @classmethod + def uses_periodic_tuning(cls): + return True + def _init_devices(self): self._devices_supported = True self._free_devices = set() From 7476d32aadb669b73b7707fb154d2d275b35fd5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= Date: Tue, 16 Apr 2024 10:46:19 +0200 Subject: [PATCH 3/3] Make the scheduler plugin dynamic 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. --- profiles/openshift/tuned.conf | 2 +- .../runtest.sh | 2 +- tuned-main.conf | 7 +- tuned/plugins/plugin_scheduler.py | 78 +++++++++---------- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/profiles/openshift/tuned.conf b/profiles/openshift/tuned.conf index 197850c5..6c30c2a4 100644 --- a/profiles/openshift/tuned.conf +++ b/profiles/openshift/tuned.conf @@ -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 diff --git a/tests/beakerlib/Traceback-caused-by-scheduler-plugin-with/runtest.sh b/tests/beakerlib/Traceback-caused-by-scheduler-plugin-with/runtest.sh index d5cce712..5a13b7f0 100755 --- a/tests/beakerlib/Traceback-caused-by-scheduler-plugin-with/runtest.sh +++ b/tests/beakerlib/Traceback-caused-by-scheduler-plugin-with/runtest.sh @@ -43,7 +43,7 @@ rlJournalStart rlRun "pushd $PROFILE_DIR/test-profile" cat << EOF > tuned.conf [scheduler] -runtime=0 +dynamic=0 EOF rlRun "popd" diff --git a/tuned-main.conf b/tuned-main.conf index 1f1269f8..edbd49e4 100644 --- a/tuned-main.conf +++ b/tuned-main.conf @@ -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. diff --git a/tuned/plugins/plugin_scheduler.py b/tuned/plugins/plugin_scheduler.py index 171faa98..64cf0cbe 100644 --- a/tuned/plugins/plugin_scheduler.py +++ b/tuned/plugins/plugin_scheduler.py @@ -273,7 +273,7 @@ class SchedulerPlugin(base.Plugin): processes are not processed by the scheduler plug-in. + The CPU overhead of the scheduler plugin can be mitigated by using - the scheduler [option]`runtime` option and setting it to `0`. This + the scheduler [option]`dynamic` option and setting it to `0`. This will completely disable the dynamic scheduler functionality and the perf events will not be monitored and acted upon. The disadvantage ot this approach is the procees/thread tuning will be done only at @@ -283,7 +283,7 @@ class SchedulerPlugin(base.Plugin): ==== ---- [scheduler] - runtime=0 + dynamic=0 isolated_cores=1,3 ---- ==== @@ -465,6 +465,10 @@ def supports_static_tuning(cls): @classmethod def supports_dynamic_tuning(cls): + return True + + @classmethod + def uses_periodic_tuning(cls): return False def _calc_mmap_pages(self, mmap_pages): @@ -482,10 +486,6 @@ def _calc_mmap_pages(self, mmap_pages): def _instance_init(self, instance): super(SchedulerPlugin, self)._instance_init(instance) instance._evlist = None - # this is hack, runtime_tuning should be covered by dynamic_tuning configuration - # TODO: add per plugin dynamic tuning configuration and use dynamic_tuning configuration - # instead of runtime_tuning - instance._runtime_tuning = True # FIXME: do we want to do this here? # recover original values in case of crash @@ -510,42 +510,44 @@ def _instance_init(self, instance): instance._scheduler = instance.options perf_mmap_pages_raw = self._variables.expand(instance.options["perf_mmap_pages"]) - perf_mmap_pages = self._calc_mmap_pages(perf_mmap_pages_raw) - if perf_mmap_pages == 0: + instance._perf_mmap_pages = self._calc_mmap_pages(perf_mmap_pages_raw) + if instance._perf_mmap_pages == 0: log.error("Invalid 'perf_mmap_pages' value specified: '%s', using default kernel value" % perf_mmap_pages_raw) - perf_mmap_pages = None - if perf_mmap_pages is not None and str(perf_mmap_pages) != perf_mmap_pages_raw: + instance._perf_mmap_pages = None + if instance._perf_mmap_pages is not None and str(instance._perf_mmap_pages) != perf_mmap_pages_raw: log.info("'perf_mmap_pages' value has to be power of two, specified: '%s', using: '%d'" % - (perf_mmap_pages_raw, perf_mmap_pages)) + (perf_mmap_pages_raw, instance._perf_mmap_pages)) for k in instance._scheduler: instance._scheduler[k] = self._variables.expand(instance._scheduler[k]) - if self._cmd.get_bool(instance._scheduler.get("runtime", 1)) == "0": - instance._runtime_tuning = False - instance._terminate = threading.Event() - if self._daemon and instance._runtime_tuning: - try: - instance._threads = perf.thread_map() - evsel = perf.evsel(type = perf.TYPE_SOFTWARE, - config = perf.COUNT_SW_DUMMY, - task = 1, comm = 1, mmap = 0, freq = 0, - wakeup_events = 1, watermark = 1, - sample_type = perf.SAMPLE_TID | perf.SAMPLE_CPU) - evsel.open(cpus = self._cpus, threads = instance._threads) - instance._evlist = perf.evlist(self._cpus, instance._threads) - instance._evlist.add(evsel) - if perf_mmap_pages is None: - instance._evlist.mmap() - else: - instance._evlist.mmap(pages = perf_mmap_pages) - # no perf - except: - instance._runtime_tuning = False def _instance_cleanup(self, instance): if instance._evlist: for fd in instance._evlist.get_pollfd(): os.close(fd.name) + def _instance_init_dynamic(self, instance): + super(SchedulerPlugin, self)._instance_init_dynamic(instance) + instance._terminate = threading.Event() + try: + instance._threads = perf.thread_map() + evsel = perf.evsel(type = perf.TYPE_SOFTWARE, + config = perf.COUNT_SW_DUMMY, + task = 1, comm = 1, mmap = 0, freq = 0, + wakeup_events = 1, watermark = 1, + sample_type = perf.SAMPLE_TID | perf.SAMPLE_CPU) + evsel.open(cpus = self._cpus, threads = instance._threads) + instance._evlist = perf.evlist(self._cpus, instance._threads) + instance._evlist.add(evsel) + if instance._perf_mmap_pages is None: + instance._evlist.mmap() + else: + instance._evlist.mmap(pages = instance._perf_mmap_pages) + instance._thread = threading.Thread(target = self._thread_code, args = [instance]) + instance._thread.start() + # no perf + except Exception: + instance._dynamic_tuning_enabled = False + @classmethod def _get_config_options(cls): return { @@ -949,7 +951,7 @@ def _instance_apply_static(self, instance): and len(vals) == 5] sched_cfg = sorted(buf, key=lambda option_vals: option_vals[1][0]) sched_all = dict() - # for runtime tuning + # for dynamic tuning instance._sched_lookup = {} for option, (rule_prio, scheduler, priority, affinity, regex) \ in sched_cfg: @@ -973,9 +975,6 @@ def _instance_apply_static(self, instance): priority, affinity) self._storage.set(self._scheduler_storage_key, self._scheduler_original) - if self._daemon and instance._runtime_tuning: - instance._thread = threading.Thread(target = self._thread_code, args = [instance]) - instance._thread.start() def _restore_ps_affinity(self): try: @@ -1020,9 +1019,6 @@ def _cgroup_cleanup_tasks(self): def _instance_unapply_static(self, instance, rollback = consts.ROLLBACK_SOFT): super(SchedulerPlugin, self)._instance_unapply_static(instance, rollback) - if self._daemon and instance._runtime_tuning: - instance._terminate.set() - instance._thread.join() self._restore_ps_affinity() self._cgroup_restore_affinity() self._cgroup_cleanup_tasks() @@ -1031,6 +1027,10 @@ def _instance_unapply_static(self, instance, rollback = consts.ROLLBACK_SOFT): if self._cgroup_mount_point_init: self._cgroup_finalize() + def _instance_deinit_dynamic(self, instance): + instance._terminate.set() + instance._thread.join() + def _cgroup_verify_affinity_one(self, cgroup, affinity): log.debug("Verifying cgroup '%s' affinity" % cgroup) path = "%s/%s/%s" % (self._cgroup_mount_point, cgroup, "cpuset.cpus")