Skip to content

Commit c7f6d62

Browse files
committed
lint
1 parent 20a2938 commit c7f6d62

5 files changed

Lines changed: 136 additions & 73 deletions

File tree

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
repos:
22
- repo: https://github.com/pycqa/isort
3-
rev: 5.13.2
3+
rev: 6.0.1
44
hooks:
55
- id: isort
66
args: ["--filter-files"]
77
- repo: https://github.com/psf/black
8-
rev: 24.8.0
8+
rev: 25.1.0
99
hooks:
1010
- id: black
1111
args: [--safe]
1212
- repo: https://github.com/PyCQA/flake8
13-
rev: 7.1.1
13+
rev: 7.2.0
1414
hooks:
1515
- id: flake8
1616
args: ["--config=.flake8"]

codecarbon/emissions_tracker.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,9 @@ def start_task(self, task_name=None) -> None:
486486
for hardware in self._hardware:
487487
hardware.start()
488488
prepared_data_for_task_start = self._prepare_emissions_data()
489-
self._active_task_emissions_at_start = dataclasses.replace(prepared_data_for_task_start)
489+
self._active_task_emissions_at_start = dataclasses.replace(
490+
prepared_data_for_task_start
491+
)
490492
# The existing call to _compute_emissions_delta uses the result of _prepare_emissions_data.
491493
# Let's make sure it uses the same one we captured.
492494
self._compute_emissions_delta(prepared_data_for_task_start)
@@ -527,7 +529,9 @@ def stop_task(self, task_name: str = None) -> EmissionsData:
527529
# # f"Total Energy: {self._total_energy.kWh} kWh"
528530
# # )
529531

530-
emissions_data = self._prepare_emissions_data() # This is emissions_data_at_stop
532+
emissions_data = (
533+
self._prepare_emissions_data()
534+
) # This is emissions_data_at_stop
531535

532536
# # logger.info(
533537
# # f"STOP_TASK_DEBUG: emissions_data (totals at task stop): "
@@ -544,7 +548,7 @@ def stop_task(self, task_name: str = None) -> EmissionsData:
544548
# # f"Total Energy: {self._previous_emissions.energy_consumed} kWh"
545549
# # )
546550

547-
emissions_data_delta: EmissionsData # Type hint for clarity
551+
emissions_data_delta: EmissionsData # Type hint for clarity
548552

549553
if self._active_task_emissions_at_start is None:
550554
# This logger.warning should remain, as it's not a DEBUG log but a genuine warning for an unexpected state.
@@ -563,7 +567,9 @@ def stop_task(self, task_name: str = None) -> EmissionsData:
563567
emissions_data_delta.energy_consumed = 0.0
564568
else:
565569
emissions_data_delta = dataclasses.replace(emissions_data)
566-
emissions_data_delta.compute_delta_emission(self._active_task_emissions_at_start)
570+
emissions_data_delta.compute_delta_emission(
571+
self._active_task_emissions_at_start
572+
)
567573
# # logger.info(
568574
# # f"STOP_TASK_DEBUG: emissions_data_delta (task-specific): "
569575
# # # ... fields ...
@@ -578,12 +584,14 @@ def stop_task(self, task_name: str = None) -> EmissionsData:
578584

579585
# task_emission_data is the final delta object to be returned and stored
580586
task_emission_data = emissions_data_delta
581-
task_emission_data.duration = task_duration.seconds # Set the correct duration for the task
587+
task_emission_data.duration = (
588+
task_duration.seconds
589+
) # Set the correct duration for the task
582590

583591
self._tasks[task_name].emissions_data = task_emission_data
584592
self._tasks[task_name].is_active = False
585593
self._active_task = None
586-
self._active_task_emissions_at_start = None # Clear task-specific start data
594+
self._active_task_emissions_at_start = None # Clear task-specific start data
587595

588596
return task_emission_data
589597

codecarbon/output_methods/emissions_data.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
from collections import OrderedDict
33
from dataclasses import dataclass
44

5-
from codecarbon.external.logger import logger
6-
75

86
@dataclass
97
class EmissionsData:

test_fix.py

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
import time
21
import math
32
import os
4-
from codecarbon.emissions_tracker import EmissionsTracker # Assuming codecarbon is installable or in PYTHONPATH
3+
import time
4+
5+
from codecarbon.emissions_tracker import (
6+
EmissionsTracker, # Assuming codecarbon is installable or in PYTHONPATH
7+
)
58
from codecarbon.external.logger import logger, set_logger_level
69

710
# Set a verifiable experiment name for tracking if needed (optional)
811
os.environ["CODECARBON_EXPERIMENT_ID"] = "task-energy-test"
912

13+
1014
def cpu_intensive_task(duration_seconds):
1115
"""A simple CPU-intensive task."""
1216
start_time = time.time()
1317
while (time.time() - start_time) < duration_seconds:
1418
_ = math.sqrt(time.time()) * math.factorial(100)
1519

20+
1621
def main():
17-
set_logger_level("ERROR") # Keep CodeCarbon's own logs quiet unless error
22+
set_logger_level("ERROR") # Keep CodeCarbon's own logs quiet unless error
1823

1924
logger.info("Starting task energy consumption test script.")
2025

@@ -25,7 +30,7 @@ def main():
2530
tracker = EmissionsTracker(
2631
project_name="TaskEnergyTest",
2732
measure_power_secs=1,
28-
api_call_interval=2, # This is the key to potentially trigger the old bug
33+
api_call_interval=2, # This is the key to potentially trigger the old bug
2934
save_to_file=False, # Don't write to emissions.csv for this test
3035
# log_level="DEBUG" # Use "DEBUG" if you want to see CodeCarbon's internal debug logs
3136
)
@@ -37,51 +42,67 @@ def main():
3742
failing_rounds = []
3843
test_passed = True
3944

40-
NUM_ROUNDS = 30 # Number of tasks to run
41-
TASK_DURATION_SEC = 4 # Duration of each CPU task
42-
43-
logger.info(f"Tracker initialized. Running {NUM_ROUNDS} rounds of {TASK_DURATION_SEC}s tasks.")
44-
print(f"Tracker initialized. Running {NUM_ROUNDS} rounds of {TASK_DURATION_SEC}s tasks.")
45+
NUM_ROUNDS = 30 # Number of tasks to run
46+
TASK_DURATION_SEC = 4 # Duration of each CPU task
4547

48+
logger.info(
49+
f"Tracker initialized. Running {NUM_ROUNDS} rounds of {TASK_DURATION_SEC}s tasks."
50+
)
51+
print(
52+
f"Tracker initialized. Running {NUM_ROUNDS} rounds of {TASK_DURATION_SEC}s tasks."
53+
)
4654

4755
for i in range(NUM_ROUNDS):
48-
print(f"Starting round {i+1}/{NUM_ROUNDS}")
56+
print(f"Starting round {i + 1}/{NUM_ROUNDS}")
4957
try:
50-
tracker.start_task(f"CPU_Task_Round_{i+1}")
58+
tracker.start_task(f"CPU_Task_Round_{i + 1}")
5159
cpu_intensive_task(TASK_DURATION_SEC)
5260
emissions_data = tracker.stop_task()
5361

5462
if emissions_data:
55-
task_name = emissions_data.run_id # Using run_id as a stand-in for task_name if not directly available
63+
task_name = (
64+
emissions_data.run_id
65+
) # Using run_id as a stand-in for task_name if not directly available
5666
# In a real scenario, task_name might be part of emissions_data or retrieved via the task_id
57-
print(f"Round {i+1}: Task '{task_name}' (task_idx_{i+1}) completed. Duration: {emissions_data.duration:.4f}s, Energy: {emissions_data.energy_consumed:.6f} kWh, Emissions: {emissions_data.emissions:.6f} kg")
67+
print(
68+
f"Round {i + 1}: Task '{task_name}' (task_idx_{i + 1}) completed. Duration: {emissions_data.duration:.4f}s, Energy: {emissions_data.energy_consumed:.6f} kWh, Emissions: {emissions_data.emissions:.6f} kg"
69+
)
5870

5971
# Check for the bug: zero energy for a non-trivial task duration
60-
if emissions_data.duration > 0.1 and emissions_data.energy_consumed == 0.0:
61-
failing_rounds.append({
62-
"round": i + 1,
63-
"task_name": task_name,
64-
"duration": emissions_data.duration,
65-
"energy_consumed": emissions_data.energy_consumed,
66-
"error": "Zero energy for non-trivial duration"
67-
})
72+
if (
73+
emissions_data.duration > 0.1
74+
and emissions_data.energy_consumed == 0.0
75+
):
76+
failing_rounds.append(
77+
{
78+
"round": i + 1,
79+
"task_name": task_name,
80+
"duration": emissions_data.duration,
81+
"energy_consumed": emissions_data.energy_consumed,
82+
"error": "Zero energy for non-trivial duration",
83+
}
84+
)
6885
test_passed = False
6986
else:
70-
print(f"Round {i+1}: stop_task() did not return emissions_data.")
71-
failing_rounds.append({
72-
"round": i + 1,
73-
"task_name": f"CPU_Task_Round_{i+1}_NoData",
74-
"error": "stop_task returned None"
75-
})
87+
print(f"Round {i + 1}: stop_task() did not return emissions_data.")
88+
failing_rounds.append(
89+
{
90+
"round": i + 1,
91+
"task_name": f"CPU_Task_Round_{i + 1}_NoData",
92+
"error": "stop_task returned None",
93+
}
94+
)
7695
test_passed = False
7796

7897
except Exception as e:
79-
print(f"Round {i+1}: An error occurred: {e}")
80-
failing_rounds.append({
81-
"round": i + 1,
82-
"task_name": f"CPU_Task_Round_{i+1}_Exception",
83-
"error": str(e)
84-
})
98+
print(f"Round {i + 1}: An error occurred: {e}")
99+
failing_rounds.append(
100+
{
101+
"round": i + 1,
102+
"task_name": f"CPU_Task_Round_{i + 1}_Exception",
103+
"error": str(e),
104+
}
105+
)
85106
test_passed = False
86107
# Optionally, decide if one error should stop the whole test
87108
# break
@@ -90,23 +111,32 @@ def main():
90111
# and to let background scheduler of tracker run.
91112
time.sleep(1)
92113

93-
tracker.stop() # Stop the main tracker
114+
tracker.stop() # Stop the main tracker
94115

95116
if test_passed:
96-
print("TEST PASSED: No tasks with zero energy consumption detected for non-trivial durations.")
117+
print(
118+
"TEST PASSED: No tasks with zero energy consumption detected for non-trivial durations."
119+
)
97120
else:
98-
print("TEST FAILED: Some tasks reported zero energy consumption or other errors.")
121+
print(
122+
"TEST FAILED: Some tasks reported zero energy consumption or other errors."
123+
)
99124
print("Failing rounds details:")
100125
for detail in failing_rounds:
101126
# Ensure all fields are present with defaults for printing
102-
round_num = detail.get('round', 'N/A')
103-
task_name_val = detail.get('task_name', 'N/A')
104-
duration_val = detail.get('duration', float('nan')) # Use float('nan') for unavail num
105-
energy_val = detail.get('energy_consumed', float('nan'))
106-
error_val = detail.get('error', 'None')
107-
print(f" - Round {round_num}: Task '{task_name_val}', "
108-
f"Duration: {duration_val:.4f}s, Energy: {energy_val:.6f} kWh, "
109-
f"Error: {error_val}")
127+
round_num = detail.get("round", "N/A")
128+
task_name_val = detail.get("task_name", "N/A")
129+
duration_val = detail.get(
130+
"duration", float("nan")
131+
) # Use float('nan') for unavail num
132+
energy_val = detail.get("energy_consumed", float("nan"))
133+
error_val = detail.get("error", "None")
134+
print(
135+
f" - Round {round_num}: Task '{task_name_val}', "
136+
f"Duration: {duration_val:.4f}s, Energy: {energy_val:.6f} kWh, "
137+
f"Error: {error_val}"
138+
)
139+
110140

111141
if __name__ == "__main__":
112142
main()

tests/test_emissions_tracker.py

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
import requests
1111
import responses
1212

13+
from codecarbon.core.units import Energy, Power
1314
from codecarbon.emissions_tracker import (
1415
EmissionsTracker,
1516
OfflineEmissionsTracker,
1617
track_emissions,
1718
)
18-
from codecarbon.core.units import Energy, Power
1919
from codecarbon.external.geography import CloudMetadata
2020
from tests.fake_modules import pynvml as fake_pynvml
2121
from tests.testdata import (
@@ -407,31 +407,41 @@ def test_carbon_tracker_online_context_manager_TWO_GPU_PRIVATE_INFRA_CANADA(
407407
)
408408
self.assertIsInstance(tracker.final_emissions, float)
409409

410-
@mock.patch("codecarbon.external.ram.RAM.measure_power_and_energy") # Corrected path for RAM
411-
@mock.patch("codecarbon.external.hardware.CPU.measure_power_and_energy") # Path for CPU is likely correct
410+
@mock.patch(
411+
"codecarbon.external.ram.RAM.measure_power_and_energy"
412+
) # Corrected path for RAM
413+
@mock.patch(
414+
"codecarbon.external.hardware.CPU.measure_power_and_energy"
415+
) # Path for CPU is likely correct
412416
def test_task_energy_with_live_update_interference(
413417
self,
414-
mock_cpu_measure, # Method decorator (innermost)
415-
mock_ram_measure, # Method decorator (outermost)
416-
mock_setup_intel_cli, # Class decorator (innermost)
417-
mock_log_values, # Class decorator
418-
mocked_env_cloud_details, # Class decorator
419-
mocked_get_gpu_details, # Class decorator
420-
mocked_is_gpu_details_available # Class decorator (outermost relevant one)
418+
mock_cpu_measure, # Method decorator (innermost)
419+
mock_ram_measure, # Method decorator (outermost)
420+
mock_setup_intel_cli, # Class decorator (innermost)
421+
mock_log_values, # Class decorator
422+
mocked_env_cloud_details, # Class decorator
423+
mocked_get_gpu_details, # Class decorator
424+
mocked_is_gpu_details_available, # Class decorator (outermost relevant one)
421425
):
422426
# --- Test Setup ---
423427
# Configure mocks to return specific, non-zero energy values
424428
cpu_energy_val_task = 0.0001
425429
ram_energy_val_task = 0.00005
426-
mock_cpu_measure.return_value = (Power.from_watts(10), Energy.from_energy(kWh=cpu_energy_val_task))
427-
mock_ram_measure.return_value = (Power.from_watts(5), Energy.from_energy(kWh=ram_energy_val_task))
430+
mock_cpu_measure.return_value = (
431+
Power.from_watts(10),
432+
Energy.from_energy(kWh=cpu_energy_val_task),
433+
)
434+
mock_ram_measure.return_value = (
435+
Power.from_watts(5),
436+
Energy.from_energy(kWh=ram_energy_val_task),
437+
)
428438

429439
tracker = EmissionsTracker(
430440
project_name="TestLiveUpdateInterference",
431441
measure_power_secs=1,
432442
api_call_interval=1, # Trigger live update on first opportunity
433-
output_handlers=[], # Clear any default handlers like FileOutput
434-
save_to_file=False, # Ensure no file is created by default
443+
output_handlers=[], # Clear any default handlers like FileOutput
444+
save_to_file=False, # Ensure no file is created by default
435445
save_to_api=False,
436446
# Config file is mocked by get_custom_mock_open in setUp
437447
)
@@ -459,14 +469,31 @@ def test_task_energy_with_live_update_interference(
459469
self.assertIsNotNone(task_data, "Task data should not be None")
460470

461471
self.assertGreater(task_data.cpu_energy, 0, "CPU energy should be non-zero")
462-
self.assertAlmostEqual(task_data.cpu_energy, cpu_energy_val_task, places=7, msg="CPU energy does not match expected task energy")
472+
self.assertAlmostEqual(
473+
task_data.cpu_energy,
474+
cpu_energy_val_task,
475+
places=7,
476+
msg="CPU energy does not match expected task energy",
477+
)
463478

464479
self.assertGreater(task_data.ram_energy, 0, "RAM energy should be non-zero")
465-
self.assertAlmostEqual(task_data.ram_energy, ram_energy_val_task, places=7, msg="RAM energy does not match expected task energy")
480+
self.assertAlmostEqual(
481+
task_data.ram_energy,
482+
ram_energy_val_task,
483+
places=7,
484+
msg="RAM energy does not match expected task energy",
485+
)
466486

467487
expected_total_energy = cpu_energy_val_task + ram_energy_val_task
468-
self.assertGreater(task_data.energy_consumed, 0, "Total energy consumed should be non-zero")
469-
self.assertAlmostEqual(task_data.energy_consumed, expected_total_energy, places=7, msg="Total energy consumed does not match sum of components")
488+
self.assertGreater(
489+
task_data.energy_consumed, 0, "Total energy consumed should be non-zero"
490+
)
491+
self.assertAlmostEqual(
492+
task_data.energy_consumed,
493+
expected_total_energy,
494+
places=7,
495+
msg="Total energy consumed does not match sum of components",
496+
)
470497

471498
# Verify mocks were called as expected
472499
# They are called once in _measure_power_and_energy inside stop_task

0 commit comments

Comments
 (0)