Compare commits

...

11 Commits

Author SHA1 Message Date
Jan-Otto Kröpke
f0591d85cd process: fix windows_process_start_time_seconds_timestamp return relative time on Windows Server 2019 (#2137) 2025-07-20 02:14:17 +02:00
Jan-Otto Kröpke
255b01f610 container: fix network metrics (#2136) 2025-07-17 21:07:55 +02:00
Jan-Otto Kröpke
ab7db07836 filetime: support windows paths (#2118) 2025-07-13 02:01:44 +02:00
Jan-Otto Kröpke
52056a5cd9 filetime: support case insensitive matching (#2132) 2025-07-12 23:51:40 +00:00
Jan-Otto Kröpke
524fea08c4 gpu: fix windows_gpu_info metric (#2130) 2025-07-13 01:05:59 +02:00
Jan-Otto Kröpke
6b8c895a68 container: fix memory leaks (#2129) 2025-07-11 20:10:45 +02:00
Jan-Otto Kröpke
eade0da514 config: fix lists (#2124) 2025-07-11 19:50:58 +02:00
renovate[bot]
b07e866b4a chore(deps): update dependency golangci/golangci-lint to v2.2.2 (#2126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 19:50:19 +02:00
renovate[bot]
98618408ce fix(deps): update golang.org/x/ (#2127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 18:33:04 +02:00
Jan-Otto Kröpke
56b9f7fd27 docs: strip readme to avoid dockerhub limitations (#2123) 2025-07-09 20:17:26 +02:00
Jan-Otto Kröpke
8d267336c1 Update README.md (#2121) 2025-07-08 18:17:07 +02:00
26 changed files with 707 additions and 678 deletions

View File

@@ -91,5 +91,5 @@ jobs:
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
# renovate: github=golangci/golangci-lint
version: v2.2.1
version: v2.2.2
args: "--max-same-issues=0"

View File

@@ -2,8 +2,12 @@
<dictionary name="project">
<words>
<w>containerd</w>
<w>endpointstats</w>
<w>gochecknoglobals</w>
<w>luid</w>
<w>setupapi</w>
<w>spdx</w>
<w>vmcompute</w>
</words>
</dictionary>
</component>

View File

@@ -182,9 +182,6 @@ The windows_exporter can be run as a Docker container. The Docker image is avail
The Docker image is tagged with the version of the exporter. The `latest` tag is also available and points to the latest release.
Additionally, a flavor `hostprocess` with `-hostprocess` as suffix is based on the https://github.com/microsoft/windows-host-process-containers-base-image
which is designed to run as a Windows host process container. The size of that images is smaller than the default one.
## Kubernetes Implementation
See detailed steps to install on Windows Kubernetes [here](./kubernetes/kubernetes.md).
@@ -195,16 +192,6 @@ See detailed steps to install on Windows Kubernetes [here](./kubernetes/kubernet
There are known compatibility issues with Windows Server 2012 R2 and earlier versions.
## Usage
go get -u github.com/prometheus/promu
go get -u github.com/prometheus-community/windows_exporter
cd $env:GOPATH/src/github.com/prometheus-community/windows_exporter
promu build -v
.\windows_exporter.exe
The prometheus metrics will be exposed on [localhost:9182](http://localhost:9182)
### HTTP Endpoints
windows_exporter provides the following HTTP endpoints:
@@ -213,18 +200,6 @@ windows_exporter provides the following HTTP endpoints:
* `/health`: Returns 200 OK when the exporter is running.
* `/debug/pprof/`: Exposes the [pprof](https://golang.org/pkg/net/http/pprof/) endpoints. Only, if `--debug.enabled` is set.
## Examples
### Enable only service collector and specify a custom query
.\windows_exporter.exe --collectors.enabled "service" --collector.service.include="windows_exporter"
### Enable only process collector and specify a custom query
.\windows_exporter.exe --collectors.enabled "process" --collector.process.include="firefox.+"
When there are multiple processes with the same name, WMI represents those after the first instance as `process-name#index`. So to get them all, rather than just the first one, the [regular expression](https://en.wikipedia.org/wiki/Regular_expression) must use `.+`. See [process](docs/collector.process.md) for more information.
### Using [defaults] with `--collectors.enabled` argument
Using `[defaults]` with `--collectors.enabled` argument which gets expanded with all default collectors.
@@ -237,10 +212,6 @@ This enables the additional process and container collectors on top of the defau
YAML configuration files can be specified with the `--config.file` flag. e.g. `.\windows_exporter.exe --config.file=config.yml`. If you are using the absolute path, make sure to quote the path, e.g. `.\windows_exporter.exe --config.file="C:\Program Files\windows_exporter\config.yml"`
It is also possible to load the configuration from a URL. e.g. `.\windows_exporter.exe --config.file="https://example.com/config.yml"`
If you need to skip TLS verification, you can use the `--config.file.insecure-skip-verify` flag. e.g. `.\windows_exporter.exe --config.file="https://example.com/config.yml" --config.file.insecure-skip-verify`
```yaml
collectors:
enabled: cpu,net,service

View File

@@ -3,6 +3,10 @@
collectors:
enabled: cpu,cpu_info,exchange,iis,logical_disk,memory,net,os,performancecounter,process,remote_fx,service,system,tcp,time,terminal_services,textfile
collector:
textfile:
directories:
- 'C:\MyDir1'
- 'C:\MyDir2'
service:
include: "windows_exporter"
performancecounter:

View File

@@ -20,29 +20,32 @@ These metrics are available on supported versions of Windows with compatible GPU
### Adapter-level Metrics
| Name | Description | Type | Labels |
|----------------------------------------------|-------------------------------------------------------------------------|-------|--------------------------------------------------------------------------------------|
| `windows_gpu_adapter_memory_committed_bytes` | Total committed GPU memory in bytes per physical GPU | gauge | `phys` |
| `windows_gpu_adapter_memory_dedicated_bytes` | Dedicated GPU memory usage in bytes per physical GPU | gauge | `phys` |
| `windows_gpu_adapter_memory_shared_bytes` | Shared GPU memory usage in bytes per physical GPU | gauge | `phys` |
| `windows_gpu_info` | A metric with a constant '1' value labeled with gpu device information. | gauge | `phys`, `physical_device_object_name`, `hardware_id`, `friendly_name`, `description` |
| `windows_gpu_local_adapter_memory_bytes` | Local adapter memory usage in bytes per physical GPU | gauge | `phys` |
| `windows_gpu_non_local_adapter_memory_bytes` | Non-local adapter memory usage in bytes per physical GPU | gauge | `phys` |
| Name | Description | Type | Labels |
|--------------------------------------------------|------------------------------------------------------------------------------------|-------|---------------|
| `windows_gpu_info` | A metric with a constant '1' value labeled with gpu device information. | gauge | `luid`,`name`,`bus_number`,`phys`,`function_number` |
| `windows_gpu_dedicated_system_memory_size_bytes` | The size, in bytes, of memory that is dedicated from system memory. | gauge | `luid` |
| `windows_gpu_dedicated_video_memory_size_bytes` | The size, in bytes, of memory that is dedicated from video memory. | gauge | `luid` |
| `windows_gpu_shared_system_memory_size_bytes` | The size, in bytes, of memory from system memory that can be shared by many users. | gauge | `luid` |
| `windows_gpu_adapter_memory_committed_bytes` | Total committed GPU memory in bytes per physical GPU | gauge | `luid`,`phys` |
| `windows_gpu_adapter_memory_dedicated_bytes` | Dedicated GPU memory usage in bytes per physical GPU | gauge | `luid`,`phys` |
| `windows_gpu_adapter_memory_shared_bytes` | Shared GPU memory usage in bytes per physical GPU | gauge | `luid`,`phys` |
| `windows_gpu_local_adapter_memory_bytes` | Local adapter memory usage in bytes per physical GPU | gauge | `luid`,`phys` |
| `windows_gpu_non_local_adapter_memory_bytes` | Non-local adapter memory usage in bytes per physical GPU | gauge | `luid`,`phys` |
### Per-process Metrics
| Name | Description | Type | Labels |
|----------------------------------------------|-------------------------------------------------------------------------|---------|--------------------------------------------------------------------------------------|
| `windows_gpu_engine_time_seconds` | Total running time of the GPU engine in seconds | counter | `phys`, `eng`, `engtype`, `process_id` |
| `windows_gpu_process_memory_committed_bytes` | Total committed GPU memory in bytes per process | gauge | `phys`,`process_id` |
| `windows_gpu_process_memory_dedicated_bytes` | Dedicated GPU memory usage in bytes per process | gauge | `phys`,`process_id` |
| `windows_gpu_process_memory_local_bytes` | Local GPU memory usage in bytes per process | gauge | `phys`,`process_id` |
| `windows_gpu_process_memory_non_local_bytes` | Non-local GPU memory usage in bytes per process | gauge | `phys`,`process_id` |
| `windows_gpu_process_memory_shared_bytes` | Shared GPU memory usage in bytes per process | gauge | `phys`,`process_id` |
| Name | Description | Type | Labels |
|----------------------------------------------|-------------------------------------------------|---------|-----------------------------------------------|
| `windows_gpu_engine_time_seconds` | Total running time of the GPU engine in seconds | counter | `luid`,`phys`, `eng`, `engtype`, `process_id` |
| `windows_gpu_process_memory_committed_bytes` | Total committed GPU memory in bytes per process | gauge | `luid`,`phys`,`process_id` |
| `windows_gpu_process_memory_dedicated_bytes` | Dedicated GPU memory usage in bytes per process | gauge | `luid`,`phys`,`process_id` |
| `windows_gpu_process_memory_local_bytes` | Local GPU memory usage in bytes per process | gauge | `luid`,`phys`,`process_id` |
| `windows_gpu_process_memory_non_local_bytes` | Non-local GPU memory usage in bytes per process | gauge | `luid`,`phys`,`process_id` |
| `windows_gpu_process_memory_shared_bytes` | Shared GPU memory usage in bytes per process | gauge | `luid`,`phys`,`process_id` |
## Metric Labels
* `phys`: Physical GPU index (e.g., "0")
* `luid`,`phys`: Physical GPU index (e.g., "0")
* `eng`: GPU engine index (e.g., "0", "1", ...)
* `engtype`: GPU engine type (e.g., "3D", "Copy", "VideoDecode", etc.)
* `process_id`: Process ID

12
go.mod
View File

@@ -4,7 +4,7 @@ go 1.24
require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/bmatcuk/doublestar/v4 v4.8.1
github.com/bmatcuk/doublestar/v4 v4.9.0
github.com/dimchansky/utfbom v1.1.1
github.com/go-ole/go-ole v1.3.0
github.com/prometheus/client_golang v1.22.0
@@ -12,7 +12,7 @@ require (
github.com/prometheus/common v0.65.0
github.com/prometheus/exporter-toolkit v0.14.0
github.com/stretchr/testify v1.10.0
golang.org/x/sys v0.33.0
golang.org/x/sys v0.34.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -30,11 +30,11 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

24
go.sum
View File

@@ -4,8 +4,8 @@ github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vS
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.9.0 h1:DBvuZxjdKkRP/dr4GVV4w2fnmrk5Hxc90T51LZjv0JA=
github.com/bmatcuk/doublestar/v4 v4.9.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
@@ -65,19 +65,19 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -29,10 +29,8 @@ import (
"unsafe"
"github.com/alecthomas/kingpin/v2"
"github.com/go-ole/go-ole"
"github.com/prometheus-community/windows_exporter/internal/headers/hcn"
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
"github.com/prometheus-community/windows_exporter/internal/headers/iphlpapi"
"github.com/prometheus-community/windows_exporter/internal/headers/kernel32"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/pdh"
@@ -523,7 +521,7 @@ func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDe
// collectNetworkMetrics collects network metrics for containers.
func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error {
endpoints, err := hcn.EnumerateEndpoints()
endpoints, err := hcn.ListEndpoints()
if err != nil {
return fmt.Errorf("error in fetching HCN endpoints: %w", err)
}
@@ -533,57 +531,24 @@ func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error {
}
for _, endpoint := range endpoints {
properties, err := hcn.GetEndpointProperties(endpoint)
if len(endpoint.SharedContainers) == 0 {
continue
}
endpointStats, err := hcn.GetHNSEndpointStats(endpoint.ID)
if err != nil {
c.logger.Warn("Failed to collect properties for interface "+endpoint.String(),
c.logger.Warn("Failed to collect network stats for interface "+endpoint.ID,
slog.Any("err", err),
)
continue
}
if len(properties.SharedContainers) == 0 {
continue
}
var nicGUID *ole.GUID
for _, allocator := range properties.Resources.Allocators {
if allocator.AdapterNetCfgInstanceId != nil {
nicGUID = allocator.AdapterNetCfgInstanceId
break
}
}
if nicGUID == nil {
c.logger.Warn("Failed to get nic GUID for endpoint " + endpoint.String())
continue
}
luid, err := iphlpapi.ConvertInterfaceGUIDToLUID(*nicGUID)
if err != nil {
return fmt.Errorf("error in converting interface GUID to LUID: %w", err)
}
var endpointStats iphlpapi.MIB_IF_ROW2
endpointStats.InterfaceLuid = luid
if err := iphlpapi.GetIfEntry2Ex(&endpointStats); err != nil {
c.logger.Warn("Failed to get interface entry for endpoint "+endpoint.String(),
slog.Any("err", err),
)
continue
}
for _, containerId := range properties.SharedContainers {
for _, containerId := range endpoint.SharedContainers {
containerInfo, ok := c.annotationsCacheHCS[containerId]
if !ok {
c.logger.Debug("Unknown container " + containerId + " for endpoint " + endpoint.String())
c.logger.Debug("Unknown container " + containerId + " for endpoint " + endpoint.ID)
continue
}
@@ -593,47 +558,47 @@ func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error {
continue
}
endpointId := strings.ToUpper(endpoint.String())
endpointId := strings.ToUpper(endpoint.ID)
ch <- prometheus.MustNewConstMetric(
c.bytesReceived,
prometheus.CounterValue,
float64(endpointStats.InOctets),
float64(endpointStats.BytesReceived),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.bytesSent,
prometheus.CounterValue,
float64(endpointStats.OutOctets),
float64(endpointStats.BytesSent),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceived,
prometheus.CounterValue,
float64(endpointStats.InUcastPkts+endpointStats.InNUcastPkts),
float64(endpointStats.PacketsReceived),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.packetsSent,
prometheus.CounterValue,
float64(endpointStats.OutUcastPkts+endpointStats.OutNUcastPkts),
float64(endpointStats.PacketsSent),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.droppedPacketsIncoming,
prometheus.CounterValue,
float64(endpointStats.InDiscards+endpointStats.InErrors),
float64(endpointStats.DroppedPacketsIncoming),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
ch <- prometheus.MustNewConstMetric(
c.droppedPacketsOutgoing,
prometheus.CounterValue,
float64(endpointStats.OutDiscards+endpointStats.OutErrors),
float64(endpointStats.DroppedPacketsOutgoing),
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
)
}

View File

@@ -19,6 +19,7 @@ package filetime
import (
"fmt"
"io/fs"
"log/slog"
"os"
"path/filepath"
@@ -73,19 +74,10 @@ func NewWithFlags(app *kingpin.Application) *Collector {
}
c.config.FilePatterns = make([]string, 0)
var filePatterns string
app.Flag(
"collector.filetime.file-patterns",
"Comma-separated list of file patterns. Each pattern is a glob pattern that can contain `*`, `?`, and `**` (recursive). See https://github.com/bmatcuk/doublestar#patterns",
).Default(strings.Join(ConfigDefaults.FilePatterns, ",")).StringVar(&filePatterns)
app.Action(func(*kingpin.ParseContext) error {
// doublestar.Glob() requires forward slashes
c.config.FilePatterns = strings.Split(filepath.ToSlash(filePatterns), ",")
return nil
})
).Default(strings.Join(ConfigDefaults.FilePatterns, ",")).StringsVar(&c.config.FilePatterns)
return c
}
@@ -148,16 +140,11 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
}
func (c *Collector) collectGlobFilePath(ch chan<- prometheus.Metric, filePattern string) error {
basePath, pattern := doublestar.SplitPattern(filePattern)
basePath, pattern := doublestar.SplitPattern(filepath.ToSlash(filePattern))
basePathFS := os.DirFS(basePath)
matches, err := doublestar.Glob(basePathFS, pattern, doublestar.WithFilesOnly())
if err != nil {
return fmt.Errorf("failed to glob: %w", err)
}
for _, match := range matches {
filePath := filepath.Join(basePath, match)
err := doublestar.GlobWalk(basePathFS, pattern, func(path string, d fs.DirEntry) error {
filePath := filepath.Join(basePath, path)
fileInfo, err := os.Stat(filePath)
if err != nil {
@@ -166,7 +153,7 @@ func (c *Collector) collectGlobFilePath(ch chan<- prometheus.Metric, filePattern
slog.Any("err", err),
)
continue
return nil
}
ch <- prometheus.MustNewConstMetric(
@@ -175,6 +162,11 @@ func (c *Collector) collectGlobFilePath(ch chan<- prometheus.Metric, filePattern
float64(fileInfo.ModTime().UTC().UnixMicro())/1e6,
filePath,
)
return nil
}, doublestar.WithFilesOnly(), doublestar.WithCaseInsensitive())
if err != nil {
return fmt.Errorf("failed to glob: %w", err)
}
return nil

View File

@@ -24,7 +24,7 @@ import (
"strconv"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/headers/setupapi"
"github.com/prometheus-community/windows_exporter/internal/headers/gdi32"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/pdh"
"github.com/prometheus-community/windows_exporter/internal/types"
@@ -41,6 +41,8 @@ var ConfigDefaults = Config{}
type Collector struct {
config Config
gpuDeviceCache map[string]gdi32.GPUDevice
// GPU Engine
gpuEnginePerfDataCollector *pdh.Collector
gpuEnginePerfDataObject []gpuEnginePerfDataCounterValues
@@ -48,6 +50,10 @@ type Collector struct {
gpuInfo *prometheus.Desc
gpuEngineRunningTime *prometheus.Desc
gpuSharedSystemMemorySize *prometheus.Desc
gpuDedicatedSystemMemorySize *prometheus.Desc
gpuDedicatedVideoMemorySize *prometheus.Desc
// GPU Adapter Memory
gpuAdapterMemoryPerfDataCollector *pdh.Collector
gpuAdapterMemoryPerfDataObject []gpuAdapterMemoryPerfDataCounterValues
@@ -115,78 +121,97 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) 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"},
[]string{"luid", "name", "bus_number", "phys", "function_number"},
nil,
)
c.gpuSharedSystemMemorySize = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "shared_system_memory_size_bytes"),
"The size, in bytes, of memory from system memory that can be shared by many users.",
[]string{"luid"},
nil,
)
c.gpuDedicatedSystemMemorySize = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "dedicated_system_memory_size_bytes"),
"The size, in bytes, of memory that is dedicated from system memory.",
[]string{"luid"},
nil,
)
c.gpuDedicatedVideoMemorySize = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "dedicated_video_memory_size_bytes"),
"The size, in bytes, of memory that is dedicated from video memory.",
[]string{"luid"},
nil,
)
c.gpuEngineRunningTime = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "engine_time_seconds"),
"Total running time of the GPU in seconds.",
[]string{"process_id", "phys", "eng", "engtype"},
[]string{"process_id", "luid", "phys", "eng", "engtype"},
nil,
)
c.gpuAdapterMemoryDedicatedUsage = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "adapter_memory_dedicated_bytes"),
"Dedicated GPU memory usage in bytes.",
[]string{"phys"},
[]string{"luid", "phys"},
nil,
)
c.gpuAdapterMemorySharedUsage = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "adapter_memory_shared_bytes"),
"Shared GPU memory usage in bytes.",
[]string{"phys"},
[]string{"luid", "phys"},
nil,
)
c.gpuAdapterMemoryTotalCommitted = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "adapter_memory_committed_bytes"),
"Total committed GPU memory in bytes.",
[]string{"phys"},
[]string{"luid", "phys"},
nil,
)
c.gpuLocalAdapterMemoryUsage = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "local_adapter_memory_bytes"),
"Local adapter memory usage in bytes.",
[]string{"phys"},
[]string{"luid", "phys"},
nil,
)
c.gpuNonLocalAdapterMemoryUsage = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "non_local_adapter_memory_bytes"),
"Non-local adapter memory usage in bytes.",
[]string{"phys"},
[]string{"luid", "phys"},
nil,
)
c.gpuProcessMemoryDedicatedUsage = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "process_memory_dedicated_bytes"),
"Dedicated process memory usage in bytes.",
[]string{"process_id", "phys"},
[]string{"process_id", "luid", "phys"},
nil,
)
c.gpuProcessMemoryLocalUsage = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "process_memory_local_bytes"),
"Local process memory usage in bytes.",
[]string{"process_id", "phys"},
[]string{"process_id", "luid", "phys"},
nil,
)
c.gpuProcessMemoryNonLocalUsage = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "process_memory_non_local_bytes"),
"Non-local process memory usage in bytes.",
[]string{"process_id", "phys"},
[]string{"process_id", "luid", "phys"},
nil,
)
c.gpuProcessMemorySharedUsage = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "process_memory_shared_bytes"),
"Shared process memory usage in bytes.",
[]string{"process_id", "phys"},
[]string{"process_id", "luid", "phys"},
nil,
)
c.gpuProcessMemoryTotalCommitted = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "process_memory_committed_bytes"),
"Total committed process memory in bytes.",
[]string{"process_id", "phys"},
[]string{"process_id", "luid", "phys"},
nil,
)
@@ -217,15 +242,31 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
errs = append(errs, fmt.Errorf("failed to create GPU Process Memory perf data collector: %w", err))
}
gpus, err := gdi32.GetGPUDevices()
if err != nil {
errs = append(errs, fmt.Errorf("failed to get GPU devices: %w", err))
}
for _, gpu := range gpus {
if gpu.AdapterString == "" {
continue
}
if c.gpuDeviceCache == nil {
c.gpuDeviceCache = make(map[string]gdi32.GPUDevice)
}
luidKey := fmt.Sprintf("0x%08X_0x%08X", gpu.LUID.HighPart, gpu.LUID.LowPart)
c.gpuDeviceCache[luidKey] = gpu
}
return errors.Join(errs...)
}
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
errs := make([]error, 0)
if err := c.collectGpuInfo(ch); err != nil {
errs = append(errs, err)
}
c.collectGpuInfo(ch)
if err := c.collectGpuEngineMetrics(ch); err != nil {
errs = append(errs, err)
@@ -250,26 +291,40 @@ 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 {
func (c *Collector) collectGpuInfo(ch chan<- prometheus.Metric) {
for luid, gpu := range c.gpuDeviceCache {
ch <- prometheus.MustNewConstMetric(
c.gpuInfo,
prometheus.GaugeValue,
1.0,
strconv.Itoa(i),
gpu.PhysicalDeviceObjectName,
gpu.HardwareID,
gpu.FriendlyName,
gpu.DeviceDesc,
luid,
gpu.AdapterString,
strconv.FormatInt(int64(gpu.BusNumber), 10),
strconv.FormatInt(int64(gpu.DeviceNumber), 10),
strconv.FormatInt(int64(gpu.FunctionNumber), 10),
)
ch <- prometheus.MustNewConstMetric(
c.gpuSharedSystemMemorySize,
prometheus.GaugeValue,
float64(gpu.SharedSystemMemorySize),
luid,
)
ch <- prometheus.MustNewConstMetric(
c.gpuDedicatedSystemMemorySize,
prometheus.GaugeValue,
float64(gpu.DedicatedSystemMemorySize),
luid,
)
ch <- prometheus.MustNewConstMetric(
c.gpuDedicatedVideoMemorySize,
prometheus.GaugeValue,
float64(gpu.DedicatedVideoMemorySize),
luid,
)
}
return nil
}
func (c *Collector) collectGpuEngineMetrics(ch chan<- prometheus.Metric) error {
@@ -283,9 +338,14 @@ func (c *Collector) collectGpuEngineMetrics(ch chan<- prometheus.Metric) error {
for _, data := range c.gpuEnginePerfDataObject {
instance := parseGPUCounterInstanceString(data.Name)
if _, ok := c.gpuDeviceCache[instance.Luid]; !ok {
continue
}
key := PidPhysEngEngType{
Pid: instance.Pid,
Phys: instance.Phys,
Luid: instance.Luid,
Eng: instance.Eng,
Engtype: instance.Engtype,
}
@@ -297,7 +357,7 @@ func (c *Collector) collectGpuEngineMetrics(ch chan<- prometheus.Metric) error {
c.gpuEngineRunningTime,
prometheus.CounterValue,
runningTime,
key.Pid, key.Phys, key.Eng, key.Engtype,
key.Pid, key.Luid, key.Phys, key.Eng, key.Engtype,
)
}
@@ -317,8 +377,13 @@ func (c *Collector) collectGpuAdapterMemoryMetrics(ch chan<- prometheus.Metric)
for _, data := range c.gpuAdapterMemoryPerfDataObject {
instance := parseGPUCounterInstanceString(data.Name)
if _, ok := c.gpuDeviceCache[instance.Luid]; !ok {
continue
}
key := PidPhysEngEngType{
Pid: instance.Pid,
Luid: instance.Luid,
Phys: instance.Phys,
Eng: instance.Eng,
Engtype: instance.Engtype,
@@ -333,21 +398,21 @@ func (c *Collector) collectGpuAdapterMemoryMetrics(ch chan<- prometheus.Metric)
c.gpuAdapterMemoryDedicatedUsage,
prometheus.GaugeValue,
dedicatedUsage,
key.Phys,
key.Luid, key.Phys,
)
ch <- prometheus.MustNewConstMetric(
c.gpuAdapterMemorySharedUsage,
prometheus.GaugeValue,
sharedUsageMap[key],
key.Phys,
key.Luid, key.Phys,
)
ch <- prometheus.MustNewConstMetric(
c.gpuAdapterMemoryTotalCommitted,
prometheus.GaugeValue,
totalCommittedMap[key],
key.Phys,
key.Luid, key.Phys,
)
}
@@ -360,20 +425,29 @@ func (c *Collector) collectGpuLocalAdapterMemoryMetrics(ch chan<- prometheus.Met
return fmt.Errorf("failed to collect GPU Local Adapter Memory perf data: %w", err)
}
localAdapterMemoryMap := make(map[string]float64)
localAdapterMemoryMap := make(map[PidPhysEngEngType]float64)
for _, data := range c.gpuLocalAdapterMemoryPerfDataObject {
instance := parseGPUCounterInstanceString(data.Name)
localAdapterMemoryMap[instance.Phys] += data.LocalUsage
if _, ok := c.gpuDeviceCache[instance.Luid]; !ok {
continue
}
key := PidPhysEngEngType{
Luid: instance.Luid,
Phys: instance.Phys,
}
localAdapterMemoryMap[key] += data.LocalUsage
}
for phys, localUsage := range localAdapterMemoryMap {
for key, localUsage := range localAdapterMemoryMap {
ch <- prometheus.MustNewConstMetric(
c.gpuLocalAdapterMemoryUsage,
prometheus.GaugeValue,
localUsage,
phys,
key.Luid, key.Phys,
)
}
@@ -386,20 +460,28 @@ func (c *Collector) collectGpuNonLocalAdapterMemoryMetrics(ch chan<- prometheus.
return fmt.Errorf("failed to collect GPU Non Local Adapter Memory perf data: %w", err)
}
nonLocalAdapterMemoryMap := make(map[string]float64)
nonLocalAdapterMemoryMap := make(map[PidPhysEngEngType]float64)
for _, data := range c.gpuNonLocalAdapterMemoryPerfDataObject {
instance := parseGPUCounterInstanceString(data.Name)
nonLocalAdapterMemoryMap[instance.Phys] += data.NonLocalUsage
if _, ok := c.gpuDeviceCache[instance.Luid]; !ok {
continue
}
key := PidPhysEngEngType{
Luid: instance.Luid,
Phys: instance.Phys,
}
nonLocalAdapterMemoryMap[key] += data.NonLocalUsage
}
for phys, nonLocalUsage := range nonLocalAdapterMemoryMap {
for key, nonLocalUsage := range nonLocalAdapterMemoryMap {
ch <- prometheus.MustNewConstMetric(
c.gpuNonLocalAdapterMemoryUsage,
prometheus.GaugeValue,
nonLocalUsage,
phys,
key.Luid, key.Phys,
)
}
@@ -421,8 +503,13 @@ func (c *Collector) collectGpuProcessMemoryMetrics(ch chan<- prometheus.Metric)
for _, data := range c.gpuProcessMemoryPerfDataObject {
instance := parseGPUCounterInstanceString(data.Name)
if _, ok := c.gpuDeviceCache[instance.Luid]; !ok {
continue
}
key := PidPhys{
Pid: instance.Pid,
Luid: instance.Luid,
Phys: instance.Phys,
}
processDedicatedUsageMap[key] += data.DedicatedUsage
@@ -437,35 +524,35 @@ func (c *Collector) collectGpuProcessMemoryMetrics(ch chan<- prometheus.Metric)
c.gpuProcessMemoryDedicatedUsage,
prometheus.GaugeValue,
dedicatedUsage,
key.Pid, key.Phys,
key.Pid, key.Luid, key.Phys,
)
ch <- prometheus.MustNewConstMetric(
c.gpuProcessMemoryLocalUsage,
prometheus.GaugeValue,
processLocalUsageMap[key],
key.Pid, key.Phys,
key.Pid, key.Luid, key.Phys,
)
ch <- prometheus.MustNewConstMetric(
c.gpuProcessMemoryNonLocalUsage,
prometheus.GaugeValue,
processNonLocalUsageMap[key],
key.Pid, key.Phys,
key.Pid, key.Luid, key.Phys,
)
ch <- prometheus.MustNewConstMetric(
c.gpuProcessMemorySharedUsage,
prometheus.GaugeValue,
processSharedUsageMap[key],
key.Pid, key.Phys,
key.Pid, key.Luid, key.Phys,
)
ch <- prometheus.MustNewConstMetric(
c.gpuProcessMemoryTotalCommitted,
prometheus.GaugeValue,
processTotalCommittedMap[key],
key.Pid, key.Phys,
key.Pid, key.Luid, key.Phys,
)
}

View File

@@ -18,12 +18,13 @@
package gpu
import (
"fmt"
"strings"
)
type Instance struct {
Pid string
Luid [2]string
Luid string
Phys string
Eng string
Engtype string
@@ -32,11 +33,13 @@ type Instance struct {
type PidPhys struct {
Pid string
Luid string
Phys string
}
type PidPhysEngEngType struct {
Pid string
Luid string
Phys string
Eng string
Engtype string
@@ -58,8 +61,7 @@ func parseGPUCounterInstanceString(s string) Instance {
}
case "luid":
if i+2 < len(parts) {
instance.Luid[0] = parts[i+1]
instance.Luid[1] = parts[i+2]
instance.Luid = fmt.Sprintf("%s_%s", parts[i+1], parts[i+2])
}
case "phys":
if i+1 < len(parts) {

View File

@@ -144,7 +144,12 @@ func (c *Collector) collectWorker() {
name, pidString, parentPID, strconv.Itoa(int(processGroupID)), processOwner, cmdLine,
)
startTime := float64(time.Now().UnixMicro())/1e6 - data.ElapsedTime
startTime := data.ElapsedTime
if c.config.CounterVersion == 2 {
// For V2, the ElapsedTime is in seconds, so we need to convert it to a timestamp.
// The start time is the current time minus the elapsed time.
startTime = float64(time.Now().UnixMicro())/1e6 - data.ElapsedTime
}
ch <- prometheus.MustNewConstMetric(
c.startTime,

View File

@@ -19,72 +19,52 @@ package config
import (
"fmt"
"strconv"
"strings"
)
// flatten flattens the nested struct.
//
// All keys will be joined by dot
// e.g. {"a": {"b":"c"}} => {"a.b":"c"}
// or {"a": {"b":[1,2]}} => {"a.b.0":1, "a.b.1": 2}.
func flatten(data map[string]interface{}) map[string]string {
ret := make(map[string]string)
for k, v := range data {
switch typed := v.(type) {
case map[interface{}]interface{}:
for fk, fv := range flatten(convertMap(typed)) {
ret[fmt.Sprintf("%s.%s", k, fk)] = fv
}
case map[string]interface{}:
for fk, fv := range flatten(typed) {
ret[fmt.Sprintf("%s.%s", k, fk)] = fv
}
case []interface{}:
for fk, fv := range flattenSlice(typed) {
ret[fmt.Sprintf("%s.%s", k, fk)] = fv
}
default:
ret[k] = fmt.Sprint(typed)
}
}
return ret
}
func flattenSlice(data []interface{}) map[string]string {
ret := make(map[string]string)
for idx, v := range data {
switch typed := v.(type) {
case map[interface{}]interface{}:
for fk, fv := range flatten(convertMap(typed)) {
ret[fmt.Sprintf("%d,%s", idx, fk)] = fv
}
case map[string]interface{}:
for fk, fv := range flatten(typed) {
ret[fmt.Sprintf("%d,%s", idx, fk)] = fv
}
case []interface{}:
for fk, fv := range flattenSlice(typed) {
ret[fmt.Sprintf("%d,%s", idx, fk)] = fv
}
default:
ret[strconv.Itoa(idx)] = fmt.Sprint(typed)
}
}
return ret
}
func convertMap(originalMap map[interface{}]interface{}) map[string]interface{} {
convertedMap := map[string]interface{}{}
// convertMap converts a map with any comparable key type to a map with string keys.
func convertMap[K comparable, V any](originalMap map[K]V) map[string]V {
convertedMap := make(map[string]V, len(originalMap))
for key, value := range originalMap {
if keyString, ok := key.(string); ok {
if keyString, ok := any(key).(string); ok {
convertedMap[keyString] = value
}
}
return convertedMap
}
// flatten flattens a nested map, joining keys with dots.
// e.g. {"a": {"b":"c"}} => {"a.b":"c"}
func flatten(data map[string]any) map[string]string {
result := make(map[string]string)
flattenHelper("", data, result)
return result
}
func flattenHelper(prefix string, data map[string]any, result map[string]string) {
for k, v := range data {
fullKey := k
if prefix != "" {
fullKey = prefix + "." + k
}
switch val := v.(type) {
case map[any]any:
flattenHelper(fullKey, convertMap(val), result)
case map[string]any:
flattenHelper(fullKey, val, result)
case []any:
strSlice := make([]string, len(val))
for i, elem := range val {
strSlice[i] = fmt.Sprint(elem)
}
result[fullKey] = strings.Join(strSlice, ",")
default:
result[fullKey] = fmt.Sprint(val)
}
}
}

View File

@@ -0,0 +1,192 @@
// 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 gdi32
import (
"errors"
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/ne-d3dkmthk-_kmtqueryadapterinfotype
// https://github.com/nalilord/AMDPlugin/blob/bb405b6d58ea543ff630f3488384473bee79f447/Common/d3dkmthk.pas#L54
const (
// KMTQAITYPE_GETSEGMENTSIZE pPrivateDriverData points to a D3DKMT_SEGMENTSIZEINFO structure that contains information about the size of memory and aperture segments.
KMTQAITYPE_GETSEGMENTSIZE = 3
// KMTQAITYPE_ADAPTERADDRESS pPrivateDriverData points to a D3DKMT_ADAPTERADDRESS structure that contains information about the physical location on the PCI bus of the adapter.
KMTQAITYPE_ADAPTERADDRESS = 6
// KMTQAITYPE_ADAPTERREGISTRYINFO pPrivateDriverData points to a D3DKMT_ADAPTERREGISTRYINFO structure that contains registry information about the graphics adapter.
KMTQAITYPE_ADAPTERREGISTRYINFO = 8
)
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
// Try segment size first
var size D3DKMT_SEGMENTSIZEINFO
query := D3DKMT_QUERYADAPTERINFO{
hAdapter: hAdapter,
queryType: KMTQAITYPE_GETSEGMENTSIZE,
pPrivateDriverData: unsafe.Pointer(&size),
privateDriverDataSize: uint32(unsafe.Sizeof(size)),
}
if err := D3DKMTQueryAdapterInfo(&query); err != nil {
return gpuDevice, fmt.Errorf("D3DKMTQueryAdapterInfo (segment size) failed: %w", err)
}
gpuDevice.DedicatedVideoMemorySize = size.DedicatedVideoMemorySize
gpuDevice.DedicatedSystemMemorySize = size.DedicatedSystemMemorySize
gpuDevice.SharedSystemMemorySize = size.SharedSystemMemorySize
// Now try registry info
var address D3DKMT_ADAPTERADDRESS
query.queryType = KMTQAITYPE_ADAPTERADDRESS
query.pPrivateDriverData = unsafe.Pointer(&address)
query.privateDriverDataSize = uint32(unsafe.Sizeof(address))
if err := D3DKMTQueryAdapterInfo(&query); err != nil {
return gpuDevice, fmt.Errorf("D3DKMTQueryAdapterInfo (adapter address) failed: %w", err)
}
gpuDevice.BusNumber = address.BusNumber
gpuDevice.DeviceNumber = address.DeviceNumber
gpuDevice.FunctionNumber = address.FunctionNumber
// Now try registry info
var info D3DKMT_ADAPTERREGISTRYINFO
query.queryType = KMTQAITYPE_ADAPTERREGISTRYINFO
query.pPrivateDriverData = unsafe.Pointer(&info)
query.privateDriverDataSize = uint32(unsafe.Sizeof(info))
if err := D3DKMTQueryAdapterInfo(&query); err != nil && !errors.Is(err, windows.ERROR_FILE_NOT_FOUND) {
return gpuDevice, fmt.Errorf("D3DKMTQueryAdapterInfo (info) failed: %w", err)
}
gpuDevice.AdapterString = windows.UTF16ToString(info.AdapterString[:])
return gpuDevice, nil
}
func GetGPUDevices() ([]GPUDevice, error) {
gpuDevices := make([]GPUDevice, 0, 2)
// First call: Get the number of adapters
enumAdapters := D3DKMT_ENUMADAPTERS2{
NumAdapters: 0,
PAdapters: nil,
}
if err := D3DKMTEnumAdapters2(&enumAdapters); err != nil {
return gpuDevices, fmt.Errorf("D3DKMTEnumAdapters2 (get count) failed: %w", err)
}
if enumAdapters.NumAdapters == 0 {
return gpuDevices, ErrNoGPUDevices
}
// Second call: Get the actual adapter information
pAdapters := make([]D3DKMT_ADAPTERINFO, enumAdapters.NumAdapters)
enumAdapters.PAdapters = &pAdapters[0]
if err := D3DKMTEnumAdapters2(&enumAdapters); err != nil {
return gpuDevices, fmt.Errorf("D3DKMTEnumAdapters2 (get adapters) failed: %w", err)
}
var errs []error
// Process each adapter
for i := range enumAdapters.NumAdapters {
adapter := pAdapters[i]
// Validate handle before using it
if adapter.HAdapter == 0 {
errs = append(errs, fmt.Errorf("adapter %d has null handle", i))
continue
}
func() {
defer func() {
if closeErr := D3DKMTCloseAdapter(&D3DKMT_CLOSEADAPTER{
HAdapter: adapter.HAdapter,
}); closeErr != nil {
errs = append(errs, fmt.Errorf("failed to close adapter %v: %w", adapter.AdapterLUID, closeErr))
}
}()
gpuDevice, err := GetGPUDevice(adapter.HAdapter)
if err != nil {
errs = append(errs, fmt.Errorf("failed to get GPU device for adapter %v: %w", adapter.AdapterLUID, err))
return
}
gpuDevice.LUID = adapter.AdapterLUID
gpuDevices = append(gpuDevices, gpuDevice)
}()
}
if len(errs) > 0 {
return gpuDevices, errors.Join(errs...)
}
if len(gpuDevices) == 0 {
return gpuDevices, ErrNoGPUDevices
}
return gpuDevices, nil
}

View File

@@ -13,19 +13,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
package setupapi_test
package gdi32_test
import (
"testing"
"github.com/prometheus-community/windows_exporter/internal/headers/setupapi"
"github.com/prometheus-community/windows_exporter/internal/headers/gdi32"
"github.com/stretchr/testify/require"
)
func TestGetGPUDevices(t *testing.T) {
devices, err := setupapi.GetGPUDevices()
devices, err := gdi32.GetGPUDevices()
require.NoError(t, err, "Failed to get GPU devices")
require.NotNil(t, devices)

View File

@@ -0,0 +1,77 @@
// 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 gdi32
import (
"fmt"
"unsafe"
"github.com/prometheus-community/windows_exporter/internal/headers/ntdll"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
modGdi32 = windows.NewLazySystemDLL("gdi32.dll")
procD3DKMTOpenAdapterFromLuid = modGdi32.NewProc("D3DKMTOpenAdapterFromLuid")
procD3DKMTQueryAdapterInfo = modGdi32.NewProc("D3DKMTQueryAdapterInfo")
procD3DKMTCloseAdapter = modGdi32.NewProc("D3DKMTCloseAdapter")
procD3DKMTEnumAdapters2 = modGdi32.NewProc("D3DKMTEnumAdapters2")
)
func D3DKMTOpenAdapterFromLuid(ptr *D3DKMT_OPENADAPTERFROMLUID) error {
ret, _, _ := procD3DKMTOpenAdapterFromLuid.Call(
uintptr(unsafe.Pointer(ptr)),
)
if ret != 0 {
return fmt.Errorf("D3DKMTOpenAdapterFromLuid failed: 0x%X: %w", ret, ntdll.RtlNtStatusToDosError(ret))
}
return nil
}
func D3DKMTEnumAdapters2(ptr *D3DKMT_ENUMADAPTERS2) error {
ret, _, _ := procD3DKMTEnumAdapters2.Call(
uintptr(unsafe.Pointer(ptr)),
)
if ret != 0 {
return fmt.Errorf("D3DKMTEnumAdapters2 failed: 0x%X: %w", ret, ntdll.RtlNtStatusToDosError(ret))
}
return nil
}
func D3DKMTQueryAdapterInfo(query *D3DKMT_QUERYADAPTERINFO) error {
ret, _, _ := procD3DKMTQueryAdapterInfo.Call(
uintptr(unsafe.Pointer(query)),
)
if ret != 0 {
return fmt.Errorf("D3DKMTQueryAdapterInfo failed: 0x%X: %w", ret, ntdll.RtlNtStatusToDosError(ret))
}
return nil
}
func D3DKMTCloseAdapter(ptr *D3DKMT_CLOSEADAPTER) error {
ret, _, _ := procD3DKMTCloseAdapter.Call(
uintptr(unsafe.Pointer(ptr)),
)
if ret != 0 {
return fmt.Errorf("D3DKMTCloseAdapter failed: 0x%X: %w", ret, ntdll.RtlNtStatusToDosError(ret))
}
return nil
}

View File

@@ -0,0 +1,85 @@
// 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 gdi32
import (
"unsafe"
"github.com/prometheus-community/windows_exporter/internal/headers/win32"
"golang.org/x/sys/windows"
)
type D3DKMT_HANDLE = win32.UINT
type D3DKMT_OPENADAPTERFROMLUID struct {
AdapterLUID windows.LUID
HAdapter D3DKMT_HANDLE
}
type D3DKMT_CLOSEADAPTER struct {
HAdapter D3DKMT_HANDLE
}
type D3DKMT_QUERYADAPTERINFO struct {
hAdapter D3DKMT_HANDLE
queryType int32
pPrivateDriverData unsafe.Pointer
privateDriverDataSize uint32
}
type D3DKMT_ENUMADAPTERS2 struct {
NumAdapters uint32
PAdapters *D3DKMT_ADAPTERINFO
}
type D3DKMT_ADAPTERINFO struct {
HAdapter D3DKMT_HANDLE
AdapterLUID windows.LUID
NumOfSources win32.ULONG
Present win32.BOOL
}
type D3DKMT_ADAPTERREGISTRYINFO struct {
AdapterString [win32.MAX_PATH]uint16
BiosString [win32.MAX_PATH]uint16
DacType [win32.MAX_PATH]uint16
ChipType [win32.MAX_PATH]uint16
}
type D3DKMT_SEGMENTSIZEINFO struct {
DedicatedVideoMemorySize uint64
DedicatedSystemMemorySize uint64
SharedSystemMemorySize uint64
}
type D3DKMT_ADAPTERADDRESS struct {
BusNumber win32.UINT
DeviceNumber win32.UINT
FunctionNumber win32.UINT
}
type GPUDevice struct {
AdapterString string
LUID windows.LUID
DedicatedVideoMemorySize uint64
DedicatedSystemMemorySize uint64
SharedSystemMemorySize uint64
BusNumber win32.UINT
DeviceNumber win32.UINT
FunctionNumber win32.UINT
}

View File

@@ -18,30 +18,88 @@
package hcn
import (
"encoding/json"
"fmt"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
"github.com/prometheus-community/windows_exporter/internal/utils"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
defaultQuery = utils.Must(windows.UTF16PtrFromString(`{"SchemaVersion":{"Major": 2,"Minor": 0},"Flags":"None"}`))
modvmcompute = windows.NewLazySystemDLL("vmcompute.dll")
procHNSCall = modvmcompute.NewProc("HNSCall")
hcnBodyEmpty = utils.Must(windows.UTF16PtrFromString(""))
hcnMethodGet = utils.Must(windows.UTF16PtrFromString("GET"))
hcnPathEndpoints = utils.Must(windows.UTF16PtrFromString("/endpoints/"))
hcnPathEndpointStats = utils.Must(windows.UTF16FromString("/endpointstats/"))
)
func GetEndpointProperties(endpointID ole.GUID) (EndpointProperties, error) {
endpoint, err := OpenEndpoint(endpointID)
func ListEndpoints() ([]EndpointProperties, error) {
result, err := hnsCall(hcnMethodGet, hcnPathEndpoints, hcnBodyEmpty)
if err != nil {
return EndpointProperties{}, fmt.Errorf("failed to open endpoint: %w", err)
return nil, err
}
defer CloseEndpoint(endpoint)
result, err := QueryEndpointProperties(endpoint, defaultQuery)
if err != nil {
return EndpointProperties{}, fmt.Errorf("failed to query endpoint properties: %w", err)
var endpoints struct {
Success bool `json:"success"`
Error string `json:"error"`
Output []EndpointProperties `json:"output"`
}
return result, nil
if err := json.Unmarshal([]byte(result), &endpoints); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON %s: %w", result, err)
}
if !endpoints.Success {
return nil, fmt.Errorf("HNSCall failed: %s", endpoints.Error)
}
return endpoints.Output, nil
}
func GetHNSEndpointStats(endpointID string) (EndpointStats, error) {
endpointIDUTF16, err := windows.UTF16FromString(endpointID)
if err != nil {
return EndpointStats{}, fmt.Errorf("failed to convert endpoint ID to UTF16: %w", err)
}
path := hcnPathEndpointStats[:len(hcnPathEndpointStats)-1]
path = append(path, endpointIDUTF16...)
result, err := hnsCall(hcnMethodGet, &path[0], hcnBodyEmpty)
if err != nil {
return EndpointStats{}, err
}
var stats EndpointStats
if err := json.Unmarshal([]byte(result), &stats); err != nil {
return EndpointStats{}, fmt.Errorf("failed to unmarshal JSON %s: %w", result, err)
}
return stats, nil
}
func hnsCall(method, path, body *uint16) (string, error) {
var responseJSON *uint16
r1, _, _ := procHNSCall.Call(
uintptr(unsafe.Pointer(method)),
uintptr(unsafe.Pointer(path)),
uintptr(unsafe.Pointer(body)),
uintptr(unsafe.Pointer(&responseJSON)),
)
response := windows.UTF16PtrToString(responseJSON)
windows.CoTaskMemFree(unsafe.Pointer(responseJSON))
if r1 != 0 {
return "", fmt.Errorf("HNSCall failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1))
}
return response, nil
}

View File

@@ -1,47 +0,0 @@
// 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 hcn_test
import (
"testing"
"github.com/prometheus-community/windows_exporter/internal/headers/hcn"
"github.com/stretchr/testify/require"
)
func TestEnumerateEndpoints(t *testing.T) {
t.Parallel()
endpoints, err := hcn.EnumerateEndpoints()
require.NoError(t, err)
require.NotNil(t, endpoints)
}
func TestQueryEndpointProperties(t *testing.T) {
t.Parallel()
endpoints, err := hcn.EnumerateEndpoints()
require.NoError(t, err)
if len(endpoints) == 0 {
t.Skip("No endpoints found")
}
_, err = hcn.GetEndpointProperties(endpoints[0])
require.NoError(t, err)
}

View File

@@ -1,134 +0,0 @@
// 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 hcn
import (
"encoding/json"
"fmt"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
modComputeNetwork = windows.NewLazySystemDLL("computenetwork.dll")
procHcnEnumerateEndpoints = modComputeNetwork.NewProc("HcnEnumerateEndpoints")
procHcnOpenEndpoint = modComputeNetwork.NewProc("HcnOpenEndpoint")
procHcnQueryEndpointProperties = modComputeNetwork.NewProc("HcnQueryEndpointProperties")
procHcnCloseEndpoint = modComputeNetwork.NewProc("HcnCloseEndpoint")
)
// EnumerateEndpoints enumerates the endpoints.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnenumerateendpoints
func EnumerateEndpoints() ([]ole.GUID, error) {
var (
endpointsJSON *uint16
errorRecord *uint16
)
r1, _, _ := procHcnEnumerateEndpoints.Call(
0,
uintptr(unsafe.Pointer(&endpointsJSON)),
uintptr(unsafe.Pointer(&errorRecord)),
)
windows.CoTaskMemFree(unsafe.Pointer(errorRecord))
result := windows.UTF16PtrToString(endpointsJSON)
if r1 != 0 {
return nil, fmt.Errorf("HcnEnumerateEndpoints failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1))
}
var endpoints []ole.GUID
if err := json.Unmarshal([]byte(result), &endpoints); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
return endpoints, nil
}
// OpenEndpoint opens an endpoint.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnopenendpoint
func OpenEndpoint(id ole.GUID) (Endpoint, error) {
var (
endpoint Endpoint
errorRecord *uint16
)
r1, _, _ := procHcnOpenEndpoint.Call(
uintptr(unsafe.Pointer(&id)),
uintptr(unsafe.Pointer(&endpoint)),
uintptr(unsafe.Pointer(&errorRecord)),
)
windows.CoTaskMemFree(unsafe.Pointer(errorRecord))
if r1 != 0 {
return 0, fmt.Errorf("HcnOpenEndpoint failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1))
}
return endpoint, nil
}
// QueryEndpointProperties queries the properties of an endpoint.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnqueryendpointproperties
func QueryEndpointProperties(endpoint Endpoint, propertyQuery *uint16) (EndpointProperties, error) {
var (
resultDocument *uint16
errorRecord *uint16
)
r1, _, _ := procHcnQueryEndpointProperties.Call(
uintptr(endpoint),
uintptr(unsafe.Pointer(&propertyQuery)),
uintptr(unsafe.Pointer(&resultDocument)),
uintptr(unsafe.Pointer(&errorRecord)),
)
windows.CoTaskMemFree(unsafe.Pointer(errorRecord))
result := windows.UTF16PtrToString(resultDocument)
windows.CoTaskMemFree(unsafe.Pointer(resultDocument))
if r1 != 0 {
return EndpointProperties{}, fmt.Errorf("HcsGetComputeSystemProperties failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1))
}
var properties EndpointProperties
if err := json.Unmarshal([]byte(result), &properties); err != nil {
return EndpointProperties{}, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
return properties, nil
}
// CloseEndpoint close a handle to an Endpoint.
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcncloseendpoint
func CloseEndpoint(endpoint Endpoint) {
_, _, _ = procHcnCloseEndpoint.Call(uintptr(endpoint))
}

View File

@@ -18,7 +18,6 @@
package hcn
import (
"github.com/go-ole/go-ole"
"golang.org/x/sys/windows"
)
@@ -28,17 +27,9 @@ type Endpoint = windows.Handle
//
// https://learn.microsoft.com/en-us/virtualization/api/hcn/hns_schema#HostComputeEndpoint
type EndpointProperties struct {
ID string `json:"ID"`
State int `json:"State"`
SharedContainers []string `json:"SharedContainers"`
Resources EndpointPropertiesResources `json:"Resources"`
}
type EndpointPropertiesResources struct {
Allocators []EndpointPropertiesAllocators `json:"Allocators"`
}
type EndpointPropertiesAllocators struct {
AdapterNetCfgInstanceId *ole.GUID `json:"AdapterNetCfgInstanceId"`
ID string `json:"ID"`
State int `json:"State"`
SharedContainers []string `json:"SharedContainers"`
}
type EndpointStats struct {

View File

@@ -22,16 +22,13 @@ import (
"fmt"
"unsafe"
"github.com/go-ole/go-ole"
"golang.org/x/sys/windows"
)
//nolint:gochecknoglobals
var (
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex")
procConvertInterfaceGuidToLuid = modiphlpapi.NewProc("ConvertInterfaceGuidToLuid")
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
)
func GetTCPConnectionStates(family uint32) (map[MIB_TCP_STATE]uint32, error) {
@@ -131,38 +128,3 @@ func getExtendedTcpTable[T any](ulAf uint32, tableClass uint32) ([]T, error) {
return unsafe.Slice((*T)(unsafe.Pointer(&buf[4])), binary.LittleEndian.Uint32(buf)), nil
}
// GetIfEntry2Ex function retrieves the specified level of information for the specified interface on the local computer.
//
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getifentry2ex
func GetIfEntry2Ex(row *MIB_IF_ROW2) error {
ret, _, _ := procGetIfEntry2Ex.Call(
uintptr(0),
uintptr(unsafe.Pointer(row)),
)
if ret != 0 {
return fmt.Errorf("GetIfEntry2Ex failed with code %d: %w", ret, windows.Errno(ret))
}
return nil
}
// ConvertInterfaceGUIDToLUID function converts a globally unique identifier (GUID) for a network interface to the
// locally unique identifier (LUID) for the interface.
//
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-convertinterfaceguidtoluid
func ConvertInterfaceGUIDToLUID(guid ole.GUID) (uint64, error) {
var luid uint64
ret, _, _ := procConvertInterfaceGuidToLuid.Call(
uintptr(unsafe.Pointer(&guid)),
uintptr(unsafe.Pointer(&luid)),
)
if ret != 0 {
return 0, fmt.Errorf("ConvertInterfaceGUIDToLUID failed with code %d: %w", ret, windows.Errno(ret))
}
return luid, nil
}

View File

@@ -15,7 +15,7 @@
//go:build windows
package setupapi
package ntdll
import (
"golang.org/x/sys/windows"
@@ -23,9 +23,15 @@ import (
//nolint:gochecknoglobals
var (
modSetupAPI = windows.NewLazySystemDLL("setupapi.dll")
procSetupDiGetClassDevsW = modSetupAPI.NewProc("SetupDiGetClassDevsW")
procSetupDiEnumDeviceInfo = modSetupAPI.NewProc("SetupDiEnumDeviceInfo")
procSetupDiGetDeviceRegistryPropertyW = modSetupAPI.NewProc("SetupDiGetDeviceRegistryPropertyW")
procSetupDiDestroyDeviceInfoList = modSetupAPI.NewProc("SetupDiDestroyDeviceInfoList")
modNtdll = windows.NewLazySystemDLL("ntdll.dll")
procRtlNtStatusToDosError = modNtdll.NewProc("RtlNtStatusToDosError")
)
func RtlNtStatusToDosError(status uintptr) error {
ret, _, _ := procRtlNtStatusToDosError.Call(status)
if ret == 0 {
return nil
}
return windows.Errno(ret)
}

View File

@@ -1,135 +0,0 @@
// 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

@@ -1,42 +0,0 @@
// 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
}

View File

@@ -23,12 +23,17 @@ import (
"golang.org/x/sys/windows"
)
const MAX_PATH = 260
type (
BOOL = int32 // BOOL is a 32-bit signed int in Win32
DATE_TIME = windows.Filetime
DWORD = uint32
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
)
// NewLPWSTR creates a new LPWSTR from a string.