Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Emission(Base):
gpu_utilization_percent = Column(Float, nullable=True)
ram_utilization_percent = Column(Float, nullable=True)
wue = Column(Float, nullable=False, default=0)
water_consumed = Column(Float, nullable=False, default=0)
run_id = Column(UUID(as_uuid=True), ForeignKey("runs.id", ondelete="CASCADE"))
run = relationship("Run", back_populates="emissions")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ def add_emission(self, emission: EmissionCreate) -> UUID:
gpu_energy=emission.gpu_energy,
ram_energy=emission.ram_energy,
energy_consumed=emission.energy_consumed,
# coalesce None to 0: the column is NOT NULL
water_consumed=emission.water_consumed or 0,
cpu_utilization_percent=emission.cpu_utilization_percent,
gpu_utilization_percent=emission.gpu_utilization_percent,
ram_utilization_percent=emission.ram_utilization_percent,
Expand Down Expand Up @@ -108,6 +110,7 @@ def map_sql_to_schema(emission: sql_models.Emission) -> Emission:
gpu_energy=emission.gpu_energy,
ram_energy=emission.ram_energy,
energy_consumed=emission.energy_consumed,
water_consumed=emission.water_consumed,
cpu_utilization_percent=emission.cpu_utilization_percent,
gpu_utilization_percent=emission.gpu_utilization_percent,
ram_utilization_percent=emission.ram_utilization_percent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def get_project_global_sums_by_experiment(self, project_id):
SqlModelExperiment.description,
func.sum(SqlModelEmission.emissions_sum).label("emission_sum"),
func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"),
func.sum(SqlModelEmission.water_consumed).label("water_consumed"),
func.sum(SqlModelEmission.duration).label("duration"),
)
.join(
Expand Down Expand Up @@ -133,6 +134,7 @@ def get_project_detailed_sums_by_experiment(
func.sum(SqlModelEmission.gpu_energy).label("gpu_energy"),
func.sum(SqlModelEmission.ram_energy).label("ram_energy"),
func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"),
func.sum(SqlModelEmission.water_consumed).label("water_consumed"),
func.sum(SqlModelEmission.duration).label("duration"),
func.avg(SqlModelEmission.emissions_rate).label("emissions_rate"),
func.avg(SqlModelEmission.cpu_utilization_percent).label(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def get_organization_detailed_sums(
func.sum(SqlModelEmission.gpu_energy).label("gpu_energy"),
func.sum(SqlModelEmission.ram_energy).label("ram_energy"),
func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"),
func.sum(SqlModelEmission.water_consumed).label("water_consumed"),
func.sum(SqlModelEmission.duration).label("duration"),
func.avg(SqlModelEmission.emissions_rate).label("emissions_rate"),
func.avg(SqlModelEmission.cpu_utilization_percent).label(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def get_project_detailed_sums(
func.sum(SqlModelEmission.gpu_energy).label("gpu_energy"),
func.sum(SqlModelEmission.ram_energy).label("ram_energy"),
func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"),
func.sum(SqlModelEmission.water_consumed).label("water_consumed"),
func.sum(SqlModelEmission.duration).label("duration"),
func.avg(SqlModelEmission.emissions_rate).label("emissions_rate"),
func.avg(SqlModelEmission.cpu_utilization_percent).label(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def get_experiment_detailed_sums_by_run(
func.sum(SqlModelEmission.gpu_energy).label("gpu_energy"),
func.sum(SqlModelEmission.ram_energy).label("ram_energy"),
func.sum(SqlModelEmission.energy_consumed).label("energy_consumed"),
func.sum(SqlModelEmission.water_consumed).label("water_consumed"),
func.sum(SqlModelEmission.duration).label("duration"),
func.avg(SqlModelEmission.emissions_rate).label("emissions_rate"),
func.avg(SqlModelEmission.cpu_utilization_percent).label(
Expand Down
10 changes: 10 additions & 0 deletions carbonserver/carbonserver/api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ class EmissionBase(BaseModel):
ge=0,
description="The WUE (Water Usage Effectiveness) must be greater than or equal to zero",
)
water_consumed: Optional[float] = Field(
default=0,
ge=0,
description="The water consumed (L) must be greater than or equal to zero",
)

model_config = ConfigDict(
json_schema_extra={
Expand All @@ -117,6 +122,7 @@ class EmissionBase(BaseModel):
"ram_energy": 2.0,
"energy_consumed": 57.21874,
"wue": 0,
"water_consumed": 0,
}
}
)
Expand Down Expand Up @@ -190,6 +196,7 @@ class RunReport(RunBase):
gpu_energy: float
ram_energy: float
energy_consumed: float
water_consumed: float = 0
duration: float
emissions_rate: float
emissions_count: int
Expand Down Expand Up @@ -283,6 +290,7 @@ class ExperimentReport(ExperimentBase):
gpu_energy: float
ram_energy: float
energy_consumed: float
water_consumed: float = 0
duration: int
emissions_rate: float
emissions_count: int
Expand Down Expand Up @@ -396,6 +404,7 @@ class ProjectReport(ProjectBase):
gpu_energy: float
ram_energy: float
energy_consumed: float
water_consumed: float = 0
duration: int
emissions_rate: float
emissions_count: int
Expand Down Expand Up @@ -443,6 +452,7 @@ class OrganizationReport(OrganizationBase):
gpu_energy: float
ram_energy: float
energy_consumed: float
water_consumed: float = 0
duration: int
emissions_rate: float
emissions_count: int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""add water_consumed to emissions

Revision ID: 20260902_add_water
Revises: 20251119_add_utilization
Create Date: 2026-09-02

"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "20260902_add_water"
down_revision = "20251119_add_utilization"
branch_labels = None
depends_on = None


def upgrade():
op.add_column(
"emissions",
sa.Column("water_consumed", sa.Float(), nullable=False, server_default="0"),
)


def downgrade():
op.drop_column("emissions", "water_consumed")
13 changes: 12 additions & 1 deletion carbonserver/tests/api/routers/test_emissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"ram_energy": 2.0,
"energy_consumed": 57.21874,
"wue": 0,
"water_consumed": 0.0,
}

EMISSION_1 = {
Expand All @@ -57,6 +58,7 @@
"ram_energy": 2.0,
"energy_consumed": 57.21874,
"wue": 0,
"water_consumed": 0.0,
"cpu_utilization_percent": None,
"gpu_utilization_percent": None,
"ram_utilization_percent": None,
Expand All @@ -77,6 +79,7 @@
"ram_energy": 2.0,
"energy_consumed": 57.21874,
"wue": 0,
"water_consumed": 0.0,
}


Expand All @@ -95,6 +98,7 @@
"ram_energy": 2.0,
"energy_consumed": 57.21874,
"wue": 0,
"water_consumed": 0.0,
}


Expand Down Expand Up @@ -229,7 +233,7 @@ def test_add_emission_with_default_wue_value(client, custom_test_server):
"gpu_energy": 0.0,
"ram_energy": 2.0,
"energy_consumed": 57.21874,
# Note: wue is not provided, should default to 0
# Note: wue and water_consumed are not provided, should default to 0
}

repository_mock = mock.Mock(spec=EmissionRepository)
Expand Down Expand Up @@ -266,6 +270,9 @@ def test_add_emission_with_default_wue_value(client, custom_test_server):
# Verify that the repository was called with WUE defaulting to 0
called_emission = repository_mock.add_emission.call_args[0][0]
assert called_emission.wue == 0, "WUE should default to 0 when not provided"
assert (
called_emission.water_consumed == 0
), "water_consumed should default to 0 when not provided"


def test_add_emission_with_custom_wue_value(client, custom_test_server):
Expand All @@ -285,6 +292,7 @@ def test_add_emission_with_custom_wue_value(client, custom_test_server):
"ram_energy": 2.0,
"energy_consumed": 57.21874,
"wue": 1.5,
"water_consumed": 12.5,
}

repository_mock = mock.Mock(spec=EmissionRepository)
Expand Down Expand Up @@ -321,3 +329,6 @@ def test_add_emission_with_custom_wue_value(client, custom_test_server):
# Verify that the repository was called with the correct WUE value
called_emission = repository_mock.add_emission.call_args[0][0]
assert called_emission.wue == 1.5, "WUE should be set to the provided value"
assert (
called_emission.water_consumed == 12.5
), "water_consumed should be set to the provided value"
1 change: 1 addition & 0 deletions codecarbon/core/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def add_emission(self, carbon_emission: dict):
gpu_utilization_percent=carbon_emission.get("gpu_utilization_percent"),
ram_utilization_percent=carbon_emission.get("ram_utilization_percent"),
wue=carbon_emission.get("wue", 0),
water_consumed=carbon_emission.get("water_consumed", 0),
)
try:
payload = dataclasses.asdict(emission)
Expand Down
1 change: 1 addition & 0 deletions codecarbon/core/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class EmissionBase:
gpu_utilization_percent: Optional[float] = None
ram_utilization_percent: Optional[float] = None
wue: Optional[float] = 0
water_consumed: Optional[float] = 0


class EmissionCreate(EmissionBase):
Expand Down
15 changes: 15 additions & 0 deletions codecarbon/core/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ def from_kgs_per_kWh(cls, kgs_per_kWh: float) -> "EmissionsPerKWh":
return cls(kgs_per_kWh=kgs_per_kWh)


@dataclass
class WaterPerKWh:
"""
Measured in L/kWh
"""

GALUS_TO_L = 3.785411784

l_per_kWh: float

@classmethod
def from_gal_us_per_MWh(cls, gal_us_per_MWh: float) -> "WaterPerKWh":
return cls(l_per_kWh=gal_us_per_MWh * WaterPerKWh.GALUS_TO_L * 0.001)


@dataclass(order=True)
class Energy:
"""
Expand Down
Loading
Loading