diff options
| -rw-r--r-- | robusta_krr/core/abstract/strategies.py | 7 | ||||
| -rw-r--r-- | robusta_krr/core/integrations/kubernetes.py | 12 | ||||
| -rw-r--r-- | robusta_krr/core/models/allocations.py | 3 | ||||
| -rw-r--r-- | robusta_krr/core/models/result.py | 5 | ||||
| -rw-r--r-- | robusta_krr/core/runner.py | 2 | ||||
| -rw-r--r-- | robusta_krr/formatters/table.py | 13 | ||||
| -rw-r--r-- | robusta_krr/strategies/simple.py | 14 |
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) |
