diff options
| -rw-r--r-- | examples/custom_strategy.py | 2 | ||||
| -rw-r--r-- | robusta_krr/core/abstract/formatters.py | 3 | ||||
| -rw-r--r-- | robusta_krr/core/abstract/strategies.py | 3 | ||||
| -rw-r--r-- | robusta_krr/strategies/simple.py | 33 | ||||
| -rw-r--r-- | robusta_krr/utils/display_name.py | 20 |
5 files changed, 47 insertions, 14 deletions
diff --git a/examples/custom_strategy.py b/examples/custom_strategy.py index f5f2e6c..5917dc4 100644 --- a/examples/custom_strategy.py +++ b/examples/custom_strategy.py @@ -16,8 +16,6 @@ class CustomStrategySettings(StrategySettings): class CustomStrategy(BaseStrategy[CustomStrategySettings]): - __display_name__ = "my_strategy" - def run(self, history_data: HistoryData, object_data: K8sObjectData) -> RunResult: return { ResourceType.CPU: ResourceRecommendation(request=self.settings.param_1, limit=None), diff --git a/robusta_krr/core/abstract/formatters.py b/robusta_krr/core/abstract/formatters.py index 359edab..a4d1e7b 100644 --- a/robusta_krr/core/abstract/formatters.py +++ b/robusta_krr/core/abstract/formatters.py @@ -4,6 +4,8 @@ import abc import os from typing import TYPE_CHECKING, Any, TypeVar +from robusta_krr.utils.display_name import add_display_name + if TYPE_CHECKING: from robusta_krr.core.models.result import Result @@ -14,6 +16,7 @@ DEFAULT_FORMATTERS_PATH = os.path.join(os.path.dirname(__file__), "formatters") Self = TypeVar("Self", bound="BaseFormatter") +@add_display_name(postfix="Formatter") class BaseFormatter(abc.ABC): """Base class for result formatters.""" diff --git a/robusta_krr/core/abstract/strategies.py b/robusta_krr/core/abstract/strategies.py index 476779a..061f94a 100644 --- a/robusta_krr/core/abstract/strategies.py +++ b/robusta_krr/core/abstract/strategies.py @@ -8,6 +8,7 @@ from typing import Generic, Optional, TypeVar, get_args import pydantic as pd from robusta_krr.core.models.result import K8sObjectData, ResourceType +from robusta_krr.utils.display_name import add_display_name class ResourceRecommendation(pd.BaseModel): @@ -38,8 +39,10 @@ RunResult = dict[ResourceType, ResourceRecommendation] Self = TypeVar("Self", bound="BaseStrategy") +@add_display_name(postfix="Strategy") class BaseStrategy(abc.ABC, Generic[_StrategySettings]): __display_name__: str + settings: _StrategySettings def __init__(self, settings: _StrategySettings): diff --git a/robusta_krr/strategies/simple.py b/robusta_krr/strategies/simple.py index bb20082..5dba2db 100644 --- a/robusta_krr/strategies/simple.py +++ b/robusta_krr/strategies/simple.py @@ -14,27 +14,36 @@ from robusta_krr.core.abstract.strategies import ( class SimpleStrategySettings(StrategySettings): - cpu_percentile: Decimal = pd.Field(99, gt=0, description="The percentile to use for the request recommendation.") - memory_percentile: Decimal = pd.Field( - 105, gt=0, description="The percentile to use for the request recommendation." + cpu_percentile: Decimal = pd.Field( + 99, gt=0, le=100, description="The percentile to use for the CPU recommendation." ) + memory_buffer_percentage: Decimal = pd.Field( + 5, gt=0, description="The percentage of added buffer to the peak memory usage for memory recommendation." + ) + + def calculate_memory_proposal(self, data: dict[str, list[Decimal]]) -> Decimal: + data_ = [value for values in data.values() for value in values] + if len(data_) == 0: + return Decimal("NaN") + + return max(data_) * Decimal(1 + self.memory_buffer_percentage / 100) + + def calculate_cpu_proposal(self, data: dict[str, list[Decimal]]) -> Decimal: + data_ = [value for values in data.values() for value in values] + if len(data_) == 0: + return Decimal("NaN") + + return data_[int((len(data_) - 1) * self.cpu_percentile / 100)] class SimpleStrategy(BaseStrategy[SimpleStrategySettings]): __display_name__ = "simple" def run(self, history_data: HistoryData, object_data: K8sObjectData) -> RunResult: - cpu_usage = self._calculate_percentile(history_data[ResourceType.CPU], self.settings.cpu_percentile) - memory_usage = self._calculate_percentile(history_data[ResourceType.Memory], self.settings.memory_percentile) + cpu_usage = self.settings.calculate_cpu_proposal(history_data[ResourceType.CPU]) + memory_usage = self.settings.calculate_memory_proposal(history_data[ResourceType.Memory]) return { ResourceType.CPU: ResourceRecommendation(request=cpu_usage, limit=None), ResourceType.Memory: ResourceRecommendation(request=memory_usage, limit=memory_usage), } - - def _calculate_percentile(self, data: dict[str, list[Decimal]], percentile: Decimal) -> Decimal: - data_ = [value for values in data.values() for value in values] - if len(data_) == 0: - return Decimal("NaN") - - return max(data_) * percentile / 100 diff --git a/robusta_krr/utils/display_name.py b/robusta_krr/utils/display_name.py new file mode 100644 index 0000000..3cc89ce --- /dev/null +++ b/robusta_krr/utils/display_name.py @@ -0,0 +1,20 @@ +from typing import Callable, TypeVar + +_T = TypeVar("_T") + + +def add_display_name(*, postfix: str) -> Callable[[type[_T]], type[_T]]: + """Add a decorator factory to add __display_name__ property to the class.""" + + def decorator(cls: type[_T]) -> type[_T]: + class DisplayNameProperty: + def __get__(self, instance, owner): + if owner.__name__.lower().endswith(postfix.lower()): + return owner.__name__[: -len(postfix)] + + return owner.__name__ + + cls.__display_name__ = DisplayNameProperty() + return cls + + return decorator |
