Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,21 @@ Get a free breakdown of KRR recommendations in the [Robusta SaaS](#free-krr-ui-o

### Algorithm

By default, we use a _simple_ strategy to calculate resource recommendations. It is calculated as follows (_The exact numbers can be customized in CLI arguments_):
KRR provides multiple strategies for calculating resource recommendations:

- For CPU, we set a request at the 95th percentile with no limit. Meaning, in 95% of the cases, your CPU request will be sufficient. For the remaining 5%, we set no limit. This means your pod can burst and use any CPU available on the node - e.g. CPU that other pods requested but aren’t using right now.
#### Simple Strategy (default)
By default, we use the _simple_ strategy (`krr simple`). It is calculated as follows (_The exact numbers can be customized in CLI arguments_):

- For memory, we take the maximum value over the past week and add a 15% buffer.
- **CPU**: Request at the 95th percentile, **limit unset** (allows unlimited bursting)
- **Memory**: Maximum value over the past week + 15% buffer (same for request and limit)

#### Simple-Limit Strategy
The _simple-limit_ strategy (`krr simple_limit`) provides both CPU requests and limits:

- **CPU**: Request and limit both set to configurable percentiles (default 95th percentile for both)
- **Memory**: Maximum value over the past week + 15% buffer (same for request and limit)

**Key difference**: The simple strategy leaves CPU limits unset to allow unlimited bursting, while simple-limit sets explicit CPU limits.

### Prometheus connection

Expand Down
6 changes: 4 additions & 2 deletions robusta_krr/strategies/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@


class SimpleStrategySettings(StrategySettings):
cpu_percentile: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU recommendation.")
cpu_percentile: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU request recommendation. CPU limits are always unset to allow unlimited bursting.")
memory_buffer_percentage: float = pd.Field(
15, gt=0, description="The percentage of added buffer to the peak memory usage for memory recommendation."
)
Expand Down Expand Up @@ -92,10 +92,12 @@ def metrics(self) -> list[type[PrometheusMetric]]:
@property
def description(self):
s = textwrap.dedent(f"""\
CPU request: {self.settings.cpu_percentile}% percentile, limit: unset
CPU request: {self.settings.cpu_percentile}% percentile, limit: unset (allows unlimited bursting)
Memory request: max + {self.settings.memory_buffer_percentage}%, limit: max + {self.settings.memory_buffer_percentage}%
History: {self.settings.history_duration} hours
Step: {self.settings.timeframe_duration} minutes

This is the default strategy. If you want to set CPU limits, use 'krr simple-limit' instead.

All parameters can be customized. For example: `krr simple --cpu_percentile=90 --memory_buffer_percentage=15 --history_duration=24 --timeframe_duration=0.5`
""")
Expand Down
7 changes: 5 additions & 2 deletions robusta_krr/strategies/simple_limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@


class SimpleLimitStrategySettings(StrategySettings):
cpu_request: float = pd.Field(66, gt=0, le=100, description="The percentile to use for the CPU request.")
cpu_limit: float = pd.Field(96, gt=0, le=100, description="The percentile to use for the CPU limit.")
cpu_request: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU request.")
cpu_limit: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU limit. Unlike 'simple' strategy, this strategy sets explicit CPU limits.")
memory_buffer_percentage: float = pd.Field(
Comment on lines +28 to 30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation so cpu_limit is not lower than cpu_request

With both defaults set to 95, users might pass values such that
cpu_limit < cpu_request, which is an invalid Kubernetes spec and will fail
server-side. A simple Pydantic validator prevents misconfiguration:

 class SimpleLimitStrategySettings(StrategySettings):
@@
     cpu_request: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU request.")
     cpu_limit:   float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU limit. Unlike 'simple' strategy, this strategy sets explicit CPU limits.")
+
+    @pd.validator("cpu_limit")
+    def _limit_not_lower_than_request(cls, v, values):
+        req = values.get("cpu_request")
+        if req is not None and v < req:
+            raise ValueError("cpu_limit must be greater than or equal to cpu_request")
+        return v
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cpu_request: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU request.")
cpu_limit: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU limit. Unlike 'simple' strategy, this strategy sets explicit CPU limits.")
memory_buffer_percentage: float = pd.Field(
class SimpleLimitStrategySettings(StrategySettings):
cpu_request: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU request.")
cpu_limit: float = pd.Field(95, gt=0, le=100, description="The percentile to use for the CPU limit. Unlike 'simple' strategy, this strategy sets explicit CPU limits.")
@pd.validator("cpu_limit")
def _limit_not_lower_than_request(cls, v, values):
req = values.get("cpu_request")
if req is not None and v < req:
raise ValueError("cpu_limit must be greater than or equal to cpu_request")
return v
memory_buffer_percentage: float = pd.Field(
🤖 Prompt for AI Agents
In robusta_krr/strategies/simple_limit.py around lines 28 to 30, add a Pydantic
validator to ensure that cpu_limit is never set lower than cpu_request.
Implement a method decorated with @pd.validator that compares cpu_limit and
cpu_request values and raises a validation error if cpu_limit is less than
cpu_request. This prevents invalid Kubernetes specs and user misconfiguration.

15, gt=0, description="The percentage of added buffer to the peak memory usage for memory recommendation."
)
Expand Down Expand Up @@ -97,6 +97,9 @@ def description(self):
Memory request: max + {self.settings.memory_buffer_percentage}%, limit: max + {self.settings.memory_buffer_percentage}%
History: {self.settings.history_duration} hours
Step: {self.settings.timeframe_duration} minutes

Unlike the 'simple' strategy, this strategy sets explicit CPU limits instead of leaving them unset.
Use this when you want to prevent CPU bursting beyond the configured limit.

All parameters can be customized. For example: `krr simple_limit --cpu_request=66 --cpu_limit=96 --memory_buffer_percentage=15 --history_duration=24 --timeframe_duration=0.5`
""")
Expand Down