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
10 changes: 8 additions & 2 deletions codecarbon/core/resource_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@

from codecarbon.core import cpu, gpu, powermetrics
from codecarbon.core.config import parse_gpu_ids
from codecarbon.core.util import detect_cpu_model, is_linux_os, is_mac_os, is_windows_os
from codecarbon.core.util import (
detect_cpu_model,
is_linux_os,
is_mac_arm,
is_mac_os,
is_windows_os,
)
from codecarbon.external.hardware import CPU, GPU, MODE_CPU_LOAD, AppleSiliconChip
from codecarbon.external.logger import logger
from codecarbon.external.ram import RAM
Expand Down Expand Up @@ -99,7 +105,7 @@ def _get_install_instructions(self):
"""Get CPU tracking installation instructions for the current OS."""
if is_mac_os():
cpu_model = detect_cpu_model()
if "M1" in cpu_model or "M2" in cpu_model or "M3" in cpu_model:
if cpu_model and is_mac_arm(cpu_model):
return "Mac OS and ARM processor detected: Please enable PowerMetrics sudo to measure CPU"
else:
return "Mac OS detected: Please install Intel Power Gadget or enable PowerMetrics sudo to measure CPU"
Expand Down
4 changes: 4 additions & 0 deletions codecarbon/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ def is_mac_os() -> bool:
return system.startswith("dar")


def is_mac_arm(cpu_model: str) -> bool:
return bool(re.search(r"\bM\d{1,2}\b", cpu_model))


def is_windows_os() -> bool:
system = sys.platform.lower()
return system.startswith("win")
Expand Down
2 changes: 1 addition & 1 deletion docs/introduction/methodology.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ Tracks Intel processors energy consumption using the
. But has been discontinued. There is a discussion about it on [github
issues #457](https://github.com/mlco2/codecarbon/issues/457).

- **Apple Silicon Chips (M1, M2)**
- **Apple Silicon Chips (M1, M2, M3, ...)**

Apple Silicon Chips contain both the CPU and the GPU.

Expand Down
32 changes: 31 additions & 1 deletion tests/test_core_util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import shutil
import tempfile

from codecarbon.core.util import backup, detect_cpu_model, resolve_path
import pytest

from codecarbon.core.util import backup, detect_cpu_model, is_mac_arm, resolve_path


def test_detect_cpu_model_caching():
Expand Down Expand Up @@ -42,3 +44,31 @@ def test_backup():
backup(first_file.name)
backup_of_backup_path = resolve_path(f"{first_file.name}_0.bak")
assert backup_of_backup_path.exists()


@pytest.mark.parametrize(
"cpu_model, expected",
[
# Apple Silicon chips that should match
("Apple M1", True),
("Apple M2", True),
("Apple M3", True),
("Apple M4", True),
("Apple M1 Pro", True),
("Apple M2 Max", True),
("Apple M3 Ultra", True),
("Apple M4 Pro", True),
("Apple M10", True),
# Non-Apple ARM or unrelated chips that should NOT match
("Intel Core i7-9750H", False),
("AMD Ryzen 9 5900X", False),
("Qualcomm Snapdragon 8cx Gen 3", False),
# Partial matches that should NOT match (no word boundary)
("SuperM2000 Processor", False),
("M2fast chip", False),
# Empty string
("", False),
],
)
def test_is_mac_arm(cpu_model, expected):
assert is_mac_arm(cpu_model) == expected
43 changes: 43 additions & 0 deletions tests/test_resource_tracker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from unittest.mock import MagicMock, patch

import pytest

from codecarbon.core.resource_tracker import ResourceTracker


@pytest.mark.parametrize(
"is_mac, is_windows, is_linux, cpu_model, expected_fragment",
[
# Mac + ARM chip
(True, False, False, "Apple M4", "PowerMetrics sudo"),
# Mac + Intel chip
(True, False, False, "Intel Core i7", "Intel Power Gadget"),
# Mac + cpu_model is None
(True, False, False, None, "Intel Power Gadget"),
# Windows
(False, True, False, "Intel Core i7", "Intel Power Gadget"),
# Linux
(False, False, True, "Intel Core i7", "RAPL"),
# Unknown OS
(False, False, False, "Intel Core i7", ""),
],
)
def test_get_install_instructions(
is_mac, is_windows, is_linux, cpu_model, expected_fragment
):
tracker = MagicMock()
resource_tracker = ResourceTracker(tracker)

with (
patch("codecarbon.core.resource_tracker.is_mac_os", return_value=is_mac),
patch(
"codecarbon.core.resource_tracker.is_windows_os", return_value=is_windows
),
patch("codecarbon.core.resource_tracker.is_linux_os", return_value=is_linux),
patch(
"codecarbon.core.resource_tracker.detect_cpu_model", return_value=cpu_model
),
):
result = resource_tracker._get_install_instructions()

assert expected_fragment in result
Loading