Skip to content

Commit

Permalink
RaspberryPi 4: determining CPU online state
Browse files Browse the repository at this point in the history
  • Loading branch information
iwankgb committed Jan 4, 2021
1 parent cb10d46 commit 0e4529d
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 22 deletions.
84 changes: 64 additions & 20 deletions utils/sysfs/sysfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"strconv"
"strings"

"go.etcd.io/etcd/pkg/osutil"
"golang.org/x/sys/unix"
"k8s.io/klog/v2"
)
Expand All @@ -52,7 +51,7 @@ const (
var (
nodeDir = "/sys/devices/system/node/"
// It is evil but I need to know what the architecture is.
uname func (*unix.Utsname) error = unix.Uname
uname = unix.Uname
)

type CacheInfo struct {
Expand Down Expand Up @@ -344,7 +343,7 @@ func (fs *realSysFs) IsCPUOnline(cpuPath string) bool {

// IsCPUOnline verifies if a CPU in online. Takes path to /sys/devices/system/cpu/cpu*/ directory as a parameter.
func IsCPUOnline(cpuPath string) bool {
if isx86() && isZeroCPU(cpuPath) {
if isx86() && isZeroCPU(cpuPath) {
// It *is* possible to enable CPU hotplug for CPU0 but it requires setting a boot flag.
// It is assumed it's a rare situation.
return true
Expand All @@ -362,16 +361,14 @@ func IsCPUOnline(cpuPath string) bool {
// If online file exists but reading failed we assume that CPU is offline.
klog.V(2).Infof("Cannot determine CPU %s online state, assuming offline", cpuPath)
return false
} else {
// If online file does not exists then we should check one more location.
klog.V(2).Infof("%s not found, checking %s directory", onlinePath, filepath.Join(cpuPath, "hotplug"))
if isOnlineHotplug(cpuPath) {
return true
} else {
klog.V(2).Infof("online file for CPU %s not found; assuming that CPU hotplug is disabled", cpuPath)
return false
}
}
// If online file does not exists then we should check one more location.
klog.V(2).Infof("%s not found, checking %s directory", onlinePath, filepath.Join(cpuPath, "hotplug"))
if isOnlineHotplug(cpuPath) {
return true
}
klog.V(2).Infof("online file for CPU %s not found; assuming that CPU hotplug is disabled", cpuPath)
return false
}
}
// Check if online file value is 1.
Expand All @@ -385,21 +382,68 @@ func IsCPUOnline(cpuPath string) bool {

func isx86() bool {
uts := unix.Utsname{}
_ = uname(&uts)
err := uname(&uts)
if err != nil {
return false
}
return uts.Machine[0] == 120 && uts.Machine[1] == 56 && uts.Machine[2] == 54
}

func isZeroCPU(dir string) bool {
regex := regexp.MustCompile("cpu([0-9]*)")
matches := regex.FindStringSubmatch(dir)
return len(matches) == 2 && matches[1] == "0"
regex := regexp.MustCompile("cpu([0-9]*)")
matches := regex.FindStringSubmatch(dir)
return len(matches) == 2 && matches[1] == "0"
}


// isOnlineHotplug checks if CPU state matches online state from /sys/devices/system/cpu/hotplug/states.
// It allows to determine if CPU is online on some ARM platforms such as RPi4.
// See: https://www.kernel.org/doc/html/latest/core-api/cpu_hotplug.html#testing-of-hotplug-states
func isOnlineHotplug(cpuPath string) bool {
desiredState, err := getOnlineStateID(cpuPath)
if err != nil {
klog.V(4).Infof("Cannot determine online state ID for CPU %s", cpuPath)
return false
}
hotplugDir := filepath.Join(cpuPath, "hotplug", "state")
state, _ := ioutil.ReadFile(hotplugDir)
state = bytes.Trim(state, " ")
state = bytes.TrimSpace(state)
realState, _ := strconv.Atoi(string(state))
return realState == desiredState
}

return false
}
// getOnlineStateID looks for line similar to `206: online` and extracts number from the beginning.
// See: https://www.kernel.org/doc/html/latest/core-api/cpu_hotplug.html#testing-of-hotplug-states
func getOnlineStateID(cpuPath string) (int, error) {
const (
lf byte = 0x0A
colon byte = 0x3A
onlineState = "online"
)

statePath, err := filepath.Abs(path.Join(cpuPath, "..", "hotplug", "states"))
if err != nil {
return 0, err
}
states, err := ioutil.ReadFile(statePath)
if err != nil {
return 0, err
}
var online int
statesByLine := bytes.Split(states, []byte{lf})

// We start from last row as it's usually where online can be found.
for i := len(statesByLine) - 1; i >= 0; i-- {
state := bytes.Split(statesByLine[i], []byte{colon})
if len(state) != 2 {
continue
}
if string(bytes.TrimSpace(state[1])) == onlineState {
online, err = strconv.Atoi(string(state[0]))
if err != nil {
return 0, err
}
break
}
}
return online, nil
}
19 changes: 17 additions & 2 deletions utils/sysfs/sysfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,21 @@ func TestIsCPUOnline(t *testing.T) {
}

func TestIsCPUOnlineNoFileAndCPU0MustBeOnline(t *testing.T) {
// Mock unix.Uname() so it returns x86_64, no matter what.
unameOrig := uname
defer func() {
uname = unameOrig
}()
uname = func(u *unix.Utsname) error {
// x86_64
u.Machine[0] = 120
u.Machine[1] = 56
u.Machine[2] = 54
u.Machine[3] = 95
u.Machine[4] = 54
u.Machine[5] = 52
return nil
}
sysFs := NewRealSysFs()
online := sysFs.IsCPUOnline("./testdata/missing_online/node0/cpu33")
assert.False(t, online)
Expand Down Expand Up @@ -164,7 +179,7 @@ func TestIsCPU0OnlineOnx86(t *testing.T) {
}

func TestIsCPU0OnlineSyscallErrors(t *testing.T) {
// Mock unix.Uname() so it returns x86_64, no matter what.
// Mock unix.Uname() so errors, no matter what.
unameOrig := uname
defer func() {
uname = unameOrig
Expand Down Expand Up @@ -194,7 +209,7 @@ func TestCPU0OfflineOnNotx86(t *testing.T) {
}

func TestIsCpuOnlineRaspberryPi4(t *testing.T) {
// Mock unix.Uname() so it returns x86_64, no matter what.
// Mock unix.Uname() so it returns aarch64, no matter what.
unameOrig := uname
defer func() {
uname = unameOrig
Expand Down

0 comments on commit 0e4529d

Please sign in to comment.