Skip to content
Merged
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
6 changes: 3 additions & 3 deletions codecarbon/output_methods/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ def has_valid_headers(self, data: EmissionsData) -> bool:
# No entries
return True
dict_from_csv = dict(csv_entries_list[0])
list_of_column_names = list(dict_from_csv.keys())
return list(data.values.keys()) == list_of_column_names
list_of_column_names = sorted(dict_from_csv.keys())
return sorted(data.values.keys()) == list_of_column_names

def out(self, total: EmissionsData, _: EmissionsData):
def out(self, total: EmissionsData, _):
"""
Save the emissions data from a whole run to a CSV file.

Expand Down
6 changes: 3 additions & 3 deletions codecarbon/output_methods/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class HTTPOutput(BaseOutput):
def __init__(self, endpoint_url: str):
self.endpoint_url: str = endpoint_url

def out(self, total: EmissionsData, delta: EmissionsData):
def out(self, total: EmissionsData, _: EmissionsData):
try:
payload = dataclasses.asdict(total)
payload["user"] = getpass.getuser()
Expand Down Expand Up @@ -56,14 +56,14 @@ def __init__(
)
self.run_id = self.api.run_id

def live_out(self, total: EmissionsData, delta: EmissionsData):
def live_out(self, _, delta: EmissionsData):
# Called at regular intervals
try:
self.api.add_emission(dataclasses.asdict(delta))
except Exception as e:
logger.error(e, exc_info=True)

def out(self, total: EmissionsData, delta: EmissionsData):
def out(self, _, delta: EmissionsData):
# Called on exit
try:
self.api.add_emission(dataclasses.asdict(delta))
Expand Down
6 changes: 3 additions & 3 deletions codecarbon/output_methods/metrics/logfire.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def __init__(self):
"codecarbon_ram_energy", unit="(kWh)", description="Energy used per RAM"
)

def out(self, total: EmissionsData, delta: EmissionsData):
def out(self, _, delta: EmissionsData):
try:
self.duration.add(delta.duration)
self.emissions.add(delta.emissions)
Expand All @@ -75,5 +75,5 @@ def out(self, total: EmissionsData, delta: EmissionsData):
except Exception as e:
logger.error(e, exc_info=True)

def live_out(self, total: EmissionsData, delta: EmissionsData):
self.out(total, delta)
def live_out(self, _: EmissionsData, delta: EmissionsData):
self.out(None, delta)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ dev = [
"requests",
"requests-mock",
"responses",
"logfire>=1.0.1",
]
doc = [
"sphinx",
Expand Down
44 changes: 29 additions & 15 deletions tests/output_methods/test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import shutil
import tempfile
import unittest
from unittest.mock import MagicMock, patch
from unittest.mock import patch

import pandas as pd

Expand Down Expand Up @@ -66,7 +66,7 @@ def test_file_output_initialization_invalid_dir(self):

def test_has_valid_headers_success(self):
file_output = FileOutput("test.csv", self.temp_dir)
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

self.assertTrue(file_output.has_valid_headers(self.emissions_data))

Expand All @@ -77,9 +77,19 @@ def test_has_valid_headers_success_with_empty_file(self):

self.assertTrue(file_output.has_valid_headers(self.emissions_data))

def test_has_valid_headers_different_order_success(self):
file_output = FileOutput("test.csv", self.temp_dir)
file_output.out(self.emissions_data, None)

df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
df = df[list(reversed(df.columns))]
df.to_csv(os.path.join(self.temp_dir, "test.csv"), index=False)

self.assertTrue(file_output.has_valid_headers(self.emissions_data))

def test_has_valid_headers_failure(self):
file_output = FileOutput("test.csv", self.temp_dir)
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
df.rename(columns={"wue": "new_header"}, inplace=True)
Expand All @@ -90,10 +100,10 @@ def test_has_valid_headers_failure(self):
@patch("codecarbon.output_methods.file.FileOutput.has_valid_headers")
def test_file_output_out_file_exists_invalid_headers(self, mock_has_valid_headers):
file_output = FileOutput("test.csv", self.temp_dir, on_csv_write="append")
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

mock_has_valid_headers.return_value = False
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

df = pd.read_csv(os.path.join(self.temp_dir, "test.csv.bak"))
self.assertEqual(len(df), 1)
Expand All @@ -102,63 +112,67 @@ def test_file_output_out_file_exists_invalid_headers(self, mock_has_valid_header

def test_file_output_out_update_no_file_exists(self):
file_output = FileOutput("test.csv", self.temp_dir, on_csv_write="update")
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
self.assertEqual(len(df), 1)

def test_file_output_out_append_no_file_exists(self):
file_output = FileOutput("test.csv", self.temp_dir, on_csv_write="append")
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
self.assertEqual(len(df), 1)

def test_file_output_out_append_file_exists(self):
file_output = FileOutput("test.csv", self.temp_dir, on_csv_write="append")
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)
file_output.out(self.emissions_data, None)

df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
self.assertEqual(len(df), 2)

def test_file_output_out_update_file_exists_no_matching_row(self):
file_output = FileOutput("test.csv", self.temp_dir, on_csv_write="update")
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

updated_emissions_data = self.emissions_data
updated_emissions_data.run_id = "new_test_run_id"
file_output.out(updated_emissions_data, MagicMock())
file_output.out(updated_emissions_data, None)

df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
self.assertEqual(len(df), 2)

def test_file_output_out_update_file_exists_multiple_matching_rows(self):
file_output = FileOutput("test.csv", self.temp_dir, on_csv_write="update")
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

# Manually add a duplicate row to simulate the condition
df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
df = pd.concat([df, df])
df.to_csv(os.path.join(self.temp_dir, "test.csv"), index=False)

file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)

df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
self.assertEqual(len(df), 3)

def test_file_output_out_update_file_exists_one_matchingrows(self):
file_output = FileOutput("test.csv", self.temp_dir, on_csv_write="update")
file_output.out(self.emissions_data, MagicMock())
file_output.out(self.emissions_data, None)
df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
self.assertEqual(df["cpu_power"].iloc[0], 20)

new_data = self.emissions_data
new_data.cpu_power = 2
file_output.out(new_data, MagicMock())
file_output.out(new_data, None)
df = pd.read_csv(os.path.join(self.temp_dir, "test.csv"))
self.assertEqual(df["cpu_power"].iloc[0], 2)

# def test_file_output_out_consistent_column_ordering(self):
# file_output = FileOutput("test.csv", self.temp_dir, on_csv_write="append")
# file_output.out(self.emissions_data, None)

def test_file_output_task_out(self):
task_emissions_data = [
TaskEmissionsData(
Expand Down
183 changes: 183 additions & 0 deletions tests/output_methods/test_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import unittest
from unittest.mock import MagicMock, patch

from codecarbon.output_methods.emissions_data import EmissionsData
from codecarbon.output_methods.http import CodeCarbonAPIOutput, HTTPOutput


class TestHTTPOutput(unittest.TestCase):
def setUp(self):
self.emissions_data = EmissionsData(
timestamp="2023-01-01T00:00:00",
project_name="test_project",
run_id="test_run_id",
experiment_id="test_experiment_id",
duration=10,
emissions=0.5,
emissions_rate=0.05,
cpu_power=20,
gpu_power=30,
ram_power=5,
cpu_energy=200,
gpu_energy=300,
ram_energy=50,
energy_consumed=550,
water_consumed=0.1,
country_name="Testland",
country_iso_code="TS",
region="Test Region",
cloud_provider="Test Cloud",
cloud_region="test-cloud-1",
os="TestOS",
python_version="3.8",
codecarbon_version="2.0",
cpu_count=4,
cpu_model="Test CPU",
gpu_count=1,
gpu_model="Test GPU",
longitude=0,
latitude=0,
ram_total_size=16,
tracking_mode="machine",
on_cloud="true",
pue=1.5,
wue=0.5,
)
self.url = "http://test.com/emissions"
self.http_output = HTTPOutput(endpoint_url=self.url)

@patch(
"codecarbon.output_methods.http.requests.post",
return_value=MagicMock(status_code=201),
)
def test_http_output_post_success(self, mock_post):
self.http_output.out(self.emissions_data, self.emissions_data)

mock_post.assert_called_once()
self.assertEqual(mock_post.call_args[0][0], self.url)

@patch("codecarbon.output_methods.http.logger.warning")
@patch(
"codecarbon.output_methods.http.requests.post",
return_value=MagicMock(status_code=418),
)
def test_http_output_post_unexpected_status(self, mock_post, mock_logger):
self.http_output.out(self.emissions_data, self.emissions_data)

mock_post.assert_called_once()
mock_logger.assert_called_once()

@patch("codecarbon.output_methods.http.logger.error")
@patch(
"codecarbon.output_methods.http.requests.post",
side_effect=Exception("Test exception"),
)
def test_http_output_post_exception(self, mock_post, mock_logger):
self.http_output.out(self.emissions_data, self.emissions_data)
mock_post.assert_called_once()
mock_logger.assert_called_once()


class TestCodeCarbonAPIOutput(unittest.TestCase):
def setUp(self):
self.emissions_data = EmissionsData(
timestamp="2023-01-01T00:00:00",
project_name="test_project",
run_id="test_run_id",
experiment_id="test_experiment_id",
duration=10,
emissions=0.5,
emissions_rate=0.05,
cpu_power=20,
gpu_power=30,
ram_power=5,
cpu_energy=200,
gpu_energy=300,
ram_energy=50,
energy_consumed=550,
water_consumed=0.1,
country_name="Testland",
country_iso_code="TS",
region="Test Region",
cloud_provider="Test Cloud",
cloud_region="test-cloud-1",
os="TestOS",
python_version="3.8",
codecarbon_version="2.0",
cpu_count=4,
cpu_model="Test CPU",
gpu_count=1,
gpu_model="Test GPU",
longitude=0,
latitude=0,
ram_total_size=16,
tracking_mode="machine",
on_cloud="true",
pue=1.5,
wue=0.5,
)
self.url = "http://test.com/emissions"
self.experiment_id = (
None # Set to None so that ApiClient won't attempt a run on initialisation
)
self.api_key = "test_key"

self.add_emission_patcher = patch(
"codecarbon.output_methods.http.ApiClient.add_emission"
)
self.mock_add_emission = self.add_emission_patcher.start()
self.addCleanup(self.add_emission_patcher.stop)

def test_codecarbon_api_output_initialization(self):
CodeCarbonAPIOutput(
endpoint_url=self.url,
experiment_id=self.experiment_id,
api_key=self.api_key,
conf=None,
)

def test_codecarbon_api_live_out(self):
api_output = CodeCarbonAPIOutput(
endpoint_url=self.url,
experiment_id=self.experiment_id,
api_key=self.api_key,
conf=None,
)

api_output.live_out(None, self.emissions_data)
self.mock_add_emission.assert_called_once()

@patch("codecarbon.output_methods.http.logger.error")
def test_codecarbon_live_out_api_call_failure(self, mock_logger):
self.mock_add_emission.side_effect = Exception("Test exception")
api_output = CodeCarbonAPIOutput(
endpoint_url=self.url,
experiment_id=self.experiment_id,
api_key=self.api_key,
conf=None,
)
api_output.live_out(None, self.emissions_data)
mock_logger.assert_called_once()

def test_codecarbon_api_out(self):
api_output = CodeCarbonAPIOutput(
endpoint_url=self.url,
experiment_id=self.experiment_id,
api_key=self.api_key,
conf=None,
)

api_output.out(None, self.emissions_data)
self.mock_add_emission.assert_called_once()

@patch("codecarbon.output_methods.http.logger.error")
def test_codecarbon_out_api_call_failure(self, mock_logger):
self.mock_add_emission.side_effect = Exception("Test exception")
api_output = CodeCarbonAPIOutput(
endpoint_url=self.url,
experiment_id=self.experiment_id,
api_key=self.api_key,
conf=None,
)
api_output.out(None, self.emissions_data)
mock_logger.assert_called_once()
Loading
Loading