Skip to content

Commit 14a33ff

Browse files
authored
Fix cpu_freq for Apple silicon (#2222)
Apple SoC returns empty string after querying the cpu frequency using sysctl, this information however, can still be extracted from the IOKit registry. This PR adds a new method that is specific to Apple ARM architecture. Signed-off-by: Oliver T <[email protected]>
1 parent 89eac06 commit 14a33ff

File tree

3 files changed

+126
-1
lines changed

3 files changed

+126
-1
lines changed

CREDITS

+4
Original file line numberDiff line numberDiff line change
@@ -813,3 +813,7 @@ I: 2156, 2345
813813
N: Lawrence D'Anna
814814
W: https://github.com/smoofra
815815
I: 2010
816+
817+
N: Oliver Tomé
818+
W: https://github.com/snom3ad
819+
I: 2222

HISTORY.rst

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
- 2340_, [NetBSD]: if process is terminated, `Process.cwd()`_ will return an
2121
empty string instead of raising `NoSuchProcess`_.
2222
- 2345_, [Linux]: fix compilation on older compiler missing DUPLEX_UNKNOWN
23+
- 2222_, [macOS]: `cpu_freq()` now returns fixed values for `min` and `max`
24+
frequencies in all Apple Silicon chips.
2325

2426
5.9.7
2527
=====

psutil/arch/osx/cpu.c

+120-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ For reference, here's the git history with original implementations:
2626
#include <sys/sysctl.h>
2727
#include <sys/vmmeter.h>
2828
#include <mach/mach.h>
29+
#if defined(__arm64__) || defined(__aarch64__)
30+
#include <CoreFoundation/CoreFoundation.h>
31+
#include <IOKit/IOKitLib.h>
32+
#endif
2933

3034
#include "../../_psutil_common.h"
3135
#include "../../_psutil_posix.h"
@@ -109,7 +113,122 @@ psutil_cpu_stats(PyObject *self, PyObject *args) {
109113
);
110114
}
111115

116+
#if defined(__arm64__) || defined(__aarch64__)
117+
PyObject *
118+
psutil_cpu_freq(PyObject *self, PyObject *args) {
119+
uint32_t min;
120+
uint32_t curr;
121+
uint32_t pMin;
122+
uint32_t eMin;
123+
uint32_t max;
124+
kern_return_t status;
125+
CFDictionaryRef matching = NULL;
126+
CFTypeRef pCoreRef = NULL;
127+
CFTypeRef eCoreRef = NULL;
128+
io_iterator_t iter = 0;
129+
io_registry_entry_t entry = 0;
130+
io_name_t name;
131+
132+
matching = IOServiceMatching("AppleARMIODevice");
133+
if (matching == 0) {
134+
return PyErr_Format(
135+
PyExc_RuntimeError,
136+
"IOServiceMatching call failed, 'AppleARMIODevice' not found"
137+
);
138+
}
139+
140+
status = IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter);
141+
if (status != KERN_SUCCESS) {
142+
PyErr_Format(
143+
PyExc_RuntimeError, "IOServiceGetMatchingServices call failed"
144+
);
145+
goto error;
146+
}
147+
148+
while ((entry = IOIteratorNext(iter)) != 0) {
149+
status = IORegistryEntryGetName(entry, name);
150+
if (status != KERN_SUCCESS) {
151+
IOObjectRelease(entry);
152+
continue;
153+
}
154+
if (strcmp(name, "pmgr") == 0) {
155+
break;
156+
}
157+
IOObjectRelease(entry);
158+
}
112159

160+
if (entry == 0) {
161+
PyErr_Format(
162+
PyExc_RuntimeError,
163+
"'pmgr' entry was not found in AppleARMIODevice service"
164+
);
165+
goto error;
166+
}
167+
168+
pCoreRef = IORegistryEntryCreateCFProperty(
169+
entry, CFSTR("voltage-states5-sram"), kCFAllocatorDefault, 0);
170+
if (pCoreRef == NULL) {
171+
PyErr_Format(
172+
PyExc_RuntimeError, "'voltage-states5-sram' property not found");
173+
goto error;
174+
}
175+
176+
eCoreRef = IORegistryEntryCreateCFProperty(
177+
entry, CFSTR("voltage-states1-sram"), kCFAllocatorDefault, 0);
178+
if (eCoreRef == NULL) {
179+
PyErr_Format(
180+
PyExc_RuntimeError, "'voltage-states1-sram' property not found");
181+
goto error;
182+
}
183+
184+
size_t pCoreLength = CFDataGetLength(pCoreRef);
185+
size_t eCoreLength = CFDataGetLength(eCoreRef);
186+
if (pCoreLength < 8) {
187+
PyErr_Format(
188+
PyExc_RuntimeError,
189+
"expected 'voltage-states5-sram' buffer to have at least size 8"
190+
);
191+
goto error;
192+
}
193+
if (eCoreLength < 4) {
194+
PyErr_Format(
195+
PyExc_RuntimeError,
196+
"expected 'voltage-states1-sram' buffer to have at least size 4"
197+
);
198+
goto error;
199+
}
200+
201+
CFDataGetBytes(pCoreRef, CFRangeMake(0, 4), (UInt8 *) &pMin);
202+
CFDataGetBytes(eCoreRef, CFRangeMake(0, 4), (UInt8 *) &eMin);
203+
CFDataGetBytes(pCoreRef, CFRangeMake(pCoreLength - 8, 4), (UInt8 *) &max);
204+
205+
min = pMin < eMin ? pMin : eMin;
206+
curr = max;
207+
208+
CFRelease(pCoreRef);
209+
CFRelease(eCoreRef);
210+
IOObjectRelease(iter);
211+
IOObjectRelease(entry);
212+
213+
return Py_BuildValue(
214+
"IKK",
215+
curr / 1000 / 1000,
216+
min / 1000 / 1000,
217+
max / 1000 / 1000
218+
);
219+
220+
error:
221+
if (pCoreRef != NULL)
222+
CFRelease(pCoreRef);
223+
if (eCoreRef != NULL)
224+
CFRelease(eCoreRef);
225+
if (iter != 0)
226+
IOObjectRelease(iter);
227+
if (entry != 0)
228+
IOObjectRelease(entry);
229+
return NULL;
230+
}
231+
#else
113232
PyObject *
114233
psutil_cpu_freq(PyObject *self, PyObject *args) {
115234
unsigned int curr;
@@ -138,7 +257,7 @@ psutil_cpu_freq(PyObject *self, PyObject *args) {
138257
min / 1000 / 1000,
139258
max / 1000 / 1000);
140259
}
141-
260+
#endif
142261

143262
PyObject *
144263
psutil_per_cpu_times(PyObject *self, PyObject *args) {

0 commit comments

Comments
 (0)