Skip to content
Open
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
15 changes: 13 additions & 2 deletions codecarbon/emissions_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@

_sentinel = object()

# One decimal place, so about 11.1 km at the equator.
_COORDINATE_PRECISION = 1


def _round_coordinate(value: Optional[float]) -> Optional[float]:
"""Reduce the precision of a latitude or longitude for privacy."""
return value if value is None else round(value, _COORDINATE_PRECISION)


class BaseEmissionsTracker(ABC):
"""
Expand Down Expand Up @@ -382,8 +390,11 @@ def _ensure_geo_metadata(self) -> None:
self._geo = self._get_geo_metadata()
cloud: CloudMetadata = self._get_cloud_metadata()
if cloud.is_on_private_infra:
self._conf["longitude"] = self._geo.longitude
self._conf["latitude"] = self._geo.latitude
# Reduce precision for privacy, see docs/reference/output.md.
# self._geo keeps the full precision coordinates, they are needed
# for the Electricity Maps carbon intensity lookup.
self._conf["longitude"] = _round_coordinate(self._geo.longitude)
self._conf["latitude"] = _round_coordinate(self._geo.latitude)

def __init__(
self,
Expand Down
73 changes: 73 additions & 0 deletions tests/test_emissions_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,3 +1108,76 @@ def test_cumulative_emissions_with_varying_intensity(

# Verification: If it wasn't cumulative, it would be 3.0 kWh * 300 g/kWh = 0.9 kg
self.assertLess(data3.emissions, 0.8)

@mock.patch("codecarbon.emissions_tracker.EmissionsTracker._get_geo_metadata")
@mock.patch("codecarbon.emissions_tracker.EmissionsTracker._get_cloud_metadata")
@mock.patch("codecarbon.core.electricitymaps_api.requests.get")
@mock.patch("codecarbon.core.resource_tracker.ResourceTracker")
@mock.patch(
"codecarbon.emissions_tracker.BaseEmissionsTracker.get_detected_hardware"
)
@mock.patch("codecarbon.emissions_tracker.PeriodicScheduler")
def test_coordinates_precision_is_reduced_in_emissions_data(
self,
mock_scheduler,
mock_get_hw,
mock_resource_tracker,
mock_get,
mock_cloud,
mock_geo,
mock_cli_setup,
mock_log_values,
mocked_get_cloud_metadata_class,
mocked_get_gpu_details,
mocked_get_gpu_utilization_list,
mocked_is_gpu_details_available,
mocked_is_nvidia_system,
):
mock_geo.return_value = mock.MagicMock(
latitude=48.8566,
longitude=2.3522,
country_iso_code="FRA",
country_2letter_iso_code="FR",
)
mock_cloud.return_value = mock.MagicMock(
is_on_private_infra=True, provider=None, region=None
)
mock_get_hw.return_value = {
"ram_total_size": 16.0,
"cpu_count": 8,
"cpu_physical_count": 4,
"cpu_model": "Mock CPU",
"gpu_count": 0,
"gpu_model": "None",
"gpu_ids": None,
}
mock_get.return_value = mock.MagicMock(
status_code=200, json=lambda: {"carbonIntensity": 100}
)

tracker = EmissionsTracker(
electricitymaps_api_token="test-token",
save_to_file=False,
measure_power_secs=1,
allow_multiple_runs=True,
)

mock_cpu = mock.MagicMock()
from codecarbon.external.hardware import CPU

mock_cpu.__class__ = CPU
mock_cpu.measure_power_and_energy.return_value = (
Power.from_watts(100),
Energy.from_energy(kWh=1.0),
)
tracker._hardware = [mock_cpu]

tracker.start()
data = tracker._prepare_emissions_data()

# Every output method gets coordinates reduced to one decimal place.
self.assertEqual(data.latitude, 48.9)
self.assertEqual(data.longitude, 2.4)
# The carbon intensity lookup still uses the full precision coordinates.
self.assertEqual(tracker._geo.latitude, 48.8566)
self.assertEqual(tracker._geo.longitude, 2.3522)