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
12 changes: 6 additions & 6 deletions codecarbon/core/cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,19 +217,19 @@ def is_psutil_available():
return True
else:
logger.debug(
f"is_psutil_available(): psutil.cpu_times().nice is too small: {nice}"
f"is_psutil_available(): psutil.cpu_times().nice is too small: {nice}; "
"using fallback check."
)
return False

else:
# Fallback: check if psutil works by calling cpu_percent
logger.debug(
"is_psutil_available(): no 'nice' attribute, using fallback check."
)

# check CPU utilization usable
psutil.cpu_percent(interval=0.0, percpu=False)
return True
# Fallback: check if psutil CPU utilization is usable. This covers
# platforms like Windows and macOS where cpu_times().nice is absent or 0.
psutil.cpu_percent(interval=0.0, percpu=False)
return True

except Exception as e:
logger.debug(
Expand Down
37 changes: 34 additions & 3 deletions tests/test_cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,24 @@ def test_is_psutil_available_with_nice(self, mock_cpu_times):
self.assertTrue(is_psutil_available())

@mock.patch("psutil.cpu_times")
def test_is_psutil_available_with_small_nice(self, mock_cpu_times):
# Test when nice attribute is too small
def test_is_psutil_available_with_small_nice_uses_fallback(self, mock_cpu_times):
# Test when nice attribute is too small, as on macOS
mock_times = mock.Mock()
mock_times.nice = 0.00001
mock_cpu_times.return_value = mock_times
self.assertFalse(is_psutil_available())
with mock.patch("psutil.cpu_percent") as mock_cpu_percent:
self.assertTrue(is_psutil_available())
mock_cpu_percent.assert_called_once_with(interval=0.0, percpu=False)

@mock.patch("psutil.cpu_times")
def test_is_psutil_not_available_when_small_nice_fallback_fails(
self, mock_cpu_times
):
mock_times = mock.Mock()
mock_times.nice = 0.00001
mock_cpu_times.return_value = mock_times
with mock.patch("psutil.cpu_percent", side_effect=Exception("Test error")):
self.assertFalse(is_psutil_available())

@mock.patch("psutil.cpu_times")
def test_is_psutil_available_without_nice(self, mock_cpu_times):
Expand Down Expand Up @@ -359,6 +371,18 @@ def test_get_cpu_power_from_registry(self):
model = "AMD Ryzen Threadripper 1950X 16-Core Processor"
self.assertEqual(tdp._get_cpu_power_from_registry(model), 180)

def test_get_cpu_power_from_registry_returns_none_without_match(self):
tdp = TDP.__new__(TDP)
with (
mock.patch("codecarbon.core.cpu.DataSource") as mock_data_source,
mock.patch.object(tdp, "_get_matching_cpu", return_value=None),
):
mock_data_source.return_value.get_cpu_power_data.return_value = (
mock.sentinel.cpu_power_df
)

self.assertIsNone(tdp._get_cpu_power_from_registry("Mystery CPU"))

def test_get_matching_cpu(self):
tdp = TDP()
cpu_data = DataSource().get_cpu_power_data()
Expand Down Expand Up @@ -546,6 +570,13 @@ def test_main_fallback_default_power_when_unknown_cpu(self):
self.assertEqual(tdp.model, "Mystery CPU")
self.assertEqual(tdp.tdp, 8 * DEFAULT_POWER_PER_CORE)

def test_main_returns_unknown_when_cpu_detection_fails(self):
with mock.patch("codecarbon.core.cpu.detect_cpu_model", return_value=None):
tdp = TDP()

self.assertEqual(tdp.model, "Unknown")
self.assertIsNone(tdp.tdp)


class TestResourceTrackerCPUTracking(unittest.TestCase):
def test_set_cpu_tracking_skips_tdp_when_rapl_available(self):
Expand Down
Loading