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

requires/types: introduce expression type (expr.py) #913

Open
wants to merge 1 commit into
base: main
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
620 changes: 620 additions & 0 deletions doc/source/contrib/language_ref/property_ref/requirement_types.rst

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions hotsos/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class NotEnoughParametersError(Exception):
"""Raised when an operation did not get enough parameters to operate."""


class TooManyParametersError(Exception):
"""Raised when an operation did get more parameters than expected."""


class MissingRequiredParameterError(Exception):
"""Raised when an operation did not get a parameter required
for the operation."""
Expand All @@ -59,3 +63,7 @@ class PreconditionError(Exception):

class ExpectationNotMetError(Exception):
"""Raised when an operation's expectation is not met."""


class NoSuchPropertyError(Exception):
"""Raised when an object does not have a property where it should."""
6 changes: 4 additions & 2 deletions hotsos/core/plugins/kernel/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,13 @@ def huge_pages_enabled(self):

@property
def hugetlb_to_mem_total_percentage(self):
return round((self.Hugetlb * 100) / self.MemTotal)
return (self.MemTotal and round(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add or 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's redundant since (int and int) would yield an int in either case.

PS for future travelers: The code is written this way to avoid division by zero.

(self.Hugetlb * 100) / self.MemTotal))

@property
def mem_avail_to_mem_total_percentage(self):
return round((self.MemAvailable * 100) / self.MemTotal)
return (self.MemTotal and round(
(self.MemAvailable * 100) / self.MemTotal))

@property
def hugep_used_to_hugep_total_percentage(self):
Expand Down
14 changes: 13 additions & 1 deletion hotsos/core/ycheck/engine/properties/checks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from functools import cached_property

from propertree.propertree2 import PTreeLogicalGrouping
from propertree.propertree2 import (
PTreeLogicalGrouping,
PTreeOverrideLiteralType
)
from hotsos.core.log import log
from hotsos.core.ycheck.engine.properties.common import (
YPropertyOverrideBase,
Expand All @@ -11,6 +14,9 @@
from hotsos.core.ycheck.engine.properties.requires.requires import (
YPropertyRequires
)
from hotsos.core.ycheck.engine.properties.requires.types.expr import (
YPropertyExpr
)
from hotsos.core.ycheck.engine.properties.search import (
YPropertySearch,
)
Expand Down Expand Up @@ -162,6 +168,12 @@ def result(self):
stop_executon = False
for member in self.members:
for item in member:
# Allow implicit declaration of expression.
if isinstance(item, PTreeOverrideLiteralType):
item = YPropertyExpr(item.root, "expression",
item.content, item.override_path,
context=self.context)

# Ignore these here as they are used by search properties.
if isinstance(item, YPropertyInput):
continue
Expand Down
93 changes: 52 additions & 41 deletions hotsos/core/ycheck/engine/properties/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,44 +284,19 @@ def __getattr__(self, key):
return self.data.get(key)


class YPropertyBase(PTreeOverrideBase):
"""
Base class used for all YAML property objects. Implements and extends
PTreeOverrideBase to provide frequently used methods and helpers to
property implementations.
"""
def __init__(self, *args, **kwargs):
self._cache = PropertyCache()
super().__init__(*args, **kwargs)

def resolve_var(self, name):
"""
Resolve variable with name to value. This can be used speculatively and
will return the name as value if it can't be resolved.
"""
if not name.startswith('$'):
return name

if hasattr(self, 'context'):
if self.context.vars:
_name = name.partition('$')[2]
return self.context.vars.resolve(_name)
class PythonEntityResolver:
"""A class to resolve Python entities (e.g. variable, property)
by their import path."""

log.warning("could not resolve var '%s' - vars not found in "
"context", name)
# Class-level cache for Python module imports for future use

return name
import_cache = None

@property
def cache(self):
"""
All properties get their own cache object that they can use as they
wish.
"""
return self._cache
def __init__(self, context):
self.context = context

def _load_from_import_cache(self, key):
""" Retrieve from global context if one exists.
""" Retrieve from global cache if one exists.

@param key: key to retrieve
"""
Expand Down Expand Up @@ -374,16 +349,14 @@ def _add_to_import_cache(self, key, value):
@param key: key to save
@param value: value to save
"""
if self.context is None:
log.info("context not available - cannot save '%s'", key)

if not self.import_cache:
self.import_cache = {key: value}
log.debug("import cache initialized with initial key `%s`", key)
return

c = getattr(self.context, 'import_cache')
if c:
c[key] = value
else:
c = {key: value}
setattr(self.context, 'import_cache', c)
self.import_cache[key] = value
log.debug("import cache updated for key %s", key)

def get_cls(self, import_str):
""" Import and instantiate Python class.
Expand Down Expand Up @@ -535,6 +508,44 @@ def get_import(self, import_str):
return self.get_attribute(import_str)


class YPropertyBase(PTreeOverrideBase, PythonEntityResolver):
"""
Base class used for all YAML property objects. Implements and extends
PTreeOverrideBase to provide frequently used methods and helpers to
property implementations.
"""

def __init__(self, *args, **kwargs):
self._cache = PropertyCache()
super().__init__(*args, **kwargs)

def resolve_var(self, name):
"""
Resolve variable with name to value. This can be used speculatively and
will return the name as value if it can't be resolved.
"""
if not name.startswith('$'):
return name

if hasattr(self, 'context'):
if self.context.vars:
_name = name.partition('$')[2]
return self.context.vars.resolve(_name)

log.warning("could not resolve var '%s' - vars not found in "
"context", name)

return name

@property
def cache(self):
"""
All properties get their own cache object that they can use as they
wish.
"""
return self._cache


class YPropertyOverrideBase(YPropertyBase, PTreeOverrideBase):
""" Base class for all simple/flat property objects. """

Expand Down
4 changes: 3 additions & 1 deletion hotsos/core/ycheck/engine/properties/requires/requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
property as rproperty,
path,
varops,
expr,
)

CACHE_CHECK_KEY = '__PREVIOUSLY_CACHED_PROPERTY_TYPE'
Expand Down Expand Up @@ -94,7 +95,8 @@ class YPropertyRequires(YPropertyMappedOverrideBase):
systemd.YRequirementTypeSystemd,
rproperty.YRequirementTypeProperty,
path.YRequirementTypePath,
varops.YPropertyVarOps]
varops.YPropertyVarOps,
expr.YPropertyExpr,]
# We want to be able to use this property both on its own and as a member
# of other mapping properties e.g. Checks. The following setting enables
# this.
Expand Down
Loading
Loading