blob: 22d66e8623f829242a6b597c438b9d13b6cf48ba [file] [log] [blame]
// Package mac contains Mac-specific pieces of interrogation which are nonetheless testable on
// arbitrary platforms.
package mac
import (
"fmt"
"regexp"
"sort"
"strings"
"go.skia.org/infra/go/gpus"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"howett.net/plist"
)
type GPU struct {
// Any string field missing from the plist ends up "".
ID string `plist:"spdisplays_device-id"`
VendorID string `plist:"spdisplays_vendor-id"`
Vendor string `plist:"spdisplays_vendor"`
Version string `plist:"spdisplays_gmux-version"`
Model string `plist:"sppci_model"`
}
// GPUsFromSystemProfilerXML returns the GPUs found on a Mac by System Profiler, as expressed
// through its -xml output option. If the output doesn't look valid, returns an error and no GPUs.
// If there are no GPUs, will return 0 GPUs even if no error occurs.
func GPUsFromSystemProfilerXML(xml string) ([]GPU, error) {
type OuterDict struct {
GPUs []GPU `plist:"_items"`
}
var profilerOutput []OuterDict
_, err := plist.Unmarshal([]byte(xml), &profilerOutput)
if err != nil {
return nil, skerr.Wrapf(err, "couldn't unmarshal XML from System Profiler")
}
if len(profilerOutput) < 1 {
return nil, skerr.Fmt("System Profiler returned no info")
}
return profilerOutput[0].GPUs, nil
}
// DimensionsFromGPUs turns a slice of Mac GPUs into Swarming-style dimensions, e.g. ["8086",
// "8086:3e9b"]. If there are no GPUs, return ["none"].
func DimensionsFromGPUs(gpuSlice []GPU) []string {
var dimensions []string
for _, gpu := range gpuSlice {
if gpu.ID == "" {
continue
}
gpuID := strings.TrimPrefix(gpu.ID, "0x")
var vendorID gpus.VendorID
if gpu.VendorID != "" {
// NVidia
vendorID = gpus.VendorID(strings.TrimPrefix(gpu.VendorID, "0x"))
} else if gpu.Vendor != "" {
// Intel and ATI
re := regexp.MustCompile(`\(0x([0-9a-f]{4})\)`)
if matches := re.FindStringSubmatch(gpu.Vendor); matches != nil {
vendorID = gpus.VendorID(matches[1])
}
}
vendorName := ""
if gpu.Model != "" {
ok := false
// The first word is pretty much always the company name:
vendorName, _, ok = strings.Cut(gpu.Model, " ")
if !ok {
// This has apparently never happened in the history of Swarming's introspection
// code, because it would have raised an uncaught exception there:
sklog.Warningf("GPU device name was only a single word: %s", gpu.Model)
continue
}
}
// macOS 10.13 stopped including the vendor ID in the spdisplays_vendor string. Infer it
// from the vendor name instead.
if vendorID == "" {
vendorID = gpus.VendorNameToID(vendorName)
}
if vendorID == "" && gpu.Vendor != "" {
re := regexp.MustCompile(`sppci_vendor_([a-z]+)$`)
if matches := re.FindStringSubmatch(gpu.Vendor); matches != nil {
vendorName = matches[1]
vendorID = gpus.VendorNameToID(vendorName)
}
}
if vendorID == "" {
vendorID = gpus.VendorID("UNKNOWN")
} else if vendorID == gpus.VMWare {
// We consider VMWare as not having any GPUs.
return []string{"none"}
}
dimensions = append(dimensions, string(vendorID), fmt.Sprintf("%s:%s", vendorID, gpuID))
// Add GPU version:
if gpu.Version != "" {
re := regexp.MustCompile(`([0-9.]+) \[([0-9.]+)\]`)
if matches := re.FindStringSubmatch(gpu.Version); matches != nil {
dimensions = append(dimensions, fmt.Sprintf("%s:%s-%s-%s", vendorID, gpuID, matches[1], matches[2]))
}
}
}
if len(dimensions) == 0 {
dimensions = []string{"none"}
}
sort.Strings(dimensions)
return dimensions
}