summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
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
commit19b20627134302e2b1d0578a1933c60b165ceb42 (patch)
tree1625d5660e37094de8d73538cdab676905114f01
parentb472dae4750169c9eb1be831a328389244c17c6c (diff)
Implement structure for strategies and settings
-rw-r--r--.gitignore3
-rw-r--r--robusta_krr/core/config.py11
-rw-r--r--robusta_krr/core/loader.py0
-rw-r--r--robusta_krr/core/result.py26
-rw-r--r--robusta_krr/core/runner.py13
-rw-r--r--robusta_krr/core/strategies/__init__.py25
-rw-r--r--robusta_krr/core/strategies/base.py24
-rw-r--r--robusta_krr/core/strategies/simple.py19
-rw-r--r--robusta_krr/main.py2
9 files changed, 122 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 2ef7014..2930ebe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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()