From d2b80ccaf989659349417aaca1e343975e0cf81e Mon Sep 17 00:00:00 2001 From: douglas-raillard-arm Date: Thu, 11 Feb 2021 17:43:42 +0000 Subject: [PATCH] modules/sched: Fix sched domain flags parsing Recent kernels can have a space-separated list of textual flags rather than a bitfield packed in an int. --- devlib/module/sched.py | 171 +++++++++++++++++++++++++++++++---------- 1 file changed, 129 insertions(+), 42 deletions(-) diff --git a/devlib/module/sched.py b/devlib/module/sched.py index 8ec8c9c..e2ef6df 100644 --- a/devlib/module/sched.py +++ b/devlib/module/sched.py @@ -15,7 +15,6 @@ import logging import re -from enum import Enum from past.builtins import basestring @@ -147,43 +146,44 @@ class SchedProcFSNode(object): self._dyn_attrs[key] = self._build_node(key, nodes[key]) -class DocInt(int): - - # See https://stackoverflow.com/a/50473952/5096023 - def __new__(cls, value, doc): - new = super(DocInt, cls).__new__(cls, value) - new.__doc__ = doc - return new - - -class SchedDomainFlag(DocInt, Enum): +class _SchedDomainFlag: """ - Represents a sched domain flag + Backward-compatible emulation of the former :class:`enum.Enum` that will + work on recent kernels with dynamic sched domain flags name and no value + exposed. """ - # pylint: disable=bad-whitespace - # Domain flags obtained from include/linux/sched/topology.h on v4.17 - # https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux/+/v4.17/include/linux/sched/topology.h#20 - SD_LOAD_BALANCE = 0x0001, "Do load balancing on this domain" - SD_BALANCE_NEWIDLE = 0x0002, "Balance when about to become idle" - SD_BALANCE_EXEC = 0x0004, "Balance on exec" - SD_BALANCE_FORK = 0x0008, "Balance on fork, clone" - SD_BALANCE_WAKE = 0x0010, "Balance on wakeup" - SD_WAKE_AFFINE = 0x0020, "Wake task to waking CPU" - SD_ASYM_CPUCAPACITY = 0x0040, "Groups have different max cpu capacities" - SD_SHARE_CPUCAPACITY = 0x0080, "Domain members share cpu capacity" - SD_SHARE_POWERDOMAIN = 0x0100, "Domain members share power domain" - SD_SHARE_PKG_RESOURCES = 0x0200, "Domain members share cpu pkg resources" - SD_SERIALIZE = 0x0400, "Only a single load balancing instance" - SD_ASYM_PACKING = 0x0800, "Place busy groups earlier in the domain" - SD_PREFER_SIBLING = 0x1000, "Prefer to place tasks in a sibling domain" - SD_OVERLAP = 0x2000, "Sched_domains of this level overlap" - SD_NUMA = 0x4000, "Cross-node balancing" - # Only defined in Android - # https://android.googlesource.com/kernel/common/+/android-4.14/include/linux/sched/topology.h#29 - SD_SHARE_CAP_STATES = 0x8000, "(Android only) Domain members share capacity state" - @classmethod - def check_version(cls, target, logger): + _INSTANCES = {} + """ + Dictionary storing the instances so that they can be compared with ``is`` + operator. + """ + + def __new__(cls, name, value, doc=None): + self = super().__new__(cls) + self.name = name + self._value = value + self.__doc__ = doc + return cls._INSTANCES.setdefault(self, self) + + def __eq__(self, other): + # We *have to* check for "value" as well, otherwise it will be + # impossible to keep in the same set 2 instances with differing values. + return self.name == other.name and self._value == other._value + + def __hash__(self): + return hash((self.name, self._value)) + + @property + def value(self): + value = self._value + if value is None: + raise AttributeError('The kernel does not expose the sched domain flag values') + else: + return value + + @staticmethod + def check_version(target, logger): """ Check the target and see if its kernel version matches our view of the world """ @@ -197,24 +197,111 @@ class SchedDomainFlag(DocInt, Enum): "but target is running v{}".format(ref_parts, parts) ) - def __str__(self): return self.name + def __repr__(self): + return ''.format(self.name) + + +class _SchedDomainFlagMeta(type): + """ + Metaclass of :class:`SchedDomainFlag`. + + Provides some level of emulation of :class:`enum.Enum` behavior for + backward compatibility. + """ + @property + def _flags(self): + return [ + attr + for name, attr in self.__dict__.items() + if name.startswith('SD_') + ] + + def __getitem__(self, i): + return self._flags[i] + + def __len__(self): + return len(self._flags) + + # These would be provided by collections.abc.Sequence, but using it on a + # metaclass seems to have issues around __init_subclass__ + def __iter__(self): + return iter(self._flags) + + def __reversed__(self): + return reversed(self._flags) + + def __contains__(self, x): + return x in self._flags + + @property + def __members__(self): + return {flag.name: flag for flag in self._flags} + + +class SchedDomainFlag(_SchedDomainFlag, metaclass=_SchedDomainFlagMeta): + """ + Represents a sched domain flag. + + .. note:: ``SD_*`` class attributes are deprecated, new code should never + test a given flag against one of these attributes with ``is`` (.e.g ``x + is SchedDomainFlag.SD_LOAD_BALANCE``. This is because the + ``SD_LOAD_BALANCE`` flag exists in two flavors that are not equal: one + with a value (the class attribute) and one without (dynamically created + when parsing flags for new kernels). Old code ran on old kernels should + work fine though. + """ + # pylint: disable=bad-whitespace + # Domain flags obtained from include/linux/sched/topology.h on v4.17 + # https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux/+/v4.17/include/linux/sched/topology.h#20 + SD_LOAD_BALANCE = _SchedDomainFlag("SD_LOAD_BALANCE", 0x0001, "Do load balancing on this domain") + SD_BALANCE_NEWIDLE = _SchedDomainFlag("SD_BALANCE_NEWIDLE", 0x0002, "Balance when about to become idle") + SD_BALANCE_EXEC = _SchedDomainFlag("SD_BALANCE_EXEC", 0x0004, "Balance on exec") + SD_BALANCE_FORK = _SchedDomainFlag("SD_BALANCE_FORK", 0x0008, "Balance on fork, clone") + SD_BALANCE_WAKE = _SchedDomainFlag("SD_BALANCE_WAKE", 0x0010, "Balance on wakeup") + SD_WAKE_AFFINE = _SchedDomainFlag("SD_WAKE_AFFINE", 0x0020, "Wake task to waking CPU") + SD_ASYM_CPUCAPACITY = _SchedDomainFlag("SD_ASYM_CPUCAPACITY", 0x0040, "Groups have different max cpu capacities") + SD_SHARE_CPUCAPACITY = _SchedDomainFlag("SD_SHARE_CPUCAPACITY", 0x0080, "Domain members share cpu capacity") + SD_SHARE_POWERDOMAIN = _SchedDomainFlag("SD_SHARE_POWERDOMAIN", 0x0100, "Domain members share power domain") + SD_SHARE_PKG_RESOURCES = _SchedDomainFlag("SD_SHARE_PKG_RESOURCES", 0x0200, "Domain members share cpu pkg resources") + SD_SERIALIZE = _SchedDomainFlag("SD_SERIALIZE", 0x0400, "Only a single load balancing instance") + SD_ASYM_PACKING = _SchedDomainFlag("SD_ASYM_PACKING", 0x0800, "Place busy groups earlier in the domain") + SD_PREFER_SIBLING = _SchedDomainFlag("SD_PREFER_SIBLING", 0x1000, "Prefer to place tasks in a sibling domain") + SD_OVERLAP = _SchedDomainFlag("SD_OVERLAP", 0x2000, "Sched_domains of this level overlap") + SD_NUMA = _SchedDomainFlag("SD_NUMA", 0x4000, "Cross-node balancing") + # Only defined in Android + # https://android.googlesource.com/kernel/common/+/android-4.14/include/linux/sched/topology.h#29 + SD_SHARE_CAP_STATES = _SchedDomainFlag("SD_SHARE_CAP_STATES", 0x8000, "(Android only) Domain members share capacity state") + class SchedDomain(SchedProcFSNode): """ Represents a sched domain as seen through procfs """ def __init__(self, nodes): - super(SchedDomain, self).__init__(nodes) + super().__init__(nodes) - obj_flags = set() - for flag in list(SchedDomainFlag): - if self.flags & flag.value == flag.value: - obj_flags.add(flag) + flags = self.flags + # Recent kernels now have a space-separated list of flags instead of a + # packed bitfield + if isinstance(flags, str): + flags = { + _SchedDomainFlag(name=name, value=None) + for name in flags.split() + } + else: + def has_flag(flags, flag): + return flags & flag.value == flag.value - self.flags = obj_flags + flags = { + flag + for flag in SchedDomainFlag + if has_flag(flags, flag) + } + + self.flags = flags class SchedProcFSData(SchedProcFSNode):