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
18 changes: 11 additions & 7 deletions codecarbon/external/hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import math
import re
import threading
import time
from abc import ABC, abstractmethod
from dataclasses import dataclass
Expand Down Expand Up @@ -207,6 +208,8 @@ def __init__(
):
assert tracking_mode in ["machine", "process"]
self._power_history: List[Power] = []
# the monitor thread appends while the measurement thread drains
self._power_history_lock = threading.Lock()
self._output_dir = output_dir
self._mode = mode
self._model = model
Expand Down Expand Up @@ -393,12 +396,12 @@ def _get_energy_from_cpus(self, delay: Time) -> Energy:
return Energy.from_energy(energy)

def total_power(self) -> Power:
self._power_history.append(self._get_power_from_cpus())
power_history_in_W = [power.W for power in self._power_history]
self._power_history = []
if not power_history_in_W:
logger.warning("No power samples collected, returning 0 W")
return Power.from_watts(0)
latest_sample = self._get_power_from_cpus()
with self._power_history_lock:
power_history = self._power_history
self._power_history = []
power_history.append(latest_sample)
power_history_in_W = [power.W for power in power_history]
cpu_power = sum(power_history_in_W) / len(power_history_in_W)
return Power.from_watts(cpu_power)

Expand Down Expand Up @@ -432,7 +435,8 @@ def start(self):

def monitor_power(self):
cpu_power = self._get_power_from_cpus()
self._power_history.append(cpu_power)
with self._power_history_lock:
self._power_history.append(cpu_power)

def get_model(self):
return self._model
Expand Down
35 changes: 35 additions & 0 deletions tests/test_cpu_load.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import threading
import unittest
from time import sleep
from unittest import mock
Expand Down Expand Up @@ -172,6 +173,40 @@ def test_cpu_total_power_fetches_sample_when_history_is_empty(
mocked_get_power_from_cpus.assert_called_once()
self.assertEqual(cpu._power_history, [])

@mock.patch(
"codecarbon.external.hardware.CPU._get_power_from_cpus",
return_value=Power.from_watts(1),
)
def test_cpu_total_power_keeps_samples_added_while_draining(
self,
mocked_get_power_from_cpus,
mocked_is_psutil_available,
mocked_is_powergadget_available,
mocked_is_rapl_available,
):
"""A sample appended by the monitor thread while total_power drains the
history must not be lost (see issue #1315)."""
cpu = CPU.from_utils(
None, MODE_CPU_LOAD, "Intel(R) Core(TM) i7-7600U CPU @ 2.80GHz", 100
)
cpu._power_history = [Power.from_watts(1)]
appended = threading.Event()

def monitor():
cpu.monitor_power()
appended.set()

monitor_thread = threading.Thread(target=monitor)
with cpu._power_history_lock:
monitor_thread.start()
# The monitor thread must wait instead of appending to a history
# total_power is about to discard.
self.assertFalse(appended.wait(0.2))
monitor_thread.join(1)

self.assertTrue(appended.is_set())
self.assertEqual(len(cpu._power_history), 2)

@mock.patch(
"codecarbon.external.hardware.CPU._get_power_from_cpus",
return_value=Power.from_watts(30),
Expand Down
Loading