Skip to content

Commit

Permalink
Add explanation when HPA prevents the recommendation
Browse files Browse the repository at this point in the history
  • Loading branch information
LeaveMyYard committed Jun 28, 2023
1 parent cac3bbd commit a7524ed
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 13 deletions.
7 changes: 5 additions & 2 deletions robusta_krr/core/abstract/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 8 additions & 4 deletions robusta_krr/core/integrations/kubernetes.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,19 @@ async def __list_pods(self, resource: Union[V1Deployment, V1DaemonSet, V1Statefu
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]:
Expand Down
3 changes: 2 additions & 1 deletion robusta_krr/core/models/allocations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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
Expand All @@ -29,6 +29,7 @@ class ResourceType(str, enum.Enum):
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:
Expand Down
5 changes: 4 additions & 1 deletion robusta_krr/core/models/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions robusta_krr/core/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def _format_result(self, result: RunResult) -> RunResult:
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()
}
Expand Down Expand Up @@ -144,6 +145,7 @@ async def _gather_objects_recommendations(
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,
)
Expand Down
13 changes: 10 additions & 3 deletions robusta_krr/formatters/table.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import itertools
from typing import Any
from typing import Any, Optional

from rich.table import Table

Expand All @@ -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
Expand All @@ -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

Expand All @@ -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 "")
)


Expand Down
14 changes: 12 additions & 2 deletions robusta_krr/strategies/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,34 @@ 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]
"""

__display_name__ = "simple"
__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)

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)
Expand Down

0 comments on commit a7524ed

Please sign in to comment.