[machine] Add Windows GPU interrogation to TMM.
Add smoke-testing for GPUs(). Found a simple, non-repetitive way to do
it while tolerating failures on Linux.
Change-Id: I3554ae6a267e0f45ff877d88eda580e23602a1e7
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/577160
Reviewed-by: Eric Boren <borenet@google.com>
Commit-Queue: Erik Rose <erikrose@google.com>
diff --git a/machine/go/test_machine_monitor/standalone/BUILD.bazel b/machine/go/test_machine_monitor/standalone/BUILD.bazel
index 04943b0..8c5bbba 100644
--- a/machine/go/test_machine_monitor/standalone/BUILD.bazel
+++ b/machine/go/test_machine_monitor/standalone/BUILD.bazel
@@ -46,6 +46,7 @@
"//machine/go/test_machine_monitor/standalone/crossplatform",
"//machine/go/test_machine_monitor/standalone/windows",
"@com_github_shirou_gopsutil//host",
+ "@com_github_yusufpapurcu_wmi//:wmi",
],
"//conditions:default": [],
}),
@@ -55,5 +56,8 @@
name = "standalone_test",
srcs = ["standalone_test.go"],
embed = [":standalone"],
- deps = ["@com_github_stretchr_testify//assert"],
+ deps = [
+ "@com_github_stretchr_testify//assert",
+ "@com_github_stretchr_testify//require",
+ ],
)
diff --git a/machine/go/test_machine_monitor/standalone/gputable/gputable.go b/machine/go/test_machine_monitor/standalone/gputable/gputable.go
index 5e00b85..91cac75 100644
--- a/machine/go/test_machine_monitor/standalone/gputable/gputable.go
+++ b/machine/go/test_machine_monitor/standalone/gputable/gputable.go
@@ -15,8 +15,11 @@
Devices map[string]string
}
-const Nvidia = "10de"
-const Intel = "8086"
+const (
+ Nvidia = "10de"
+ Intel = "8086"
+ VMWare = "15ad"
+)
// Static lookup tables:
var vendorMap = map[VendorID]vendorNameAndDevices{
diff --git a/machine/go/test_machine_monitor/standalone/mac/mac.go b/machine/go/test_machine_monitor/standalone/mac/mac.go
index 4ec835c..6a1947f 100644
--- a/machine/go/test_machine_monitor/standalone/mac/mac.go
+++ b/machine/go/test_machine_monitor/standalone/mac/mac.go
@@ -43,9 +43,8 @@
return profilerOutput[0].GPUs, nil
}
-// DimensionsFromGPUs turns a slice of Mac GPUs into Swarming-style dimensions, e.g. ["Intel
-// (8086)", "Intel Coffee Lake H UHD Graphics 630 (8086:3e9b)"]. If there are no GPUs, return
-// ["none"].
+// 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(gpus []GPU) []string {
var dimensions []string
for _, gpu := range gpus {
@@ -92,8 +91,8 @@
}
if vendorID == "" {
vendorID = gputable.VendorID("UNKNOWN")
- } else if vendorID == "15ad" {
- // This is VMWare, which we consider as not having any GPUs.
+ } else if vendorID == gputable.VMWare {
+ // We consider VMWare as not having any GPUs.
return []string{"none"}
}
diff --git a/machine/go/test_machine_monitor/standalone/standalone_darwin.go b/machine/go/test_machine_monitor/standalone/standalone_darwin.go
index 9bf9c04..52e2774 100644
--- a/machine/go/test_machine_monitor/standalone/standalone_darwin.go
+++ b/machine/go/test_machine_monitor/standalone/standalone_darwin.go
@@ -30,14 +30,14 @@
}
// GPUs returns Swarming-style descriptions of all the host's GPUs, in various precisions, all
-// flattened into a single array, e.g. ["Intel (8086)", "Intel Broadwell HD Graphics 6000
-// (8086:1626)", "Intel (8086)", "8086:9a49", "8086:9a49-22.0.5"]. At most, an array element may
-// have 4 elements of precision: vendor ID, vendor name, device ID, and device name (in that order).
-// However, the formats of these are device- and OS-dependent.
+// flattened into a single array, e.g. ["8086", "8086:1626", "8086", "8086:9a49",
+// "8086:9a49-22.0.5"]. At most, an array element may have 4 elements of precision: vendor ID,
+// vendor name, device ID, and device name (in that order). However, the formats of these are
+// device- and OS-dependent.
func GPUs(ctx context.Context) ([]string, error) {
xml, err := common.TrimmedCommandOutput(ctx, "system_profiler", "SPDisplaysDataType", "-xml")
if err != nil {
- return nil, skerr.Wrapf(err, "failed to run System Profiler to get GPU info. Output was '%s'", xml)
+ return nil, skerr.Wrapf(err, "failed to run System Profiler to get GPU info. Output was %q", xml)
}
gpus, err := mac.GPUsFromSystemProfilerXML(xml)
diff --git a/machine/go/test_machine_monitor/standalone/standalone_test.go b/machine/go/test_machine_monitor/standalone/standalone_test.go
index d743670..551e4cb 100644
--- a/machine/go/test_machine_monitor/standalone/standalone_test.go
+++ b/machine/go/test_machine_monitor/standalone/standalone_test.go
@@ -6,6 +6,7 @@
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
// Smoke-test CPUs(). The interesting (and hopefully thus the error-prone) parts of it have been
@@ -13,7 +14,7 @@
// straight line through, determined by the platform the tests are running on.
func TestCPUs_Smoke(t *testing.T) {
cpus, err := CPUs(context.Background())
- assert.NoError(t, err)
+ require.NoError(t, err)
if len(cpus) != 2 && len(cpus) != 3 {
assert.Fail(t, "Length of CPUs() output should have at least an ISA and a bit-width element.")
}
@@ -24,6 +25,20 @@
func TestOSVersions_Smoke(t *testing.T) {
versions, err := OSVersions(context.Background())
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.GreaterOrEqual(t, len(versions), 2, "OSVersions() should return at least PlatformName and PlatformName-SomeVersion.")
}
+
+func TestGPUs_Smoke(t *testing.T) {
+ gpus, err := GPUs(context.Background())
+ if err != nil {
+ if strings.Contains(err.Error(), "failed to run lspci") {
+ // This assertion is allowed to fail on Linux CI machines, which may not have lspci
+ // installed.
+ return
+ } else {
+ require.NoError(t, err)
+ }
+ }
+ assert.GreaterOrEqual(t, len(gpus), 1, "GPUs() should return at least 1 dimension ({\"none\"} at worst).")
+}
diff --git a/machine/go/test_machine_monitor/standalone/standalone_windows.go b/machine/go/test_machine_monitor/standalone/standalone_windows.go
index c324040..0a045f5 100644
--- a/machine/go/test_machine_monitor/standalone/standalone_windows.go
+++ b/machine/go/test_machine_monitor/standalone/standalone_windows.go
@@ -4,6 +4,7 @@
"context"
"github.com/shirou/gopsutil/host"
+ "github.com/yusufpapurcu/wmi"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/machine/go/test_machine_monitor/standalone/crossplatform"
"go.skia.org/infra/machine/go/test_machine_monitor/standalone/windows"
@@ -25,7 +26,14 @@
return crossplatform.CPUs("", "")
}
+// GPUs returns Swarming-style dimensions representing all GPUs on the host. Each GPU may yield up
+// to 3 returned elements: "vendorID", "vendorID:deviceID", and "vendorID:deviceID-driverVersion".
+// If no GPUs are found or if the host is running within VMWare, returns ["none"].
func GPUs(ctx context.Context) ([]string, error) {
- var ret []string
- return ret, nil
+ var results []windows.GPUQueryResult
+ err := wmi.Query("SELECT DriverVersion, PNPDeviceID FROM Win32_VideoController", &results)
+ if err != nil {
+ return nil, skerr.Wrapf(err, "failed to run WMI query to get GPU info")
+ }
+ return windows.GPUs(results), nil
}
diff --git a/machine/go/test_machine_monitor/standalone/windows/BUILD.bazel b/machine/go/test_machine_monitor/standalone/windows/BUILD.bazel
index f64a6da..bfd8cae 100644
--- a/machine/go/test_machine_monitor/standalone/windows/BUILD.bazel
+++ b/machine/go/test_machine_monitor/standalone/windows/BUILD.bazel
@@ -6,7 +6,10 @@
srcs = ["windows.go"],
importpath = "go.skia.org/infra/machine/go/test_machine_monitor/standalone/windows",
visibility = ["//visibility:public"],
- deps = ["//go/skerr"],
+ deps = [
+ "//go/skerr",
+ "//machine/go/test_machine_monitor/standalone/gputable",
+ ],
)
go_test(
@@ -14,6 +17,7 @@
srcs = ["windows_test.go"],
embed = [":windows"],
deps = [
+ "//machine/go/test_machine_monitor/standalone/gputable",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
diff --git a/machine/go/test_machine_monitor/standalone/windows/windows.go b/machine/go/test_machine_monitor/standalone/windows/windows.go
index 3e1c6ba..580d55b 100644
--- a/machine/go/test_machine_monitor/standalone/windows/windows.go
+++ b/machine/go/test_machine_monitor/standalone/windows/windows.go
@@ -4,8 +4,10 @@
import (
"regexp"
+ "strings"
"go.skia.org/infra/go/skerr"
+ "go.skia.org/infra/machine/go/test_machine_monitor/standalone/gputable"
)
var platformRegex = regexp.MustCompile(`Microsoft Windows ([^ ]+)`)
@@ -33,3 +35,40 @@
return []string{"Windows", "Windows-" + major, "Windows-" + major + "-" + build}, nil
}
+
+type GPUQueryResult struct {
+ DriverVersion string
+ PNPDeviceID string
+}
+
+var gpuVendorRegex = regexp.MustCompile(`VEN_([0-9A-F]{4})`)
+var gpuDeviceRegex = regexp.MustCompile(`DEV_([0-9A-F]{4})`)
+
+func GPUs(results []GPUQueryResult) []string {
+ // Extract the first group of the regex from a raw device ID, returning "UNKNOWN" on failure.
+ extract := func(regex *regexp.Regexp, rawDeviceID string) string {
+ groups := regex.FindStringSubmatch(rawDeviceID)
+ if groups != nil {
+ return strings.ToLower(groups[1])
+ }
+ return "UNKNOWN"
+ }
+
+ var dimensions []string
+ for _, result := range results {
+ vendorID := extract(gpuVendorRegex, result.PNPDeviceID)
+ deviceID := extract(gpuDeviceRegex, result.PNPDeviceID)
+
+ if vendorID == gputable.VMWare {
+ return []string{"none"}
+ }
+ dimensions = append(dimensions, vendorID, vendorID+":"+deviceID)
+ if result.DriverVersion != "" {
+ dimensions = append(dimensions, vendorID+":"+deviceID+"-"+result.DriverVersion)
+ }
+ }
+ if len(dimensions) == 0 {
+ return []string{"none"}
+ }
+ return dimensions
+}
diff --git a/machine/go/test_machine_monitor/standalone/windows/windows_test.go b/machine/go/test_machine_monitor/standalone/windows/windows_test.go
index 33b7d32..28c9945 100644
--- a/machine/go/test_machine_monitor/standalone/windows/windows_test.go
+++ b/machine/go/test_machine_monitor/standalone/windows/windows_test.go
@@ -1,10 +1,12 @@
package windows
import (
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "go.skia.org/infra/machine/go/test_machine_monitor/standalone/gputable"
)
func TestOSVersions_HappyPath(t *testing.T) {
@@ -21,3 +23,58 @@
_, err := OSVersions("Schlockosoft Grindows", "10.0.17763 Build 17763")
require.Error(t, err)
}
+
+func TestGPUsFindsVendorButNotDeviceID_ReturnsUnknown(t *testing.T) {
+ assert.Equal(
+ t,
+ GPUs(
+ []GPUQueryResult{
+ {
+ DriverVersion: "",
+ PNPDeviceID: `PCI\VEN_80A1&SUBSYS_20898086&REV_00\3&11583659&1&10`,
+ },
+ },
+ ),
+ []string{"80a1", "80a1:UNKNOWN"}, // Note lowercase "a" as well.
+ )
+}
+
+func TestGPUsFindsDeviceIDAndVendorAndVersion(t *testing.T) {
+ assert.Equal(
+ t,
+ GPUs(
+ []GPUQueryResult{
+ {
+ DriverVersion: "1.2.3",
+ PNPDeviceID: `PCI\VEN_8086&DEV_3E9B&SUBSYS_20898086&REV_00\3&11583659&1&10`,
+ },
+ },
+ ),
+ []string{"8086", "8086:3e9b", "8086:3e9b-1.2.3"},
+ )
+}
+
+func TestGPUsFindsNoGPUs(t *testing.T) {
+ assert.Equal(
+ t,
+ GPUs(
+ []GPUQueryResult{},
+ ),
+ []string{"none"},
+ )
+}
+
+func TestGPUsSeesVMWare_ReturnsNone(t *testing.T) {
+ assert.Equal(
+ t,
+ GPUs(
+ []GPUQueryResult{
+ {
+ DriverVersion: "1.2.3",
+ PNPDeviceID: `PCI\VEN_` + strings.ToUpper(gputable.VMWare) + `&DEV_3E9B&SUBSYS_20898086&REV_00\3&11583659&1&10`,
+ },
+ },
+ ),
+ []string{"none"},
+ )
+}