Skip to content

Commit c6b6cb1

Browse files
committed
Expand settings
1 parent 8cc4095 commit c6b6cb1

4 files changed

Lines changed: 178 additions & 44 deletions

File tree

alchemiscale/compute/monitor.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ def __init__(self, settings):
2121
self._lock = Lock()
2222
self._terminate = False
2323

24+
if not hasattr(self, "sample_time"):
25+
raise AttributeError(
26+
f"{self.__class__.__name__} implementation requires definition of the `sample_time` attribute"
27+
)
28+
2429
def signal(self) -> ResourceSignal:
2530
with self._lock:
2631
return self._signal()
@@ -32,8 +37,7 @@ def monitor_cycle(self):
3237
while not self._terminate:
3338
with self._lock:
3439
self._monitor_cycle()
35-
# TODO: make configurable
36-
time.sleep(1)
40+
time.sleep(self.sample_time)
3741

3842
@abstractmethod
3943
def _setup(self, settings):
@@ -53,7 +57,11 @@ class GPUMonitor(Monitor):
5357

5458
def _setup(self, settings):
5559
self.history = []
56-
self.gpu_index = settings.gpu_monitor_gpu_id
60+
self.history_size = settings.gpu_monitor_sample_history_size
61+
self.gpu_index = settings.gpu_monitor_gpu_index
62+
self.grow_limit = settings.gpu_monitor_grow_limit
63+
self.maintain_limit = settings.gpu_monitor_maintain_limit
64+
self.sample_time = settings.gpu_monitor_sample_time
5765

5866
@staticmethod
5967
def _nvidia_smi() -> int:
@@ -78,7 +86,7 @@ def _nvidia_smi() -> int:
7886
def _monitor_cycle(self):
7987
util = self._nvidia_smi()
8088
self.history.append(util)
81-
self.history = self.history[-60:]
89+
self.history = self.history[-self.history_size :]
8290

8391
def _signal(self) -> ResourceSignal:
8492
utilization = sum(self.history) / len(self.history)
@@ -91,12 +99,12 @@ def _signal(self) -> ResourceSignal:
9199

92100
class CPUMonitor(Monitor):
93101

94-
# TODO: make configurable
95-
grow_limit = 0.50
96-
maintain_limit = 0.75
97-
98102
def _setup(self, settings):
99103
self.history = []
104+
self.history_size = settings.cpu_monitor_sample_history_size
105+
self.sample_time = settings.cpu_monitor_sample_time
106+
self.grow_limit = settings.cpu_monitor_grow_limit
107+
self.maintain_limit = settings.cpu_monitor_maintain_limit
100108

101109
def _signal(self) -> ResourceSignal:
102110
total_load = sum(self.history) / len(self.history)
@@ -111,7 +119,7 @@ def _monitor_cycle(self):
111119
cpu_count = os.cpu_count()
112120
total_load = load / cpu_count
113121
self.history.append(total_load)
114-
self.history = self.history[-60:]
122+
self.history = self.history[-self.history_size :]
115123

116124

117125
class MemInfoParseError(Exception):
@@ -120,12 +128,12 @@ class MemInfoParseError(Exception):
120128

121129
class MemoryMonitor(Monitor):
122130

123-
# TODO: make configurable
124-
grow_limit = 0.65
125-
maintain_limit = 0.85
126-
127131
def _setup(self, settings):
128132
self.history = []
133+
self.history_size = settings.memory_monitor_sample_history_size
134+
self.sample_time = settings.memory_monitor_sample_time
135+
self.grow_limit = settings.memory_monitor_grow_limit
136+
self.maintain_limit = settings.memory_monitor_maintain_limit
129137

130138
@staticmethod
131139
def _get_memory() -> tuple[int, int]:
@@ -157,7 +165,7 @@ def _monitor_cycle(self):
157165
fraction_used = (total - avail) / total
158166
self.history.append(fraction_used)
159167
# roughly the last minute of entries
160-
self.history = self.history[-60:]
168+
self.history = self.history[-self.history_size :]
161169
except Exception:
162170
self._terminate = True
163171

alchemiscale/compute/service.py

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -692,35 +692,26 @@ class AsynchronousComputeService(SynchronousComputeService):
692692
_child_env: dict[str, str]
693693

694694
def __init__(self, settings: ComputeServiceSettings):
695-
self._child_env = dict()
695+
self.settings = settings
696+
697+
# asynccomputeservice specific data structures and resource
698+
# monitors.
699+
self._child_env = dict() # mods to child process env
696700
self._task_data = dict()
697701
self._initialize_dag_tree()
698-
self._resource_monitors = None
699-
self._initialize_resource_monitors(settings)
700-
701-
self.settings = settings
702+
self._initialize_resource_monitors()
702703

703704
self.api_url = self.settings.api_url
704705
self.name = self.settings.name
706+
self.compute_manager_id = self.settings.compute_manager_id
705707
self.sleep_interval = self.settings.sleep_interval
708+
self.deep_sleep_interval = self.settings.deep_sleep_interval
706709
self.heartbeat_interval = self.settings.heartbeat_interval
707710
self.claim_limit = self.settings.claim_limit
708711

709-
self.scheduler = sched.scheduler(time.monotonic, time.sleep)
710-
711-
self.client = AlchemiscaleComputeClient(
712-
self.settings.api_url,
713-
self.settings.identifier,
714-
self.settings.key,
715-
cache_directory=self.settings.client_cache_directory,
716-
cache_size_limit=self.settings.client_cache_size_limit,
717-
use_local_cache=self.settings.client_use_local_cache,
718-
max_retries=self.settings.client_max_retries,
719-
retry_base_seconds=self.settings.client_retry_base_seconds,
720-
retry_max_seconds=self.settings.client_retry_max_seconds,
721-
verify=self.settings.client_verify,
722-
)
712+
self.client = self._initialize_client()
723713
self.scopes = self.settings.scopes or [Scope()]
714+
724715
self.shared_basedir = Path(self.settings.shared_basedir).absolute()
725716
self.shared_basedir.mkdir(exist_ok=True)
726717
self.keep_shared = self.settings.keep_shared
@@ -731,19 +722,59 @@ def __init__(self, settings: ComputeServiceSettings):
731722

732723
self.compute_service_id = ComputeServiceID.new_from_name(self.name)
733724

734-
def _initialize_resource_monitors(self, settings):
725+
self.int_sleep = InterruptableSleep()
726+
self._initialize_logger()
727+
728+
def _initialize_logger(self):
729+
extra = {"compute_service_id": str(self.compute_service_id)}
730+
logger = logging.getLogger("AlchemiscaleAsynchronousComputeService")
731+
logger.setLevel(self.settings.loglevel)
732+
733+
formatter = logging.Formatter(
734+
"[%(asctime)s] [%(compute_service_id)s] [%(levelname)s] %(message)s"
735+
)
736+
formatter.converter = time.gmtime # use utc time for logging timestamps
737+
738+
sh = logging.StreamHandler()
739+
sh.setFormatter(formatter)
740+
logger.addHandler(sh)
741+
742+
if self.settings.logfile is not None:
743+
fh = logging.FileHandler(self.settings.logfile)
744+
fh.setFormatter(formatter)
745+
logger.addHandler(fh)
746+
747+
self.logger = logging.LoggerAdapter(logger, extra)
748+
749+
def _initialize_client(self):
750+
return AlchemiscaleComputeClient(
751+
api_url=self.settings.api_url,
752+
identifier=self.settings.identifier,
753+
key=self.settings.key,
754+
cache_directory=self.settings.client_cache_directory,
755+
cache_size_limit=self.settings.client_cache_size_limit,
756+
use_local_cache=self.settings.client_use_local_cache,
757+
max_retries=self.settings.client_max_retries,
758+
retry_base_seconds=self.settings.client_retry_base_seconds,
759+
retry_max_seconds=self.settings.client_retry_max_seconds,
760+
verify=self.settings.client_verify,
761+
)
762+
763+
def _initialize_resource_monitors(self):
735764
self._resource_monitors = []
736765

737-
if settings.memory_monitor_enabled:
738-
self._resource_monitors.append(MemoryMonitor(settings))
766+
if self.settings.memory_monitor_enabled:
767+
self._resource_monitors.append(MemoryMonitor(self.settings))
739768

740-
if settings.cpu_monitor_enabled:
741-
self._resource_monitors.append(CPUMonitor(settings))
769+
if self.settings.cpu_monitor_enabled:
770+
self._resource_monitors.append(CPUMonitor(self.settings))
742771

743-
if settings.gpu_monitor_enabled:
744-
self._resource_monitors.append(GPUMonitor(settings))
772+
if self.settings.gpu_monitor_enabled:
773+
self._resource_monitors.append(GPUMonitor(self.settings))
745774
# reliable monitoring of the GPU requires pinning the GPU index
746-
self._child_env |= {"CUDA_VISIBLE_DEVICES": settings.gpu_monitor_gpu_id}
775+
self._child_env |= {
776+
"CUDA_VISIBLE_DEVICES": self.settings.gpu_monitor_gpu_id
777+
}
747778

748779
for monitor in self._resource_monitors:
749780
threading.Thread(target=monitor.monitor_cycle, daemon=True).start()

alchemiscale/compute/settings.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,74 @@ def validate_scopes(cls, values) -> list[Scope]:
136136
return _values
137137

138138

139+
class AsynchronousComputeServiceSettings(ComputeServiceSettings):
140+
141+
stack_size: int = Field(
142+
2,
143+
description="The number of concurrent protocol units that are able to run at once.",
144+
)
145+
146+
gpu_monitor_enabled: bool = Field(
147+
True, description="If the GPU monitor is enabled."
148+
)
149+
gpu_monitor_gpu_index: str = Field(
150+
"0",
151+
description="The GPU index to perform calculations on. This sets the CUDA_VISIBLE_DEVICES environment variable for spawned compute tasks.",
152+
)
153+
gpu_monitor_grow_limit: float = Field(
154+
0.7,
155+
description="GPU utilization percentage below this value will allow greater concurrency. See utilization.gpu in nvidia-smi for more information.",
156+
)
157+
gpu_monitor_maintain_limit: float = Field(
158+
1.1,
159+
description="GPU utilization percentage above this value will scale back concurrency. See utilization.gpu in nvidia-smi for more information.",
160+
)
161+
gpu_monitor_sample_time: int = Field(
162+
1,
163+
description="Number of seconds between collecting GPU utilization measurements.",
164+
)
165+
gpu_monitor_sample_history_size: int = Field(
166+
60,
167+
description="Maximum number of samples to use when considering reactive concurrency behavior.",
168+
)
169+
memory_monitor_enabled: bool = Field(
170+
True, description="If the memory monitor is enabled."
171+
)
172+
memory_monitor_grow_limit: float = Field(
173+
0.7,
174+
description="Memory usage percentage below this value will allow greater concurrency.",
175+
)
176+
memory_monitor_maintain_limit: float = Field(
177+
0.9,
178+
description="Memory usage percentage above this value will scale back concurrency.",
179+
)
180+
memory_monitor_sample_time: int = Field(
181+
1, description="Number of seconds between collecting memory usage measurements."
182+
)
183+
memory_monitor_sample_history_size: int = Field(
184+
60,
185+
description="Maximum number of samples to use when considering reactive concurrency behavior.",
186+
)
187+
cpu_monitor_enabled: bool = Field(
188+
True, description="If the CPU monitor is enabled."
189+
)
190+
cpu_monitor_grow_limit: float = Field(
191+
0.8,
192+
description="CPU usage percentage below this value will allow greater concurrency.",
193+
)
194+
cpu_monitor_maintain_limit: float = Field(
195+
1.2,
196+
description="CPU usage percentage above this value will scale back concurrency.",
197+
)
198+
cpu_monitor_sample_time: int = Field(
199+
1, description="Number of seconds between collecting CPU usage measurements."
200+
)
201+
cpu_monitor_sample_history_size: int = Field(
202+
60,
203+
description="Maximum number of samples to use when considering reactive concurrency behavior.",
204+
)
205+
206+
139207
class ComputeManagerSettings(BaseModel):
140208
name: str = Field(
141209
...,

service.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,45 @@
88
@dataclass
99
class MockSettings:
1010
memory_monitor_enabled: bool
11+
memory_monitor_sample_time: int
12+
memory_monitor_sample_history_size: int
13+
memory_monitor_grow_limit: float
14+
memory_monitor_maintain_limit: float
1115
cpu_monitor_enabled: bool
16+
cpu_monitor_sample_time: int
17+
cpu_monitor_sample_history_size: int
18+
cpu_monitor_grow_limit: float
19+
cpu_monitor_maintain_limit: float
1220
gpu_monitor_enabled: bool
1321
gpu_monitor_gpu_id: int
22+
gpu_monitor_sample_time: int
23+
gpu_monitor_sample_history_size: int
24+
gpu_monitor_grow_limit: float
25+
gpu_monitor_maintain_limit: float
1426

1527
class MockService(AsynchronousComputeService):
1628

1729
def __init__(self, scratch_basedir, shared_basedir, stack_size, keep_scratch, keep_shared, n_retries, claim_limit, task_generator):
1830
self._child_env = dict()
1931
self._initialize_dag_tree()
20-
settings = MockSettings(memory_monitor_enabled=True, cpu_monitor_enabled=True, gpu_monitor_enabled=False, gpu_monitor_gpu_id="0")
21-
#settings = MockSettings(memory_monitor_enabled=False, cpu_monitor_enabled=False, gpu_monitor_enabled=False, gpu_monitor_gpu_id="0")
22-
self._initialize_resource_monitors(settings)
32+
self.settings = MockSettings(memory_monitor_enabled=True,
33+
cpu_monitor_enabled=True,
34+
gpu_monitor_enabled=False,
35+
gpu_monitor_gpu_id="0",
36+
gpu_monitor_sample_time=1,
37+
gpu_monitor_sample_history_size=60,
38+
gpu_monitor_grow_limit=0.7,
39+
gpu_monitor_maintain_limit=0.9,
40+
memory_monitor_sample_time=1,
41+
memory_monitor_sample_history_size=60,
42+
memory_monitor_grow_limit=0.7,
43+
memory_monitor_maintain_limit=0.9,
44+
cpu_monitor_sample_time=1,
45+
cpu_monitor_sample_history_size=60,
46+
cpu_monitor_grow_limit=0.9,
47+
cpu_monitor_maintain_limit=1.2,
48+
)
49+
self._initialize_resource_monitors()
2350
self._task_data = dict()
2451
self._executor_stack = ExecutorStack(stack_size)
2552

0 commit comments

Comments
 (0)