Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions codecarbon/emissions_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from codecarbon.lock import Lock
from codecarbon.output import (
BaseOutput,
BoAmpsOutput,
CodeCarbonAPIOutput,
EmissionsData,
FileOutput,
Expand Down Expand Up @@ -177,6 +178,7 @@ def __init__(
logging_logger: Optional[LoggerOutput] = _sentinel,
save_to_prometheus: Optional[bool] = _sentinel,
save_to_logfire: Optional[bool] = _sentinel,
save_to_boamps: Optional[bool] = _sentinel,
prometheus_url: Optional[str] = _sentinel,
output_handlers: Optional[List[BaseOutput]] = _sentinel,
gpu_ids: Optional[List] = _sentinel,
Expand Down Expand Up @@ -227,6 +229,8 @@ def __init__(
pushed to prometheus, defaults to False.
:param save_to_logfire: Indicates if the emission artifacts should be written
to a logfire observability platform, defaults to False.
:param save_to_boamps: Indicates if the emission artifacts should be written
to a BoAmps JSON report, defaults to False.
:param prometheus_url: url of the prometheus server, defaults to `localhost:9091`.
:param output_handlers: List of custom output handlers to use. Defaults to [].
:param gpu_ids: Comma-separated list of GPU ids to track, defaults to None.
Expand Down Expand Up @@ -337,6 +341,7 @@ def __init__(
self._set_from_conf(logging_logger, "logging_logger")
self._set_from_conf(save_to_prometheus, "save_to_prometheus", False, bool)
self._set_from_conf(save_to_logfire, "save_to_logfire", False, bool)
self._set_from_conf(save_to_boamps, "save_to_boamps", False, bool)
self._set_from_conf(prometheus_url, "prometheus_url", "localhost:9091")
self._set_from_conf(output_handlers, "output_handlers", [])
self._set_from_conf(tracking_mode, "tracking_mode", "machine")
Expand Down Expand Up @@ -496,6 +501,9 @@ def _init_output_methods(self, *, api_key: str = None):
if self._save_to_logfire:
self._output_handlers.append(LogfireOutput())

if self._save_to_boamps:
self._output_handlers.append(BoAmpsOutput(output_dir=self._output_dir))

def get_detected_hardware(self) -> Dict[str, Any]:
"""
Get the detected hardware.
Expand Down Expand Up @@ -1286,6 +1294,7 @@ def track_emissions(
logging_logger: Optional[LoggerOutput] = _sentinel,
save_to_prometheus: Optional[bool] = _sentinel,
save_to_logfire: Optional[bool] = _sentinel,
save_to_boamps: Optional[bool] = _sentinel,
prometheus_url: Optional[str] = _sentinel,
output_handlers: Optional[List[BaseOutput]] = _sentinel,
gpu_ids: Optional[List] = _sentinel,
Expand Down Expand Up @@ -1340,6 +1349,8 @@ def track_emissions(
pushed to prometheus, defaults to False.
:param save_to_logfire: Indicates if the emission artifacts should be
pushed to logfire, defaults to False.
:param save_to_boamps: Indicates if the emission artifacts should be
written to a BoAmps JSON report, defaults to False.
:param prometheus_url: url of the prometheus server, defaults to `localhost:9091`.
:param output_handlers: List of output handlers to use.
:param gpu_ids: User-specified known gpu ids to track.
Expand Down Expand Up @@ -1430,6 +1441,7 @@ def wrapped_fn(*args, **kwargs):
logging_logger=logging_logger,
save_to_prometheus=save_to_prometheus,
save_to_logfire=save_to_logfire,
save_to_boamps=save_to_boamps,
prometheus_url=prometheus_url,
output_handlers=output_handlers,
gpu_ids=gpu_ids,
Expand Down Expand Up @@ -1466,6 +1478,7 @@ def wrapped_fn(*args, **kwargs):
logging_logger=logging_logger,
save_to_prometheus=save_to_prometheus,
save_to_logfire=save_to_logfire,
save_to_boamps=save_to_boamps,
prometheus_url=prometheus_url,
output_handlers=output_handlers,
gpu_ids=gpu_ids,
Expand Down
25 changes: 25 additions & 0 deletions docs/reference/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,31 @@ tracker.stop()

The first time it will ask you to log in to Logfire. Once you log in and set the default Logfire project, the metrics will appear following the format `codecarbon_*`.

## BoAmps

[BoAmps](https://github.com/Boavizta/BoAmps) is a standardized JSON format for reporting AI and ML energy consumption.

### How to use it

Run your EmissionsTracker as usual, with `save_to_boamps=True`:

```python-skip
from codecarbon import OfflineEmissionsTracker

tracker = OfflineEmissionsTracker(
Copy link
Copy Markdown
Collaborator

@davidberenstein1957 davidberenstein1957 Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benoit-cty do you think it might be worth refactoring something to save_format or save_to allowing to pass a string for the format as the number of arguments seem to be growing quite large. WDYT?

tracker = OfflineEmissionsTracker(
    save_to="boamps"
)
# or if we really want to enable duplciates
tracker = OfflineEmissionsTracker(
    save_to=["boamps", "logfire"]
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we definitely needs better arguments readability.

I did what you suggest with an Enum:

from codecarbon import EmissionsTracker, OutputMethod
tracker = EmissionsTracker(
    output_methods=[OutputMethod.BOAMPS],
)

project_name="my_project",
country_iso_code="USA",
save_to_boamps=True,
)
tracker.start()
# Your code here
tracker.stop()
```

CodeCarbon writes a final report named `boamps_report_<run_id>.json` in `output_dir`.

If you need to enrich the report with task metadata, datasets, or publisher information, use `BoAmpsOutput` directly through `output_handlers` or start from [examples/boamps_output.py](../../examples/boamps_output.py).

## HTTP Output

The HTTP Output allows calling a webhook with emission data when the tracker is stopped. Use the `emissions_endpoint` parameter to specify your endpoint.
Expand Down
34 changes: 34 additions & 0 deletions examples/boamps_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import multiprocessing
from time import sleep

from codecarbon import EmissionsTracker


def no_load_task(number):
sleep(60)


def cpu_load_task(number):
a = 0
for i in range(1000):
for i in range(int(1e6)):
a = a + i**number


tracker = EmissionsTracker(
measure_power_secs=10,
force_mode_cpu_load=False,
log_level="debug",
save_to_file=False,
save_to_boamps=True,
)
try:
tracker.start()
with multiprocessing.Pool() as pool:
# call the function for each item in parallel
pool.map(cpu_load_task, [i for i in range(100)])
finally:
emissions: float = tracker.stop()

print(f"Emissions: {emissions} kg")
print(f"BoAmps report written to ./boamps_report_{tracker.run_id}.json")
50 changes: 50 additions & 0 deletions tests/test_emissions_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
track_emissions,
)
from codecarbon.external.geography import CloudMetadata
from codecarbon.output import BoAmpsOutput
from tests.fake_modules import pynvml as fake_pynvml
from tests.testdata import (
GEO_METADATA_CANADA,
Expand Down Expand Up @@ -212,6 +213,29 @@ def raise_exception(*args, **kwargs):
tracker._measure_power = raise_exception
tracker.stop()

def test_save_to_boamps_adds_boamps_output_handler(
self,
mock_cli_setup,
mock_log_values,
mocked_get_gpu_details,
mocked_env_cloud_details,
mocked_is_gpu_details_available,
mocked_is_nvidia_system,
):
tracker = EmissionsTracker(
output_dir=self.temp_path,
output_handlers=[],
save_to_file=False,
save_to_boamps=True,
)

self.assertTrue(
any(
isinstance(handler, BoAmpsOutput)
for handler in tracker._output_handlers
)
)

@responses.activate
def test_decorator_ONLINE_NO_ARGS(
self,
Expand Down Expand Up @@ -268,6 +292,32 @@ def dummy_train_model():
# THEN
self.verify_output_file(self.emissions_file_path, 2)

def test_decorator_online_passes_save_to_boamps(
self,
mock_cli_setup,
mock_log_values,
mocked_get_gpu_details,
mocked_env_cloud_details,
mocked_is_gpu_details_available,
mocked_is_nvidia_system,
):
mocked_tracker = mock.Mock()

with mock.patch(
"codecarbon.emissions_tracker.EmissionsTracker",
return_value=mocked_tracker,
) as mocked_tracker_cls:

@track_emissions(save_to_boamps=True)
def dummy_train_model():
return 42

self.assertEqual(dummy_train_model(), 42)

self.assertTrue(mocked_tracker_cls.call_args.kwargs["save_to_boamps"])
mocked_tracker.start.assert_called_once()
mocked_tracker.stop.assert_called_once()

def test_decorator_OFFLINE_NO_COUNTRY(
self,
mock_cli_setup,
Expand Down
Loading