Skip to content

Commit c2b8df8

Browse files
authored
Merge pull request #1112 from mlco2/copilot/add-support-for-apple-m4
Add support for Apple M4
2 parents b335365 + 7c4da32 commit c2b8df8

5 files changed

Lines changed: 87 additions & 4 deletions

File tree

codecarbon/core/resource_tracker.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33

44
from codecarbon.core import cpu, gpu, powermetrics
55
from codecarbon.core.config import parse_gpu_ids
6-
from codecarbon.core.util import detect_cpu_model, is_linux_os, is_mac_os, is_windows_os
6+
from codecarbon.core.util import (
7+
detect_cpu_model,
8+
is_linux_os,
9+
is_mac_arm,
10+
is_mac_os,
11+
is_windows_os,
12+
)
713
from codecarbon.external.hardware import CPU, GPU, MODE_CPU_LOAD, AppleSiliconChip
814
from codecarbon.external.logger import logger
915
from codecarbon.external.ram import RAM
@@ -99,7 +105,7 @@ def _get_install_instructions(self):
99105
"""Get CPU tracking installation instructions for the current OS."""
100106
if is_mac_os():
101107
cpu_model = detect_cpu_model()
102-
if "M1" in cpu_model or "M2" in cpu_model or "M3" in cpu_model:
108+
if cpu_model and is_mac_arm(cpu_model):
103109
return "Mac OS and ARM processor detected: Please enable PowerMetrics sudo to measure CPU"
104110
else:
105111
return "Mac OS detected: Please install Intel Power Gadget or enable PowerMetrics sudo to measure CPU"

codecarbon/core/util.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ def is_mac_os() -> bool:
8888
return system.startswith("dar")
8989

9090

91+
def is_mac_arm(cpu_model: str) -> bool:
92+
return bool(re.search(r"\bM\d{1,2}\b", cpu_model))
93+
94+
9195
def is_windows_os() -> bool:
9296
system = sys.platform.lower()
9397
return system.startswith("win")

docs/introduction/methodology.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ Tracks Intel processors energy consumption using the
186186
. But has been discontinued. There is a discussion about it on [github
187187
issues #457](https://github.com/mlco2/codecarbon/issues/457).
188188

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

191191
Apple Silicon Chips contain both the CPU and the GPU.
192192

tests/test_core_util.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import shutil
22
import tempfile
33

4-
from codecarbon.core.util import backup, detect_cpu_model, resolve_path
4+
import pytest
5+
6+
from codecarbon.core.util import backup, detect_cpu_model, is_mac_arm, resolve_path
57

68

79
def test_detect_cpu_model_caching():
@@ -42,3 +44,31 @@ def test_backup():
4244
backup(first_file.name)
4345
backup_of_backup_path = resolve_path(f"{first_file.name}_0.bak")
4446
assert backup_of_backup_path.exists()
47+
48+
49+
@pytest.mark.parametrize(
50+
"cpu_model, expected",
51+
[
52+
# Apple Silicon chips that should match
53+
("Apple M1", True),
54+
("Apple M2", True),
55+
("Apple M3", True),
56+
("Apple M4", True),
57+
("Apple M1 Pro", True),
58+
("Apple M2 Max", True),
59+
("Apple M3 Ultra", True),
60+
("Apple M4 Pro", True),
61+
("Apple M10", True),
62+
# Non-Apple ARM or unrelated chips that should NOT match
63+
("Intel Core i7-9750H", False),
64+
("AMD Ryzen 9 5900X", False),
65+
("Qualcomm Snapdragon 8cx Gen 3", False),
66+
# Partial matches that should NOT match (no word boundary)
67+
("SuperM2000 Processor", False),
68+
("M2fast chip", False),
69+
# Empty string
70+
("", False),
71+
],
72+
)
73+
def test_is_mac_arm(cpu_model, expected):
74+
assert is_mac_arm(cpu_model) == expected

tests/test_resource_tracker.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from unittest.mock import MagicMock, patch
2+
3+
import pytest
4+
5+
from codecarbon.core.resource_tracker import ResourceTracker
6+
7+
8+
@pytest.mark.parametrize(
9+
"is_mac, is_windows, is_linux, cpu_model, expected_fragment",
10+
[
11+
# Mac + ARM chip
12+
(True, False, False, "Apple M4", "PowerMetrics sudo"),
13+
# Mac + Intel chip
14+
(True, False, False, "Intel Core i7", "Intel Power Gadget"),
15+
# Mac + cpu_model is None
16+
(True, False, False, None, "Intel Power Gadget"),
17+
# Windows
18+
(False, True, False, "Intel Core i7", "Intel Power Gadget"),
19+
# Linux
20+
(False, False, True, "Intel Core i7", "RAPL"),
21+
# Unknown OS
22+
(False, False, False, "Intel Core i7", ""),
23+
],
24+
)
25+
def test_get_install_instructions(
26+
is_mac, is_windows, is_linux, cpu_model, expected_fragment
27+
):
28+
tracker = MagicMock()
29+
resource_tracker = ResourceTracker(tracker)
30+
31+
with (
32+
patch("codecarbon.core.resource_tracker.is_mac_os", return_value=is_mac),
33+
patch(
34+
"codecarbon.core.resource_tracker.is_windows_os", return_value=is_windows
35+
),
36+
patch("codecarbon.core.resource_tracker.is_linux_os", return_value=is_linux),
37+
patch(
38+
"codecarbon.core.resource_tracker.detect_cpu_model", return_value=cpu_model
39+
),
40+
):
41+
result = resource_tracker._get_install_instructions()
42+
43+
assert expected_fragment in result

0 commit comments

Comments
 (0)