Skip to content

Commit 83b611d

Browse files
committed
feat: add BoAmps as output method
1 parent b335365 commit 83b611d

11 files changed

Lines changed: 2383 additions & 0 deletions

File tree

codecarbon/output.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
from codecarbon.output_methods.base_output import BaseOutput # noqa: F401
66

7+
# Output to BoAmps format
8+
from codecarbon.output_methods.boamps import BoAmpsOutput # noqa: F401
9+
710
# emissions data
811
from codecarbon.output_methods.emissions_data import ( # noqa: F401
912
EmissionsData,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
BoAmps output support for CodeCarbon.
3+
4+
Provides first-class support for generating BoAmps (Boavizta) standardized
5+
JSON reports from CodeCarbon emission tracking data.
6+
"""
7+
8+
from codecarbon.output_methods.boamps.mapper import ( # noqa: F401
9+
map_emissions_to_boamps,
10+
)
11+
from codecarbon.output_methods.boamps.models import ( # noqa: F401
12+
BoAmpsAlgorithm,
13+
BoAmpsDataset,
14+
BoAmpsEnvironment,
15+
BoAmpsHardware,
16+
BoAmpsHeader,
17+
BoAmpsInfrastructure,
18+
BoAmpsMeasure,
19+
BoAmpsPublisher,
20+
BoAmpsReport,
21+
BoAmpsSoftware,
22+
BoAmpsSystem,
23+
BoAmpsTask,
24+
)
25+
from codecarbon.output_methods.boamps.output import BoAmpsOutput # noqa: F401
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
"""
2+
Maps CodeCarbon EmissionsData to BoAmps report format.
3+
"""
4+
5+
import warnings
6+
from typing import Optional
7+
8+
from codecarbon.output_methods.boamps.models import (
9+
BoAmpsEnvironment,
10+
BoAmpsHardware,
11+
BoAmpsHeader,
12+
BoAmpsInfrastructure,
13+
BoAmpsMeasure,
14+
BoAmpsReport,
15+
BoAmpsSoftware,
16+
BoAmpsSystem,
17+
BoAmpsTask,
18+
)
19+
from codecarbon.output_methods.emissions_data import EmissionsData
20+
21+
BOAMPS_FORMAT_VERSION = "0.1"
22+
BOAMPS_FORMAT_SPEC_URI = "https://github.com/Boavizta/BoAmps/tree/main/model"
23+
24+
25+
def map_emissions_to_boamps(
26+
emissions: EmissionsData,
27+
task: Optional[BoAmpsTask] = None,
28+
header: Optional[BoAmpsHeader] = None,
29+
quality: Optional[str] = None,
30+
infra_overrides: Optional[dict] = None,
31+
environment_overrides: Optional[dict] = None,
32+
) -> BoAmpsReport:
33+
"""
34+
Map CodeCarbon EmissionsData to a BoAmps report.
35+
36+
Auto-fills fields from EmissionsData and merges with user-provided context.
37+
User-provided values take precedence over auto-detected values.
38+
39+
Args:
40+
emissions: CodeCarbon emissions data from a completed run.
41+
task: User-provided task context (required for schema-valid BoAmps).
42+
header: User-provided header overrides.
43+
quality: Quality assessment ("high", "medium", "low").
44+
infra_overrides: Additional infrastructure fields (cloud_instance, cloud_service).
45+
environment_overrides: Additional environment fields (power_source, etc.).
46+
47+
Returns:
48+
A BoAmpsReport populated with auto-detected and user-provided data.
49+
"""
50+
report_header = _build_header(emissions, header)
51+
measures = [_build_measure(emissions)]
52+
system = _build_system(emissions)
53+
software = _build_software(emissions)
54+
infrastructure = _build_infrastructure(emissions, infra_overrides)
55+
environment = _build_environment(emissions, environment_overrides)
56+
57+
if task is None:
58+
warnings.warn(
59+
"No BoAmps task context provided. The output will be missing required "
60+
"fields (taskStage, taskFamily, algorithms, dataset) and will not "
61+
"validate against the BoAmps schema.",
62+
UserWarning,
63+
stacklevel=2,
64+
)
65+
66+
return BoAmpsReport(
67+
header=report_header,
68+
task=task,
69+
measures=measures,
70+
system=system,
71+
software=software,
72+
infrastructure=infrastructure,
73+
environment=environment,
74+
quality=quality,
75+
)
76+
77+
78+
def _build_header(
79+
emissions: EmissionsData, user_header: Optional[BoAmpsHeader]
80+
) -> BoAmpsHeader:
81+
"""Build header from EmissionsData, merging with user overrides."""
82+
auto_header = BoAmpsHeader(
83+
format_version=BOAMPS_FORMAT_VERSION,
84+
format_version_specification_uri=BOAMPS_FORMAT_SPEC_URI,
85+
report_id=emissions.run_id,
86+
report_datetime=emissions.timestamp,
87+
)
88+
89+
if user_header is None:
90+
return auto_header
91+
92+
# User values override auto-detected values
93+
return BoAmpsHeader(
94+
licensing=user_header.licensing or auto_header.licensing,
95+
format_version=user_header.format_version or auto_header.format_version,
96+
format_version_specification_uri=(
97+
user_header.format_version_specification_uri
98+
or auto_header.format_version_specification_uri
99+
),
100+
report_id=user_header.report_id or auto_header.report_id,
101+
report_datetime=user_header.report_datetime or auto_header.report_datetime,
102+
report_status=user_header.report_status or auto_header.report_status,
103+
publisher=user_header.publisher or auto_header.publisher,
104+
)
105+
106+
107+
def _build_measure(emissions: EmissionsData) -> BoAmpsMeasure:
108+
"""Build a BoAmps measure from EmissionsData."""
109+
measure = BoAmpsMeasure(
110+
measurement_method="codecarbon",
111+
version=emissions.codecarbon_version,
112+
power_consumption=emissions.energy_consumed,
113+
measurement_duration=emissions.duration,
114+
measurement_date_time=emissions.timestamp,
115+
cpu_tracking_mode=emissions.tracking_mode,
116+
)
117+
118+
# CPU utilization as fraction (0-1)
119+
if emissions.cpu_utilization_percent > 0:
120+
measure.average_utilization_cpu = round(
121+
emissions.cpu_utilization_percent / 100.0, 4
122+
)
123+
124+
# GPU fields only if GPU is present
125+
if emissions.gpu_count and emissions.gpu_count > 0:
126+
measure.gpu_tracking_mode = emissions.tracking_mode
127+
if emissions.gpu_utilization_percent > 0:
128+
measure.average_utilization_gpu = round(
129+
emissions.gpu_utilization_percent / 100.0, 4
130+
)
131+
132+
return measure
133+
134+
135+
def _build_system(emissions: EmissionsData) -> BoAmpsSystem:
136+
"""Build system info from EmissionsData."""
137+
return BoAmpsSystem(os=emissions.os)
138+
139+
140+
def _build_software(emissions: EmissionsData) -> BoAmpsSoftware:
141+
"""Build software info from EmissionsData."""
142+
return BoAmpsSoftware(
143+
language="python",
144+
version=emissions.python_version,
145+
)
146+
147+
148+
def _build_infrastructure(
149+
emissions: EmissionsData, overrides: Optional[dict] = None
150+
) -> BoAmpsInfrastructure:
151+
"""Build infrastructure from EmissionsData hardware fields."""
152+
components = []
153+
154+
# CPU component (always present)
155+
# emissions.cpu_count is logical thread count. BoAmps nbComponent expects
156+
# physical cores. Standard SMT/HT uses 2 threads per core.
157+
cpu_cores = max(1, int(emissions.cpu_count) // 2) if emissions.cpu_count else 1
158+
cpu_component = BoAmpsHardware(
159+
component_type="cpu",
160+
component_name=emissions.cpu_model,
161+
nb_component=cpu_cores,
162+
)
163+
components.append(cpu_component)
164+
165+
# GPU component (only if present)
166+
if emissions.gpu_count and emissions.gpu_count > 0:
167+
gpu_component = BoAmpsHardware(
168+
component_type="gpu",
169+
component_name=emissions.gpu_model if emissions.gpu_model else None,
170+
nb_component=int(emissions.gpu_count),
171+
)
172+
components.append(gpu_component)
173+
174+
# RAM component (always present)
175+
ram_component = BoAmpsHardware(
176+
component_type="ram",
177+
nb_component=1,
178+
memory_size=emissions.ram_total_size,
179+
)
180+
components.append(ram_component)
181+
182+
# emissions.on_cloud can be "N" even on public cloud (the tracker clears
183+
# cloud_provider/region for some providers). Use cloud_provider as a
184+
# secondary signal to avoid misreporting cloud runs as on-premise.
185+
is_cloud = emissions.on_cloud == "Y" or bool(emissions.cloud_provider)
186+
infra = BoAmpsInfrastructure(
187+
infra_type="publicCloud" if is_cloud else "onPremise",
188+
cloud_provider=emissions.cloud_provider if is_cloud and emissions.cloud_provider else None,
189+
components=components,
190+
)
191+
192+
# Apply overrides from context file
193+
if overrides:
194+
for attr in ("cloud_instance", "cloud_service", "infra_type"):
195+
if attr in overrides:
196+
setattr(infra, attr, overrides[attr])
197+
198+
# Merge user-provided components: enrich auto-detected components
199+
# with user-supplied details (manufacturer, family, series, share, etc.)
200+
# by matching on component_type. Extra user components are appended.
201+
if "components" in overrides:
202+
user_components = overrides["components"]
203+
auto_by_type = {c.component_type: c for c in infra.components}
204+
merged = []
205+
used_types = set()
206+
for user_comp in user_components:
207+
if user_comp.component_type in auto_by_type:
208+
auto = auto_by_type[user_comp.component_type]
209+
# User values take precedence; auto-detected fill blanks
210+
for f in ("component_name", "nb_component", "memory_size",
211+
"manufacturer", "family", "series", "share"):
212+
user_val = getattr(user_comp, f)
213+
if user_val is None:
214+
setattr(user_comp, f, getattr(auto, f))
215+
used_types.add(user_comp.component_type)
216+
merged.append(user_comp)
217+
# Keep auto-detected components that the user didn't override
218+
for auto in infra.components:
219+
if auto.component_type not in used_types:
220+
merged.append(auto)
221+
infra.components = merged
222+
223+
return infra
224+
225+
226+
def _build_environment(
227+
emissions: EmissionsData, overrides: Optional[dict] = None
228+
) -> BoAmpsEnvironment:
229+
"""Build environment from EmissionsData location fields."""
230+
env = BoAmpsEnvironment(
231+
country=emissions.country_name or None,
232+
latitude=emissions.latitude if emissions.latitude is not None else None,
233+
longitude=emissions.longitude if emissions.longitude is not None else None,
234+
)
235+
236+
if overrides:
237+
for attr in (
238+
"location",
239+
"power_supplier_type",
240+
"power_source",
241+
"power_source_carbon_intensity",
242+
):
243+
if attr in overrides:
244+
setattr(env, attr, overrides[attr])
245+
246+
return env

0 commit comments

Comments
 (0)