Skip to content

Commit 939efd2

Browse files
committed
Strengthen the system metrics tests
The test for the default settings was asserting on state that close() clears anyway, so it would have passed even if the monitor had run. Assert that the monitor is never constructed instead. Also assert the monitor is given the run of the handler, that handlers sharing a run all resolve the same one, and match the expected warning rather than any warning. Cover the two failure paths: a monitor that cannot start, and one that cannot stop, neither of which should stop the workflow. Signed-off-by: uditmahato <uditmahato29271@gmail.com>
1 parent 03f57c5 commit 939efd2

1 file changed

Lines changed: 83 additions & 31 deletions

File tree

tests/handlers/test_handler_mlflow.py

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -230,44 +230,43 @@ def _update_metric(engine):
230230
else:
231231
self.assertEqual(handler._default_iteration_log.call_count, 2) # 2 = len([1, 3]) from event_filter
232232

233+
@staticmethod
234+
def _train_func(engine, batch):
235+
return [batch + 1.0]
236+
233237
def test_system_metrics_disabled_by_default(self):
234238
"""
235-
Test that a handler left at its default settings does not sample the system metrics.
239+
Test that a handler left at its default settings does not sample the system metrics,
240+
even where mlflow is able to.
236241
"""
237242
with tempfile.TemporaryDirectory() as tempdir:
238-
239-
def _train_func(engine, batch):
240-
return [batch + 1.0]
241-
242-
engine = Engine(_train_func)
243+
engine = Engine(self._train_func)
243244
test_path = os.path.join(tempdir, "mlflow_system_metrics_off")
244245
handler = MLFlowHandler(iteration_log=False, tracking_uri=path_to_uri(test_path), close_on_complete=True)
245-
handler.attach(engine)
246-
engine.run(range(3), max_epochs=1)
246+
with (
247+
patch("monai.handlers.mlflow_handler.SystemMetricsMonitor") as monitor_class,
248+
patch("monai.handlers.mlflow_handler.has_system_metrics", True),
249+
):
250+
handler.attach(engine)
251+
engine.run(range(3), max_epochs=1)
247252

248-
self.assertIsNone(handler.system_metrics_monitor)
249-
run = handler.client.get_run(handler.cur_run.info.run_id) if handler.cur_run else None
250-
self.assertIsNone(run)
253+
monitor_class.assert_not_called()
251254

252255
def test_system_metrics_monitor_life_cycle(self):
253256
"""
254257
Test that the monitor samples the run of the handler with the requested settings,
255258
and stops when the workflow completes.
256259
"""
257260
with tempfile.TemporaryDirectory() as tempdir:
258-
259-
def _train_func(engine, batch):
260-
return [batch + 1.0]
261-
262-
engine = Engine(_train_func)
261+
engine = Engine(self._train_func)
263262
test_path = os.path.join(tempdir, "mlflow_system_metrics")
264263
handler = MLFlowHandler(
265264
iteration_log=False,
266265
tracking_uri=path_to_uri(test_path),
267266
log_system_metrics=True,
268267
system_metrics_sampling_interval=1,
269268
system_metrics_samples_before_logging=1,
270-
close_on_complete=True,
269+
close_on_complete=False,
271270
)
272271
monitor = MagicMock()
273272
with (
@@ -279,24 +278,22 @@ def _train_func(engine, batch):
279278

280279
# the monitor samples the run of the handler, with the requested sampling settings
281280
monitor_class.assert_called_once()
281+
self.assertEqual(monitor_class.call_args.args[0], handler.cur_run.info.run_id)
282282
self.assertEqual(monitor_class.call_args.kwargs["sampling_interval"], 1)
283283
self.assertEqual(monitor_class.call_args.kwargs["samples_before_logging"], 1)
284284
monitor.start.assert_called_once()
285285
# the sampling is stopped when the workflow completes
286286
monitor.finish.assert_called_once()
287287
self.assertIsNone(handler.system_metrics_monitor)
288+
handler.close()
288289

289290
def test_system_metrics_monitor_shared_by_handlers(self):
290291
"""
291292
Test that handlers sharing a run sample it once, and that the run keeps being sampled
292293
until the handler that started the sampling completes.
293294
"""
294295
with tempfile.TemporaryDirectory() as tempdir:
295-
296-
def _train_func(engine, batch):
297-
return [batch + 1.0]
298-
299-
engine = Engine(_train_func)
296+
engine = Engine(self._train_func)
300297
test_path = os.path.join(tempdir, "mlflow_system_metrics_shared")
301298
# a workflow attaches one handler per engine, all of them sharing a run
302299
handlers = [
@@ -313,8 +310,13 @@ def _train_func(engine, batch):
313310
for handler in handlers:
314311
handler.start(engine)
315312

313+
run_ids = {handler.cur_run.info.run_id for handler in handlers}
314+
self.assertEqual(len(run_ids), 1)
315+
316316
# the run is sampled by the first handler only
317317
monitor_class.assert_called_once()
318+
self.assertEqual(monitor_class.call_args.args[0], run_ids.pop())
319+
monitor.start.assert_called_once()
318320

319321
# the handlers that do not sample the run leave it running when they complete
320322
for handler in handlers[1:]:
@@ -328,17 +330,13 @@ def _train_func(engine, batch):
328330
for handler in handlers:
329331
handler.close()
330332

331-
def test_system_metrics_warns_when_mlflow_is_too_old(self):
333+
def test_system_metrics_warns_when_unavailable(self):
332334
"""
333-
Test that a workflow still runs, with a warning, when the installed mlflow cannot
334-
record the system metrics.
335+
Test that a workflow still runs, with a warning, when the installed mlflow does not
336+
support recording the system metrics.
335337
"""
336338
with tempfile.TemporaryDirectory() as tempdir:
337-
338-
def _train_func(engine, batch):
339-
return [batch + 1.0]
340-
341-
engine = Engine(_train_func)
339+
engine = Engine(self._train_func)
342340
test_path = os.path.join(tempdir, "mlflow_system_metrics_unavailable")
343341
handler = MLFlowHandler(
344342
iteration_log=False,
@@ -347,11 +345,65 @@ def _train_func(engine, batch):
347345
close_on_complete=True,
348346
)
349347
with patch("monai.handlers.mlflow_handler.has_system_metrics", False):
350-
with self.assertWarns(Warning):
348+
with self.assertWarnsRegex(Warning, "Please install mlflow>=2.8.0 to record the system metrics."):
349+
handler.attach(engine)
350+
engine.run(range(3), max_epochs=1)
351+
352+
self.assertIsNone(handler.system_metrics_monitor)
353+
354+
def test_system_metrics_start_failure_does_not_stop_the_workflow(self):
355+
"""
356+
Test that a workflow still runs, with a warning, when the monitor cannot be started.
357+
"""
358+
with tempfile.TemporaryDirectory() as tempdir:
359+
engine = Engine(self._train_func)
360+
test_path = os.path.join(tempdir, "mlflow_system_metrics_start_failure")
361+
handler = MLFlowHandler(
362+
iteration_log=False,
363+
tracking_uri=path_to_uri(test_path),
364+
log_system_metrics=True,
365+
close_on_complete=True,
366+
)
367+
with (
368+
patch(
369+
"monai.handlers.mlflow_handler.SystemMetricsMonitor", side_effect=RuntimeError("no monitor for you")
370+
),
371+
patch("monai.handlers.mlflow_handler.has_system_metrics", True),
372+
):
373+
with self.assertWarnsRegex(Warning, "Failed to record the system metrics"):
374+
handler.attach(engine)
375+
engine.run(range(3), max_epochs=1)
376+
377+
self.assertEqual(engine.state.epoch, 1)
378+
self.assertIsNone(handler.system_metrics_monitor)
379+
self.assertEqual(len(MLFlowHandler._monitored_run_ids), 0)
380+
381+
def test_system_metrics_stop_failure_is_reported(self):
382+
"""
383+
Test that a monitor which fails to stop is reported and released, so that the run can
384+
be sampled again.
385+
"""
386+
with tempfile.TemporaryDirectory() as tempdir:
387+
engine = Engine(self._train_func)
388+
test_path = os.path.join(tempdir, "mlflow_system_metrics_stop_failure")
389+
handler = MLFlowHandler(
390+
iteration_log=False,
391+
tracking_uri=path_to_uri(test_path),
392+
log_system_metrics=True,
393+
close_on_complete=True,
394+
)
395+
monitor = MagicMock()
396+
monitor.finish.side_effect = RuntimeError("monitor will not stop")
397+
with (
398+
patch("monai.handlers.mlflow_handler.SystemMetricsMonitor", return_value=monitor),
399+
patch("monai.handlers.mlflow_handler.has_system_metrics", True),
400+
):
401+
with self.assertWarnsRegex(Warning, "Failed to stop recording the system metrics"):
351402
handler.attach(engine)
352403
engine.run(range(3), max_epochs=1)
353404

354405
self.assertIsNone(handler.system_metrics_monitor)
406+
self.assertEqual(len(MLFlowHandler._monitored_run_ids), 0)
355407

356408
def test_system_metrics_settings_are_validated(self):
357409
"""

0 commit comments

Comments
 (0)