Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cpu.Info() errors on Apple Silicon M1 (darwin/arm64) #1000

Closed
tmm1 opened this issue Nov 24, 2020 · 11 comments · Fixed by #1192 or #1436
Closed

cpu.Info() errors on Apple Silicon M1 (darwin/arm64) #1000

tmm1 opened this issue Nov 24, 2020 · 11 comments · Fixed by #1192 or #1436

Comments

@tmm1
Copy link
Contributor

tmm1 commented Nov 24, 2020

Getting "no such file or directory", from here:

// Use the rated frequency of the CPU. This is a static value and does not
// account for low power or Turbo Boost modes.
cpuFrequency, err := unix.SysctlUint64("hw.cpufrequency")
if err != nil {
return ret, err

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
@tmm1
Copy link
Contributor Author

tmm1 commented Nov 24, 2020

looks like this will be fixed with #999

@tmm1 tmm1 closed this as completed Nov 24, 2020
@Lomanic Lomanic reopened this Nov 30, 2020
RobAtticus pushed a commit to timescale/tsbs that referenced this issue May 14, 2021
This PR updates gopsutil to v3, given v2 is incompatible with Darwin arm64 (dep issue on github: shirou/gopsutil#1000).
@Lomanic
Copy link
Collaborator

Lomanic commented Dec 11, 2021

@BitesPotatoBacks commented in giampaolo/psutil#1892 (comment)

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 👍

From a quick glance at the linked function, sadly it needs some inline assembly, which looks quite hard to port to go or cgo.

@dehydratedpotato
Copy link

dehydratedpotato commented Dec 14, 2021

@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...

@mpkouznetsov
Copy link

I cannot wait for #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.

@shirou
Copy link
Owner

shirou commented Dec 18, 2021

Sadly, there are no response other than kind Lomanic, so I did not merge. but from your comment, I will merge it.

@shirou
Copy link
Owner

shirou commented Dec 18, 2021

#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.

BrewTestBot pushed a commit to Homebrew/homebrew-core that referenced this issue Feb 25, 2022
See shirou/gopsutil#1000

Closes #95800.

Signed-off-by: Sean Molenaar <[email protected]>
Signed-off-by: BrewTestBot <[email protected]>
@JakeCooper
Copy link

JakeCooper commented Jul 20, 2022

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

@JakeCooper
Copy link

JakeCooper commented Jul 20, 2022

@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)?

@dehydratedpotato
Copy link

dehydratedpotato commented Jul 20, 2022

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

@shoenig
Copy link
Contributor

shoenig commented Oct 26, 2022

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)
}

@shoenig
Copy link
Contributor

shoenig commented Oct 26, 2022

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)
}

shoenig added a commit to shoenig/gopsutil that referenced this issue Mar 23, 2023
This PR adds support for reading the frequency of Apple Silicon
M1/M2 CPUs. We do so by reading the values out of the IOKit
framework, as a few other projects have now demonstrated to be
possible. This requires the use of CGO. The library provides a
convenience IsAppleSilicon() guard to detect whether the values
can be read.

Currently gopsutil does not support the big.LITTLE CPU architectures
(i think?) - in fact the P and E cores have different max frequencies.
For now, just read the P core frequency. The E core data is readily
available if we want to read it in the future.

Closes shirou#1000
shoenig added a commit to shoenig/gopsutil that referenced this issue Mar 23, 2023
This PR adds support for reading the frequency of Apple Silicon
M1/M2 CPUs. We do so by reading the values out of the IOKit
framework, as a few other projects have now demonstrated to be
possible. This requires the use of CGO. The library provides a
convenience IsAppleSilicon() guard to detect whether the values
can be read.

Currently gopsutil does not support the big.LITTLE CPU architectures
(i think?) - in fact the P and E cores have different max frequencies.
For now, just read the P core frequency. The E core data is readily
available if we want to read it in the future.

Closes shirou#1000

Small example program

```go
package main

import (
        "fmt"

        "github.com/shoenig/go-m1cpu"

        "github.com/shirou/gopsutil/v3/cpu"
)

func main() {
        fmt.Println("is Apple Silicon:", m1cpu.IsAppleSilicon())
        fmt.Println("model name", m1cpu.ModelName())
        fmt.Println("pCore GHz", m1cpu.PCoreGHz())
        fmt.Println("eCore GHz", m1cpu.ECoreGHz())
        fmt.Println("pCore Hz", m1cpu.PCoreHz())
        fmt.Println("eCore Hz", m1cpu.ECoreHz())

        fmt.Println("----- gopsutil ----")

        infos, err := cpu.Info()
        if err != nil {
                panic(err)
        }

        for _, info := range infos {
                fmt.Println("info.Mhz", info.Mhz)
        }
}
```

```shell
go run main.go
is Apple Silicon: true
model name Apple M2 Pro
pCore GHz 3.504
eCore GHz 2.424
pCore Hz 3504000000
eCore Hz 2424000000
----- gopsutil ----
info.Mhz 3.504e+09
```
shoenig added a commit to shoenig/gopsutil that referenced this issue Mar 25, 2023
This PR adds support for reading the frequency of Apple Silicon
M1/M2 CPUs. We do so by reading the values out of the IOKit
framework, as a few other projects have now demonstrated to be
possible. This requires the use of CGO. The library provides a
convenience IsAppleSilicon() guard to detect whether the values
can be read.

Currently gopsutil does not support the big.LITTLE CPU architectures
(i think?) - in fact the P and E cores have different max frequencies.
For now, just read the P core frequency. The E core data is readily
available if we want to read it in the future.

Closes shirou#1000

Small example program

```go
package main

import (
        "fmt"

        "github.com/shoenig/go-m1cpu"

        "github.com/shirou/gopsutil/v3/cpu"
)

func main() {
        fmt.Println("is Apple Silicon:", m1cpu.IsAppleSilicon())
        fmt.Println("model name", m1cpu.ModelName())
        fmt.Println("pCore GHz", m1cpu.PCoreGHz())
        fmt.Println("eCore GHz", m1cpu.ECoreGHz())
        fmt.Println("pCore Hz", m1cpu.PCoreHz())
        fmt.Println("eCore Hz", m1cpu.ECoreHz())

        fmt.Println("----- gopsutil ----")

        infos, err := cpu.Info()
        if err != nil {
                panic(err)
        }

        for _, info := range infos {
                fmt.Println("info.Mhz", info.Mhz)
        }
}
```

```shell
go run main.go
is Apple Silicon: true
model name Apple M2 Pro
pCore GHz 3.504
eCore GHz 2.424
pCore Hz 3504000000
eCore Hz 2424000000
----- gopsutil ----
info.Mhz 3.504e+09
```
shoenig added a commit to shoenig/gopsutil that referenced this issue Mar 28, 2023
This PR adds support for reading the frequency of Apple Silicon
M1/M2 CPUs. We do so by reading the values out of the IOKit
framework, as a few other projects have now demonstrated to be
possible. This requires the use of CGO. The library provides a
convenience IsAppleSilicon() guard to detect whether the values
can be read.

Currently gopsutil does not support the big.LITTLE CPU architectures
(i think?) - in fact the P and E cores have different max frequencies.
For now, just read the P core frequency. The E core data is readily
available if we want to read it in the future.

Closes shirou#1000

Small example program

```go
package main

import (
        "fmt"

        "github.com/shoenig/go-m1cpu"

        "github.com/shirou/gopsutil/v3/cpu"
)

func main() {
        fmt.Println("is Apple Silicon:", m1cpu.IsAppleSilicon())
        fmt.Println("model name", m1cpu.ModelName())
        fmt.Println("pCore GHz", m1cpu.PCoreGHz())
        fmt.Println("eCore GHz", m1cpu.ECoreGHz())
        fmt.Println("pCore Hz", m1cpu.PCoreHz())
        fmt.Println("eCore Hz", m1cpu.ECoreHz())

        fmt.Println("----- gopsutil ----")

        infos, err := cpu.Info()
        if err != nil {
                panic(err)
        }

        for _, info := range infos {
                fmt.Println("info.Mhz", info.Mhz)
        }
}
```

```shell
go run main.go
is Apple Silicon: true
model name Apple M2 Pro
pCore GHz 3.504
eCore GHz 2.424
pCore Hz 3504000000
eCore Hz 2424000000
----- gopsutil ----
info.Mhz 3.504e+09
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants