5
5
import sys
6
6
import warnings
7
7
from concurrent .futures import ThreadPoolExecutor
8
- from typing import Optional , Union
8
+ from typing import Optional , Union , List
9
9
from datetime import timedelta , datetime
10
10
from prometrix import PrometheusNotFound
11
11
from rich .console import Console
@@ -33,6 +33,49 @@ def custom_print(*objects, rich: bool = True, force: bool = False) -> None:
33
33
if not settings .quiet or force :
34
34
print_func (* objects ) # type: ignore
35
35
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
+
36
79
37
80
class CriticalRunnerException (Exception ): ...
38
81
@@ -300,6 +343,48 @@ async def _collect_result(self) -> Result:
300
343
301
344
successful_scans = [scan for scan in scans if scan is not None ]
302
345
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
+
303
388
if len (scans ) == 0 :
304
389
logger .warning ("Current filters resulted in no objects available to scan." )
305
390
logger .warning ("Try to change the filters or check if there is anything available." )
@@ -308,11 +393,11 @@ async def _collect_result(self) -> Result:
308
393
"Note that you are using the '*' namespace filter, which by default excludes kube-system."
309
394
)
310
395
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." )
313
398
314
399
return Result (
315
- scans = successful_scans ,
400
+ scans = filtered_scans ,
316
401
description = f"[b]{ self ._strategy .display_name .title ()} Strategy[/b]\n \n { self ._strategy .description } " ,
317
402
strategy = StrategyData (
318
403
name = str (self ._strategy ).lower (),
0 commit comments