blob: 5222c9ef659c22cff75e29b262afe8025408cad6 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package arc
import (
"context"
"fmt"
"io/ioutil"
"regexp"
"runtime"
"strings"
"github.com/shirou/gopsutil/process"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/bundles/cros/arc/cpuset"
"chromiumos/tast/shutil"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: CPUSet,
Desc: "Verifies cpuset",
Contacts: []string{
"risan@chromium.org",
"arc-core@google.com",
"arc-storage@google.com",
"hidehiko@chromium.org", // Tast port author.
},
SoftwareDeps: []string{
"chrome",
},
Attr: []string{"group:mainline"},
Fixture: "arcBooted",
Params: []testing.Param{{
ExtraSoftwareDeps: []string{"android_p"},
}, {
Name: "vm",
ExtraSoftwareDeps: []string{"android_vm"},
}},
})
}
// getInitPIDs returns all PIDs corresponding to ARC init processes.
// TODO (hidehiko@): merge this and InitPID()
func getInitPIDs() ([]int, error) {
ver, err := arc.SDKVersion()
if err != nil {
return nil, err
}
// The path to the ARC init executable.
var initExecPath = "/init"
if ver >= arc.SDKQ {
initExecPath = "/system/bin/init"
}
all, err := process.Pids()
if err != nil {
return nil, err
}
var pids []int
for _, pid := range all {
proc, err := process.NewProcess(pid)
if err != nil {
// Assume that the process exited.
continue
}
if exe, err := proc.Exe(); err == nil && exe == initExecPath {
if username, err := proc.Username(); err == nil && username == "android-root" {
pids = append(pids, int(pid))
}
}
}
return pids, nil
}
// getRootPID returns the PID of the root ARC init process.
func getRootPID() (int, error) {
pids, err := getInitPIDs()
if err != nil {
return -1, err
}
pm := make(map[int]struct{}, len(pids))
for _, pid := range pids {
pm[pid] = struct{}{}
}
for _, pid := range pids {
// If we see errors, assume that the process exited.
proc, err := process.NewProcess(int32(pid))
if err != nil {
continue
}
ppid, err := proc.Ppid()
if err != nil || ppid <= 0 {
continue
}
if _, ok := pm[int(ppid)]; !ok {
return pid, nil
}
}
return -1, errors.New("root not found")
}
// Intel does not have any 'CPU part' line. ARM does, and when it's
// big.LITTLE, it has two different CPU parts (e.g. 0xd03 and 0xd09).
var cpuPartRE = regexp.MustCompile(`^CPU part\s+:\s+(0x[0-9a-f]+)$`)
// isHeterogeneousCores returns true if cores are heterogeneous ones
// such as ARM's big.LITTLE.
func isHeterogeneousCores(ctx context.Context) (bool, error) {
if arch := runtime.GOARCH; arch != "arm" && arch != "arm64" {
return false, nil
}
out, err := ioutil.ReadFile("/proc/cpuinfo")
if err != nil {
return false, errors.Wrap(err, "failed to read cpuinfo")
}
lines := strings.Split(string(out), "\n")
var lastCPUPart string
for _, line := range lines {
matches := cpuPartRE.FindAllStringSubmatch(strings.TrimSpace(line), -1)
if matches == nil {
continue
}
cpuPart := matches[0][1]
if lastCPUPart == "" {
lastCPUPart = cpuPart
continue
}
if lastCPUPart != cpuPart {
testing.ContextLog(ctx, "Detected heterogeneous cores")
return true, nil
}
}
if lastCPUPart == "" {
return false, errors.New("no CPU part information found")
}
return false, nil
}
func readCPUSetInfo(ctx context.Context, t string) (string, []byte, error) {
isVMEnabled, err := arc.VMEnabled()
if err != nil {
return "", nil, err
}
if isVMEnabled {
path := fmt.Sprintf("/proc/1/root/dev/cpuset/%s/effective_cpus", t)
out, err := testexec.CommandContext(ctx, "android-sh", "-c", fmt.Sprintf("cat %s", shutil.Escape(path))).Output(testexec.DumpLogOnError)
return path, out, err
}
initPID, err := getRootPID()
if err != nil {
return "", nil, err
}
path := fmt.Sprintf("/proc/%d/root/dev/cpuset/%s/effective_cpus", initPID, t)
// cgroup pseudo file cannot be "adb pull"ed. Additionally, it is not
// accessible via adb shell user in P. Access by procfs instead.
out, err := ioutil.ReadFile(path)
return path, out, err
}
func CPUSet(ctx context.Context, s *testing.State) {
s.Log("Running testCPUSet")
// Verify that /dev/cpuset is properly set up.
types := []string{"foreground", "background", "system-background", "top-app", "restricted"}
numOtherCores := 0
for _, t := range types {
// cgroup pseudo file cannot be "adb pull"ed. Additionally, it is not
// accessible via adb shell user in P. Access by procfs instead.
path, out, err := readCPUSetInfo(ctx, t)
if err != nil {
s.Errorf("Failed to read %s: %v", path, err)
continue
}
val := strings.TrimSpace(string(out))
cpusInUse, err := cpuset.Parse(ctx, val)
if err != nil {
s.Errorf("Failed to parse %s: %v", path, err)
continue
}
if t == "foreground" || t == "top-app" {
// Even after full boot, these processes should be able
// to use all CPU cores.
if len(cpusInUse) != runtime.NumCPU() {
s.Errorf("Unexpected CPU setting %q for %s: got %d CPUs, want %d CPUs", val, path,
len(cpusInUse), runtime.NumCPU())
}
} else {
// Other processes should not.
if len(cpusInUse) == runtime.NumCPU() {
s.Errorf("Unexpected CPU setting %q for %s: should not be %d CPUs", val, path,
runtime.NumCPU())
}
numOtherCores += len(cpusInUse)
}
}
heterogeneousCores, err := isHeterogeneousCores(ctx)
if err != nil {
s.Error("Failed to determine core type: ", err)
return
}
if heterogeneousCores {
// The cpuset settings done in init.{cheets,bertha}.rc work fine
// for homogeneous systems, but heterogeneous systems require
// their own init.cpusets.rc to properly set up non-foreground
// cpusets. Verify that init.cpusets.rc exists on heterogeneous
// systems.
s.Log("Running testCPUSet (for boards with heterogeneous cores)")
if numOtherCores == 3 {
s.Error("Unexpected CPU setting; found heterogeneous cores but lacks proper init.cpusets.rc file")
}
}
}