Skip to content

Commit b08b602

Browse files
author
benoit-cty
committed
Updated Emissions, EmissionsTracker, OfflineEmissionsTracker, and @track_emissions.
1 parent 5a523de commit b08b602

4 files changed

Lines changed: 102 additions & 43 deletions

File tree

codecarbon/core/emissions.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(
2525
co2_signal_api_token: Optional[
2626
str
2727
] = None, # Deprecated, for backward compatibility
28-
custom_carbon_intensity_g_co2e_kwh: Optional[float] = None,
28+
force_carbon_intensity_g_co2e_kwh: Optional[float] = None,
2929
):
3030
self._data_source = data_source
3131

@@ -39,7 +39,7 @@ def __init__(
3939
electricitymaps_api_token = co2_signal_api_token
4040

4141
self._electricitymaps_api_token = electricitymaps_api_token
42-
self._custom_carbon_intensity_g_co2e_kwh = custom_carbon_intensity_g_co2e_kwh
42+
self._force_carbon_intensity_g_co2e_kwh = force_carbon_intensity_g_co2e_kwh
4343

4444
def get_cloud_emissions(
4545
self, energy: Energy, cloud: CloudMetadata, geo: GeoMetadata = None
@@ -52,11 +52,11 @@ def get_cloud_emissions(
5252
:return: CO2 emissions in kg
5353
"""
5454

55-
if self._custom_carbon_intensity_g_co2e_kwh is not None:
55+
if self._force_carbon_intensity_g_co2e_kwh is not None:
5656
logger.info(
57-
f"Using custom carbon intensity for cloud emissions: {self._custom_carbon_intensity_g_co2e_kwh} gCO2e/kWh"
57+
f"Using forced carbon intensity for cloud emissions: {self._force_carbon_intensity_g_co2e_kwh} gCO2e/kWh"
5858
)
59-
return energy.kWh * (self._custom_carbon_intensity_g_co2e_kwh / 1000.0)
59+
return energy.kWh * (self._force_carbon_intensity_g_co2e_kwh / 1000.0)
6060

6161
df: pd.DataFrame = self._data_source.get_cloud_emissions_data()
6262
try:
@@ -146,11 +146,11 @@ def get_private_infra_emissions(self, energy: Energy, geo: GeoMetadata) -> float
146146
:param geo: Country and region metadata
147147
:return: CO2 emissions in kg
148148
"""
149-
if self._custom_carbon_intensity_g_co2e_kwh is not None:
149+
if self._force_carbon_intensity_g_co2e_kwh is not None:
150150
logger.info(
151-
f"Using custom carbon intensity for private infrastructure emissions: {self._custom_carbon_intensity_g_co2e_kwh} gCO2e/kWh"
151+
f"Using forced carbon intensity for private infrastructure emissions: {self._force_carbon_intensity_g_co2e_kwh} gCO2e/kWh"
152152
)
153-
return energy.kWh * (self._custom_carbon_intensity_g_co2e_kwh / 1000.0)
153+
return energy.kWh * (self._force_carbon_intensity_g_co2e_kwh / 1000.0)
154154

155155
if self._electricitymaps_api_token:
156156
try:

codecarbon/emissions_tracker.py

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ def __init__(
195195
force_ram_power: Optional[int] = _sentinel,
196196
pue: Optional[float] = _sentinel,
197197
wue: Optional[float] = _sentinel,
198+
force_carbon_intensity_g_co2e_kwh: Optional[float] = _sentinel,
198199
force_mode_cpu_load: Optional[bool] = _sentinel,
199200
allow_multiple_runs: Optional[bool] = _sentinel,
200201
rapl_include_dram: Optional[bool] = _sentinel,
@@ -259,11 +260,13 @@ def __init__(
259260
then RAM power (W) = Number of RAM Slots × 5 Watts.
260261
:param pue: PUE (Power Usage Effectiveness) of the data center where the
261262
experiment is being run.
263+
:param wue: WUE (Water Usage Effectiveness) of the data center. Units of L/kWh:
264+
litres of water consumed per kilowatt-hour of electricity consumed.
265+
:param force_carbon_intensity_g_co2e_kwh: Override grid carbon intensity
266+
in gCO2e/kWh for emissions calculations.
262267
:param force_mode_cpu_load: Force the addition of a CPU in MODE_CPU_LOAD
263268
:param allow_multiple_runs: Allow multiple CodeCarbon instances on the same machine.
264269
Defaults to True since v3 (was False in v2).
265-
:param wue: WUE (Water Usage Effectiveness) of the data center. Units of L/kWh:
266-
litres of water consumed per kilowatt-hour of electricity consumed.
267270
:param rapl_include_dram: Include DRAM (memory) power in RAPL measurements on Linux,
268271
defaults to False. When True, measures CPU package + DRAM.
269272
Only affects systems where RAPL exposes separate DRAM domains.
@@ -276,33 +279,31 @@ def __init__(
276279

277280
# logger.info("base tracker init")
278281
self._external_conf = get_hierarchical_config()
279-
custom_intensity_str = self._external_conf.get(
280-
"custom_carbon_intensity_g_co2e_kwh"
282+
self._set_from_conf(
283+
force_carbon_intensity_g_co2e_kwh,
284+
"force_carbon_intensity_g_co2e_kwh",
285+
None,
286+
float,
281287
)
282288
parsed_intensity = None
283-
if custom_intensity_str is not None:
284-
custom_intensity_str_stripped = custom_intensity_str.strip()
285-
if custom_intensity_str_stripped == "":
286-
logger.warning(
287-
f"CODECARBON : Invalid value for custom_carbon_intensity_g_co2e_kwh: '{custom_intensity_str}'. "
288-
"It cannot be empty or whitespace. Using default calculation methods."
289-
)
290-
else:
291-
try:
292-
value = float(custom_intensity_str_stripped)
293-
if value > 0:
294-
parsed_intensity = value
295-
else:
296-
logger.warning(
297-
f"CODECARBON : Invalid value for custom_carbon_intensity_g_co2e_kwh: '{custom_intensity_str_stripped}'. "
298-
"It must be a positive number. Using default calculation methods."
299-
)
300-
except ValueError:
289+
if self._force_carbon_intensity_g_co2e_kwh is not None:
290+
try:
291+
value = float(self._force_carbon_intensity_g_co2e_kwh)
292+
if value >= 0:
293+
parsed_intensity = value
294+
else:
301295
logger.warning(
302-
f"CODECARBON : Invalid value for custom_carbon_intensity_g_co2e_kwh: '{custom_intensity_str_stripped}'. "
303-
"It must be a numeric value. Using default calculation methods."
296+
f"CODECARBON : Invalid value for force_carbon_intensity_g_co2e_kwh: '{self._force_carbon_intensity_g_co2e_kwh}'. "
297+
"It must be a non-negative number. Using default calculation methods."
304298
)
305-
self.custom_carbon_intensity_g_co2e_kwh = parsed_intensity
299+
except (ValueError, TypeError):
300+
logger.warning(
301+
f"CODECARBON : Invalid value for force_carbon_intensity_g_co2e_kwh: '{self._force_carbon_intensity_g_co2e_kwh}'. "
302+
"It must be a numeric value. Using default calculation methods."
303+
)
304+
self._force_carbon_intensity_g_co2e_kwh = parsed_intensity
305+
self._conf["force_carbon_intensity_g_co2e_kwh"] = parsed_intensity
306+
self.force_carbon_intensity_g_co2e_kwh = parsed_intensity
306307
self._set_from_conf(allow_multiple_runs, "allow_multiple_runs", True, bool)
307308
if self._allow_multiple_runs:
308309
logger.warning(
@@ -380,9 +381,9 @@ def __init__(
380381
experiment_id, "experiment_id", "5b0fa12a-3dd7-45bb-9766-cc326314d9f1"
381382
)
382383

383-
if self.custom_carbon_intensity_g_co2e_kwh is not None:
384+
if self.force_carbon_intensity_g_co2e_kwh is not None:
384385
logger.info(
385-
f"CODECARBON : Using custom carbon intensity: {self.custom_carbon_intensity_g_co2e_kwh} gCO2e/kWh."
386+
f"CODECARBON : Using forced carbon intensity: {self.force_carbon_intensity_g_co2e_kwh} gCO2e/kWh."
386387
)
387388

388389
assert self._tracking_mode in ["machine", "process"]
@@ -480,7 +481,7 @@ def __init__(
480481
self._emissions: Emissions = Emissions(
481482
self._data_source,
482483
self._electricitymaps_api_token,
483-
custom_carbon_intensity_g_co2e_kwh=self.custom_carbon_intensity_g_co2e_kwh,
484+
force_carbon_intensity_g_co2e_kwh=self.force_carbon_intensity_g_co2e_kwh,
484485
)
485486
self._init_output_methods(api_key=self._api_key)
486487

@@ -1344,6 +1345,7 @@ def track_emissions(
13441345
force_ram_power: Optional[int] = _sentinel,
13451346
pue: Optional[float] = _sentinel,
13461347
wue: Optional[float] = _sentinel,
1348+
force_carbon_intensity_g_co2e_kwh: Optional[float] = _sentinel,
13471349
allow_multiple_runs: Optional[bool] = _sentinel,
13481350
rapl_include_dram: Optional[bool] = _sentinel,
13491351
rapl_prefer_psys: Optional[bool] = _sentinel,
@@ -1426,6 +1428,8 @@ def track_emissions(
14261428
:param pue: PUE (Power Usage Effectiveness) of the data center.
14271429
:param wue: WUE (Water Usage Effectiveness) of the data center. Units of L/kWh:
14281430
litres of water consumed per kilowatt-hour of electricity consumed.
1431+
:param force_carbon_intensity_g_co2e_kwh: Override grid carbon intensity
1432+
in gCO2e/kWh for emissions calculations.
14291433
:param rapl_include_dram: Include DRAM in RAPL measurements on Linux (default: False).
14301434
When True, measures CPU package + DRAM.
14311435
:param rapl_prefer_psys: Prefer psys over package domains for RAPL on Linux
@@ -1481,6 +1485,7 @@ def wrapped_fn(*args, **kwargs):
14811485
force_ram_power=force_ram_power,
14821486
pue=pue,
14831487
wue=wue,
1488+
force_carbon_intensity_g_co2e_kwh=force_carbon_intensity_g_co2e_kwh,
14841489
allow_multiple_runs=allow_multiple_runs,
14851490
rapl_include_dram=rapl_include_dram,
14861491
rapl_prefer_psys=rapl_prefer_psys,
@@ -1515,6 +1520,7 @@ def wrapped_fn(*args, **kwargs):
15151520
force_ram_power=force_ram_power,
15161521
pue=pue,
15171522
wue=wue,
1523+
force_carbon_intensity_g_co2e_kwh=force_carbon_intensity_g_co2e_kwh,
15181524
allow_multiple_runs=allow_multiple_runs,
15191525
rapl_include_dram=rapl_include_dram,
15201526
rapl_prefer_psys=rapl_prefer_psys,

tests/test_config.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
parse_env_config,
1212
parse_gpu_ids,
1313
)
14-
from codecarbon.emissions_tracker import EmissionsTracker
14+
from codecarbon.emissions_tracker import (
15+
EmissionsTracker,
16+
OfflineEmissionsTracker,
17+
track_emissions,
18+
)
1519
from codecarbon.external.hardware import GPU
1620
from tests.testutils import get_custom_mock_open
1721

@@ -199,7 +203,7 @@ def test_full_hierarchy(self):
199203
force_ram_power=50.5
200204
output_dir=ERROR:not overwritten
201205
save_to_file=ERROR:not overwritten
202-
custom_carbon_intensity_g_co2e_kwh=123.4
206+
force_carbon_intensity_g_co2e_kwh=123.4
203207
"""
204208
)
205209
local_conf = dedent(
@@ -226,10 +230,59 @@ def test_full_hierarchy(self):
226230
self.assertEqual(tracker._emissions_endpoint, "http://testhost:2000")
227231
self.assertEqual(tracker._gpu_ids, ["0", "1"])
228232
self.assertEqual(tracker._electricitymaps_api_token, "signal-token")
229-
self.assertEqual(tracker.custom_carbon_intensity_g_co2e_kwh, 123.4)
233+
self.assertEqual(tracker.force_carbon_intensity_g_co2e_kwh, 123.4)
230234
self.assertEqual(tracker._project_name, "test-project")
231235
self.assertTrue(tracker._save_to_file)
232236

237+
def test_force_carbon_intensity_constructor_overrides_config(self):
238+
global_conf = dedent(
239+
"""\
240+
[codecarbon]
241+
force_carbon_intensity_g_co2e_kwh=123.4
242+
"""
243+
)
244+
245+
with patch("builtins.open", new_callable=get_custom_mock_open(global_conf, "")):
246+
with patch("os.path.exists", return_value=True):
247+
tracker = EmissionsTracker(
248+
force_carbon_intensity_g_co2e_kwh=456.7,
249+
save_to_file=False,
250+
allow_multiple_runs=True,
251+
)
252+
253+
self.assertEqual(tracker.force_carbon_intensity_g_co2e_kwh, 456.7)
254+
self.assertEqual(tracker._conf["force_carbon_intensity_g_co2e_kwh"], 456.7)
255+
256+
def test_offline_tracker_accepts_force_carbon_intensity_parameter(self):
257+
with patch("builtins.open", new_callable=get_custom_mock_open("", "")):
258+
with patch("os.path.exists", return_value=True):
259+
tracker = OfflineEmissionsTracker(
260+
country_iso_code="FRA",
261+
force_carbon_intensity_g_co2e_kwh=0,
262+
save_to_file=False,
263+
allow_multiple_runs=True,
264+
)
265+
266+
self.assertEqual(tracker.force_carbon_intensity_g_co2e_kwh, 0.0)
267+
268+
def test_track_emissions_forwards_force_carbon_intensity_parameter(self):
269+
with patch("codecarbon.emissions_tracker.EmissionsTracker") as tracker_class:
270+
271+
@track_emissions(
272+
force_carbon_intensity_g_co2e_kwh=321.0,
273+
save_to_file=False,
274+
)
275+
def tracked_function():
276+
return "success"
277+
278+
self.assertEqual(tracked_function(), "success")
279+
280+
tracker_class.assert_called_once()
281+
self.assertEqual(
282+
tracker_class.call_args.kwargs["force_carbon_intensity_g_co2e_kwh"],
283+
321.0,
284+
)
285+
233286
@mock.patch.dict(
234287
os.environ,
235288
{

tests/test_emissions.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,9 @@ def test_get_emissions_PRIVATE_INFRA_unknown_country(self):
175175
self.assertAlmostEqual(emissions, 0.475, places=2)
176176

177177
@patch("codecarbon.core.electricitymaps_api.get_emissions")
178-
def test_private_infra_uses_custom_intensity_when_set(self, mocked_get_emissions):
178+
def test_private_infra_uses_forced_intensity_when_set(self, mocked_get_emissions):
179179
emissions_calculator = Emissions(
180-
self._data_source, custom_carbon_intensity_g_co2e_kwh=50.0
180+
self._data_source, force_carbon_intensity_g_co2e_kwh=50.0
181181
)
182182

183183
emissions = emissions_calculator.get_private_infra_emissions(
@@ -188,9 +188,9 @@ def test_private_infra_uses_custom_intensity_when_set(self, mocked_get_emissions
188188
self.assertAlmostEqual(emissions, 0.1, places=6)
189189
mocked_get_emissions.assert_not_called()
190190

191-
def test_cloud_uses_custom_intensity_when_set(self):
191+
def test_cloud_uses_forced_intensity_when_set(self):
192192
emissions_calculator = Emissions(
193-
self._data_source, custom_carbon_intensity_g_co2e_kwh=100.0
193+
self._data_source, force_carbon_intensity_g_co2e_kwh=100.0
194194
)
195195

196196
emissions = emissions_calculator.get_cloud_emissions(

0 commit comments

Comments
 (0)