[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"}, + ) +}