gopsutil
gopsutil copied to clipboard
cpu.Info() errors on Apple Silicon M1 (darwin/arm64)
Getting "no such file or directory", from here:
https://github.com/shirou/gopsutil/blob/340db113de4d1b4f14905e76ca5012d4d45a4fc9/cpu/cpu_darwin.go#L94-L98
I confirmed this sysctl is not present on M1:
tmm1@m1 ~ % sysctl hw.cpufrequency
tmm1@m1 ~ % echo $?
0
It only appears under Rosetta 2:
tmm1@m1 ~ % arch -x86_64 /bin/bash -c 'sysctl hw.cpufrequency'
hw.cpufrequency: 2400000000
looks like this will be fixed with #999
@BitesPotatoBacks commented in https://github.com/giampaolo/psutil/issues/1892#issuecomment-989959636
This is my first time posting in the psutil repo, but I have been trying to figure out how to pull the current CPU frequency on Apple Silicon for a few months now, and I've finally managed to create a script that can do that.
In case anyone wants to take a look at it, the entire project is here, but this is the magical function that does all the work.
Good day :+1:
From a quick glance at the linked function, sadly it needs some inline assembly, which looks quite hard to port to go or cgo.
@Lomanic I compiled a binary for my script as well...since porting the code over to GO would be hard, perhaps gopsutil could just use that binary, and execute it using the GO exec package.
If you guys want to be able to fetch the current CPU speed on Apple Silicon, this method would (probably) be the only way to do that here (unless GO has a secret inline assembly capability hidden somewhere).
Though, this method would require you guys to bundle my binary within gopsutil's source, and I don't know how the devs feel about doing that sort of thing...
I cannot wait for https://github.com/shirou/gopsutil/pull/1192 (a partial fix for this issue) to be merged. Will it make it into the December release? We are using this package to get the CPU brand and the number of cores so to us not returning an error when the CPU frequency is not available but most other information is makes sense.
Sadly, there are no response other than kind Lomanic, so I did not merge. but from your comment, I will merge it.
#1192 is only partial fix. reopen it until more good solution implemented. @BitesPotatoBacks 's great work should fix. I think we can implement by using asm in go world, but not yet implemented.
Current enjoyer of this fine library here
Anything I could do to PR this in and resolve this issue?
Have an M1 so happy to test and write, but I think we should be able to just pull CPU info from /usr/bin/powermetrics?
➜ railway_gh git:(master) ✗ sudo /usr/bin/powermetrics -s cpu_power -n 1
Machine model: MacBookPro18,2
OS version: 21F79
Boot arguments:
Boot time: Fri Jul 1 15:29:34 2022
*** Sampled system activity (Tue Jul 19 17:59:15 2022 -0700) (5004.65ms elapsed) ***
**** Processor usage ****
E-Cluster Power: 148 mW
E-Cluster HW active frequency: 1448 MHz
E-Cluster HW active residency: 70.74% (600 MHz: 0% 972 MHz: 24% 1332 MHz: 39% 1704 MHz: 19% 2064 MHz: 18%)
E-Cluster idle residency: 29.26%
E-Cluster instructions retired: 1.0427e+10
E-Cluster instructions per clock: 1.30576
CPU 0 frequency: 1438 MHz
CPU 0 idle residency: 44.65%
CPU 0 active residency: 55.35% (600 MHz: 0% 972 MHz: 14% 1332 MHz: 21% 1704 MHz: 11% 2064 MHz: 9.5%)
CPU 1 frequency: 1438 MHz
CPU 1 idle residency: 44.09%
CPU 1 active residency: 55.91% (600 MHz: 0% 972 MHz: 14% 1332 MHz: 21% 1704 MHz: 11% 2064 MHz: 9.4%)
P0-Cluster Power: 4737 mW
P0-Cluster HW active frequency: 3182 MHz
P0-Cluster HW active residency: 97.80% (600 MHz: .11% 828 MHz: 0% 1056 MHz: 0% 1296 MHz: .27% 1524 MHz: .01% 1752 MHz: .18% 1980 MHz: .13% 2208 MHz: 0% 2448 MHz: .17% 2676 MHz: .13% 2904 MHz: .02% 3036 MHz: 5.1% 3132 MHz: 23% 3168 MHz: 0% 3228 MHz: 71%)
P0-Cluster idle residency: 2.20%
P0-Cluster instructions retired: 8.26585e+10
P0-Cluster instructions per clock: 4.04083
CPU 2 frequency: 3220 MHz
CPU 2 idle residency: 52.47%
CPU 2 active residency: 47.53% (600 MHz: .00% 828 MHz: 0% 1056 MHz: 0% 1296 MHz: .07% 1524 MHz: .01% 1752 MHz: .06% 1980 MHz: .07% 2208 MHz: 0% 2448 MHz: .03% 2676 MHz: .01% 2904 MHz: 0% 3036 MHz: 0% 3132 MHz: 0% 3168 MHz: 0% 3228 MHz: 47%)
CPU 3 frequency: 3224 MHz
CPU 3 idle residency: 62.82%
CPU 3 active residency: 37.18% (600 MHz: .00% 828 MHz: 0% 1056 MHz: 0% 1296 MHz: .00% 1524 MHz: .00% 1752 MHz: .04% 1980 MHz: .02% 2208 MHz: 0% 2448 MHz: .03% 2676 MHz: .06% 2904 MHz: 0% 3036 MHz: 0% 3132 MHz: 0% 3168 MHz: 0% 3228 MHz: 37%)
CPU 4 frequency: 3227 MHz
CPU 4 idle residency: 77.34%
CPU 4 active residency: 22.66% (600 MHz: 0% 828 MHz: 0% 1056 MHz: 0% 1296 MHz: 0% 1524 MHz: 0% 1752 MHz: .00% 1980 MHz: .01% 2208 MHz: 0% 2448 MHz: .00% 2676 MHz: .00% 2904 MHz: 0% 3036 MHz: 0% 3132 MHz: 0% 3168 MHz: 0% 3228 MHz: 23%)
CPU 5 frequency: 3228 MHz
CPU 5 idle residency: 78.08%
CPU 5 active residency: 21.92% (600 MHz: 0% 828 MHz: 0% 1056 MHz: 0% 1296 MHz: 0% 1524 MHz: 0% 1752 MHz: .00% 1980 MHz: .00% 2208 MHz: 0% 2448 MHz: 0% 2676 MHz: .00% 2904 MHz: 0% 3036 MHz: 0% 3132 MHz: 0% 3168 MHz: 0% 3228 MHz: 22%)
P1-Cluster Power: 282 mW
P1-Cluster HW active frequency: 1028 MHz
P1-Cluster HW active residency: 7.06% (600 MHz: 75% 828 MHz: .44% 1056 MHz: 1.9% 1296 MHz: 2.3% 1524 MHz: 2.6% 1752 MHz: 2.8% 1980 MHz: 1.6% 2208 MHz: 1.1% 2448 MHz: 1.1% 2676 MHz: .68% 2904 MHz: .48% 3036 MHz: 1.0% 3132 MHz: .52% 3168 MHz: 0% 3228 MHz: 8.5%)
P1-Cluster idle residency: 92.94%
P1-Cluster instructions retired: 4.26293e+09
P1-Cluster instructions per clock: 3.0665
CPU 6 frequency: 2628 MHz
CPU 6 idle residency: 95.20%
CPU 6 active residency: 4.80% (600 MHz: .18% 828 MHz: .00% 1056 MHz: .30% 1296 MHz: .21% 1524 MHz: .31% 1752 MHz: .30% 1980 MHz: .11% 2208 MHz: .13% 2448 MHz: .08% 2676 MHz: .05% 2904 MHz: .02% 3036 MHz: 0% 3132 MHz: .01% 3168 MHz: 0% 3228 MHz: 3.1%)
CPU 7 frequency: 2858 MHz
CPU 7 idle residency: 97.16%
CPU 7 active residency: 2.84% (600 MHz: .03% 828 MHz: .00% 1056 MHz: .06% 1296 MHz: .18% 1524 MHz: .07% 1752 MHz: .08% 1980 MHz: .16% 2208 MHz: .01% 2448 MHz: .08% 2676 MHz: .00% 2904 MHz: 0% 3036 MHz: 0% 3132 MHz: .01% 3168 MHz: 0% 3228 MHz: 2.2%)
CPU 8 frequency: 3175 MHz
CPU 8 idle residency: 98.09%
CPU 8 active residency: 1.91% (600 MHz: .01% 828 MHz: .00% 1056 MHz: .01% 1296 MHz: .02% 1524 MHz: .01% 1752 MHz: .01% 1980 MHz: .00% 2208 MHz: .00% 2448 MHz: .00% 2676 MHz: .00% 2904 MHz: 0% 3036 MHz: 0% 3132 MHz: 0% 3168 MHz: 0% 3228 MHz: 1.9%)
CPU 9 frequency: 3195 MHz
CPU 9 idle residency: 98.91%
CPU 9 active residency: 1.09% (600 MHz: .00% 828 MHz: .00% 1056 MHz: .00% 1296 MHz: .00% 1524 MHz: .00% 1752 MHz: .01% 1980 MHz: .00% 2208 MHz: .00% 2448 MHz: .00% 2676 MHz: .00% 2904 MHz: 0% 3036 MHz: 0% 3132 MHz: 0% 3168 MHz: 0% 3228 MHz: 1.1%)
System instructions retired: 9.73485e+10
System instructions per clock: 3.26329
ANE Power: 0 mW
DRAM Power: 1429 mW
CPU Power: 5167 mW
GPU Power: 21 mW
Package Power: 5188 mW
@BitesPotatoBacks' newer released binary is functional now on M1 + M1 Pro. Would it be reasonable to bundle the binary as part of this library, calling it in the M1 case (or using the powermetrics method)?
If my project was to been implemented in this library, I could compile a special barebones version of my binary with minimal output formatting, (to make it easier for gopsutil to parse the output of the frequency metrics) as well, if desired.
My binary also doesn't require sudo
, so that could be an added benefit over powermetrics
Would it be reasonable to bundle the binary as part of this library, calling it in the M1 case
Including a compiled binary in a prevalent Go library would be suspect, at best. Perhaps that binary could be shipped via homebrew, and gopsutil could be smart enough to look for its existence in the standard location, but that is still some out-in-the-weeds requirement just for looking up CPU frequency.
It's not impossible to do this with CGO, e.g. printing out a serial number below. But someone with more knowledge / patience than me is needed to dig through the docs / other examples to get the CPU info. I believe we need the equivalent of this oshi implementation.
➜ cat main2.go
package main
// #cgo LDFLAGS: -framework CoreFoundation -framework IOKit
// #include <CoreFoundation/CoreFoundation.h>
// #include <IOKit/IOKitLib.h>
//
// const char *
// getSerialNumber() {
// CFMutableDictionaryRef matching = IOServiceMatching("IOPlatformExpertDevice");
// io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, matching);
// CFStringRef serialNumber = IORegistryEntryCreateCFProperty(service,CFSTR("IOPlatformSerialNumber"), kCFAllocatorDefault, 0);
// const char *str = CFStringGetCStringPtr(serialNumber, kCFStringEncodingUTF8);
// IOObjectRelease(service);
// return str;
// }
import "C"
import (
"fmt"
)
func main() {
sn := C.GoString(C.getSerialNumber())
fmt.Println(sn)
}
FWIW I also got the naive implementation working, but it basically amounts to a PRNG between [0, max_freq]
➜ cat main.go
package main
// #include <stdio.h>
// #include <unistd.h>
// #include <math.h>
// #include <mach/mach_time.h>
//
// double frequency()
// {
// mach_timebase_info_data_t info;
// mach_timebase_info(&info);
// const size_t testDurationFromCycles = 65536;
// uint64_t firstSampleBegin = mach_absolute_time();
// size_t cycles = 2 * testDurationFromCycles;
// __asm volatile(".align 4\n Lcyclemeasure1:\nsubs %[counter],%[counter],#1\nbne Lcyclemeasure1\n " : [counter] "+r"(cycles));
// uint64_t firstSampleEnd = mach_absolute_time();
// double firstNanosecondSet = (double) (firstSampleEnd - firstSampleBegin) * (double)info.numer / (double)info.denom;
// uint64_t lastSampleBegin = mach_absolute_time();
// cycles = testDurationFromCycles;
// __asm volatile(".align 4\n Lcyclemeasure2:\nsubs %[counter],%[counter],#1\nbne Lcyclemeasure2\n " : [counter] "+r"(cycles));
// uint64_t lastSampleEnd = mach_absolute_time();
// double lastNanosecondSet = (double) (lastSampleEnd - lastSampleBegin) * (double)info.numer / (double)info.denom;
// double nanoseconds = (firstNanosecondSet - lastNanosecondSet);
// if ((fabs(nanoseconds - firstNanosecondSet / 2) > 0.05 * nanoseconds) || (fabs(nanoseconds - lastNanosecondSet) > 0.05 * nanoseconds)) { return 0; }
// double frequency = (double)(testDurationFromCycles) / nanoseconds;
// return frequency;
// }
import "C"
import (
"fmt"
)
func main() {
f := C.frequency()
fmt.Printf("%.4f\n", f)
}