blob: 56f45e6a712308de93f842ffbc388894628ed8de [file] [log] [blame]
// Package linux contains Linux-specific pieces of interrogation which are nonetheless testable on
// arbitrary platforms.
package linux
import (
"bufio"
"context"
"io"
"regexp"
"strings"
shell "github.com/kballard/go-shellquote"
"go.skia.org/infra/go/gpus"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/util"
"go.skia.org/infra/machine/go/test_machine_monitor/standalone/crossplatform"
)
// VendorAndBrand returns the vendor and brand string of the first encountered CPU in the provided
// contents of /proc/cpuinfo. Extraction may fail and they may be empty--those are valid
// values--even if error is nil.
func VendorAndBrand(cpuInfo io.Reader) (vendor, brandString string, err error) {
// Fields to be pulled out of /proc/cpuinfo:
var vendorID, isa, cpuModel, hardware, modelName, processor string
interestingFields := map[string]*string{
"vendor_id": &vendorID,
"isa": &isa,
"cpu model": &cpuModel,
"Hardware": &hardware,
"model name": &modelName,
"Processor": &processor,
}
// cpuinfo typically consists of a similar multi-line stanza for each processor core. The cores
// are usually all the same, to the level of detail we need, so we bail out after the first.
// This saves some IO, since cpuinfo is about 200K on a 96-core cloudtop.
scanner := bufio.NewScanner(cpuInfo)
for scanner.Scan() {
line := scanner.Text()
k, v, _ := strings.Cut(line, ": ")
if line == "" {
// We've reached the end of the description of the first processor.
break
}
if v == "" {
// Weird line without a colon. Shouldn't happen, but don't zero out an already-found key
// "foo" if we happen across a line with just "foo" on it.
continue
}
k = strings.TrimSpace(k)
field, found := interestingFields[k]
if found {
*field = v
}
}
if err := scanner.Err(); err != nil {
return "", "", skerr.Wrapf(err, "failed to iterate over lines of CPU info")
}
// Use logic pilfered from Swarming to come up with final answers.
if vendorID != "" {
vendor = vendorID
brandString = modelName
} else if strings.Contains(isa, "mips") {
brandString = cpuModel
} else {
if hardware != "" {
brandString = strings.TrimSuffix(hardware, " (Flattened Device Tree)") // on Samsungs
}
vendor = util.FirstNonEmpty(modelName, processor, "N/A")
}
return vendor, brandString, nil
}
func OSVersions(platform, version string) []string {
ret := []string{"Linux"}
if platform != "" {
platform = strings.ToUpper(platform[0:1]) + platform[1:]
ret = append(ret, crossplatform.VersionsOfAllPrecisions(platform, version)...)
}
return ret
}
var idRegex = regexp.MustCompile(`^(.+?) \[([0-9a-f]{4})\]$`)
// GPUs returns a slice of Swarming-style descriptors of all the GPUs on the host, in all
// precisions: "vendorID", "vendorID-deviceID", and, if detectable,
// "vendorID-deviceID-driverVersion". nvidiaVersionGetter is a thunk that returns the version of the
// installed Nvidia driver. intelVersionGetter is similar but for the Intel driver.
func GPUs(ctx context.Context, lspciOutput string, nvidiaVersionGetter func() string, intelVersionGetter func(context.Context) string) ([]string, error) {
var ret []string
for _, line := range util.SplitLines(lspciOutput) {
fields, err := shell.Split(line)
if err != nil {
return nil, skerr.Wrapf(err, "failed to parse lspci output \"%s\"", line)
}
if len(fields) < 4 {
continue
}
groups := idRegex.FindStringSubmatch(fields[1])
if groups == nil {
continue
}
deviceType := groups[2]
// Look for display class as noted at http://wiki.osdev.org/PCI.
if !strings.HasPrefix(deviceType, "03") {
continue
}
groups = idRegex.FindStringSubmatch(fields[2])
if groups == nil {
continue
}
vendorName := groups[1]
vendorID := groups[2]
groups = idRegex.FindStringSubmatch(fields[3])
if groups == nil {
continue
}
deviceID := groups[2]
version := ""
if vendorID == gpus.Nvidia {
version = nvidiaVersionGetter()
} else if vendorID == gpus.Intel {
version = intelVersionGetter(ctx)
}
// Prefer vendor name from table.
vendorName, _ = gpus.IDsToNames(gpus.VendorID(vendorID), vendorName, "dummy", "dummy")
ret = append(ret, vendorID, vendorID+":"+deviceID)
if version != "" {
ret = append(ret, vendorID+":"+deviceID+"-"+version)
}
}
return ret, nil
}