Skip to content

Commit 99c8520

Browse files
committed
Fix(aws): Sentinel that actually does something
1 parent 3af69a5 commit 99c8520

1 file changed

Lines changed: 73 additions & 33 deletions

File tree

aws/sentinel.py

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
7988
def 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

9599
class 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

Comments
 (0)