mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-22 04:36:35 +00:00
gpu: add device id label (#2186)
This commit is contained in:
92
internal/headers/cfgmgr32/cfgmgr32.go
Normal file
92
internal/headers/cfgmgr32/cfgmgr32.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// 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.
|
||||
|
||||
package cfgmgr32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/win32"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func GetDevicesInstanceIDs(deviceID string) ([]Device, error) {
|
||||
var (
|
||||
err error
|
||||
listSize uint32
|
||||
)
|
||||
|
||||
deviceIDLWStr := win32.NewLPWSTR(deviceID)
|
||||
|
||||
err = CMGetDeviceIDListSize(deviceIDLWStr, &listSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listBuffer := make([]uint16, listSize)
|
||||
|
||||
err = CMGetDeviceIDList(deviceIDLWStr, listBuffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deviceInstanceIDs := win32.ParseMultiSz(listBuffer)
|
||||
devices := make([]Device, 0, len(deviceInstanceIDs))
|
||||
|
||||
for _, deviceInstanceID := range deviceInstanceIDs {
|
||||
var devNode *windows.Handle
|
||||
|
||||
err = CMLocateDevNode(&devNode, deviceInstanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
94
internal/headers/cfgmgr32/syscall.go
Normal file
94
internal/headers/cfgmgr32/syscall.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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.
|
||||
|
||||
package cfgmgr32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/win32"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
cfgmgr32 = windows.NewLazySystemDLL("cfgmgr32.dll")
|
||||
|
||||
procCMGetDeviceIDListW = cfgmgr32.NewProc("CM_Get_Device_ID_ListW")
|
||||
procCMGetDeviceIDListSize = cfgmgr32.NewProc("CM_Get_Device_ID_List_SizeW")
|
||||
procCMGetDevNodePropertyW = cfgmgr32.NewProc("CM_Get_DevNode_PropertyW")
|
||||
procCMLocateDevNodeW = cfgmgr32.NewProc("CM_Locate_DevNodeW")
|
||||
)
|
||||
|
||||
func CMGetDeviceIDListSize(filter *win32.LPWSTR, size *uint32) error {
|
||||
ret, _, _ := procCMGetDeviceIDListSize.Call(
|
||||
uintptr(unsafe.Pointer(size)),
|
||||
filter.Pointer(),
|
||||
uintptr(CM_GETIDLIST_FILTER_PRESENT|CM_GETIDLIST_FILTER_ENUMERATOR),
|
||||
)
|
||||
|
||||
if ret != CR_SUCCESS {
|
||||
return fmt.Errorf("CMGetDeviceIDListSize failed: 0x%02X", ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CMGetDeviceIDList(filter *win32.LPWSTR, buf []uint16) error {
|
||||
ret, _, _ := procCMGetDeviceIDListW.Call(
|
||||
filter.Pointer(),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(len(buf)),
|
||||
uintptr(CM_GETIDLIST_FILTER_PRESENT|CM_GETIDLIST_FILTER_ENUMERATOR),
|
||||
)
|
||||
|
||||
if ret != CR_SUCCESS {
|
||||
return fmt.Errorf("CMGetDeviceIDList failed: 0x%02X", ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CMLocateDevNode(devInst **windows.Handle, deviceID []uint16) error {
|
||||
ret, _, _ := procCMLocateDevNodeW.Call(
|
||||
uintptr(unsafe.Pointer(devInst)),
|
||||
uintptr(unsafe.Pointer(&deviceID[0])),
|
||||
0,
|
||||
)
|
||||
|
||||
if ret != CR_SUCCESS {
|
||||
return fmt.Errorf("CMLocateDevNode failed: 0x%02X", ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CMGetDevNodeProperty(devInst *windows.Handle, propKey *DEVPROPKEY, propType *uint32, buf unsafe.Pointer, bufLen *uint32) error {
|
||||
ret, _, _ := procCMGetDevNodePropertyW.Call(
|
||||
uintptr(unsafe.Pointer(devInst)),
|
||||
uintptr(unsafe.Pointer(propKey)),
|
||||
uintptr(unsafe.Pointer(propType)),
|
||||
uintptr(buf),
|
||||
uintptr(unsafe.Pointer(bufLen)),
|
||||
0,
|
||||
)
|
||||
|
||||
if ret != CR_SUCCESS {
|
||||
return fmt.Errorf("CMGetDevNodeProperty failed: 0x%02X", ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
70
internal/headers/cfgmgr32/types.go
Normal file
70
internal/headers/cfgmgr32/types.go
Normal file
@@ -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.
|
||||
|
||||
package cfgmgr32
|
||||
|
||||
import (
|
||||
"github.com/go-ole/go-ole"
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/win32"
|
||||
)
|
||||
|
||||
const (
|
||||
// Configuration Manager return codes
|
||||
CR_SUCCESS = 0x00
|
||||
|
||||
// Filter flags
|
||||
CM_GETIDLIST_FILTER_ENUMERATOR = 0x00000001
|
||||
CM_GETIDLIST_FILTER_PRESENT = 0x00000100
|
||||
|
||||
DEVPROP_TYPE_UINT32 uint32 = 0x00000007
|
||||
)
|
||||
|
||||
// DEVPROPKEY represents a device property key (GUID + pid)
|
||||
type DEVPROPKEY struct {
|
||||
FmtID ole.GUID
|
||||
PID uint32
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
InstanceID string
|
||||
BusNumber win32.UINT
|
||||
DeviceNumber win32.UINT
|
||||
FunctionNumber win32.UINT
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
// https://github.com/Infinidat/infi.devicemanager/blob/8be9ead6b04ff45c63d9e3bc70d82cceafb75c47/src/infi/devicemanager/setupapi/properties.py#L138C1-L143C34
|
||||
DEVPKEYDeviceBusNumber = &DEVPROPKEY{
|
||||
FmtID: ole.GUID{
|
||||
Data1: 0xa45c254e,
|
||||
Data2: 0xdf1c,
|
||||
Data3: 0x4efd,
|
||||
Data4: [8]byte{0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0},
|
||||
},
|
||||
PID: 23, // DEVPROP_TYPE_UINT32
|
||||
}
|
||||
|
||||
// https://github.com/Infinidat/infi.devicemanager/blob/8be9ead6b04ff45c63d9e3bc70d82cceafb75c47/src/infi/devicemanager/setupapi/properties.py#L187-L192
|
||||
DEVPKEYDeviceAddress = &DEVPROPKEY{
|
||||
FmtID: ole.GUID{
|
||||
Data1: 0xa45c254e,
|
||||
Data2: 0xdf1c,
|
||||
Data3: 0x4efd,
|
||||
Data4: [8]byte{0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0},
|
||||
},
|
||||
PID: 30, // DEVPROP_TYPE_UINT32
|
||||
}
|
||||
)
|
||||
@@ -34,41 +34,12 @@ 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_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
|
||||
)
|
||||
|
||||
var ErrNoGPUDevices = errors.New("no GPU devices found")
|
||||
|
||||
func GetGPUDeviceByLUID(adapterLUID windows.LUID) (GPUDevice, error) {
|
||||
open := D3DKMT_OPENADAPTERFROMLUID{
|
||||
AdapterLUID: adapterLUID,
|
||||
}
|
||||
|
||||
if err := D3DKMTOpenAdapterFromLuid(&open); err != nil {
|
||||
return GPUDevice{}, fmt.Errorf("D3DKMTOpenAdapterFromLuid failed: %w", err)
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
|
||||
gpuDevice, err := GetGPUDevice(open.HAdapter)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("GetGPUDevice failed: %w", err))
|
||||
}
|
||||
|
||||
if err := D3DKMTCloseAdapter(&D3DKMT_CLOSEADAPTER{
|
||||
HAdapter: open.HAdapter,
|
||||
}); err != nil {
|
||||
errs = append(errs, fmt.Errorf("D3DKMTCloseAdapter failed: %w", err))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return gpuDevice, fmt.Errorf("errors occurred while getting GPU device: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
gpuDevice.LUID = adapterLUID
|
||||
|
||||
return gpuDevice, nil
|
||||
}
|
||||
|
||||
func GetGPUDevice(hAdapter D3DKMT_HANDLE) (GPUDevice, error) {
|
||||
var gpuDevice GPUDevice
|
||||
|
||||
@@ -118,6 +89,18 @@ func GetGPUDevice(hAdapter D3DKMT_HANDLE) (GPUDevice, error) {
|
||||
|
||||
gpuDevice.AdapterString = windows.UTF16ToString(info.AdapterString[:])
|
||||
|
||||
var deviceIDs D3DKMT_QUERY_DEVICE_IDS
|
||||
|
||||
query.queryType = KMTQAITYPE_PHYSICALADAPTERDEVICEIDS
|
||||
query.pPrivateDriverData = unsafe.Pointer(&deviceIDs)
|
||||
query.privateDriverDataSize = uint32(unsafe.Sizeof(deviceIDs))
|
||||
|
||||
if err := D3DKMTQueryAdapterInfo(&query); err != nil && !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) {
|
||||
return gpuDevice, fmt.Errorf("D3DKMTQueryAdapterInfo (Device IDs) failed: %w", err)
|
||||
}
|
||||
|
||||
gpuDevice.DeviceID = formatPNPDeviceID(deviceIDs)
|
||||
|
||||
return gpuDevice, nil
|
||||
}
|
||||
|
||||
@@ -151,7 +134,7 @@ func GetGPUDevices() ([]GPUDevice, error) {
|
||||
// Process each adapter
|
||||
for i := range enumAdapters.NumAdapters {
|
||||
adapter := pAdapters[i]
|
||||
// Validate handle before using it
|
||||
// Validate the handle before using it.
|
||||
if adapter.HAdapter == 0 {
|
||||
errs = append(errs, fmt.Errorf("adapter %d has null handle", i))
|
||||
|
||||
@@ -190,3 +173,13 @@ func GetGPUDevices() ([]GPUDevice, error) {
|
||||
|
||||
return gpuDevices, nil
|
||||
}
|
||||
|
||||
func formatPNPDeviceID(deviceIDs D3DKMT_QUERY_DEVICE_IDS) string {
|
||||
return fmt.Sprintf("PCI\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X&REV_%02X",
|
||||
uint16(deviceIDs.DeviceIds.VendorID),
|
||||
uint16(deviceIDs.DeviceIds.DeviceID),
|
||||
uint16(deviceIDs.DeviceIds.SubSystemID),
|
||||
uint16(deviceIDs.DeviceIds.SubVendorID),
|
||||
uint8(deviceIDs.DeviceIds.RevisionID),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,9 +73,22 @@ type D3DKMT_ADAPTERADDRESS struct {
|
||||
FunctionNumber win32.UINT
|
||||
}
|
||||
|
||||
type D3DKMT_QUERY_DEVICE_IDS struct {
|
||||
PhysicalAdapterIndex win32.UINT
|
||||
DeviceIds struct {
|
||||
VendorID win32.UINT
|
||||
DeviceID win32.UINT
|
||||
SubVendorID win32.UINT
|
||||
SubSystemID win32.UINT
|
||||
RevisionID win32.UINT
|
||||
BusType win32.UINT
|
||||
}
|
||||
}
|
||||
|
||||
type GPUDevice struct {
|
||||
AdapterString string
|
||||
LUID windows.LUID
|
||||
DeviceID string
|
||||
DedicatedVideoMemorySize uint64
|
||||
DedicatedSystemMemorySize uint64
|
||||
SharedSystemMemorySize uint64
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package win32
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -32,8 +33,8 @@ type (
|
||||
LPWSTR struct {
|
||||
*uint16
|
||||
}
|
||||
ULONG = uint32 // ULONG is a 32-bit unsigned int in Win32
|
||||
UINT = uint32 // UINT is a 32-bit unsigned int in Win32
|
||||
ULONG uint32 // ULONG is a 32-bit unsigned int in Win32
|
||||
UINT uint32 // UINT is a 32-bit unsigned int in Win32
|
||||
)
|
||||
|
||||
// NewLPWSTR creates a new LPWSTR from a string.
|
||||
@@ -60,3 +61,7 @@ func (s *LPWSTR) Pointer() uintptr {
|
||||
func (s *LPWSTR) String() string {
|
||||
return windows.UTF16PtrToString(s.uint16)
|
||||
}
|
||||
|
||||
func (u *UINT) String() string {
|
||||
return strconv.FormatUint(uint64(*u), 10)
|
||||
}
|
||||
|
||||
55
internal/headers/win32/utils.go
Normal file
55
internal/headers/win32/utils.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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.
|
||||
|
||||
package win32
|
||||
|
||||
// ParseMultiSz splits a UTF-16 encoded MULTI_SZ buffer (Windows style) into
|
||||
// individual UTF-16 string slices.
|
||||
//
|
||||
// A MULTI_SZ buffer is a sequence of UTF-16 strings separated by single null
|
||||
// terminators (0x0000) and terminated by an extra null (i.e., two consecutive
|
||||
// nulls) to mark the end of the list.
|
||||
//
|
||||
// Example layout in memory (UTF-16):
|
||||
//
|
||||
// "foo\0bar\0baz\0\0"
|
||||
//
|
||||
// Given such a []uint16, this function returns a [][]uint16 where each inner
|
||||
// slice is one null-terminated string segment without the trailing null.
|
||||
//
|
||||
// The returned slices reference the original buffer (no copying).
|
||||
func ParseMultiSz(buf []uint16) [][]uint16 {
|
||||
var (
|
||||
result [][]uint16
|
||||
start int
|
||||
)
|
||||
|
||||
for i := range buf {
|
||||
if buf[i] == 0 {
|
||||
// Found a null terminator.
|
||||
if i == start {
|
||||
// Two consecutive nulls → end of list.
|
||||
break
|
||||
}
|
||||
|
||||
// Append current string slice (excluding null).
|
||||
result = append(result, buf[start:i])
|
||||
// Move start to next character after null.
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user