mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-18 10:39:05 +02:00
gpu: make device discovery non-fatal and refresh it on unknown LUIDs (#2573)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c78f1c64d8
commit
fa3abe8c3c
@@ -16,6 +16,7 @@
|
||||
package cfgmgr32
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
@@ -46,47 +47,64 @@ func GetDevicesInstanceIDs(deviceID string) ([]Device, error) {
|
||||
deviceInstanceIDs := win32.ParseMultiSz(listBuffer)
|
||||
devices := make([]Device, 0, len(deviceInstanceIDs))
|
||||
|
||||
var errs []error
|
||||
|
||||
// Devices whose properties can't be read are skipped. The remaining devices
|
||||
// are returned together with an error describing the skipped ones.
|
||||
for _, deviceInstanceID := range deviceInstanceIDs {
|
||||
var devNode *windows.Handle
|
||||
|
||||
err = CMLocateDevNode(&devNode, deviceInstanceID)
|
||||
device, err := getDevice(deviceInstanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
errs = append(errs, fmt.Errorf("device %s: %w", windows.UTF16ToString(deviceInstanceID), err))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
busNumber uint32
|
||||
deviceAddress uint32
|
||||
propType uint32
|
||||
)
|
||||
|
||||
propLen := uint32(4)
|
||||
|
||||
err = CMGetDevNodeProperty(devNode, DEVPKEYDeviceBusNumber, &propType, unsafe.Pointer(&busNumber), &propLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if propType != DEVPROP_TYPE_UINT32 {
|
||||
return nil, fmt.Errorf("unexpected property type: 0x%08X", propType)
|
||||
}
|
||||
|
||||
err = CMGetDevNodeProperty(devNode, DEVPKEYDeviceAddress, &propType, unsafe.Pointer(&deviceAddress), &propLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if propType != DEVPROP_TYPE_UINT32 {
|
||||
return nil, fmt.Errorf("unexpected property type: 0x%08X", propType)
|
||||
}
|
||||
|
||||
devices = append(devices, Device{
|
||||
InstanceID: windows.UTF16ToString(deviceInstanceID),
|
||||
BusNumber: win32.UINT(busNumber),
|
||||
DeviceNumber: win32.UINT(deviceAddress >> 16),
|
||||
FunctionNumber: win32.UINT(deviceAddress & 0xFFFF),
|
||||
})
|
||||
devices = append(devices, device)
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
return devices, errors.Join(errs...)
|
||||
}
|
||||
|
||||
func getDevice(deviceInstanceID []uint16) (Device, error) {
|
||||
var devInst uint32
|
||||
|
||||
if err := CMLocateDevNode(&devInst, deviceInstanceID); err != nil {
|
||||
return Device{}, err
|
||||
}
|
||||
|
||||
busNumber, err := getDevNodePropertyUint32(devInst, DEVPKEYDeviceBusNumber)
|
||||
if err != nil {
|
||||
return Device{}, fmt.Errorf("bus number: %w", err)
|
||||
}
|
||||
|
||||
deviceAddress, err := getDevNodePropertyUint32(devInst, DEVPKEYDeviceAddress)
|
||||
if err != nil {
|
||||
return Device{}, fmt.Errorf("device address: %w", err)
|
||||
}
|
||||
|
||||
return Device{
|
||||
InstanceID: windows.UTF16ToString(deviceInstanceID),
|
||||
BusNumber: win32.UINT(busNumber),
|
||||
DeviceNumber: win32.UINT(deviceAddress >> 16),
|
||||
FunctionNumber: win32.UINT(deviceAddress & 0xFFFF),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getDevNodePropertyUint32(devInst uint32, propKey *DEVPROPKEY) (uint32, error) {
|
||||
var (
|
||||
value uint32
|
||||
propType uint32
|
||||
)
|
||||
|
||||
propLen := uint32(unsafe.Sizeof(value))
|
||||
|
||||
if err := CMGetDevNodeProperty(devInst, propKey, &propType, unsafe.Pointer(&value), &propLen); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if propType != DEVPROP_TYPE_UINT32 {
|
||||
return 0, fmt.Errorf("unexpected property type: 0x%08X", propType)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package cfgmgr32_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/cfgmgr32"
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/gdi32"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetDevicesInstanceIDs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gpus, err := gdi32.GetGPUDevices()
|
||||
require.NoError(t, err)
|
||||
|
||||
var tested bool
|
||||
|
||||
for _, gpu := range gpus {
|
||||
// Only test physical render adapters, which are enumerated on the PCI bus.
|
||||
// Microsoft adapters (VEN_1414), like the Microsoft Basic Render Driver, are virtual
|
||||
// and not always flagged as software device.
|
||||
if gpu.IsSoftwareDevice() ||
|
||||
strings.HasPrefix(gpu.DeviceID, `PCI\VEN_1414&`) ||
|
||||
gpu.AdapterType&gdi32.D3DKMT_ADAPTERTYPE_RENDER_SUPPORTED == 0 ||
|
||||
gpu.AdapterType&gdi32.D3DKMT_ADAPTERTYPE_PARAVIRTUALIZED != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
devices, err := cfgmgr32.GetDevicesInstanceIDs(gpu.DeviceID)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, devices)
|
||||
|
||||
var found bool
|
||||
|
||||
for _, device := range devices {
|
||||
require.NotEmpty(t, device.InstanceID)
|
||||
|
||||
if device.BusNumber == gpu.BusNumber && device.DeviceNumber == gpu.DeviceNumber && device.FunctionNumber == gpu.FunctionNumber {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
require.True(t, found, "no device instance matches the PCI address of %s", gpu.DeviceID)
|
||||
|
||||
tested = true
|
||||
}
|
||||
|
||||
if !tested {
|
||||
t.Skip("no hardware GPU found")
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,8 @@ func CMGetDeviceIDList(filter *win32.LPWSTR, buf []uint16) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CMLocateDevNode(devInst **windows.Handle, deviceID []uint16) error {
|
||||
// CMLocateDevNode stores the DEVINST (a DWORD) of the device node in devInst.
|
||||
func CMLocateDevNode(devInst *uint32, deviceID []uint16) error {
|
||||
ret, _, _ := procCMLocateDevNodeW.Call(
|
||||
uintptr(unsafe.Pointer(devInst)),
|
||||
uintptr(unsafe.Pointer(&deviceID[0])),
|
||||
@@ -76,9 +77,9 @@ func CMLocateDevNode(devInst **windows.Handle, deviceID []uint16) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CMGetDevNodeProperty(devInst *windows.Handle, propKey *DEVPROPKEY, propType *uint32, buf unsafe.Pointer, bufLen *uint32) error {
|
||||
func CMGetDevNodeProperty(devInst uint32, propKey *DEVPROPKEY, propType *uint32, buf unsafe.Pointer, bufLen *uint32) error {
|
||||
ret, _, _ := procCMGetDevNodePropertyW.Call(
|
||||
uintptr(unsafe.Pointer(devInst)),
|
||||
uintptr(devInst),
|
||||
uintptr(unsafe.Pointer(propKey)),
|
||||
uintptr(unsafe.Pointer(propType)),
|
||||
uintptr(buf),
|
||||
|
||||
@@ -34,6 +34,8 @@ const (
|
||||
KMTQAITYPE_ADAPTERADDRESS = 6
|
||||
// KMTQAITYPE_ADAPTERREGISTRYINFO pPrivateDriverData points to a D3DKMT_ADAPTERREGISTRYINFO structure that contains registry information about the graphics adapter.
|
||||
KMTQAITYPE_ADAPTERREGISTRYINFO = 8
|
||||
// KMTQAITYPE_ADAPTERTYPE pPrivateDriverData points to a D3DKMT_ADAPTERTYPE structure that specifies the type of graphics adapter. Supported starting with Windows 8 (WDDM 1.2).
|
||||
KMTQAITYPE_ADAPTERTYPE = 15
|
||||
// KMTQAITYPE_PHYSICALADAPTERDEVICEIDS pPrivateDriverData points to a D3DKMT_QUERY_DEVICE_IDS structure that specifies the device ID(s) of the physical adapters. Supported starting with Windows 10 (WDDM 2.0).
|
||||
KMTQAITYPE_PHYSICALADAPTERDEVICEIDS = 31
|
||||
// KMTQAITYPE_DRIVERVERSION pPrivateDriverData points to a D3DKMT_DRIVERVERSION value that indicates the WDDM version of the display miniport driver.
|
||||
@@ -103,6 +105,18 @@ func GetGPUDevice(hAdapter D3DKMT_HANDLE) (GPUDevice, error) {
|
||||
|
||||
gpuDevice.AdapterString = windows.UTF16ToString(info.AdapterString[:])
|
||||
|
||||
var adapterType D3DKMT_ADAPTERTYPE
|
||||
|
||||
query.queryType = KMTQAITYPE_ADAPTERTYPE
|
||||
query.pPrivateDriverData = unsafe.Pointer(&adapterType)
|
||||
query.privateDriverDataSize = uint32(unsafe.Sizeof(adapterType))
|
||||
|
||||
if err := D3DKMTQueryAdapterInfo(&query); err != nil {
|
||||
return gpuDevice, fmt.Errorf("D3DKMTQueryAdapterInfo (adapter type) failed: %w", err)
|
||||
}
|
||||
|
||||
gpuDevice.AdapterType = adapterType
|
||||
|
||||
var deviceIDs D3DKMT_QUERY_DEVICE_IDS
|
||||
|
||||
query.queryType = KMTQAITYPE_PHYSICALADAPTERDEVICEIDS
|
||||
@@ -118,6 +132,9 @@ func GetGPUDevice(hAdapter D3DKMT_HANDLE) (GPUDevice, error) {
|
||||
return gpuDevice, nil
|
||||
}
|
||||
|
||||
// GetGPUDevices returns all GPU adapters known to the graphics kernel.
|
||||
// If a single adapter can't be queried, the remaining adapters are still
|
||||
// returned, together with an error describing the failed adapters.
|
||||
func GetGPUDevices() ([]GPUDevice, error) {
|
||||
gpuDevices := make([]GPUDevice, 0, 2)
|
||||
|
||||
@@ -177,15 +194,11 @@ func GetGPUDevices() ([]GPUDevice, error) {
|
||||
}()
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return gpuDevices, errors.Join(errs...)
|
||||
}
|
||||
|
||||
if len(gpuDevices) == 0 {
|
||||
return gpuDevices, ErrNoGPUDevices
|
||||
errs = append(errs, ErrNoGPUDevices)
|
||||
}
|
||||
|
||||
return gpuDevices, nil
|
||||
return gpuDevices, errors.Join(errs...)
|
||||
}
|
||||
|
||||
func formatPNPDeviceID(deviceIDs D3DKMT_QUERY_DEVICE_IDS) string {
|
||||
|
||||
@@ -27,4 +27,11 @@ func TestGetGPUDevices(t *testing.T) {
|
||||
require.NoError(t, err, "Failed to get GPU devices")
|
||||
|
||||
require.NotNil(t, devices)
|
||||
|
||||
// The adapter type flags differ between systems, e.g. the Microsoft Basic Render Driver
|
||||
// is not flagged as software device on Windows Server 2022. Log them for diagnostics.
|
||||
for _, device := range devices {
|
||||
t.Logf("adapter %q: device ID %s, adapter type 0b%b, software device %t",
|
||||
device.AdapterString, device.DeviceID, uint32(device.AdapterType), device.IsSoftwareDevice())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ type GPUDevice struct {
|
||||
BusNumber win32.UINT
|
||||
DeviceNumber win32.UINT
|
||||
FunctionNumber win32.UINT
|
||||
AdapterType D3DKMT_ADAPTERTYPE
|
||||
}
|
||||
|
||||
// D3DKMT_NODE_PERFDATA is the output of KMTQAITYPE_NODEPERFDATA.
|
||||
@@ -163,3 +164,30 @@ type D3DKMT_GPUVERSION struct {
|
||||
BiosVersion [32]uint16
|
||||
GpuArchitecture [32]uint16
|
||||
}
|
||||
|
||||
// IsSoftwareDevice reports whether the adapter is a software device,
|
||||
// e.g. the Microsoft Basic Render Driver.
|
||||
func (d GPUDevice) IsSoftwareDevice() bool {
|
||||
return d.AdapterType&D3DKMT_ADAPTERTYPE_SOFTWARE_DEVICE != 0
|
||||
}
|
||||
|
||||
// D3DKMT_ADAPTERTYPE is the bitfield returned by KMTQAITYPE_ADAPTERTYPE.
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/ns-d3dkmthk-_d3dkmt_adaptertype
|
||||
type D3DKMT_ADAPTERTYPE uint32
|
||||
|
||||
const (
|
||||
D3DKMT_ADAPTERTYPE_RENDER_SUPPORTED D3DKMT_ADAPTERTYPE = 1 << 0
|
||||
D3DKMT_ADAPTERTYPE_DISPLAY_SUPPORTED D3DKMT_ADAPTERTYPE = 1 << 1
|
||||
D3DKMT_ADAPTERTYPE_SOFTWARE_DEVICE D3DKMT_ADAPTERTYPE = 1 << 2
|
||||
D3DKMT_ADAPTERTYPE_POST_DEVICE D3DKMT_ADAPTERTYPE = 1 << 3
|
||||
D3DKMT_ADAPTERTYPE_HYBRID_DISCRETE D3DKMT_ADAPTERTYPE = 1 << 4
|
||||
D3DKMT_ADAPTERTYPE_HYBRID_INTEGRATED D3DKMT_ADAPTERTYPE = 1 << 5
|
||||
D3DKMT_ADAPTERTYPE_INDIRECT_DISPLAY_DEVICE D3DKMT_ADAPTERTYPE = 1 << 6
|
||||
D3DKMT_ADAPTERTYPE_PARAVIRTUALIZED D3DKMT_ADAPTERTYPE = 1 << 7
|
||||
D3DKMT_ADAPTERTYPE_ACG_SUPPORTED D3DKMT_ADAPTERTYPE = 1 << 8
|
||||
D3DKMT_ADAPTERTYPE_SET_TIMINGS_FROM_VIDPN D3DKMT_ADAPTERTYPE = 1 << 9
|
||||
D3DKMT_ADAPTERTYPE_DETACHABLE D3DKMT_ADAPTERTYPE = 1 << 10
|
||||
D3DKMT_ADAPTERTYPE_COMPUTE_ONLY D3DKMT_ADAPTERTYPE = 1 << 11
|
||||
D3DKMT_ADAPTERTYPE_PROTOTYPE D3DKMT_ADAPTERTYPE = 1 << 12
|
||||
D3DKMT_ADAPTERTYPE_RUNTIME_POWER_MANAGEMENT D3DKMT_ADAPTERTYPE = 1 << 13
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user