From 8d9cd00387426ed0cf220f920be4f9e185f61e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= Date: Fri, 22 Nov 2019 14:56:27 +0100 Subject: [PATCH] plugins: support cpuinfo_regex and uname_regex matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also added cpuinfo_check builtin function. It's possible to write now: [variables] cannonalake_cpuinfo=.*\bGenuineIntel\b.* cannonlake_uname=x86_64 [cpu_cannonlake] type=cpu cpuinfo_regex=${cannonlake_cpuinfo} uname_regex=${cannonlake_uname} force_latency=C1 [disk_cannonlake] type=disk cpuinfo_regex=${cannonlake_cpuinfo} uname_regex=${cannonlake_uname} readahead=4092 ... And the tuning will be applied only on machines which have 'GenuineIntel' string in the /proc/cpuinfo and x86_64 architecture (from uname). Both 'cpuinfo_regex' and 'uname_regex' are optional - if not used it has the same effect as if matching regex is used. This example used variables but it also works without it. Also cpuinfo_check builtin function was added, so it's possible to write the following now: [main] include=${f:cpuinfo_check:\bGenuineIntel\b:intel_profile:other_profile} It tries to match the regex '\bGenuineIntel\b' in the /proc/cpuinfo and if it matches it includes 'intel_profile', if not it includes 'other_profile'. If colon ':' needs to be used in the regex, it needs to be escaped, i.e.: '\:' It's even possible to combine it with the generic profiles, e.g.: [main] include=base_profile${f:cpuinfo_check:\bGenuineIntel\b:,intel_profile} It will always include 'base_profile' and if the cpuinfo matches GenuineIntel it also adds the intel_profile. More variants are possible, the generic syntax is: ${f:cpuinfo_check:REGEX1:STR1:REGEX2:STR2:...[:FALLBACK_STR]} It returns STR1 if REGEX1 matches, STR2 if REGEX2 matches, and FALLBACK_STR if nothing matches. If there is no FALLBACK_STR it returns empty string on no match. It exits on the first match found and no more regexes are processed in such case TODO: Current limitation: Variables are not expanded in the include, so it's not possible to write: [main] include=${f:cpuinfo_check:${regex}:test} [variables] regex=TEST Resolves: rhbz#1748965 Signed-off-by: Jaroslav Škarvada --- .../functions/function_cpuinfo_check.py | 32 +++++++++++++++++++ tuned/profiles/loader.py | 6 ++++ tuned/profiles/merger.py | 4 +++ tuned/profiles/unit.py | 20 +++++++++++- tuned/units/manager.py | 25 +++++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tuned/profiles/functions/function_cpuinfo_check.py diff --git a/tuned/profiles/functions/function_cpuinfo_check.py b/tuned/profiles/functions/function_cpuinfo_check.py new file mode 100644 index 0000000..6519482 --- /dev/null +++ b/tuned/profiles/functions/function_cpuinfo_check.py @@ -0,0 +1,32 @@ +import re +import tuned.logs +from . import base + +log = tuned.logs.get() + +class cpuinfo_check(base.Function): + """ + Checks regexes against /proc/cpuinfo. Accepts arguments in the + following form: REGEX1, STR1, REGEX2, STR2, ...[, STR_FALLBACK] + If REGEX1 matches something in /proc/cpuinfo it expands to STR1, + if REGEX2 matches it expands to STR2. It stops on the first match, + i.e. if REGEX1 matches, no more regexes are processed. If none + regex matches it expands to STR_FALLBACK. If there is no fallback, + it expands to empty string. + """ + def __init__(self): + # unlimited number of arguments, min 2 arguments + super(cpuinfo_check, self).__init__("cpuinfo_check", 0, 2) + + def execute(self, args): + if not super(cpuinfo_check, self).execute(args): + return None + cpuinfo = self._cmd.read_file("/proc/cpuinfo") + for i in range(0, len(args), 2): + if i + 1 < len(args): + if re.search(args[i], cpuinfo, re.MULTILINE): + return args[i + 1] + if len(args) % 2: + return args[-1] + else: + return "" diff --git a/tuned/profiles/loader.py b/tuned/profiles/loader.py index e844472..050c1d8 100644 --- a/tuned/profiles/loader.py +++ b/tuned/profiles/loader.py @@ -62,12 +62,18 @@ class Loader(object): del(final_profile.units["variables"]) # FIXME hack, do all variable expansions in one place self._expand_vars_in_devices(final_profile) + self._expand_vars_in_regexes(final_profile) return final_profile def _expand_vars_in_devices(self, profile): for unit in profile.units: profile.units[unit].devices = self._variables.expand(profile.units[unit].devices) + def _expand_vars_in_regexes(self, profile): + for unit in profile.units: + profile.units[unit].cpuinfo_regex = self._variables.expand(profile.units[unit].cpuinfo_regex) + profile.units[unit].uname_regex = self._variables.expand(profile.units[unit].uname_regex) + def _load_profile(self, profile_names, profiles, processed_files): for name in profile_names: filename = self._profile_locator.get_config(name, processed_files) diff --git a/tuned/profiles/merger.py b/tuned/profiles/merger.py index 2364543..d3520c5 100644 --- a/tuned/profiles/merger.py +++ b/tuned/profiles/merger.py @@ -35,6 +35,10 @@ class Merger(object): profile_a.units[unit_name].devices = unit.devices if unit.devices_udev_regex is not None: profile_a.units[unit_name].devices_udev_regex = unit.devices_udev_regex + if unit.cpuinfo_regex is not None: + profile_a.units[unit_name].cpuinfo_regex = unit.cpuinfo_regex + if unit.uname_regex is not None: + profile_a.units[unit_name].uname_regex = unit.uname_regex if unit.script_pre is not None: profile_a.units[unit_name].script_pre = unit.script_pre if unit.script_post is not None: diff --git a/tuned/profiles/unit.py b/tuned/profiles/unit.py index f944d1d..0f5778e 100644 --- a/tuned/profiles/unit.py +++ b/tuned/profiles/unit.py @@ -6,7 +6,7 @@ class Unit(object): """ __slots__ = [ "_name", "_type", "_enabled", "_replace", "_devices", "_devices_udev_regex", \ - "_script_pre", "_script_post", "_options" ] + "_cpuinfo_regex", "_uname_regex", "_script_pre", "_script_post", "_options" ] def __init__(self, name, config): self._name = name @@ -15,6 +15,8 @@ class Unit(object): self._replace = config.pop("replace", False) in [True, "true", 1, "1"] self._devices = config.pop("devices", "*") self._devices_udev_regex = config.pop("devices_udev_regex", None) + self._cpuinfo_regex = config.pop("cpuinfo_regex", None) + self._uname_regex = config.pop("uname_regex", None) self._script_pre = config.pop("script_pre", None) self._script_post = config.pop("script_post", None) self._options = collections.OrderedDict(config) @@ -59,6 +61,22 @@ class Unit(object): def devices_udev_regex(self, value): self._devices_udev_regex = value + @property + def cpuinfo_regex(self): + return self._cpuinfo_regex + + @cpuinfo_regex.setter + def cpuinfo_regex(self, value): + self._cpuinfo_regex = value + + @property + def uname_regex(self): + return self._uname_regex + + @uname_regex.setter + def uname_regex(self, value): + self._uname_regex = value + @property def script_pre(self): return self._script_pre diff --git a/tuned/units/manager.py b/tuned/units/manager.py index 3594340..2fee283 100644 --- a/tuned/units/manager.py +++ b/tuned/units/manager.py @@ -1,9 +1,12 @@ import collections +import os +import re import traceback import tuned.exceptions import tuned.logs import tuned.plugins.exceptions import tuned.consts as consts +from tuned.utils.commands import commands log = tuned.logs.get() @@ -23,6 +26,7 @@ class Manager(object): self._hardware_inventory = hardware_inventory self._instances = [] self._plugins = [] + self._cmd = commands() @property def plugins(self): @@ -36,12 +40,33 @@ class Manager(object): def plugins_repository(self): return self._plugins_repository + def _unit_matches_cpuinfo(self, unit): + if unit.cpuinfo_regex is None: + return True + return re.search(unit.cpuinfo_regex, + self._cmd.read_file("/proc/cpuinfo"), + re.MULTILINE) is not None + + def _unit_matches_uname(self, unit): + if unit.uname_regex is None: + return True + return re.search(unit.uname_regex, + " ".join(os.uname()), + re.MULTILINE) is not None + def create(self, instances_config): instance_info_list = [] for instance_name, instance_info in list(instances_config.items()): if not instance_info.enabled: log.debug("skipping disabled instance '%s'" % instance_name) continue + if not self._unit_matches_cpuinfo(instance_info): + log.debug("skipping instance '%s', cpuinfo does not match" % instance_name) + continue + if not self._unit_matches_uname(instance_info): + log.debug("skipping instance '%s', uname does not match" % instance_name) + continue + instance_info.options.setdefault("priority", self._def_instance_priority) instance_info.options["priority"] = int(instance_info.options["priority"]) instance_info_list.append(instance_info)