diff options
| author | Павел Жуков <33721692+LeaveMyYard@users.noreply.github.com> | 2023-02-23 20:17:56 +0200 |
|---|---|---|
| committer | Павел Жуков <33721692+LeaveMyYard@users.noreply.github.com> | 2023-02-23 20:17:56 +0200 |
| commit | 19b20627134302e2b1d0578a1933c60b165ceb42 (patch) | |
| tree | 1625d5660e37094de8d73538cdab676905114f01 | |
| parent | b472dae4750169c9eb1be831a328389244c17c6c (diff) | |
Implement structure for strategies and settings
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | robusta_krr/core/config.py | 11 | ||||
| -rw-r--r-- | robusta_krr/core/loader.py | 0 | ||||
| -rw-r--r-- | robusta_krr/core/result.py | 26 | ||||
| -rw-r--r-- | robusta_krr/core/runner.py | 13 | ||||
| -rw-r--r-- | robusta_krr/core/strategies/__init__.py | 25 | ||||
| -rw-r--r-- | robusta_krr/core/strategies/base.py | 24 | ||||
| -rw-r--r-- | robusta_krr/core/strategies/simple.py | 19 | ||||
| -rw-r--r-- | robusta_krr/main.py | 2 |
9 files changed, 122 insertions, 1 deletions
@@ -129,4 +129,5 @@ dmypy.json .pyre/ -.DS_Store
\ No newline at end of file +.DS_Store +robusta_lib
\ No newline at end of file diff --git a/robusta_krr/core/config.py b/robusta_krr/core/config.py index b76488a..57dd939 100644 --- a/robusta_krr/core/config.py +++ b/robusta_krr/core/config.py @@ -1,6 +1,7 @@ import pydantic as pd from robusta_krr.core.formatters import FormatType +from robusta_krr.core.strategies import StrategySettings, BaseStrategy, get_strategy_from_name class Config(pd.BaseSettings): @@ -9,3 +10,13 @@ class Config(pd.BaseSettings): prometheus_url: str | None = pd.Field(None) format: FormatType = pd.Field(FormatType.text) + strategy: str = pd.Field("simple") + strategy_settings: StrategySettings = pd.Field(StrategySettings()) + + def create_strategy(self) -> BaseStrategy: + return get_strategy_from_name(self.strategy)(self.strategy_settings) + + @pd.validator("strategy") + def validate_strategy(cls, v: str) -> str: + get_strategy_from_name(v) # raises if strategy is not found + return v diff --git a/robusta_krr/core/loader.py b/robusta_krr/core/loader.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/robusta_krr/core/loader.py diff --git a/robusta_krr/core/result.py b/robusta_krr/core/result.py index 6d13e6e..bd4f572 100644 --- a/robusta_krr/core/result.py +++ b/robusta_krr/core/result.py @@ -1,9 +1,34 @@ import pydantic as pd +import enum from robusta_krr.core.formatters import BaseFormatter, get_formatter, FormatType +class ResourceRecommendation(pd.BaseModel): + current: float + recommended: float + + +class ResourceType(str, enum.Enum): + cpu = "cpu" + memory = "memory" + + +class ObjectData(pd.BaseModel): + name: str + kind: str + namespace: str + + +class ResourceScan(pd.BaseModel): + object: ObjectData + requests: dict[ResourceType, ResourceRecommendation] + limits: dict[ResourceType, ResourceRecommendation] + + class Result(pd.BaseModel): + scans: list[ResourceScan] + def format(self, formatter: BaseFormatter | FormatType) -> str: """Format the result. @@ -13,6 +38,7 @@ class Result(pd.BaseModel): Returns: The formatted result. """ + if isinstance(formatter, str): formatter = get_formatter(formatter) diff --git a/robusta_krr/core/runner.py b/robusta_krr/core/runner.py index 57fcb73..72cd1a5 100644 --- a/robusta_krr/core/runner.py +++ b/robusta_krr/core/runner.py @@ -1,6 +1,14 @@ from robusta_krr.core.result import Result from robusta_krr.utils.configurable import Configurable from robusta_krr.utils.version import get_version +from robusta_krr.core.strategies import ( + BaseStrategy, + StrategySettings, + HistoryData, + ObjectData, + ResourceType, + get_strategy_from_name, +) class Runner(Configurable): @@ -12,6 +20,11 @@ class Runner(Configurable): self.echo(formatted) def _collect_result(self) -> Result: + data: HistoryData = {} + strategy = self.config.create_strategy() + + strategy.run(data, {}, ResourceType.cpu) + return Result() def run(self) -> None: diff --git a/robusta_krr/core/strategies/__init__.py b/robusta_krr/core/strategies/__init__.py new file mode 100644 index 0000000..309340e --- /dev/null +++ b/robusta_krr/core/strategies/__init__.py @@ -0,0 +1,25 @@ +from .base import BaseStrategy, StrategySettings, HistoryData, ObjectData, ResourceType +from .simple import SimpleStrategy, SimpleStrategySettings + + +def get_strategy_from_name(name: str) -> type[BaseStrategy]: + """Get a strategy from its name.""" + + strategies = {cls.__name__.lower(): cls for cls in BaseStrategy.__subclasses__()} + if name.lower() in strategies: + return strategies[name.lower()] + + raise ValueError(f"Unknown strategy name: {name}. Available strategies: {', '.join(strategies)}") + + +__all__ = [ + "AVAILABLE_STRATEGIES", + "get_strategy_from_name", + "BaseStrategy", + "StrategySettings", + "HistoryData", + "ObjectData", + "ResourceType", + "SimpleStrategy", + "SimpleStrategySettings", +] diff --git a/robusta_krr/core/strategies/base.py b/robusta_krr/core/strategies/base.py new file mode 100644 index 0000000..ad2ae33 --- /dev/null +++ b/robusta_krr/core/strategies/base.py @@ -0,0 +1,24 @@ +import abc +import pydantic as pd +from typing import Generic, TypeVar +from robusta_krr.core.result import ResourceType, ObjectData + + +class StrategySettings(pd.BaseModel): + history_duration: float = pd.Field( + 24 * 7 * 2, ge=1, description="The duration of the history data to use (in hours)." + ) + + +_StrategySettings = TypeVar("_StrategySettings", bound=StrategySettings) +HistoryData = dict[str, list[float]] + + +class BaseStrategy(abc.ABC, Generic[_StrategySettings]): + __name__: str + + def __init__(self, settings: _StrategySettings): + self.settings = settings + + def run(self, history_data: HistoryData, object_data: ObjectData, resource_type: ResourceType) -> float: + raise NotImplementedError diff --git a/robusta_krr/core/strategies/simple.py b/robusta_krr/core/strategies/simple.py new file mode 100644 index 0000000..5d2fe04 --- /dev/null +++ b/robusta_krr/core/strategies/simple.py @@ -0,0 +1,19 @@ +import pydantic as pd +from .base import BaseStrategy, StrategySettings, HistoryData, ObjectData, ResourceType + + +class SimpleStrategySettings(StrategySettings): + percentile: float = pd.Field(0.95, gt=0, le=1, description="The percentile to use for the recommendation.") + + +class SimpleStrategy(BaseStrategy[StrategySettings]): + __name__ = "simple" + + def run(self, history_data: HistoryData, object_data: ObjectData, resource_type: ResourceType) -> float: + return self._calculate_percentile( + [point for points in history_data.values() for point in points], self.settings.percentile + ) + + def _calculate_percentile(self, data: list[float], percentile: float) -> float: + data = sorted(data) + return data[int(len(data) * percentile)] diff --git a/robusta_krr/main.py b/robusta_krr/main.py index c068f81..83cf0ff 100644 --- a/robusta_krr/main.py +++ b/robusta_krr/main.py @@ -24,6 +24,7 @@ def run( help="Prometheus URL. If not provided, will attempt to find it in kubernetes cluster", ), format: FormatType = typer.Option("text", "--formatter", "-f", help="Output formatter"), + strategy: str = typer.Option("simple", "--strategy", "-s", help="Strategy to use"), verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose mode"), quiet: bool = typer.Option(False, "--quiet", "-q", help="Enable quiet mode"), ) -> None: @@ -32,6 +33,7 @@ def run( format=format, verbose=verbose, quiet=quiet, + strategy=strategy, ) runner = Runner(config) runner.run() |
