gpu: add info metric about devices (#2070)

This commit is contained in:
Jan-Otto Kröpke
2025-06-04 22:49:59 +02:00
committed by GitHub
parent dcf85032ca
commit 55c877f536
7 changed files with 299 additions and 15 deletions

View File

@@ -21,8 +21,10 @@ import (
"errors"
"fmt"
"log/slog"
"strconv"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/headers/setupapi"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/pdh"
"github.com/prometheus-community/windows_exporter/internal/types"
@@ -43,6 +45,7 @@ type Collector struct {
gpuEnginePerfDataCollector *pdh.Collector
gpuEnginePerfDataObject []gpuEnginePerfDataCounterValues
gpuInfo *prometheus.Desc
gpuEngineRunningTime *prometheus.Desc
// GPU Adapter Memory
@@ -109,6 +112,13 @@ func (c *Collector) Close() error {
func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
var err error
c.gpuInfo = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "info"),
"A metric with a constant '1' value labeled with gpu device information.",
[]string{"phys", "physical_device_object_name", "hardware_id", "friendly_name", "description"},
nil,
)
c.gpuEngineRunningTime = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "engine_time_seconds"),
"Total running time of the GPU in seconds.",
@@ -213,6 +223,10 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
errs := make([]error, 0)
if err := c.collectGpuInfo(ch); err != nil {
errs = append(errs, err)
}
if err := c.collectGpuEngineMetrics(ch); err != nil {
errs = append(errs, err)
}
@@ -236,6 +250,28 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
return errors.Join(errs...)
}
func (c *Collector) collectGpuInfo(ch chan<- prometheus.Metric) error {
gpus, err := setupapi.GetGPUDevices()
if err != nil {
return fmt.Errorf("failed to get GPU devices: %w", err)
}
for i, gpu := range gpus {
ch <- prometheus.MustNewConstMetric(
c.gpuInfo,
prometheus.GaugeValue,
1.0,
strconv.Itoa(i),
gpu.PhysicalDeviceObjectName,
gpu.HardwareID,
gpu.FriendlyName,
gpu.DeviceDesc,
)
}
return nil
}
func (c *Collector) collectGpuEngineMetrics(ch chan<- prometheus.Metric) error {
// Collect the GPU Engine perf data.
if err := c.gpuEnginePerfDataCollector.Collect(&c.gpuEnginePerfDataObject); err != nil {

View File

@@ -0,0 +1,135 @@
// 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 setupapi
import (
"sync"
"unsafe"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var GUID_DISPLAY_ADAPTER = sync.OnceValue(func() *windows.GUID {
return &windows.GUID{
Data1: 0x4d36e968,
Data2: 0xe325,
Data3: 0x11ce,
Data4: [8]byte{0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18},
}
})
func GetGPUDevices() ([]GPUDevice, error) {
hDevInfo, _, err := procSetupDiGetClassDevsW.Call(
uintptr(unsafe.Pointer(GUID_DISPLAY_ADAPTER())),
0,
0,
DIGCF_PRESENT,
)
if windows.Handle(hDevInfo) == windows.InvalidHandle {
return nil, err
}
var (
devices []GPUDevice
deviceData SP_DEVINFO_DATA
propertyBuffer [256]uint16
)
deviceData.CbSize = uint32(unsafe.Sizeof(deviceData))
for i := 0; ; i++ {
ret, _, _ := procSetupDiEnumDeviceInfo.Call(hDevInfo, uintptr(i), uintptr(unsafe.Pointer(&deviceData)))
if ret == 0 {
break // No more devices
}
ret, _, _ = procSetupDiGetDeviceRegistryPropertyW.Call(
hDevInfo,
uintptr(unsafe.Pointer(&deviceData)),
uintptr(SPDRP_DEVICEDESC),
0,
uintptr(unsafe.Pointer(&propertyBuffer[0])),
uintptr(len(propertyBuffer)*2),
0,
)
gpuDevice := GPUDevice{}
if ret == 0 {
gpuDevice.DeviceDesc = ""
} else {
gpuDevice.DeviceDesc = windows.UTF16ToString(propertyBuffer[:])
}
ret, _, _ = procSetupDiGetDeviceRegistryPropertyW.Call(
hDevInfo,
uintptr(unsafe.Pointer(&deviceData)),
uintptr(SPDRP_FRIENDLYNAME),
0,
uintptr(unsafe.Pointer(&propertyBuffer[0])),
uintptr(len(propertyBuffer)*2),
0,
)
if ret == 0 {
gpuDevice.FriendlyName = ""
} else {
gpuDevice.FriendlyName = windows.UTF16ToString(propertyBuffer[:])
}
ret, _, _ = procSetupDiGetDeviceRegistryPropertyW.Call(
hDevInfo,
uintptr(unsafe.Pointer(&deviceData)),
uintptr(SPDRP_HARDWAREID),
0,
uintptr(unsafe.Pointer(&propertyBuffer[0])),
uintptr(len(propertyBuffer)*2),
0,
)
if ret == 0 {
gpuDevice.HardwareID = "unknown"
} else {
gpuDevice.HardwareID = windows.UTF16ToString(propertyBuffer[:])
}
ret, _, _ = procSetupDiGetDeviceRegistryPropertyW.Call(
hDevInfo,
uintptr(unsafe.Pointer(&deviceData)),
uintptr(SPDRP_PHYSICAL_DEVICE_OBJECT_NAME),
0,
uintptr(unsafe.Pointer(&propertyBuffer[0])),
uintptr(len(propertyBuffer)*2),
0,
)
if ret == 0 {
gpuDevice.PhysicalDeviceObjectName = "unknown"
} else {
gpuDevice.PhysicalDeviceObjectName = windows.UTF16ToString(propertyBuffer[:])
}
devices = append(devices, gpuDevice)
}
_, _, _ = procSetupDiDestroyDeviceInfoList.Call(hDevInfo)
return devices, nil
}

View File

@@ -0,0 +1,32 @@
// 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 setupapi_test
import (
"testing"
"github.com/prometheus-community/windows_exporter/internal/headers/setupapi"
"github.com/stretchr/testify/require"
)
func TestGetGPUDevices(t *testing.T) {
devices, err := setupapi.GetGPUDevices()
require.NoError(t, err, "Failed to get GPU devices")
require.NotNil(t, devices)
}

View File

@@ -0,0 +1,31 @@
// 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 setupapi
import (
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
modSetupAPI = windows.NewLazySystemDLL("setupapi.dll")
procSetupDiGetClassDevsW = modSetupAPI.NewProc("SetupDiGetClassDevsW")
procSetupDiEnumDeviceInfo = modSetupAPI.NewProc("SetupDiEnumDeviceInfo")
procSetupDiGetDeviceRegistryPropertyW = modSetupAPI.NewProc("SetupDiGetDeviceRegistryPropertyW")
procSetupDiDestroyDeviceInfoList = modSetupAPI.NewProc("SetupDiDestroyDeviceInfoList")
)

View File

@@ -0,0 +1,42 @@
// 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 setupapi
import "golang.org/x/sys/windows"
const (
DIGCF_PRESENT = 0x00000002
SPDRP_DEVICEDESC = 0x00000000
SPDRP_FRIENDLYNAME = 0x0000000C
SPDRP_HARDWAREID = 0x00000001
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E
)
type SP_DEVINFO_DATA struct {
CbSize uint32
ClassGuid windows.GUID
DevInst uint32
_ uintptr // Reserved
}
type GPUDevice struct {
DeviceDesc string
FriendlyName string
HardwareID string
PhysicalDeviceObjectName string
}