Compare commits

...

22 Commits

Author SHA1 Message Date
Jan-Otto Kröpke
0e85959a4d installer: do not fail, if service can't be started. (#2163) 2025-08-03 20:11:57 +02:00
Jan-Otto Kröpke
6253bf812d process: Add flag to control the export of the process cmdline (#2153) 2025-08-03 20:09:03 +02:00
Jan-Otto Kröpke
6c2380bd04 installer: disable config file creation, if CONFIG_FILE is set to a non default location. (#2162) 2025-08-03 20:08:13 +02:00
Jan-Otto Kröpke
5266f9ebfe installer: add quote to avoid argument splitting (#2161) 2025-08-03 19:39:59 +02:00
renovate[bot]
6c9a5b66e2 chore(deps): update dependency golangci/golangci-lint to v2.3.1 (#2158)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jan-Otto Kröpke <mail@jkroepke.de>
2025-08-03 04:27:21 +02:00
renovate[bot]
c4ab8cb8a5 chore(deps): update docker/metadata-action action to v5.8.0 (#2159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 21:01:20 +02:00
renovate[bot]
7bcaf81d26 fix(deps): update module github.com/prometheus/client_golang to v1.23.0 (#2160)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 20:49:03 +02:00
renovate[bot]
5f6ba2c6e7 fix(deps): update module github.com/bmatcuk/doublestar/v4 to v4.9.1 (#2157)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 15:47:11 +02:00
Jan-Otto Kröpke
75c85fbde1 docs: add note about property preferences (#2155) 2025-07-29 20:53:50 +02:00
Jan-Otto Kröpke
120c244313 docs: Update example_config.yml (#2152) 2025-07-28 22:53:13 +02:00
Jan-Otto Kröpke
0e2d78affe docs: allow backport PR title prefix. (#2142) 2025-07-20 02:30:42 +02:00
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
35 changed files with 818 additions and 760 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.3.1
args: "--max-same-issues=0"

View File

@@ -37,7 +37,7 @@ jobs:
- name: check
run: |
PR_TITLE_PREFIX=$(echo "$PR_TITLE" | cut -d':' -f1)
if [[ -d "internal/collector/$PR_TITLE_PREFIX" ]] || [[ -d "internal/$PR_TITLE_PREFIX" ]] || [[ -d "pkg/$PR_TITLE_PREFIX" ]] || [[ -d "$PR_TITLE_PREFIX" ]] || [[ "$PR_TITLE_PREFIX" == "docs" ]] || [[ "$PR_TITLE_PREFIX" == "ci" ]] || [[ "$PR_TITLE_PREFIX" == "revert" ]] || [[ "$PR_TITLE_PREFIX" == "fix" ]] || [[ "$PR_TITLE_PREFIX" == "fix(deps)" ]] || [[ "$PR_TITLE_PREFIX" == "feat" ]] || [[ "$PR_TITLE_PREFIX" == "chore" ]] || [[ "$PR_TITLE_PREFIX" == "chore(docs)" ]] || [[ "$PR_TITLE_PREFIX" == "chore(deps)" ]] || [[ "$PR_TITLE_PREFIX" == "*" ]] || [[ "$PR_TITLE_PREFIX" == "Release"* ]] || [[ "$PR_TITLE_PREFIX" == "Synchronize common files from prometheus/prometheus" ]]; then
if [[ -d "internal/collector/$PR_TITLE_PREFIX" ]] || [[ -d "internal/$PR_TITLE_PREFIX" ]] || [[ -d "pkg/$PR_TITLE_PREFIX" ]] || [[ -d "$PR_TITLE_PREFIX" ]] || [[ "$PR_TITLE_PREFIX" == "docs" ]] || [[ "$PR_TITLE_PREFIX" == "ci" ]] || [[ "$PR_TITLE_PREFIX" == "revert" ]] || [[ "$PR_TITLE_PREFIX" == "fix" ]] || [[ "$PR_TITLE_PREFIX" == "fix(deps)" ]] || [[ "$PR_TITLE_PREFIX" == "feat" ]] || [[ "$PR_TITLE_PREFIX" == "chore" ]] || [[ "$PR_TITLE_PREFIX" == "chore(docs)" ]] || [[ "$PR_TITLE_PREFIX" == "chore(deps)" ]] || [[ "$PR_TITLE_PREFIX" == "*" ]] || [[ "$PR_TITLE_PREFIX" == "Release"* ]] || [[ "$PR_TITLE_PREFIX" == "Synchronize common files from prometheus/prometheus" ]] || [[ "$PR_TITLE_PREFIX" == "[0."* ]] || [[ "$PR_TITLE_PREFIX" == "[1."* ]]; then
exit 0
fi

View File

@@ -214,7 +214,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with:
images: |
ghcr.io/prometheus-community/windows-exporter

View File

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

View File

@@ -82,15 +82,15 @@ This can be useful for having different Prometheus servers collect specific metr
windows_exporter accepts flags to configure certain behaviours. The ones configuring the global behaviour of the exporter are listed below, while collector-specific ones are documented in the respective collector documentation above.
| Flag | Description | Default value |
|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| `--web.listen-address` | host:port for exporter. | `:9182` |
| `--telemetry.path` | URL path for surfacing collected metrics. | `/metrics` |
| `--collectors.enabled` | Comma-separated list of collectors to use. Use `[defaults]` as a placeholder which gets expanded containing all the collectors enabled by default. | `[defaults]` |
| `--scrape.timeout-margin` | Seconds to subtract from the timeout allowed by the client. Tune to allow for overhead or high loads. | `0.5` |
| `--web.config.file` | A [web config][web_config] for setting up TLS and Auth | None |
| `--config.file` | [Using a config file](#using-a-configuration-file) from path or URL | None |
| `--log.file` | Output file of log messages. One of [stdout, stderr, eventlog, \<path to log file>]<br>**NOTE:** The MSI installer will add a default argument to the installed service setting this to eventlog | stderr |
| Flag | Description | Default value |
|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| `--web.listen-address` | host:port for exporter. | `:9182` |
| `--telemetry.path` | URL path for surfacing collected metrics. | `/metrics` |
| `--collectors.enabled` | Comma-separated list of collectors to use. Use `[defaults]` as a placeholder which gets expanded containing all the collectors enabled by default. | `[defaults]` |
| `--scrape.timeout-margin` | Seconds to subtract from the timeout allowed by the client. Tune to allow for overhead or high loads. | `0.5` |
| `--web.config.file` | A [web config][web_config] for setting up TLS and Auth | None |
| `--config.file` | [Using a config file](#using-a-configuration-file) from path | None |
| `--log.file` | Output file of log messages. One of [stdout, stderr, eventlog, \<path to log file>]<br>**NOTE:** The MSI installer will add a default argument to the installed service setting this to eventlog | stderr |
## Installation
@@ -112,20 +112,22 @@ The configuration file
The following parameters are available:
| Name | Description |
|----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ENABLED_COLLECTORS` | As the `--collectors.enabled` flag, provide a comma-separated list of enabled collectors |
| `CONFIG_FILE` | Use the `--config.file` flag to specify a config file. If empty, no config file will be set. The special value `config.yaml` set the path to the config.yaml at install dir | |
| `LISTEN_ADDR` | The IP address to bind to. Defaults to an empty string. (any local address) |
| `LISTEN_PORT` | The port to bind to. Defaults to `9182`. |
| `METRICS_PATH` | The path at which to serve metrics. Defaults to `/metrics` |
| `TEXTFILE_DIRS` | Use the `--collector.textfile.directories` flag to specify one or more directories, separated by commas, where the collector should read text files containing metrics |
| `REMOTE_ADDR` | Allows setting comma separated remote IP addresses for the Windows Firewall exception (allow list). Defaults to an empty string (any remote address). |
| `EXTRA_FLAGS` | Allows passing full CLI flags. Defaults to an empty string. For `--collectors.enabled` and `--config.file`, use the specialized properties `ENABLED_COLLECTORS` and `CONFIG_FILE` |
| `ADDLOCAL` | Enables features within the windows_exporter installer. Supported values: `FirewallException` |
| `REMOVE` | Disables features within the windows_exporter installer. Supported values: `FirewallException` |
| `APPLICATIONFOLDER` | Directory to install windows_exporter. Defaults to `C:\Program Files\windows_exporter` |
| Name | Description |
|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ENABLED_COLLECTORS` | As the `--collectors.enabled` flag, provide a comma-separated list of enabled collectors |
| `CONFIG_FILE` | Use the `--config.file` flag to specify a config file. If empty, default config file at install dir will be used. If set, the config file must be exist before the installation is started. | |
| `LISTEN_ADDR` | The IP address to bind to. Defaults to an empty string. (any local address) |
| `LISTEN_PORT` | The port to bind to. Defaults to `9182`. |
| `METRICS_PATH` | The path at which to serve metrics. Defaults to `/metrics` |
| `TEXTFILE_DIRS` | Use the `--collector.textfile.directories` flag to specify one or more directories, separated by commas, where the collector should read text files containing metrics |
| `REMOTE_ADDR` | Allows setting comma separated remote IP addresses for the Windows Firewall exception (allow list). Defaults to an empty string (any remote address). |
| `EXTRA_FLAGS` | Allows passing full CLI flags. Defaults to an empty string. For `--collectors.enabled` and `--config.file`, use the specialized properties `ENABLED_COLLECTORS` and `CONFIG_FILE` |
| `ADDLOCAL` | Enables features within the windows_exporter installer. Supported values: `FirewallException` |
| `REMOVE` | Disables features within the windows_exporter installer. Supported values: `FirewallException` |
| `APPLICATIONFOLDER` | Directory to install windows_exporter. Defaults to `C:\Program Files\windows_exporter` |
> [!NOTE]
> The installer properties are always preferred over the values defined in the config file. If you prefer to configure via the config file, avoid using any of the properties listed above.
Parameters are sent to the installer via `msiexec`.
On PowerShell, the `--%` should be passed before defining properties.
@@ -182,9 +184,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 +194,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 +202,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 +214,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

@@ -171,8 +171,10 @@ func waitUntilListening(tb testing.TB, network, address string) error {
err error
)
dialer := &net.Dialer{Timeout: 100 * time.Millisecond}
for range 20 {
conn, err = net.DialTimeout(network, address, 100*time.Millisecond)
conn, err = dialer.DialContext(tb.Context(), network, address)
if err == nil {
_ = conn.Close()

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

View File

@@ -42,6 +42,11 @@ Disabled by default, and can be enabled with `--collector.process.iis`. NOTE: Ju
Version of the process collector to use. 1 for Process V1, 2 for Process V2.
Defaults to 0 which will use the latest version available.
### `--collector.process.cmdline`
Enables the `cmdline` label for the process metrics.
This label contains the command line used to start the process.
Enabled by default, and can be turned off with `--no-collector.process.cmdline`.
### Example
To match all firefox processes: `--collector.process.include="firefox.*"`.

View File

@@ -13,6 +13,5 @@ scrape:
timeout-margin: 0.5
telemetry:
path: /metrics
max-requests: 5
web:
listen-address: ":9182"

14
go.mod
View File

@@ -4,15 +4,15 @@ 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.1
github.com/dimchansky/utfbom v1.1.1
github.com/go-ole/go-ole v1.3.0
github.com/prometheus/client_golang v1.22.0
github.com/prometheus/client_golang v1.23.0
github.com/prometheus/client_model v0.6.2
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
)

30
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.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE=
github.com/bmatcuk/doublestar/v4 v4.9.1/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=
@@ -41,8 +41,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
@@ -65,19 +65,21 @@ 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=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
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

@@ -21,6 +21,14 @@
<DirectoryRef Id="APPLICATIONFOLDER">
<Component Transitive="yes">
<File Id="windows_exporter.exe" Name="windows_exporter.exe" Source="Work\windows_exporter.exe" KeyPath="yes" Vital="yes" Checksum="yes"/>
<!-- The "Name" field must match the argument to eventlog.Open() -->
<util:EventSource Log="Application" Name="windows_exporter"
EventMessageFile="%SystemRoot%\System32\EventCreate.exe"
SupportsErrors="yes"
SupportsInformationals="yes"
SupportsWarnings="yes"/>
<ServiceInstall
Id="InstallExporterService"
Name="windows_exporter"
@@ -45,13 +53,8 @@
/>
<ServiceDependency Id="wmiApSrv" />
</ServiceInstall>
<ServiceControl Id="ServiceStateControl" Name="windows_exporter" Remove="uninstall" Start="install" Stop="both"/>
<!-- The "Name" field must match the argument to eventlog.Open() -->
<util:EventSource Log="Application" Name="windows_exporter"
EventMessageFile="%SystemRoot%\System32\EventCreate.exe"
SupportsErrors="yes"
SupportsInformationals="yes"
SupportsWarnings="yes"/>
<ServiceControl Id="StartService" Name="windows_exporter" Start="install" Wait="no" />
<ServiceControl Id="StopService" Name="windows_exporter" Remove="uninstall" Stop="both" Wait="yes" />
</Component>
<Component Id="CreateTextfileDirectory" Directory="textfile_inputs" Guid="d03ef58a-9cbf-4165-ad39-d143e9b27e14">
<CreateFolder />

View File

@@ -45,6 +45,40 @@
Property="OLDERVERSIONBEINGUPGRADED" />
</Upgrade>
<Media Id="1" Cabinet="windows_exporter.cab" EmbedCab="yes" />
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit." AllowSameVersionUpgrades="yes" />
<Property Id="ENABLED_COLLECTORS" Secure="yes" />
<SetProperty Id="CollectorsFlag" After="InstallFiles" Sequence="execute" Value="--collectors.enabled [ENABLED_COLLECTORS]" Condition="ENABLED_COLLECTORS" />
<Property Id="EXTRA_FLAGS" Secure="yes" />
<SetProperty Id="ExtraFlags" After="InstallFiles" Sequence="execute" Value="[EXTRA_FLAGS]" Condition="EXTRA_FLAGS" />
<Property Id="CONFIG_FILE" Secure="yes" Value="config.yaml" />
<SetProperty Id="ConfigFile_NonDefault" After="InstallFiles" Sequence="execute" Value="[CONFIG_FILE]" Condition="CONFIG_FILE AND CONFIG_FILE&lt;&gt;&quot;config.yaml&quot;" />
<SetProperty Id="ConfigFile_Default" After="InstallFiles" Sequence="execute" Value="[APPLICATIONFOLDER]config.yaml" Condition="CONFIG_FILE=&quot;config.yaml&quot;" />
<SetProperty Id="ConfigFileFlag" After="InstallFiles" Sequence="execute" Value="--config.file=&quot;[ConfigFile_NonDefault][ConfigFile_Default]&quot;" Condition="ConfigFile_NonDefault OR ConfigFile_Default" />
<Property Id="LISTEN_PORT" Secure="yes" Value="9182" />
<SetProperty Id="ListenFlag" After="InstallFiles" Sequence="execute" Value="--web.listen-address=&quot;[LISTEN_ADDR]:[LISTEN_PORT]&quot;" Condition="LISTEN_ADDR&lt;&gt;&quot;&quot; OR LISTEN_PORT&lt;&gt;9182" />
<Property Id="METRICS_PATH" Secure="yes" />
<SetProperty Id="MetricsPathFlag" After="InstallFiles" Sequence="execute" Value="--telemetry.path=&quot;[METRICS_PATH]&quot;" Condition="METRICS_PATH" />
<Property Id="REMOTE_ADDR" Secure="yes" />
<SetProperty Id="RemoteAddressFlag" After="InstallFiles" Sequence="execute" Value="[REMOTE_ADDR]" Condition="REMOTE_ADDR" />
<Property Id="TEXTFILE_DIRS" Secure="yes" />
<SetProperty Id="TextfileDirsFlag" After="InstallFiles" Sequence="execute" Value="--collector.textfile.directories=&quot;[TEXTFILE_DIRS]&quot;" Condition="TEXTFILE_DIRS" />
<Property Id="ARPHELPLINK" Value="https://github.com/prometheus-community/windows_exporter/issues" />
<Property Id="ARPSIZE" Value="9000" />
<Property Id="ARPURLINFOABOUT" Value="https://github.com/prometheus-community/windows_exporter" />
<!--<Property Id="ARPNOMODIFY" Value="0" />-->
<!--<Property Id="ARPNOREPAIR" Value="1" />-->
<Property Id="START_MENU_FOLDER" Value="0" />
<Property Id="NOSTART" Value="0" />
<CustomAction Id="CheckExtraFlags"
Error="The parameter '--config.file' must not be included in EXTRA_FLAGS. Use CONFIG_FILE instead. Please remove it and try again." />
@@ -100,47 +134,13 @@
Condition="Installed AND (NOT REMOVE) AND (NOT UPGRADINGPRODUCTCODE)"/>
<Custom Action="set_reinstall_all_property" Before="set_reinstallmode_property" Condition="MAINTENANCE"/>
<Custom Action="set_reinstallmode_property" Before="LaunchConditions" Condition="MAINTENANCE"/>
<Custom Action="CreateConfigFile" Before="InstallServices" Condition="ConfigFile_NonDefault OR ConfigFile_Default" />
<Custom Action="CreateConfigFile" Before="InstallServices" Condition="ConfigFile_Default" />
<Custom Action="KillProcess" Before="RemoveFiles" />
<Custom Action="CheckExtraFlags" Before="InstallInitialize"
Condition="EXTRA_FLAGS AND (EXTRA_FLAGS&gt;&lt;&quot;--config.file&quot;)" />
</InstallExecuteSequence>
<Media Id="1" Cabinet="windows_exporter.cab" EmbedCab="yes" />
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit." AllowSameVersionUpgrades="yes" />
<Property Id="ENABLED_COLLECTORS" Secure="yes" />
<SetProperty Id="CollectorsFlag" After="InstallFiles" Sequence="execute" Value="--collectors.enabled [ENABLED_COLLECTORS]" Condition="ENABLED_COLLECTORS" />
<Property Id="EXTRA_FLAGS" Secure="yes" />
<SetProperty Id="ExtraFlags" After="InstallFiles" Sequence="execute" Value="[EXTRA_FLAGS]" Condition="EXTRA_FLAGS" />
<Property Id="CONFIG_FILE" Secure="yes" Value="config.yaml" />
<SetProperty Id="ConfigFile_NonDefault" After="InstallFiles" Sequence="execute" Value="[CONFIG_FILE]" Condition="CONFIG_FILE AND CONFIG_FILE&lt;&gt;&quot;config.yaml&quot;" />
<SetProperty Id="ConfigFile_Default" After="InstallFiles" Sequence="execute" Value="[APPLICATIONFOLDER]config.yaml" Condition="CONFIG_FILE=&quot;config.yaml&quot;" />
<SetProperty Id="ConfigFileFlag" After="InstallFiles" Sequence="execute" Value="--config.file=&quot;[ConfigFile_NonDefault][ConfigFile_Default]&quot;" Condition="ConfigFile_NonDefault OR ConfigFile_Default" />
<Property Id="LISTEN_PORT" Secure="yes" Value="9182" />
<SetProperty Id="ListenFlag" After="InstallFiles" Sequence="execute" Value="--web.listen-address [LISTEN_ADDR]:[LISTEN_PORT]" Condition="LISTEN_ADDR&lt;&gt;&quot;&quot; OR LISTEN_PORT&lt;&gt;9182" />
<Property Id="METRICS_PATH" Secure="yes" />
<SetProperty Id="MetricsPathFlag" After="InstallFiles" Sequence="execute" Value="--telemetry.path [METRICS_PATH]" Condition="METRICS_PATH" />
<Property Id="REMOTE_ADDR" Secure="yes" />
<SetProperty Id="RemoteAddressFlag" After="InstallFiles" Sequence="execute" Value="[REMOTE_ADDR]" Condition="REMOTE_ADDR" />
<Property Id="TEXTFILE_DIRS" Secure="yes" />
<SetProperty Id="TextfileDirsFlag" After="InstallFiles" Sequence="execute" Value="--collector.textfile.directories [TEXTFILE_DIRS]" Condition="TEXTFILE_DIRS" />
<Property Id="ARPHELPLINK" Value="https://github.com/prometheus-community/windows_exporter/issues" />
<Property Id="ARPSIZE" Value="9000" />
<Property Id="ARPURLINFOABOUT" Value="https://github.com/prometheus-community/windows_exporter" />
<!--<Property Id="ARPNOMODIFY" Value="0" />-->
<!--<Property Id="ARPNOREPAIR" Value="1" />-->
<Property Id="START_MENU_FOLDER" Value="0" />
<Property Id="NOSTART" Value="0" />
<Feature
Id="DefaultFeature"
Level="1"

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

@@ -44,6 +44,7 @@ type Config struct {
ProcessInclude *regexp.Regexp `yaml:"include"`
ProcessExclude *regexp.Regexp `yaml:"exclude"`
EnableWorkerProcess bool `yaml:"iis"`
EnableCMDLine bool `yaml:"cmdline"`
CounterVersion uint8 `yaml:"counter-version"`
}
@@ -52,6 +53,7 @@ var ConfigDefaults = Config{
ProcessInclude: types.RegExpAny,
ProcessExclude: types.RegExpEmpty,
EnableWorkerProcess: false,
EnableCMDLine: true,
CounterVersion: 0,
}
@@ -131,6 +133,11 @@ func NewWithFlags(app *kingpin.Application) *Collector {
"Enable IIS collectWorker process name queries. May cause the collector to leak memory.",
).Default(strconv.FormatBool(c.config.EnableWorkerProcess)).BoolVar(&c.config.EnableWorkerProcess)
app.Flag(
"collector.process.cmdline",
"If enabled, the full cmdline is exposed to the windows_process_info metrics.",
).Default(strconv.FormatBool(c.config.EnableCMDLine)).BoolVar(&c.config.EnableCMDLine)
app.Flag(
"collector.process.counter-version",
"Version of the process collector to use. 1 for Process V1, 2 for Process V2. Defaults to 0 which will use the latest version available.",
@@ -415,19 +422,25 @@ func (c *Collector) getExtendedProcessInformation(hProcess windows.Handle) (stri
return "", 0, fmt.Errorf("failed to read process memory: %w", err)
}
cmdLineUTF16 := make([]uint16, processParameters.CommandLine.Length)
var cmdLine string
err = windows.ReadProcessMemory(hProcess,
uintptr(unsafe.Pointer(processParameters.CommandLine.Buffer)),
(*byte)(unsafe.Pointer(&cmdLineUTF16[0])),
uintptr(processParameters.CommandLine.Length),
nil,
)
if err != nil {
return "", processParameters.ProcessGroupId, fmt.Errorf("failed to read process memory: %w", err)
if c.config.EnableCMDLine {
cmdLineUTF16 := make([]uint16, processParameters.CommandLine.Length)
err = windows.ReadProcessMemory(hProcess,
uintptr(unsafe.Pointer(processParameters.CommandLine.Buffer)),
(*byte)(unsafe.Pointer(&cmdLineUTF16[0])),
uintptr(processParameters.CommandLine.Length),
nil,
)
if err != nil {
return "", processParameters.ProcessGroupId, fmt.Errorf("failed to read process memory: %w", err)
}
cmdLine = strings.TrimSpace(windows.UTF16ToString(cmdLineUTF16))
}
return strings.TrimSpace(windows.UTF16ToString(cmdLineUTF16)), processParameters.ProcessGroupId, nil
return cmdLine, processParameters.ProcessGroupId, nil
}
func (c *Collector) getProcessOwner(logger *slog.Logger, hProcess windows.Handle) (string, error) {

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

@@ -38,7 +38,9 @@ func TestGetTCPConnectionStates(t *testing.T) {
func TestGetOwnerPIDOfTCPPort(t *testing.T) {
t.Parallel()
lister, err := net.Listen("tcp", "127.0.0.1:0")
var listenConf net.ListenConfig
lister, err := listenConf.Listen(t.Context(), "tcp", "127.0.0.1:0")
require.NoError(t, err)
t.Cleanup(func() {

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.