Skip to content

Commit d2ce064

Browse files
authored
Merge pull request #1202 from mlco2/fix/monitor-offline-tracker
fix(cli): use OfflineEmissionsTracker for offline monitor runs
2 parents 5c3331a + e68906a commit d2ce064

4 files changed

Lines changed: 124 additions & 4 deletions

File tree

codecarbon/cli/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ def monitor(
377377

378378
# If extra args are provided (e.g. `codecarbon monitor -- my_script.py`), delegate to `run_and_monitor`
379379
if getattr(ctx, "args", None):
380-
return run_and_monitor(ctx, **tracker_args)
380+
return run_and_monitor(ctx, offline=offline, **tracker_args)
381381

382382
# Instantiate the tracker
383383
if offline:

codecarbon/cli/monitor.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from rich import print
99
from typing_extensions import Annotated
1010

11-
from codecarbon.emissions_tracker import EmissionsTracker
11+
from codecarbon.emissions_tracker import EmissionsTracker, OfflineEmissionsTracker
1212

1313

1414
def run_and_monitor(
@@ -17,6 +17,7 @@ def run_and_monitor(
1717
str,
1818
typer.Option(help="Log level (critical, error, warning, info, debug)"),
1919
] = "error",
20+
offline: bool = False,
2021
**tracker_args,
2122
):
2223
"""
@@ -63,8 +64,8 @@ def run_and_monitor(
6364
)
6465
raise typer.Exit(1)
6566

66-
# Initialize tracker with specified logging level and shared args
67-
tracker = EmissionsTracker(
67+
tracker_cls = OfflineEmissionsTracker if offline else EmissionsTracker
68+
tracker = tracker_cls(
6869
log_level=log_level,
6970
save_to_logger=False,
7071
tracking_mode="process",

tests/cli/test_cli_main.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,45 @@ def stop(self):
303303
assert calls["kwargs"]["region"] == "IDF"
304304

305305

306+
def test_monitor_delegates_offline_flag_to_run_and_monitor(monkeypatch):
307+
captured = {}
308+
309+
def fake_run_and_monitor(ctx, offline=False, **kwargs):
310+
captured["offline"] = offline
311+
captured["kwargs"] = kwargs
312+
return "ok"
313+
314+
monkeypatch.setattr(cli_main, "run_and_monitor", fake_run_and_monitor)
315+
316+
ctx = SimpleNamespace(args=["python", "-c", "print(1)"])
317+
result = cli_main.monitor(
318+
ctx=ctx,
319+
offline=True,
320+
country_iso_code="FRA",
321+
)
322+
assert result == "ok"
323+
assert captured["offline"] is True
324+
assert captured["kwargs"]["country_iso_code"] == "FRA"
325+
326+
327+
def test_monitor_delegates_online_mode_to_run_and_monitor(monkeypatch):
328+
captured = {}
329+
330+
def fake_run_and_monitor(ctx, offline=False, **kwargs):
331+
captured["offline"] = offline
332+
captured["kwargs"] = kwargs
333+
return "ok"
334+
335+
monkeypatch.setattr(cli_main, "run_and_monitor", fake_run_and_monitor)
336+
monkeypatch.setattr(cli_main, "get_existing_exp_id", lambda: "exp-1")
337+
338+
ctx = SimpleNamespace(args=["python", "train.py"])
339+
result = cli_main.monitor(ctx=ctx, api=True)
340+
assert result == "ok"
341+
assert captured["offline"] is False
342+
assert captured["kwargs"]["save_to_api"] is True
343+
344+
306345
def test_monitor_delegates_to_run_and_monitor_with_extra_args(monkeypatch):
307346
captured = {}
308347

@@ -319,3 +358,21 @@ def fake_run_and_monitor(ctx, **kwargs):
319358
assert result == "ok"
320359
assert captured["args"] == ["python", "train.py"]
321360
assert captured["kwargs"]["save_to_api"] is False
361+
362+
363+
def test_monitor_no_api_skips_experiment_id_requirement(monkeypatch):
364+
captured = {}
365+
366+
def fake_run_and_monitor(ctx, offline=False, **kwargs):
367+
captured["offline"] = offline
368+
captured["kwargs"] = kwargs
369+
return "ok"
370+
371+
monkeypatch.setattr(cli_main, "run_and_monitor", fake_run_and_monitor)
372+
monkeypatch.setattr(cli_main, "get_existing_exp_id", lambda: None)
373+
374+
ctx = SimpleNamespace(args=["python", "train.py"])
375+
result = cli_main.monitor(ctx=ctx, api=False)
376+
assert result == "ok"
377+
assert captured["offline"] is False
378+
assert captured["kwargs"]["save_to_api"] is False

tests/cli/test_monitor.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,68 @@ def __init__(self, command, text=True):
6060
assert exc_info.value.exit_code == 1
6161

6262

63+
def test_run_and_monitor_uses_offline_tracker_when_offline_mode(monkeypatch):
64+
captured = {}
65+
66+
class FakeOfflineTracker(FakeTracker):
67+
def __init__(self, **kwargs):
68+
captured["kwargs"] = kwargs
69+
super().__init__()
70+
71+
class FakePopen:
72+
def __init__(self, command, text=True):
73+
pass
74+
75+
def wait(self):
76+
return 0
77+
78+
monkeypatch.setattr(monitor_module, "OfflineEmissionsTracker", FakeOfflineTracker)
79+
monkeypatch.setattr(monitor_module, "EmissionsTracker", FakeTracker)
80+
monkeypatch.setattr(monitor_module.subprocess, "Popen", FakePopen)
81+
monkeypatch.setattr(monitor_module, "print", lambda *args, **kwargs: None)
82+
83+
with pytest.raises(typer.Exit) as exc_info:
84+
monitor_module.run_and_monitor(
85+
SimpleNamespace(args=["echo", "hi"]),
86+
offline=True,
87+
country_iso_code="FRA",
88+
)
89+
90+
assert exc_info.value.exit_code == 0
91+
assert captured["kwargs"]["country_iso_code"] == "FRA"
92+
93+
94+
def test_run_and_monitor_uses_online_tracker_by_default(monkeypatch):
95+
captured = {}
96+
97+
class FakeOnlineTracker(FakeTracker):
98+
def __init__(self, **kwargs):
99+
captured["kwargs"] = kwargs
100+
super().__init__()
101+
102+
class FakePopen:
103+
def __init__(self, command, text=True):
104+
pass
105+
106+
def wait(self):
107+
return 0
108+
109+
monkeypatch.setattr(monitor_module, "EmissionsTracker", FakeOnlineTracker)
110+
monkeypatch.setattr(monitor_module, "OfflineEmissionsTracker", FakeTracker)
111+
monkeypatch.setattr(monitor_module.subprocess, "Popen", FakePopen)
112+
monkeypatch.setattr(monitor_module, "print", lambda *args, **kwargs: None)
113+
114+
with pytest.raises(typer.Exit) as exc_info:
115+
monitor_module.run_and_monitor(
116+
SimpleNamespace(args=["echo", "hi"]),
117+
save_to_api=True,
118+
)
119+
120+
assert exc_info.value.exit_code == 0
121+
assert captured["kwargs"]["tracking_mode"] == "process"
122+
assert captured["kwargs"]["save_to_api"] is True
123+
124+
63125
def test_run_and_monitor_handles_keyboard_interrupt(monkeypatch):
64126
process_info = {"terminated": 0, "killed": 0}
65127

0 commit comments

Comments
 (0)