@@ -76,56 +76,96 @@ def count_completed_recent(status: str) -> int:
7676 )
7777
7878
79+ def set_job_queue_state (enabled : bool , logger : Any = None ) -> None :
80+ """Enable or disable the job queue to control whether new jobs can start."""
81+ batch : Any = boto3 .client ("batch" , region_name = "us-east-1" )
82+ state = "ENABLED" if enabled else "DISABLED"
83+ batch .update_job_queue (jobQueue = "codeclash-queue" , state = state )
84+ if logger :
85+ logger .info (f"Job queue set to { state } " )
86+
87+
7988def set_max_vcpus (vcpus : int , logger : Any = None ) -> None :
89+ """Set the maximum vCPUs for the compute environment."""
8090 batch : Any = boto3 .client ("batch" , region_name = "us-east-1" )
81- # See aws/setup/batch/environment.json for the environment name and defaults
82- response = batch .update_compute_environment (
91+ batch .update_compute_environment (
8392 computeEnvironment = "codeclash-batch" ,
8493 computeResources = {"maxvCpus" : int (vcpus )},
8594 )
8695 if logger :
87- logger .info (f"Update response: { response } " )
88- # Verify the update
89- env = batch .describe_compute_environments (computeEnvironments = ["codeclash-batch" ])
90- status = env ["computeEnvironments" ][0 ]["status" ]
91- current_max = env ["computeEnvironments" ][0 ]["computeResources" ].get ("maxvCpus" )
92- logger .info (f"Compute environment status: { status } , current maxvCpus in config: { current_max } " )
96+ logger .info (f"Set maxvCpus to { vcpus } " )
9397
9498
9599class AutoScaler :
96- def __init__ (self , max_vcpus : int = 40 * 4 ):
97- self .default_max_vcpus = max_vcpus
98- self .current_max_vcpus = max_vcpus
100+ def __init__ (self , * , max_vcpus : int = 200 ):
101+ self .queue_enabled = True
102+ self .max_vcpus = max_vcpus
103+ self .current_vcpus = max_vcpus
104+ self .last_scale_up_time : datetime | None = None
105+ self .ramp_up_schedule = [16 , 32 , 64 , 128 , max_vcpus ]
99106 self .logger = get_logger ("AutoScaler" , emoji = "🔄" )
100107
101- def get_scale_factor (self , status_data : StatusData ) -> float :
102- if status_data .n_jobs_completed_last_2h >= 5 :
103- if status_data .failed_ratio > 0.2 :
104- return 0.0
105- elif status_data .failed_ratio > 0.1 :
106- return 0.5
107- elif status_data .failed_ratio > 0.05 :
108- return 0.75
109- return 1.0
110- # Run at least 1 vCPU, but otherwise don't change anything
111- return max (4 , self .current_max_vcpus / self .default_max_vcpus )
108+ def should_disable_queue (self , status_data : StatusData ) -> bool :
109+ """Decide whether to disable the queue based on failure rate."""
110+ if status_data .n_jobs_completed_last_2h >= 5 and status_data .failed_ratio > 0.2 :
111+ return True
112+ return False
113+
114+ def handle_gradual_scale_up (self ) -> None :
115+ """Gradually increase maxvCpus over time when ramping up after a failure."""
116+ if self .current_vcpus >= self .max_vcpus :
117+ return
118+
119+ now = datetime .now (UTC )
120+ if self .last_scale_up_time is None :
121+ return
122+
123+ time_since_last_scale = now - self .last_scale_up_time
124+ if time_since_last_scale >= timedelta (hours = 1 ):
125+ for target in self .ramp_up_schedule :
126+ if target > self .current_vcpus :
127+ self .logger .info (f"Ramping up maxvCpus from { self .current_vcpus } to { target } " )
128+ set_max_vcpus (target , logger = self .logger )
129+ self .current_vcpus = target
130+ self .last_scale_up_time = now
131+ break
112132
113133 def run (self ) -> None :
114- self .logger .info (f"Starting AutoScaler with default max vCPUs: { self .default_max_vcpus } " )
115- set_max_vcpus (self .default_max_vcpus , logger = self .logger )
116- self .current_max_vcpus = self .default_max_vcpus
134+ self .logger .info (f"Starting AutoScaler sentinel (max_vcpus={ self .max_vcpus } )" )
135+ set_job_queue_state (enabled = True , logger = self .logger )
136+ set_max_vcpus (self .max_vcpus , logger = self .logger )
137+ self .queue_enabled = True
138+ self .current_vcpus = self .max_vcpus
139+
117140 while True :
118141 try :
119142 status_data = get_status_data ()
120- print (status_data )
121- scale_factor = self .get_scale_factor (status_data )
122- new_max_vcpus = int (self .default_max_vcpus * scale_factor )
123- if new_max_vcpus != self .current_max_vcpus :
124- self .logger .info (f"Scaling from { self .current_max_vcpus } to { new_max_vcpus } " )
125- set_max_vcpus (new_max_vcpus , logger = self .logger )
126- self .current_max_vcpus = new_max_vcpus
143+ self .logger .info (
144+ f"Status: { status_data .n_jobs_running } running, { status_data .n_jobs_runnable } runnable, "
145+ f"{ status_data .n_jobs_failed_last_2h } /{ status_data .n_jobs_completed_last_2h } failed/completed (2h)"
146+ )
147+
148+ should_disable = self .should_disable_queue (status_data )
149+
150+ if should_disable and self .queue_enabled :
151+ self .logger .warning (f"High failure rate ({ status_data .failed_ratio :.1%} ), disabling job queue" )
152+ set_job_queue_state (enabled = False , logger = self .logger )
153+ self .queue_enabled = False
154+ elif not should_disable and not self .queue_enabled :
155+ self .logger .info ("Failure rate acceptable, re-enabling job queue with gradual scale-up" )
156+ set_job_queue_state (enabled = True , logger = self .logger )
157+ self .queue_enabled = True
158+ # Start gradual ramp-up from 16 vCPUs
159+ self .current_vcpus = self .ramp_up_schedule [0 ]
160+ set_max_vcpus (self .current_vcpus , logger = self .logger )
161+ self .last_scale_up_time = datetime .now (UTC )
162+
163+ # Continue gradual scale-up if in progress
164+ if self .queue_enabled and self .current_vcpus < self .max_vcpus :
165+ self .handle_gradual_scale_up ()
166+
127167 except Exception as e :
128- self .logger .error (f"Error scaling : { e } " , exc_info = True )
168+ self .logger .error (f"Error in sentinel : { e } " , exc_info = True )
129169 finally :
130170 time .sleep (600 )
131171
0 commit comments