from . import base from .decorators import * import tuned.logs import os import errno import struct import glob from tuned.utils.commands import commands log = tuned.logs.get() cmd = commands() class VMPlugin(base.Plugin): """ Tunes selected sysctl options in `/proc/sys/vm`, currently [option]`dirty_ratio`, [option]`dirty_background_ratio`, [option]`dirty_bytes`, and [option]`dirty_background_bytes`. See https://docs.kernel.org/admin-guide/sysctl/vm.html for detailed documentation of these options. Additionaly enables or disables transparent huge pages depending on the value of the [option]`transparent_hugepages` option. The option can have one of three possible values: `always`, `madvise` and `never`. .Disable transparent hugepages ==== ---- [vm] transparent_hugepages=never ---- ==== The [option]`transparent_hugepage.defrag` option specifies the defragmentation policy. Possible values for this option are `always`, `defer`, `defer+madvise`, `madvise` and `never`. For a detailed explanation of these values refer to link:https://www.kernel.org/doc/Documentation/vm/transhuge.txt[Transparent Hugepage Support]. """ @classmethod def _get_config_options(self): return { "transparent_hugepages" : None, "transparent_hugepage" : None, "transparent_hugepage.defrag" : None, "dirty_bytes" : None, "dirty_ratio" : None, "dirty_background_bytes" : None, "dirty_background_ratio" : None } @staticmethod def _check_conflicting_dirty_options(instance, first, second): if instance.options[first] is not None and instance.options[second] is not None: log.error("Conflicting options '%s' and '%s', this may cause undefined behavior." % (first, second)) @staticmethod def _proc_sys_vm_option_path(option): return os.path.join("/proc/sys/vm", option) def _instance_init(self, instance): instance._has_static_tuning = True instance._has_dynamic_tuning = False self._check_conflicting_dirty_options(instance, "dirty_bytes", "dirty_ratio") self._check_conflicting_dirty_options(instance, "dirty_background_bytes", "dirty_background_ratio") def _instance_cleanup(self, instance): pass @classmethod def _thp_path(self): path = "/sys/kernel/mm/transparent_hugepage" if not os.path.exists(path): # RHEL-6 support path = "/sys/kernel/mm/redhat_transparent_hugepage" return path @command_set("transparent_hugepages") def _set_transparent_hugepages(self, value, instance, sim, remove): if value not in ["always", "never", "madvise"]: if not sim: log.warning("Incorrect 'transparent_hugepages' value '%s'." % str(value)) return None cmdline = cmd.read_file("/proc/cmdline", no_error = True) if cmdline.find("transparent_hugepage=") > 0: if not sim: log.info("transparent_hugepage is already set in kernel boot cmdline, ignoring value from profile") return None sys_file = os.path.join(self._thp_path(), "enabled") if os.path.exists(sys_file): if not sim: cmd.write_to_file(sys_file, value, \ no_error = [errno.ENOENT] if remove else False) return value else: if not sim: log.warning("Option 'transparent_hugepages' is not supported on current hardware.") return None # just an alias to transparent_hugepages @command_set("transparent_hugepage") def _set_transparent_hugepage(self, value, instance, sim, remove): self._set_transparent_hugepages(value, instance, sim, remove) @command_get("transparent_hugepages") def _get_transparent_hugepages(self, instance): sys_file = os.path.join(self._thp_path(), "enabled") if os.path.exists(sys_file): return cmd.get_active_option(cmd.read_file(sys_file)) else: return None # just an alias to transparent_hugepages @command_get("transparent_hugepage") def _get_transparent_hugepage(self, instance): return self._get_transparent_hugepages(instance) @command_set("transparent_hugepage.defrag") def _set_transparent_hugepage_defrag(self, value, instance, sim, remove): sys_file = os.path.join(self._thp_path(), "defrag") if os.path.exists(sys_file): if not sim: cmd.write_to_file(sys_file, value, \ no_error = [errno.ENOENT] if remove else False) return value else: if not sim: log.warning("Option 'transparent_hugepage.defrag' is not supported on current hardware.") return None @command_get("transparent_hugepage.defrag") def _get_transparent_hugepage_defrag(self, instance): sys_file = os.path.join(self._thp_path(), "defrag") if os.path.exists(sys_file): return cmd.get_active_option(cmd.read_file(sys_file)) else: return None def _check_twice_pagesize(self, option, int_value): min_bytes = 2 * int(cmd.getconf("PAGESIZE")) if int_value < min_bytes: log.error("The value of '%s' must be at least twice the page size (%s)." % (option, min_bytes)) return False return True def _check_positive(self, option, int_value): if int_value <= 0: log.error("The value of '%s' must be positive." % option) return False return True def _check_ratio(self, option, int_value): if not 0 <= int_value <= 100: log.error("The value of '%s' must be between 0 and 100." % option) return False return True @command_custom("dirty_bytes") def _dirty_bytes(self, enabling, value, verify, ignore_missing, instance): return self._dirty_option("dirty_bytes", "dirty_ratio", self._check_twice_pagesize, enabling, value, verify) @command_custom("dirty_ratio") def _dirty_ratio(self, enabling, value, verify, ignore_missing, instance): return self._dirty_option("dirty_ratio", "dirty_bytes", self._check_ratio, enabling, value, verify) @command_custom("dirty_background_bytes") def _dirty_background_bytes(self, enabling, value, verify, ignore_missing, instance): return self._dirty_option("dirty_background_bytes", "dirty_background_ratio", self._check_positive, enabling, value, verify) @command_custom("dirty_background_ratio") def _dirty_background_ratio(self, enabling, value, verify, ignore_missing, instance): return self._dirty_option("dirty_background_ratio", "dirty_background_bytes", self._check_ratio, enabling, value, verify) def _dirty_option(self, option, counterpart, check_fun, enabling, value, verify): option_path = self._proc_sys_vm_option_path(option) counterpart_path = self._proc_sys_vm_option_path(counterpart) option_key = self._storage_key(command_name=option) counterpart_key = self._storage_key(command_name=counterpart) if not os.path.isfile(option_path): log.warning("Option '%s' is not supported on the current hardware." % option) current_value = cmd.read_file(option_path).strip() if verify: return current_value == value if enabling: try: int_value = int(value) except ValueError: log.error("The value of '%s' must be an integer." % option) if not check_fun(option, int_value): return None if current_value == value: log.info("Not setting option '%s' to '%s', already set." % (option, value)) return value # Backup: if the option (e.g. dirty_bytes) is currently 0, # its counterpart (dirty_ratio) is the active one, so we # back up that one instead. if int(current_value) == 0: current_counterpart_value = cmd.read_file(counterpart_path).strip() self._storage.set(counterpart_key, current_counterpart_value) else: self._storage.set(option_key, current_value) log.info("Setting option '%s' to '%s'." % (option, value)) cmd.write_to_file(option_path, value) return value # Rollback is analogous to the backup: if there is no backed up # value for this option, it means that its counterpart was active # and we have to restore that one. old_value = self._storage.get(option_key) old_counterpart_value = self._storage.get(counterpart_key) if old_value is not None: log.info("Setting option '%s' to '%s'" % (option, old_value)) cmd.write_to_file(option_path, old_value) elif old_counterpart_value is not None: log.info("Setting option '%s' to '%s'" % (counterpart, old_counterpart_value)) cmd.write_to_file(counterpart_path, old_counterpart_value) else: log.info("Not restoring '%s', previous value is the same or unknown." % option) return None