summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--robusta_krr/core/abstract/strategies.py7
-rw-r--r--robusta_krr/core/integrations/kubernetes.py12
-rw-r--r--robusta_krr/core/models/allocations.py3
-rw-r--r--robusta_krr/core/models/result.py5
-rw-r--r--robusta_krr/core/runner.py2
-rw-r--r--robusta_krr/formatters/table.py13
-rw-r--r--robusta_krr/strategies/simple.py14
7 files changed, 43 insertions, 13 deletions
diff --git a/robusta_krr/core/abstract/strategies.py b/robusta_krr/core/abstract/strategies.py
index b0d9bf4..367df50 100644
--- a/robusta_krr/core/abstract/strategies.py
+++ b/robusta_krr/core/abstract/strategies.py
@@ -24,10 +24,13 @@ class ResourceRecommendation(pd.BaseModel):
request: Optional[float]
limit: Optional[float]
+ info: Optional[str] = pd.Field(
+ None, description="Additional information about the recommendation. Currently used to explain undefined."
+ )
@classmethod
- def undefined(cls: type[SelfRR]) -> SelfRR:
- return cls(request=float("NaN"), limit=float("NaN"))
+ def undefined(cls: type[SelfRR], info: Optional[str] = None) -> SelfRR:
+ return cls(request=float("NaN"), limit=float("NaN"), info=info)
class StrategySettings(pd.BaseModel):
diff --git a/robusta_krr/core/integrations/kubernetes.py b/robusta_krr/core/integrations/kubernetes.py
index 0e0b637..4940121 100644
--- a/robusta_krr/core/integrations/kubernetes.py
+++ b/robusta_krr/core/integrations/kubernetes.py
@@ -115,15 +115,19 @@ class ClusterLoader(Configurable):
return [PodData(name=pod.metadata.name, deleted=False) for pod in ret.items]
async def __build_obj(self, item: AnyKubernetesAPIObject, container: V1Container) -> K8sObjectData:
+ name = item.metadata.name
+ namespace = item.metadata.namespace
+ kind = item.__class__.__name__[2:]
+
return K8sObjectData(
cluster=self.cluster,
- namespace=item.metadata.namespace,
- name=item.metadata.name,
- kind=item.__class__.__name__[2:],
+ namespace=namespace,
+ name=name,
+ kind=kind,
container=container.name,
allocations=ResourceAllocations.from_container(container),
pods=await self.__list_pods(item),
- hpa=self.__hpa_list.get((item.kind, item.metadata.name)),
+ hpa=self.__hpa_list.get((kind, name)),
)
async def _list_deployments(self) -> list[K8sObjectData]:
diff --git a/robusta_krr/core/models/allocations.py b/robusta_krr/core/models/allocations.py
index 1d1a272..cde5aa0 100644
--- a/robusta_krr/core/models/allocations.py
+++ b/robusta_krr/core/models/allocations.py
@@ -2,7 +2,7 @@ from __future__ import annotations
import enum
import math
-from typing import Literal, TypeVar, Union
+from typing import Literal, Optional, TypeVar, Union
import pydantic as pd
from kubernetes.client.models import V1Container
@@ -29,6 +29,7 @@ Self = TypeVar("Self", bound="ResourceAllocations")
class ResourceAllocations(pd.BaseModel):
requests: dict[ResourceType, RecommendationValue]
limits: dict[ResourceType, RecommendationValue]
+ info: dict[ResourceType, Optional[str]] = {}
@staticmethod
def __parse_resource_value(value: RecommendationValueRaw) -> RecommendationValue:
diff --git a/robusta_krr/core/models/result.py b/robusta_krr/core/models/result.py
index 1f2ab3f..68f36cc 100644
--- a/robusta_krr/core/models/result.py
+++ b/robusta_krr/core/models/result.py
@@ -19,6 +19,7 @@ class Recommendation(pd.BaseModel):
class ResourceRecommendation(pd.BaseModel):
requests: dict[ResourceType, RecommendationValue]
limits: dict[ResourceType, RecommendationValue]
+ info: dict[ResourceType, Optional[str]]
class Metric(pd.BaseModel):
@@ -41,9 +42,11 @@ class ResourceScan(pd.BaseModel):
def calculate(
cls, object: K8sObjectData, recommendation: ResourceAllocations, metrics: MetricsData
) -> ResourceScan:
- recommendation_processed = ResourceRecommendation(requests={}, limits={})
+ recommendation_processed = ResourceRecommendation(requests={}, limits={}, info={})
for resource_type in ResourceType:
+ recommendation_processed.info[resource_type] = recommendation.info.get(resource_type)
+
for selector in ["requests", "limits"]:
current = getattr(object.allocations, selector).get(resource_type)
recommended = getattr(recommendation, selector).get(resource_type)
diff --git a/robusta_krr/core/runner.py b/robusta_krr/core/runner.py
index ba46c8f..890e19d 100644
--- a/robusta_krr/core/runner.py
+++ b/robusta_krr/core/runner.py
@@ -100,6 +100,7 @@ class Runner(Configurable):
resource: ResourceRecommendation(
request=self._round_value(recommendation.request, resource),
limit=self._round_value(recommendation.limit, resource),
+ info=recommendation.info,
)
for resource, recommendation in result.items()
}
@@ -144,6 +145,7 @@ class Runner(Configurable):
ResourceAllocations(
requests={resource: recommendation[resource].request for resource in ResourceType},
limits={resource: recommendation[resource].limit for resource in ResourceType},
+ info={resource: recommendation[resource].info for resource in ResourceType},
),
metric,
)
diff --git a/robusta_krr/formatters/table.py b/robusta_krr/formatters/table.py
index cc6da69..846732a 100644
--- a/robusta_krr/formatters/table.py
+++ b/robusta_krr/formatters/table.py
@@ -1,5 +1,5 @@
import itertools
-from typing import Any
+from typing import Any, Optional
from rich.table import Table
@@ -22,7 +22,7 @@ def _format(value: RecommendationValue) -> str:
def __calc_diff(allocated, recommended, selector, multiplier=1) -> str:
- if recommended is None or isinstance(recommended, str) or selector != "requests":
+ if recommended is None or isinstance(recommended.value, str) or selector != "requests":
return ""
else:
reccomended_val = recommended.value if isinstance(recommended.value, (int, float)) else 0
@@ -34,6 +34,7 @@ def __calc_diff(allocated, recommended, selector, multiplier=1) -> str:
def _format_request_str(item: ResourceScan, resource: ResourceType, selector: str) -> str:
allocated = getattr(item.object.allocations, selector)[resource]
+ info = item.recommended.info.get(resource)
recommended = getattr(item.recommended, selector)[resource]
severity = recommended.severity
@@ -45,7 +46,13 @@ def _format_request_str(item: ResourceScan, resource: ResourceType, selector: st
diff = f"({diff}) "
return (
- diff + f"[{severity.color}]" + _format(allocated) + " -> " + _format(recommended.value) + f"[/{severity.color}]"
+ diff
+ + f"[{severity.color}]"
+ + _format(allocated)
+ + " -> "
+ + _format(recommended.value)
+ + f"[/{severity.color}]"
+ + (f" [grey27]({info})[/grey27]" if info else "")
)
diff --git a/robusta_krr/strategies/simple.py b/robusta_krr/strategies/simple.py
index 353b797..b83de50 100644
--- a/robusta_krr/strategies/simple.py
+++ b/robusta_krr/strategies/simple.py
@@ -42,6 +42,10 @@ class SimpleStrategy(BaseStrategy[SimpleStrategySettings]):
"""
CPU request: {cpu_percentile}% percentile, limit: unset
Memory request: max + {memory_buffer_percentage}%, limit: max + {memory_buffer_percentage}%
+
+ This strategy does not work with objects with HPA defined (Horizontal Pod Autoscaler).
+ If HPA is defined for CPU or Memory, the strategy will return "?" for that resource.
+
Learn more: [underline]https://github.com/robusta-dev/krr#algorithm[/underline]
"""
@@ -49,8 +53,11 @@ class SimpleStrategy(BaseStrategy[SimpleStrategySettings]):
__rich_console__ = True
def __calculate_cpu_proposal(self, history_data: HistoryData, object_data: K8sObjectData) -> ResourceRecommendation:
+ if len(history_data[ResourceType.CPU].data) == 0:
+ return ResourceRecommendation.undefined(info="No data")
+
if object_data.hpa is not None and object_data.hpa.target_cpu_utilization_percentage is not None:
- return ResourceRecommendation.undefined()
+ return ResourceRecommendation.undefined(info="HPA detected")
cpu_usage = self.settings.calculate_cpu_proposal(history_data[ResourceType.CPU].data)
return ResourceRecommendation(request=cpu_usage, limit=None)
@@ -58,8 +65,11 @@ class SimpleStrategy(BaseStrategy[SimpleStrategySettings]):
def __calculate_memory_proposal(
self, history_data: HistoryData, object_data: K8sObjectData
) -> ResourceRecommendation:
+ if len(history_data[ResourceType.Memory].data) == 0:
+ return ResourceRecommendation.undefined(info="No data")
+
if object_data.hpa is not None and object_data.hpa.target_memory_utilization_percentage is not None:
- return ResourceRecommendation.undefined()
+ return ResourceRecommendation.undefined(info="HPA detected")
memory_usage = self.settings.calculate_memory_proposal(history_data[ResourceType.Memory].data)
return ResourceRecommendation(request=memory_usage, limit=memory_usage)