Skip to content

Commit 12c9ee2

Browse files
author
juri
committed
Add option for min diff for shorter result list and tresholds
Add option for min diff for shorter result list and tresholds Add option for min diff for shorter result list and tresholds
1 parent bc2ef21 commit 12c9ee2

File tree

5 files changed

+129
-4
lines changed

5 files changed

+129
-4
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,10 @@ Most helpful flags:
293293

294294
- `--cpu-min` Sets the minimum recommended cpu value in millicores
295295
- `--mem-min` Sets the minimum recommended memory value in MB
296+
- `--cpu-min-diff` Sets the minimum cpu difference for recommendation in millicores
297+
- `--mem-min-diff` Sets the minimum memory difference for recommendation in MB
298+
- `--cpu-min-percent` Sets the minimum cpu difference in percentage for recommendation
299+
- `--mem-min-percent` Sets the minimum memory difference in percentage for recommendation
296300
- `--history_duration` The duration of the Prometheus history data to use (in hours)
297301

298302
More specific information on Strategy Settings can be found using

robusta_krr/core/models/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class Config(pd.BaseSettings):
3232
# Value settings
3333
cpu_min_value: int = pd.Field(10, ge=0) # in millicores
3434
memory_min_value: int = pd.Field(100, ge=0) # in megabytes
35+
cpu_min_diff: int = pd.Field(0, ge=0) # in millicores
36+
memory_min_diff: int = pd.Field(0, ge=0) # in megabytes
37+
cpu_min_percent: int = pd.Field(0, ge=0) # in millicores
38+
memory_min_percent: int = pd.Field(0, ge=0) # in megabytes
3539

3640
# Prometheus Settings
3741
prometheus_url: Optional[str] = pd.Field(None)

robusta_krr/core/runner.py

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
import warnings
77
from concurrent.futures import ThreadPoolExecutor
8-
from typing import Optional, Union
8+
from typing import Optional, Union, List
99
from datetime import timedelta, datetime
1010
from prometrix import PrometheusNotFound
1111
from rich.console import Console
@@ -33,6 +33,49 @@ def custom_print(*objects, rich: bool = True, force: bool = False) -> None:
3333
if not settings.quiet or force:
3434
print_func(*objects) # type: ignore
3535

36+
# Helper function to make the main logic cleaner
37+
def _meets_filter_criteria(
38+
current_val: Optional[float],
39+
recommended_val: Optional[float],
40+
min_diff: float,
41+
min_percent: float,
42+
resource: ResourceType,
43+
) -> bool:
44+
"""
45+
Checks if the difference between current and recommended values meets the threshold.
46+
For CPU, min_diff is in millicores, values are in cores.
47+
For Memory, min_diff is in MB, values are in bytes.
48+
"""
49+
current = current_val if current_val is not None else 0.0
50+
recommended = recommended_val if recommended_val is not None else 0.0
51+
52+
# If no change, it doesn't meet any "difference" criteria unless thresholds are zero
53+
if current == recommended and min_diff != 0.0 and min_percent != 0.0:
54+
return False
55+
56+
# Absolute difference check
57+
try:
58+
abs_diff_raw = abs(recommended - current)
59+
if resource == ResourceType.CPU:
60+
abs_diff = abs_diff_raw * 1000
61+
else:
62+
abs_diff = abs_diff_raw / (1024**2)
63+
except TypeError:
64+
logger.error(
65+
f"TypeError: current_val: {current_val}, recommended_val: {recommended_val}, min_diff: {min_diff}, min_percent: {min_percent}")
66+
return True
67+
68+
if abs_diff < min_diff and min_diff != 0.0:
69+
return False
70+
71+
if min_percent != 0.0:
72+
if current > 0: # Avoid division by zero; if current is 0, any increase is infinite percent
73+
percent_diff = (abs_diff_raw / current) * 100
74+
if percent_diff < min_percent:
75+
return False
76+
77+
return True
78+
3679

3780
class CriticalRunnerException(Exception): ...
3881

@@ -300,6 +343,48 @@ async def _collect_result(self) -> Result:
300343

301344
successful_scans = [scan for scan in scans if scan is not None]
302345

346+
filtered_scans: List[ResourceScan] = []
347+
for scan in successful_scans:
348+
if scan.object is None or scan.object.allocations is None or scan.recommended is None:
349+
logger.debug(f"Skipping scan for {scan.object.name if scan.object else 'Unknown'} due to missing data for filtering.")
350+
continue
351+
352+
current_cpu_request = scan.object.allocations.requests.get(ResourceType.CPU)
353+
current_memory_request = scan.object.allocations.requests.get(ResourceType.Memory)
354+
355+
recommended_cpu_request = rec.value if (rec := scan.recommended.requests.get(ResourceType.CPU)) else None
356+
recommended_memory_request = rec.value if (rec := scan.recommended.requests.get(ResourceType.Memory)) else None
357+
358+
# Check CPU criteria
359+
cpu_meets_criteria = _meets_filter_criteria(
360+
current_val=current_cpu_request,
361+
recommended_val=recommended_cpu_request,
362+
min_diff=float(settings.cpu_min_diff),
363+
min_percent=float(settings.cpu_min_percent),
364+
resource=ResourceType.CPU,
365+
)
366+
367+
# Check Memory criteria
368+
memory_meets_criteria = _meets_filter_criteria(
369+
current_val=current_memory_request,
370+
recommended_val=recommended_memory_request,
371+
min_diff=float(settings.memory_min_diff),
372+
min_percent=float(settings.memory_min_percent),
373+
resource=ResourceType.Memory,
374+
)
375+
376+
if cpu_meets_criteria or memory_meets_criteria:
377+
filtered_scans.append(scan)
378+
else:
379+
logger.debug(
380+
f"Scan for {scan.object.name} (container: {scan.object.container}) did not meet filter criteria. "
381+
f"CPU met: {cpu_meets_criteria}, Memory met: {memory_meets_criteria}. "
382+
f"Current CPU: {current_cpu_request}, Rec CPU: {recommended_cpu_request}. "
383+
f"Current Mem: {current_memory_request}, Rec Mem: {recommended_memory_request}."
384+
)
385+
386+
logger.info(f"Gathered {len(scans)} total scans, {len(successful_scans)} were valid, {len(filtered_scans)} met filter criteria.")
387+
303388
if len(scans) == 0:
304389
logger.warning("Current filters resulted in no objects available to scan.")
305390
logger.warning("Try to change the filters or check if there is anything available.")
@@ -308,11 +393,11 @@ async def _collect_result(self) -> Result:
308393
"Note that you are using the '*' namespace filter, which by default excludes kube-system."
309394
)
310395
raise CriticalRunnerException("No objects available to scan.")
311-
elif len(successful_scans) == 0:
312-
raise CriticalRunnerException("No successful scans were made. Check the logs for more information.")
396+
elif len(filtered_scans) == 0:
397+
raise CriticalRunnerException("No successful filtered scans were made. Check the logs for more information.")
313398

314399
return Result(
315-
scans=successful_scans,
400+
scans=filtered_scans,
316401
description=f"[b]{self._strategy.display_name.title()} Strategy[/b]\n\n{self._strategy.description}",
317402
strategy=StrategyData(
318403
name=str(self._strategy).lower(),

robusta_krr/main.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,30 @@ def run_strategy(
207207
help="Sets the minimum recommended memory value in MB.",
208208
rich_help_panel="Recommendation Settings",
209209
),
210+
cpu_min_diff: int = typer.Option(
211+
0,
212+
"--cpu-min-diff",
213+
help="Sets the minimum cpu difference for recommendation in millicores.",
214+
rich_help_panel="Recommendation Settings",
215+
),
216+
memory_min_diff: int = typer.Option(
217+
0,
218+
"--mem-min-diff",
219+
help="Sets the minimum memory difference for recommendation in MB.",
220+
rich_help_panel="Recommendation Settings",
221+
),
222+
cpu_min_percent: int = typer.Option(
223+
0,
224+
"--cpu-min-percent",
225+
help="Sets the minimum cpu difference in percentage for recommendation.",
226+
rich_help_panel="Recommendation Settings",
227+
),
228+
memory_min_percent: int = typer.Option(
229+
0,
230+
"--mem-min-percent",
231+
help="Sets the minimum memory difference in percentage for recommendation.",
232+
rich_help_panel="Recommendation Settings",
233+
),
210234
max_workers: int = typer.Option(
211235
10,
212236
"--max-workers",
@@ -301,6 +325,10 @@ def run_strategy(
301325
verbose=verbose,
302326
cpu_min_value=cpu_min_value,
303327
memory_min_value=memory_min_value,
328+
cpu_min_diff=cpu_min_diff,
329+
memory_min_diff=memory_min_diff,
330+
cpu_min_percent=cpu_min_percent,
331+
memory_min_percent=memory_min_percent,
304332
quiet=quiet,
305333
log_to_stderr=log_to_stderr,
306334
width=width,

tests/formatters/test_csv_formatter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@
109109
"selector": null,
110110
"cpu_min_value": 10,
111111
"memory_min_value": 100,
112+
"cpu_min_diff": 0,
113+
"memory_min_diff": 0,
114+
"cpu_min_percent": 0,
115+
"memory_min_percent": 0,
112116
"prometheus_url": null,
113117
"prometheus_auth_header": null,
114118
"prometheus_other_headers": {},

0 commit comments

Comments
 (0)