Skip to content

Commit 2c4b749

Browse files
committed
Merge branch 'v2.0' into wk9874/2.0.0_a3
2 parents 009333e + ac71d0c commit 2c4b749

File tree

4 files changed

+121
-34
lines changed

4 files changed

+121
-34
lines changed

simvue/config/parameters.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def check_token(cls, v: typing.Any) -> str | None:
4848

4949
class OfflineSpecifications(pydantic.BaseModel):
5050
cache: pathlib.Path | None = None
51+
country_iso_code: str | None = None
5152

5253

5354
class MetricsSpecifications(pydantic.BaseModel):

simvue/eco.py

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import logging
33
import datetime
44

5-
from codecarbon import EmissionsTracker
6-
from codecarbon.output_methods.base_output import BaseOutput as cc_BaseOutput
5+
from codecarbon import EmissionsTracker, OfflineEmissionsTracker
6+
from codecarbon.output import BaseOutput as cc_BaseOutput
77
from simvue.utilities import simvue_timestamp
88

99
if typing.TYPE_CHECKING:
@@ -32,30 +32,43 @@ def out(
3232

3333
if meta_update:
3434
logger.debug("Logging CodeCarbon metadata")
35-
self._simvue_run.update_metadata(
36-
{
37-
"codecarbon.country": total.country_name,
38-
"codecarbon.country_iso_code": total.country_iso_code,
39-
"codecarbon.region": total.region,
40-
"codecarbon.version": total.codecarbon_version,
41-
}
35+
try:
36+
self._simvue_run.update_metadata(
37+
{
38+
"codecarbon": {
39+
"country": total.country_name,
40+
"country_iso_code": total.country_iso_code,
41+
"region": total.region,
42+
"version": total.codecarbon_version,
43+
}
44+
}
45+
)
46+
except AttributeError as e:
47+
logger.error(f"Failed to update metadata: {e}")
48+
try:
49+
_cc_timestamp = datetime.datetime.strptime(
50+
total.timestamp, "%Y-%m-%dT%H:%M:%S"
4251
)
43-
44-
_cc_timestamp: datetime.datetime = datetime.datetime.strptime(
45-
total.timestamp, "%Y-%m-%dT%H:%M:%S"
46-
)
52+
except ValueError as e:
53+
logger.error(f"Error parsing timestamp: {e}")
54+
return
4755

4856
logger.debug("Logging CodeCarbon metrics")
49-
self._simvue_run.log_metrics(
50-
metrics={
51-
"codecarbon.total.emissions": total.emissions,
52-
"codecarbon.total.energy_consumed": total.energy_consumed,
53-
"codecarbon.delta.emissions": delta.emissions,
54-
"codecarbon.delta.energy_consumed": delta.energy_consumed,
55-
},
56-
step=self._metrics_step,
57-
timestamp=simvue_timestamp(_cc_timestamp),
58-
)
57+
try:
58+
self._simvue_run.log_metrics(
59+
metrics={
60+
"codecarbon.total.emissions": total.emissions,
61+
"codecarbon.total.energy_consumed": total.energy_consumed,
62+
"codecarbon.delta.emissions": delta.emissions,
63+
"codecarbon.delta.energy_consumed": delta.energy_consumed,
64+
},
65+
step=self._metrics_step,
66+
timestamp=simvue_timestamp(_cc_timestamp),
67+
)
68+
except ArithmeticError as e:
69+
logger.error(f"Failed to log metrics: {e}")
70+
return
71+
5972
self._metrics_step += 1
6073

6174
def live_out(self, total: "EmissionsData", delta: "EmissionsData") -> None:
@@ -71,6 +84,36 @@ def __init__(
7184
super().__init__(
7285
project_name=project_name,
7386
measure_power_secs=metrics_interval,
87+
api_call_interval=1,
88+
experiment_id=None,
89+
experiment_name=None,
90+
logging_logger=CodeCarbonOutput(simvue_run),
91+
save_to_logger=True,
92+
allow_multiple_runs=True,
93+
log_level="error",
94+
)
95+
96+
def set_measure_interval(self, interval: int) -> None:
97+
"""Set the measure interval"""
98+
self._set_from_conf(interval, "measure_power_secs")
99+
100+
def post_init(self) -> None:
101+
self._set_from_conf(self._simvue_run._id, "experiment_id")
102+
self._set_from_conf(self._simvue_run._name, "experiment_name")
103+
self.start()
104+
105+
106+
class OfflineSimvueEmissionsTracker(OfflineEmissionsTracker):
107+
def __init__(
108+
self, project_name: str, simvue_run: "Run", metrics_interval: int
109+
) -> None:
110+
self._simvue_run = simvue_run
111+
logger.setLevel(logging.ERROR)
112+
super().__init__(
113+
country_iso_code=simvue_run._user_config.offline.country_iso_code,
114+
project_name=project_name,
115+
measure_power_secs=metrics_interval,
116+
api_call_interval=1,
74117
experiment_id=None,
75118
experiment_name=None,
76119
logging_logger=CodeCarbonOutput(simvue_run),

simvue/run.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from .models import FOLDER_REGEX, NAME_REGEX, MetricKeyString
4444
from .system import get_system
4545
from .metadata import git_info, environment
46-
from .eco import SimvueEmissionsTracker
46+
from .eco import SimvueEmissionsTracker, OfflineSimvueEmissionsTracker
4747
from .utilities import (
4848
skip_if_failed,
4949
validate_timestamp,
@@ -209,11 +209,28 @@ def __init__(
209209
)
210210
else self._user_config.metrics.emission_metrics_interval
211211
)
212-
self._emissions_tracker: SimvueEmissionsTracker | None = (
213-
SimvueEmissionsTracker("simvue", self, self._emission_metrics_interval)
214-
if self._user_config.metrics.enable_emission_metrics
215-
else None
216-
)
212+
if mode == "offline":
213+
if (
214+
self._user_config.metrics.enable_emission_metrics
215+
and not self._user_config.offline.country_iso_code
216+
):
217+
raise ValueError(
218+
"Country ISO code must be provided if tracking emissions metrics in offline mode."
219+
)
220+
221+
self._emissions_tracker: OfflineSimvueEmissionsTracker | None = (
222+
OfflineSimvueEmissionsTracker(
223+
"simvue", self, self._emission_metrics_interval
224+
)
225+
if self._user_config.metrics.enable_emission_metrics
226+
else None
227+
)
228+
else:
229+
self._emissions_tracker: SimvueEmissionsTracker | None = (
230+
SimvueEmissionsTracker("simvue", self, self._emission_metrics_interval)
231+
if self._user_config.metrics.enable_emission_metrics
232+
else None
233+
)
217234

218235
def __enter__(self) -> Self:
219236
return self
@@ -1048,9 +1065,22 @@ def config(
10481065
self._emission_metrics_interval = emission_metrics_interval
10491066

10501067
if enable_emission_metrics:
1051-
self._emissions_tracker = SimvueEmissionsTracker(
1052-
"simvue", self, self._emission_metrics_interval
1053-
)
1068+
if self._user_config.run.mode == "offline":
1069+
if not self._user_config.offline.country_iso_code:
1070+
self._error(
1071+
"Country ISO code must be provided if tracking emissions metrics in offline mode."
1072+
)
1073+
self._emissions_tracker: OfflineSimvueEmissionsTracker = (
1074+
OfflineSimvueEmissionsTracker(
1075+
"simvue", self, self._emission_metrics_interval
1076+
)
1077+
)
1078+
else:
1079+
self._emissions_tracker: SimvueEmissionsTracker = (
1080+
SimvueEmissionsTracker(
1081+
"simvue", self, self._emission_metrics_interval
1082+
)
1083+
)
10541084

10551085
# If the main Run API object is initialised the run is active
10561086
# hence the tracker should start too

tests/functional/test_run_class.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,21 @@ def test_run_with_emissions() -> None:
5555
run_created.config(enable_emission_metrics=True, emission_metrics_interval=1)
5656
time.sleep(5)
5757
_run = RunObject(identifier=run_created.id)
58-
assert list(_run.metrics)
59-
58+
_metric_names = [item[0] for item in _run.metrics]
59+
client = sv_cl.Client()
60+
for _metric in ["emissions", "energy_consumed"]:
61+
_total_metric_name = f'codecarbon.total.{_metric}'
62+
_delta_metric_name = f'codecarbon.delta.{_metric}'
63+
assert _total_metric_name in _metric_names
64+
assert _delta_metric_name in _metric_names
65+
_metric_values = client.get_metric_values(metric_names=[_total_metric_name, _delta_metric_name], xaxis="time", output_format="dataframe", run_ids=[run_created.id])
66+
67+
# Check that total = previous total + latest delta
68+
_total_values = _metric_values[_total_metric_name].tolist()
69+
_delta_values = _metric_values[_delta_metric_name].tolist()
70+
assert len(_total_values) > 1
71+
for i in range(1, len(_total_values)):
72+
assert _total_values[i] == _total_values[i-1] + _delta_values[i]
6073

6174
@pytest.mark.run
6275
@pytest.mark.parametrize("timestamp", (datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f"), None), ids=("timestamp", "no_timestamp"))

0 commit comments

Comments
 (0)