55import sys
66import warnings
77from concurrent .futures import ThreadPoolExecutor
8- from typing import Optional , Union
8+ from typing import Optional , Union , List
99from datetime import timedelta , datetime
1010from prometrix import PrometheusNotFound
1111from 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
3780class 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 (),
0 commit comments