mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-08 05:56:37 +00:00
Compare commits
17 Commits
v0.31.0-be
...
v0.31.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55c877f536 | ||
|
|
dcf85032ca | ||
|
|
e673f192d2 | ||
|
|
298d820bd6 | ||
|
|
89ac99e6a2 | ||
|
|
3e58d0e568 | ||
|
|
5cf1f7e623 | ||
|
|
0580b330a5 | ||
|
|
25915bb289 | ||
|
|
5e1a802237 | ||
|
|
6dd21a8e00 | ||
|
|
92f213ca7c | ||
|
|
ecd7dcfb0d | ||
|
|
87d76b18e9 | ||
|
|
068bcb7237 | ||
|
|
898e16bcb1 | ||
|
|
6b87441729 |
4
.github/workflows/container_description.yml
vendored
4
.github/workflows/container_description.yml
vendored
@@ -19,6 +19,8 @@ jobs:
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set docker hub repo name
|
||||
run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV
|
||||
- name: Push README to Dockerhub
|
||||
@@ -41,6 +43,8 @@ jobs:
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set quay.io org name
|
||||
run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV
|
||||
- name: Set quay.io repo name
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -234,7 +234,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -4,9 +4,20 @@ VERSION
|
||||
*.un~
|
||||
output/
|
||||
.vscode
|
||||
.idea
|
||||
*.syso
|
||||
installer/*.msi
|
||||
installer/*.wixpdb
|
||||
local/
|
||||
!.idea/inspectionProfiles/Project_Default.xml
|
||||
|
||||
/.idea/*
|
||||
!/.idea/inspectionProfiles/
|
||||
/.idea/inspectionProfiles/*
|
||||
!/.idea/inspectionProfiles/Project_Default.xml
|
||||
!/.idea/dictionaries/
|
||||
/.idea/dictionaries/*
|
||||
!/.idea/dictionaries/project.xml
|
||||
/.idea/copyright/*
|
||||
!/.idea/copyright/profiles_settings.xml
|
||||
!/.idea/copyright/windows_exporter.xml
|
||||
!/.idea/vcs.xml
|
||||
!/.idea/go.imports.xml
|
||||
|
||||
@@ -4,6 +4,7 @@ linters:
|
||||
disable:
|
||||
- cyclop
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- err113
|
||||
- exhaustive
|
||||
@@ -56,6 +57,11 @@ linters:
|
||||
excludes:
|
||||
- G101
|
||||
- G115
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
- fieldalignment
|
||||
- shadow
|
||||
sloglint:
|
||||
no-mixed-args: true
|
||||
kv-only: false
|
||||
|
||||
11
.idea/copyright/profiles_settings.xml
generated
Normal file
11
.idea/copyright/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings default="windows_exporter">
|
||||
<module2copyright>
|
||||
<element module="All Changed Files" copyright="windows_exporter" />
|
||||
</module2copyright>
|
||||
<LanguageOptions name="Go">
|
||||
<option name="fileTypeOverride" value="3" />
|
||||
<option name="block" value="false" />
|
||||
</LanguageOptions>
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/copyright/windows_exporter.xml
generated
Normal file
7
.idea/copyright/windows_exporter.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="keyword" value="The Prometheus Authors" />
|
||||
<option name="notice" value="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." />
|
||||
<option name="myName" value="windows_exporter" />
|
||||
</copyright>
|
||||
</component>
|
||||
9
.idea/dictionaries/project.xml
generated
Normal file
9
.idea/dictionaries/project.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="project">
|
||||
<words>
|
||||
<w>containerd</w>
|
||||
<w>setupapi</w>
|
||||
<w>spdx</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
11
.idea/go.imports.xml
generated
Normal file
11
.idea/go.imports.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GoImports">
|
||||
<option name="excludedPackages">
|
||||
<array>
|
||||
<option value="github.com/pkg/errors" />
|
||||
<option value="golang.org/x/net/context" />
|
||||
</array>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="GoLinter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
97
README.md
97
README.md
@@ -12,54 +12,55 @@ A Prometheus exporter for Windows machines.
|
||||
|
||||
## Collectors
|
||||
|
||||
Name | Description | Enabled by default
|
||||
---------|-------------|--------------------
|
||||
[ad](docs/collector.ad.md) | Active Directory Domain Services |
|
||||
[adcs](docs/collector.adcs.md) | Active Directory Certificate Services |
|
||||
[adfs](docs/collector.adfs.md) | Active Directory Federation Services |
|
||||
[cache](docs/collector.cache.md) | Cache metrics |
|
||||
[cpu](docs/collector.cpu.md) | CPU usage | ✓
|
||||
[cpu_info](docs/collector.cpu_info.md) | CPU Information |
|
||||
[cs](docs/collector.cs.md) | "Computer System" metrics (system properties, num cpus/total memory) |
|
||||
[container](docs/collector.container.md) | Container metrics |
|
||||
[diskdrive](docs/collector.diskdrive.md) | Diskdrive metrics |
|
||||
[dfsr](docs/collector.dfsr.md) | DFSR metrics |
|
||||
[dhcp](docs/collector.dhcp.md) | DHCP Server |
|
||||
[dns](docs/collector.dns.md) | DNS Server |
|
||||
[exchange](docs/collector.exchange.md) | Exchange metrics |
|
||||
[filetime](docs/collector.filetime.md) | FileTime metrics |
|
||||
[fsrmquota](docs/collector.fsrmquota.md) | Microsoft File Server Resource Manager (FSRM) Quotas collector |
|
||||
[hyperv](docs/collector.hyperv.md) | Hyper-V hosts |
|
||||
[iis](docs/collector.iis.md) | IIS sites and applications |
|
||||
[license](docs/collector.license.md) | Windows license status |
|
||||
[logical_disk](docs/collector.logical_disk.md) | Logical disks, disk I/O | ✓
|
||||
[memory](docs/collector.memory.md) | Memory usage metrics | ✓
|
||||
[mscluster](docs/collector.mscluster.md) | MSCluster metrics |
|
||||
[msmq](docs/collector.msmq.md) | MSMQ queues |
|
||||
[mssql](docs/collector.mssql.md) | [SQL Server Performance Objects](https://docs.microsoft.com/en-us/sql/relational-databases/performance-monitor/use-sql-server-objects#SQLServerPOs) metrics |
|
||||
[netframework](docs/collector.netframework.md) | .NET Framework metrics |
|
||||
[net](docs/collector.net.md) | Network interface I/O | ✓
|
||||
[os](docs/collector.os.md) | OS metrics (memory, processes, users) | ✓
|
||||
[pagefile](docs/collector.pagefile.md) | pagefile metrics |
|
||||
[performancecounter](docs/collector.performancecounter.md) | Custom performance counter metrics |
|
||||
[physical_disk](docs/collector.physical_disk.md) | physical disk metrics | ✓
|
||||
[printer](docs/collector.printer.md) | Printer metrics |
|
||||
[process](docs/collector.process.md) | Per-process metrics |
|
||||
[remote_fx](docs/collector.remote_fx.md) | RemoteFX protocol (RDP) metrics |
|
||||
[scheduled_task](docs/collector.scheduled_task.md) | Scheduled Tasks metrics |
|
||||
[service](docs/collector.service.md) | Service state metrics | ✓
|
||||
[smb](docs/collector.smb.md) | SMB Server |
|
||||
[smbclient](docs/collector.smbclient.md) | SMB Client |
|
||||
[smtp](docs/collector.smtp.md) | IIS SMTP Server |
|
||||
[system](docs/collector.system.md) | System calls | ✓
|
||||
[tcp](docs/collector.tcp.md) | TCP connections |
|
||||
[terminal_services](docs/collector.terminal_services.md) | Terminal services (RDS)
|
||||
[textfile](docs/collector.textfile.md) | Read prometheus metrics from a text file |
|
||||
[thermalzone](docs/collector.thermalzone.md) | Thermal information |
|
||||
[time](docs/collector.time.md) | Windows Time Service |
|
||||
[udp](docs/collector.udp.md) | UDP connections |
|
||||
[update](docs/collector.update.md) | Windows Update Service |
|
||||
[vmware](docs/collector.vmware.md) | Performance counters installed by the Vmware Guest agent |
|
||||
Name | Description | Enabled by default
|
||||
------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------
|
||||
[ad](docs/collector.ad.md) | Active Directory Domain Services |
|
||||
[adcs](docs/collector.adcs.md) | Active Directory Certificate Services |
|
||||
[adfs](docs/collector.adfs.md) | Active Directory Federation Services |
|
||||
[cache](docs/collector.cache.md) | Cache metrics |
|
||||
[cpu](docs/collector.cpu.md) | CPU usage | ✓
|
||||
[cpu_info](docs/collector.cpu_info.md) | CPU Information |
|
||||
[cs](docs/collector.cs.md) | "Computer System" metrics (system properties, num cpus/total memory) |
|
||||
[container](docs/collector.container.md) | Container metrics |
|
||||
[diskdrive](docs/collector.diskdrive.md) | Diskdrive metrics |
|
||||
[dfsr](docs/collector.dfsr.md) | DFSR metrics |
|
||||
[dhcp](docs/collector.dhcp.md) | DHCP Server |
|
||||
[dns](docs/collector.dns.md) | DNS Server |
|
||||
[exchange](docs/collector.exchange.md) | Exchange metrics |
|
||||
[filetime](docs/collector.filetime.md) | FileTime metrics |
|
||||
[fsrmquota](docs/collector.fsrmquota.md) | Microsoft File Server Resource Manager (FSRM) Quotas collector |
|
||||
[gpu](docs/collector.gpu.md) | GPU metrics |
|
||||
[hyperv](docs/collector.hyperv.md) | Hyper-V hosts |
|
||||
[iis](docs/collector.iis.md) | IIS sites and applications |
|
||||
[license](docs/collector.license.md) | Windows license status |
|
||||
[logical_disk](docs/collector.logical_disk.md) | Logical disks, disk I/O | ✓
|
||||
[memory](docs/collector.memory.md) | Memory usage metrics | ✓
|
||||
[mscluster](docs/collector.mscluster.md) | MSCluster metrics |
|
||||
[msmq](docs/collector.msmq.md) | MSMQ queues |
|
||||
[mssql](docs/collector.mssql.md) | [SQL Server Performance Objects](https://docs.microsoft.com/en-us/sql/relational-databases/performance-monitor/use-sql-server-objects#SQLServerPOs) metrics |
|
||||
[netframework](docs/collector.netframework.md) | .NET Framework metrics |
|
||||
[net](docs/collector.net.md) | Network interface I/O | ✓
|
||||
[os](docs/collector.os.md) | OS metrics (memory, processes, users) | ✓
|
||||
[pagefile](docs/collector.pagefile.md) | pagefile metrics |
|
||||
[performancecounter](docs/collector.performancecounter.md) | Custom performance counter metrics |
|
||||
[physical_disk](docs/collector.physical_disk.md) | physical disk metrics | ✓
|
||||
[printer](docs/collector.printer.md) | Printer metrics |
|
||||
[process](docs/collector.process.md) | Per-process metrics |
|
||||
[remote_fx](docs/collector.remote_fx.md) | RemoteFX protocol (RDP) metrics |
|
||||
[scheduled_task](docs/collector.scheduled_task.md) | Scheduled Tasks metrics |
|
||||
[service](docs/collector.service.md) | Service state metrics | ✓
|
||||
[smb](docs/collector.smb.md) | SMB Server |
|
||||
[smbclient](docs/collector.smbclient.md) | SMB Client |
|
||||
[smtp](docs/collector.smtp.md) | IIS SMTP Server |
|
||||
[system](docs/collector.system.md) | System calls | ✓
|
||||
[tcp](docs/collector.tcp.md) | TCP connections |
|
||||
[terminal_services](docs/collector.terminal_services.md) | Terminal services (RDS)
|
||||
[textfile](docs/collector.textfile.md) | Read prometheus metrics from a text file |
|
||||
[thermalzone](docs/collector.thermalzone.md) | Thermal information |
|
||||
[time](docs/collector.time.md) | Windows Time Service |
|
||||
[udp](docs/collector.udp.md) | UDP connections |
|
||||
[update](docs/collector.update.md) | Windows Update Service |
|
||||
[vmware](docs/collector.vmware.md) | Performance counters installed by the Vmware Guest agent |
|
||||
|
||||
See the linked documentation on each collector for more information on reported metrics, configuration settings and usage examples.
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ func waitUntilListening(tb testing.TB, network, address string) error {
|
||||
err error
|
||||
)
|
||||
|
||||
for range 10 {
|
||||
for range 20 {
|
||||
conn, err = net.DialTimeout(network, address, 100*time.Millisecond)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
@@ -184,6 +184,14 @@ func waitUntilListening(tb testing.TB, network, address string) error {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
var winErr windows.Errno
|
||||
|
||||
if errors.As(err, &winErr) {
|
||||
return fmt.Errorf("listener not listening: %w (#%d)", winErr, uint32(winErr))
|
||||
}
|
||||
|
||||
return fmt.Errorf("listener not listening: %w", err)
|
||||
|
||||
@@ -5,7 +5,7 @@ The container collector exposes metrics about containers running on a Hyper-V sy
|
||||
|||
|
||||
-|-
|
||||
Metric name prefix | `container`
|
||||
Data source | [hcsshim](https://github.com/Microsoft/hcsshim)
|
||||
Data source | [HCS](https://learn.microsoft.com/en-us/virtualization/api/hcs/overview)
|
||||
Enabled by default? | No
|
||||
|
||||
## Flags
|
||||
@@ -14,26 +14,26 @@ None
|
||||
|
||||
## Metrics
|
||||
|
||||
Name | Description | Type | Labels
|
||||
-----|-------------|------|-------
|
||||
`windows_container_available` | Available | counter | `container_id`
|
||||
`windows_container_count` | Number of containers | gauge | `container_id`
|
||||
`windows_container_cpu_usage_seconds_kernelmode` | Run time in Kernel mode in Seconds | counter | `container_id`
|
||||
`windows_container_cpu_usage_seconds_usermode` | Run Time in User mode in Seconds | counter | `container_id`
|
||||
`windows_container_cpu_usage_seconds_total` | Total Run time in Seconds | counter | `container_id`
|
||||
`windows_container_memory_usage_commit_bytes` | Memory Usage Commit Bytes | gauge | `container_id`
|
||||
`windows_container_memory_usage_commit_peak_bytes` | Memory Usage Commit Peak Bytes | gauge | `container_id`
|
||||
`windows_container_memory_usage_private_working_set_bytes` | Memory Usage Private Working Set Bytes | gauge | `container_id`
|
||||
`windows_container_network_receive_bytes_total` | Bytes Received on Interface | counter | `container_id`, `interface`
|
||||
`windows_container_network_receive_packets_total` | Packets Received on Interface | counter | `container_id`, `interface`
|
||||
`windows_container_network_receive_packets_dropped_total` | Dropped Incoming Packets on Interface | counter | `container_id`, `interface`
|
||||
`windows_container_network_transmit_bytes_total` | Bytes Sent on Interface | counter | `container_id`, `interface`
|
||||
`windows_container_network_transmit_packets_total` | Packets Sent on Interface | counter | `container_id`, `interface`
|
||||
`windows_container_network_transmit_packets_dropped_total` | Dropped Outgoing Packets on Interface | counter | `container_id`, `interface`
|
||||
`windows_container_storage_read_count_normalized_total` | Read Count Normalized | counter | `container_id`
|
||||
`windows_container_storage_read_size_bytes_total` | Read Size Bytes | counter | `container_id`
|
||||
`windows_container_storage_write_count_normalized_total` | Write Count Normalized | counter | `container_id`
|
||||
`windows_container_storage_write_size_bytes_total` | Write Size Bytes | counter | `container_id`
|
||||
| Name | Description | Type | Labels |
|
||||
|------------------------------------------------------------|----------------------------------------|---------|----------------------------------------------------------|
|
||||
| `windows_container_available` | Available | counter | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_count` | Number of containers | gauge | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_cpu_usage_seconds_kernelmode` | Run time in Kernel mode in Seconds | counter | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_cpu_usage_seconds_usermode` | Run Time in User mode in Seconds | counter | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_cpu_usage_seconds_total` | Total Run time in Seconds | counter | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_memory_usage_commit_bytes` | Memory Usage Commit Bytes | gauge | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_memory_usage_commit_peak_bytes` | Memory Usage Commit Peak Bytes | gauge | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_memory_usage_private_working_set_bytes` | Memory Usage Private Working Set Bytes | gauge | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_network_receive_bytes_total` | Bytes Received on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
|
||||
| `windows_container_network_receive_packets_total` | Packets Received on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
|
||||
| `windows_container_network_receive_packets_dropped_total` | Dropped Incoming Packets on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
|
||||
| `windows_container_network_transmit_bytes_total` | Bytes Sent on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
|
||||
| `windows_container_network_transmit_packets_total` | Packets Sent on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
|
||||
| `windows_container_network_transmit_packets_dropped_total` | Dropped Outgoing Packets on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` |
|
||||
| `windows_container_storage_read_count_normalized_total` | Read Count Normalized | counter | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_storage_read_size_bytes_total` | Read Size Bytes | counter | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_storage_write_count_normalized_total` | Write Count Normalized | counter | `container_id`,`namespace`,`pod`,`container`, |
|
||||
| `windows_container_storage_write_size_bytes_total` | Write Size Bytes | counter | `container_id`,`namespace`,`pod`,`container`, |
|
||||
|
||||
### Example metric
|
||||
_windows_container_network_receive_bytes_total{container_id="docker://1bd30e8b8ac28cbd76a9b697b4d7bb9d760267b0733d1bc55c60024e98d1e43e",interface="822179E7-002C-4280-ABBA-28BCFE401826"} 9.3305343e+07_
|
||||
|
||||
146
docs/collector.gpu.md
Normal file
146
docs/collector.gpu.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# gpu collector
|
||||
|
||||
The gpu collector exposes metrics about GPU usage and memory consumption, both at the adapter (physical GPU) and
|
||||
per-process level.
|
||||
|
||||
| | |
|
||||
|---------------------|--------------------------------------|
|
||||
| Metric name prefix | `gpu` |
|
||||
| Data source | Perflib |
|
||||
| Counters | GPU Engine, GPU Adapter, GPU Process |
|
||||
| Enabled by default? | No |
|
||||
|
||||
## Flags
|
||||
|
||||
None
|
||||
|
||||
## Metrics
|
||||
|
||||
These metrics are available on supported versions of Windows with compatible GPUs and drivers:
|
||||
|
||||
### 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` |
|
||||
|
||||
### 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` |
|
||||
|
||||
## Metric Labels
|
||||
|
||||
* `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
|
||||
|
||||
## Example Metric
|
||||
|
||||
These are basic queries to help you get started with GPU monitoring on Windows using Prometheus.
|
||||
|
||||
**Show GPU information for a specific physical GPU (0):**
|
||||
|
||||
```promql
|
||||
windows_gpu_info{description="NVIDIA GeForce GTX 1070",friendly_name="",hardware_id="PCI\\VEN_10DE&DEV_1B81&SUBSYS_61733842&REV_A1",phys="0",physical_device_object_name="\\Device\\NTPNP_PCI0027"} 1
|
||||
```
|
||||
|
||||
**Show total dedicated GPU memory (in bytes) usage on GPU 0:**
|
||||
|
||||
```promql
|
||||
windows_gpu_adapter_memory_dedicated_bytes{phys="0"}
|
||||
```
|
||||
|
||||
**Aggregate GPU utilization across all processes for a physical GPU (3D engine):**
|
||||
|
||||
```promql
|
||||
sum by (phys) (
|
||||
rate(windows_gpu_engine_time_seconds{phys="0", engtype="3D"}[1m])
|
||||
) * 100
|
||||
```
|
||||
|
||||
**Show GPU utilization for a specific process (3D engine):**
|
||||
|
||||
```promql
|
||||
sum by (phys, process_id) (
|
||||
rate(windows_gpu_engine_time_seconds{process_id="1234", engtype="3D"}[1m])
|
||||
) * 100
|
||||
```
|
||||
|
||||
**Show dedicated GPU memory per process:**
|
||||
|
||||
```promql
|
||||
windows_gpu_adapter_memory_dedicated_bytes
|
||||
```
|
||||
|
||||
## Useful Queries
|
||||
|
||||
**Show top 5 processes by GPU utilization (all engines):**
|
||||
|
||||
```promql
|
||||
topk(5, sum by (process_id) (
|
||||
rate(windows_gpu_engine_time_seconds[1m])
|
||||
) * 100)
|
||||
```
|
||||
|
||||
**Show GPU memory usage per physical GPU:**
|
||||
|
||||
```promql
|
||||
sum by (phys) (
|
||||
windows_gpu_adapter_memory_dedicated_bytes
|
||||
)
|
||||
```
|
||||
|
||||
Show GPU engine time with process owner and command line:
|
||||
|
||||
```promql
|
||||
windows_gpu_engine_time_seconds * on(process_id) group_left(owner, cmdline) windows_process_info
|
||||
```
|
||||
|
||||
## Alerting Examples
|
||||
|
||||
**prometheus.rules**
|
||||
|
||||
```yaml
|
||||
# Alert on processes using more than 80% of a GPU's capacity over 10 minutes
|
||||
- alert: HighGpuUtilization
|
||||
expr: |
|
||||
sum by (process_id) (
|
||||
rate(windows_gpu_engine_time_seconds[1m])
|
||||
) * 100 > 80
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High GPU Utilization (process {{ $labels.process_id }})"
|
||||
description: "Process is using more than 80% of GPU resources\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
* Per-process metrics allow you to identify which processes are consuming GPU resources.
|
||||
* Adapter-level metrics provide an overview of total GPU memory usage.
|
||||
* For overall GPU utilization, aggregate per-process metrics in Prometheus using queries such as `sum()`.
|
||||
* The collector relies on Windows performance counters; ensure your system and drivers support these counters.
|
||||
|
||||
## Enabling the Collector
|
||||
|
||||
To enable the GPU collector, add `gpu` to the list of enabled collectors in your windows_exporter configuration.
|
||||
|
||||
Example (command line):
|
||||
|
||||
```shell
|
||||
windows_exporter.exe --collectors.enabled=gpu
|
||||
```
|
||||
@@ -5,7 +5,7 @@ The msmq collector exposes metrics about the queues on a MSMQ server
|
||||
| | |
|
||||
|---------------------|----------------------|
|
||||
| Metric name prefix | `msmq` |
|
||||
| Spource | Performance Counters |
|
||||
| Source | Performance Counters |
|
||||
| Enabled by default? | No |
|
||||
|
||||
## Flags
|
||||
|
||||
@@ -37,6 +37,11 @@ Enables IIS process name queries. IIS process names are combined with their app
|
||||
|
||||
Disabled by default, and can be enabled with `--collector.process.iis`. NOTE: Just plain parameter without `true`.
|
||||
|
||||
### `--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.
|
||||
|
||||
|
||||
### Example
|
||||
To match all firefox processes: `--collector.process.include="firefox.*"`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# time collector
|
||||
|
||||
The time collector exposes the Windows Time Service metrics. Note that the Windows Time Service must be running, else metric collection will fail.
|
||||
The time collector exposes the Windows Time Service and other time related metrics.
|
||||
If the Windows Time Service is stopped after collection has started, collector metric values will reset to 0.
|
||||
|
||||
Please note the Time Service perflib counters are only available on [Windows Server 2016 or newer](https://docs.microsoft.com/en-us/windows-server/networking/windows-time-service/windows-server-2016-improvements).
|
||||
@@ -32,9 +32,21 @@ Matching is case-sensitive.
|
||||
| `windows_time_ntp_server_incoming_requests_total` | Total number of requests received by the NTP server. | counter | None |
|
||||
| `windows_time_current_timestamp_seconds` | Current time as reported by the operating system, in [Unix time](https://en.wikipedia.org/wiki/Unix_time). See [time.Unix()](https://golang.org/pkg/time/#Unix) for details | gauge | None |
|
||||
| `windows_time_timezone` | Current timezone as reported by the operating system. | gauge | `timezone` |
|
||||
| `windows_time_clock_sync_source` | This value reflects the sync source of the system clock. | gauge | `type` |
|
||||
|
||||
### Example metric
|
||||
_This collector does not yet have explained examples, we would appreciate your help adding them!_
|
||||
```
|
||||
# HELP windows_time_clock_sync_source This value reflects the sync source of the system clock.
|
||||
# TYPE windows_time_clock_sync_source gauge
|
||||
windows_time_clock_sync_source{type="AllSync"} 0
|
||||
windows_time_clock_sync_source{type="Local CMOS Clock"} 0
|
||||
windows_time_clock_sync_source{type="NT5DS"} 0
|
||||
windows_time_clock_sync_source{type="NTP"} 1
|
||||
windows_time_clock_sync_source{type="NoSync"} 0
|
||||
# HELP windows_time_current_timestamp_seconds OperatingSystem.LocalDateTime
|
||||
# TYPE windows_time_current_timestamp_seconds gauge
|
||||
windows_time_current_timestamp_seconds 1.74862554e+09
|
||||
```
|
||||
|
||||
## Useful queries
|
||||
_This collector does not yet have any useful queries added, we would appreciate your help adding them!_
|
||||
|
||||
@@ -26,17 +26,23 @@ Define the interval of scraping Windows Update information
|
||||
|
||||
## Metrics
|
||||
|
||||
| Name | Description | Type | Labels |
|
||||
|--------------------------------|-------------------------------------------------------------|-------|-------------------------------|
|
||||
| `windows_update_pending_info` | Expose information for a single pending update item | gauge | `category`,`severity`,`title` |
|
||||
| `windows_update_scrape_query_duration_seconds` | Duration of the last scrape query to the Windows Update API | gauge | |
|
||||
| `windows_update_scrape_timestamp_seconds` | Timestamp of the last scrape | gauge | |
|
||||
| Name | Description | Type | Labels |
|
||||
|------------------------------------------------|------------------------------------------------------------------|-------|-------------------------------|
|
||||
| `windows_update_pending_info` | Expose information for a single pending update item | gauge | `category`,`severity`,`title` |
|
||||
| `windows_update_pending_published_timestamp` | Expose last published timestamp for a single pending update item | gauge | `title` |
|
||||
| `windows_update_scrape_query_duration_seconds` | Duration of the last scrape query to the Windows Update API | gauge | |
|
||||
| `windows_update_scrape_timestamp_seconds` | Timestamp of the last scrape | gauge | |
|
||||
|
||||
### Example metrics
|
||||
```
|
||||
# HELP windows_update_pending Pending Windows Updates
|
||||
# TYPE windows_update_pending gauge
|
||||
windows_update_pending{category="Drivers",severity="",title="Intel Corporation - Bluetooth - 23.60.5.10"} 1
|
||||
# HELP windows_update_pending_info Expose information for a single pending update item
|
||||
# TYPE windows_update_pending_info gauge
|
||||
windows_update_pending_info{category="Definition Updates",id="a32ca1d0-ddd4-486b-b708-d941db4f1051",revision="204",severity="",title="Update for Windows Security platform - KB5007651 (Version 10.0.27840.1000)"} 1
|
||||
windows_update_pending_info{category="Definition Updates",id="b50a64de-a0bb-465b-9842-9963b6eee21e",revision="200",severity="",title="Security Intelligence Update for Microsoft Defender Antivirus - KB2267602 (Version 1.429.146.0) - Current Channel (Broad)"} 1
|
||||
# HELP windows_update_pending_published_timestamp Expose last published timestamp for a single pending update item
|
||||
# TYPE windows_update_pending_published_timestamp gauge
|
||||
windows_update_pending_published_timestamp{id="a32ca1d0-ddd4-486b-b708-d941db4f1051",revision="204"} 1.747872e+09
|
||||
windows_update_pending_published_timestamp{id="b50a64de-a0bb-465b-9842-9963b6eee21e",revision="200"} 1.7479584e+09
|
||||
# HELP windows_update_scrape_query_duration_seconds Duration of the last scrape query to the Windows Update API
|
||||
# TYPE windows_update_scrape_query_duration_seconds gauge
|
||||
windows_update_scrape_query_duration_seconds 2.8161838
|
||||
@@ -46,7 +52,12 @@ windows_update_scrape_timestamp_seconds 1.727539734e+09
|
||||
```
|
||||
|
||||
## Useful queries
|
||||
_This collector does not yet have any useful queries added, we would appreciate your help adding them!_
|
||||
|
||||
Add extended information like cmdline or owner to other process metrics.
|
||||
|
||||
```
|
||||
windows_update_pending_published_timestamp * on(id, revision) group_left(severity, title) windows_update_pending_info
|
||||
```
|
||||
|
||||
## Alerting examples
|
||||
_This collector does not yet have alerting examples, we would appreciate your help adding them!_
|
||||
|
||||
13
go.mod
13
go.mod
@@ -3,7 +3,6 @@ module github.com/prometheus-community/windows_exporter
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/Microsoft/hcsshim v0.13.0
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.1
|
||||
github.com/dimchansky/utfbom v1.1.1
|
||||
@@ -18,36 +17,24 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.5 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mdlayher/vsock v1.2.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect
|
||||
google.golang.org/grpc v1.72.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
127
go.sum
127
go.sum
@@ -1,9 +1,3 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
|
||||
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
|
||||
@@ -12,19 +6,8 @@ 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
|
||||
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
|
||||
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -32,44 +15,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -86,14 +38,11 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
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.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||
@@ -104,105 +53,31 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
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=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
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.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
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=
|
||||
@@ -213,5 +88,3 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -18,25 +18,51 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
|
||||
"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"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const Name = "container"
|
||||
const (
|
||||
Name = "container"
|
||||
|
||||
type Config struct{}
|
||||
subCollectorHCS = "hcs"
|
||||
subCollectorHostprocess = "hostprocess"
|
||||
|
||||
JobObjectMemoryUsageInformation = 28
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CollectorsEnabled []string `yaml:"enabled"`
|
||||
ContainerDStateDir string `yaml:"containerd-state-dir"`
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var ConfigDefaults = Config{}
|
||||
var ConfigDefaults = Config{
|
||||
CollectorsEnabled: []string{
|
||||
subCollectorHCS,
|
||||
subCollectorHostprocess,
|
||||
},
|
||||
ContainerDStateDir: `C:\ProgramData\containerd\state\io.containerd.runtime.v2.task\k8s.io\`,
|
||||
}
|
||||
|
||||
// A Collector is a Prometheus Collector for containers metrics.
|
||||
type Collector struct {
|
||||
@@ -44,6 +70,9 @@ type Collector struct {
|
||||
|
||||
logger *slog.Logger
|
||||
|
||||
annotationsCacheHCS map[string]containerInfo
|
||||
annotationsCacheJob map[string]containerInfo
|
||||
|
||||
// Presence
|
||||
containerAvailable *prometheus.Desc
|
||||
|
||||
@@ -75,12 +104,27 @@ type Collector struct {
|
||||
writeSizeBytes *prometheus.Desc
|
||||
}
|
||||
|
||||
type containerInfo struct {
|
||||
id string
|
||||
namespace string
|
||||
pod string
|
||||
container string
|
||||
}
|
||||
|
||||
type ociSpec struct {
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
}
|
||||
|
||||
// New constructs a new Collector.
|
||||
func New(config *Config) *Collector {
|
||||
if config == nil {
|
||||
config = &ConfigDefaults
|
||||
}
|
||||
|
||||
if config.CollectorsEnabled == nil {
|
||||
config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled
|
||||
}
|
||||
|
||||
c := &Collector{
|
||||
config: *config,
|
||||
}
|
||||
@@ -88,8 +132,31 @@ func New(config *Config) *Collector {
|
||||
return c
|
||||
}
|
||||
|
||||
func NewWithFlags(_ *kingpin.Application) *Collector {
|
||||
return &Collector{}
|
||||
func NewWithFlags(app *kingpin.Application) *Collector {
|
||||
c := &Collector{
|
||||
config: ConfigDefaults,
|
||||
}
|
||||
c.config.CollectorsEnabled = make([]string, 0)
|
||||
|
||||
var collectorsEnabled string
|
||||
|
||||
app.Flag(
|
||||
"collector.container.enabled",
|
||||
"Comma-separated list of collectors to use. Defaults to all, if not specified.",
|
||||
).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled)
|
||||
|
||||
app.Flag(
|
||||
"collector.container.containerd-state-dir",
|
||||
"Path to the containerd state directory. Defaults to C:\\ProgramData\\containerd\\state\\io.containerd.runtime.v2.task\\k8s.io\\",
|
||||
).Default(ConfigDefaults.ContainerDStateDir).StringVar(&c.config.ContainerDStateDir)
|
||||
|
||||
app.Action(func(*kingpin.ParseContext) error {
|
||||
c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Collector) GetName() string {
|
||||
@@ -103,10 +170,19 @@ func (c *Collector) Close() error {
|
||||
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
c.logger = logger.With(slog.String("collector", Name))
|
||||
|
||||
for _, collector := range c.config.CollectorsEnabled {
|
||||
if !slices.Contains([]string{subCollectorHCS, subCollectorHostprocess}, collector) {
|
||||
return fmt.Errorf("unknown collector: %s", collector)
|
||||
}
|
||||
}
|
||||
|
||||
c.annotationsCacheHCS = make(map[string]containerInfo)
|
||||
c.annotationsCacheJob = make(map[string]containerInfo)
|
||||
|
||||
c.containerAvailable = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "available"),
|
||||
"Available",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container", "hostprocess"},
|
||||
nil,
|
||||
)
|
||||
c.containersCount = prometheus.NewDesc(
|
||||
@@ -118,97 +194,97 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
c.usageCommitBytes = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "memory_usage_commit_bytes"),
|
||||
"Memory Usage Commit Bytes",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.usageCommitPeakBytes = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "memory_usage_commit_peak_bytes"),
|
||||
"Memory Usage Commit Peak Bytes",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.usagePrivateWorkingSetBytes = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "memory_usage_private_working_set_bytes"),
|
||||
"Memory Usage Private Working Set Bytes",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.runtimeTotal = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_total"),
|
||||
"Total Run time in Seconds",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.runtimeUser = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_usermode"),
|
||||
"Run Time in User mode in Seconds",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.runtimeKernel = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_kernelmode"),
|
||||
"Run time in Kernel mode in Seconds",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.bytesReceived = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "network_receive_bytes_total"),
|
||||
"Bytes Received on Interface",
|
||||
[]string{"container_id", "interface"},
|
||||
[]string{"container_id", "namespace", "pod", "container", "interface"},
|
||||
nil,
|
||||
)
|
||||
c.bytesSent = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "network_transmit_bytes_total"),
|
||||
"Bytes Sent on Interface",
|
||||
[]string{"container_id", "interface"},
|
||||
[]string{"container_id", "namespace", "pod", "container", "interface"},
|
||||
nil,
|
||||
)
|
||||
c.packetsReceived = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "network_receive_packets_total"),
|
||||
"Packets Received on Interface",
|
||||
[]string{"container_id", "interface"},
|
||||
[]string{"container_id", "namespace", "pod", "container", "interface"},
|
||||
nil,
|
||||
)
|
||||
c.packetsSent = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "network_transmit_packets_total"),
|
||||
"Packets Sent on Interface",
|
||||
[]string{"container_id", "interface"},
|
||||
[]string{"container_id", "namespace", "pod", "container", "interface"},
|
||||
nil,
|
||||
)
|
||||
c.droppedPacketsIncoming = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "network_receive_packets_dropped_total"),
|
||||
"Dropped Incoming Packets on Interface",
|
||||
[]string{"container_id", "interface"},
|
||||
[]string{"container_id", "namespace", "pod", "container", "interface"},
|
||||
nil,
|
||||
)
|
||||
c.droppedPacketsOutgoing = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "network_transmit_packets_dropped_total"),
|
||||
"Dropped Outgoing Packets on Interface",
|
||||
[]string{"container_id", "interface"},
|
||||
[]string{"container_id", "namespace", "pod", "container", "interface"},
|
||||
nil,
|
||||
)
|
||||
c.readCountNormalized = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "storage_read_count_normalized_total"),
|
||||
"Read Count Normalized",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.readSizeBytes = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "storage_read_size_bytes_total"),
|
||||
"Read Size Bytes",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.writeCountNormalized = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "storage_write_count_normalized_total"),
|
||||
"Write Count Normalized",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
c.writeSizeBytes = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "storage_write_size_bytes_total"),
|
||||
"Write Size Bytes",
|
||||
[]string{"container_id"},
|
||||
[]string{"container_id", "namespace", "pod", "container"},
|
||||
nil,
|
||||
)
|
||||
|
||||
@@ -218,39 +294,91 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
// Collect sends the metric values for each metric
|
||||
// to the provided prometheus Metric channel.
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||
errs := make([]error, 0)
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorHCS) {
|
||||
if err := c.collectHCS(ch); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorHostprocess) {
|
||||
if err := c.collectJobContainers(ch); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Collector) collectHCS(ch chan<- prometheus.Metric) error {
|
||||
// Types Container is passed to get the containers compute systems only
|
||||
containers, err := hcsshim.GetContainers(hcsshim.ComputeSystemQuery{Types: []string{"Container"}})
|
||||
containers, err := hcs.GetContainers()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in fetching containers: %w", err)
|
||||
}
|
||||
|
||||
count := len(containers)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.containersCount,
|
||||
prometheus.GaugeValue,
|
||||
float64(count),
|
||||
)
|
||||
|
||||
if count == 0 {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.containersCount,
|
||||
prometheus.GaugeValue,
|
||||
0,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
containerPrefixes := make(map[string]string)
|
||||
collectErrors := make([]error, 0, len(containers))
|
||||
var countersCount float64
|
||||
|
||||
for _, containerDetails := range containers {
|
||||
containerIdWithPrefix := getContainerIdWithPrefix(containerDetails)
|
||||
containerIDs := make([]string, 0, len(containers))
|
||||
collectErrors := make([]error, 0)
|
||||
|
||||
if err = c.collectContainer(ch, containerDetails, containerIdWithPrefix); err != nil {
|
||||
if hcsshim.IsNotExist(err) {
|
||||
for _, container := range containers {
|
||||
if container.State != "Running" {
|
||||
continue
|
||||
}
|
||||
|
||||
containerIDs = append(containerIDs, container.ID)
|
||||
|
||||
countersCount++
|
||||
|
||||
var (
|
||||
namespace string
|
||||
podName string
|
||||
containerName string
|
||||
)
|
||||
|
||||
if _, ok := c.annotationsCacheHCS[container.ID]; !ok {
|
||||
if spec, err := c.getContainerAnnotations(container.ID); err == nil {
|
||||
namespace = spec.Annotations["io.kubernetes.cri.sandbox-namespace"]
|
||||
podName = spec.Annotations["io.kubernetes.cri.sandbox-name"]
|
||||
containerName = spec.Annotations["io.kubernetes.cri.container-name"]
|
||||
}
|
||||
|
||||
c.annotationsCacheHCS[container.ID] = containerInfo{
|
||||
id: getContainerIdWithPrefix(container),
|
||||
namespace: namespace,
|
||||
pod: podName,
|
||||
container: containerName,
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.collectHCSContainer(ch, container, c.annotationsCacheHCS[container.ID]); err != nil {
|
||||
if errors.Is(err, hcs.ErrIDNotFound) {
|
||||
c.logger.Debug("err in fetching container statistics",
|
||||
slog.String("container_id", containerDetails.ID),
|
||||
slog.String("container_id", container.ID),
|
||||
slog.String("container_name", c.annotationsCacheHCS[container.ID].container),
|
||||
slog.String("container_pod_name", c.annotationsCacheHCS[container.ID].pod),
|
||||
slog.String("container_namespace", c.annotationsCacheHCS[container.ID].namespace),
|
||||
slog.Any("err", err),
|
||||
)
|
||||
} else {
|
||||
c.logger.Error("err in fetching container statistics",
|
||||
slog.String("container_id", containerDetails.ID),
|
||||
slog.String("container_id", container.ID),
|
||||
slog.String("container_name", c.annotationsCacheHCS[container.ID].container),
|
||||
slog.String("container_pod_name", c.annotationsCacheHCS[container.ID].pod),
|
||||
slog.String("container_namespace", c.annotationsCacheHCS[container.ID].namespace),
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
@@ -259,14 +387,25 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
containerPrefixes[containerDetails.ID] = containerIdWithPrefix
|
||||
}
|
||||
|
||||
if err = c.collectNetworkMetrics(ch, containerPrefixes); err != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.containersCount,
|
||||
prometheus.GaugeValue,
|
||||
countersCount,
|
||||
)
|
||||
|
||||
if err = c.collectNetworkMetrics(ch); err != nil {
|
||||
return fmt.Errorf("error in fetching container network statistics: %w", err)
|
||||
}
|
||||
|
||||
// Remove containers that are no longer running
|
||||
for _, containerID := range c.annotationsCacheHCS {
|
||||
if !slices.Contains(containerIDs, containerID.id) {
|
||||
delete(c.annotationsCacheHCS, containerID.id)
|
||||
}
|
||||
}
|
||||
|
||||
if len(collectErrors) > 0 {
|
||||
return fmt.Errorf("errors while fetching container statistics: %w", errors.Join(collectErrors...))
|
||||
}
|
||||
@@ -274,170 +413,213 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectContainer(ch chan<- prometheus.Metric, containerDetails hcsshim.ContainerProperties, containerIdWithPrefix string) error {
|
||||
container, err := hcsshim.OpenContainer(containerDetails.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in opening container: %w", err)
|
||||
func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDetails hcs.Properties, containerInfo containerInfo) error {
|
||||
// Skip if the container is a pause container
|
||||
if containerInfo.pod != "" && containerInfo.container == "" {
|
||||
c.logger.Debug("skipping pause container",
|
||||
slog.String("container_id", containerDetails.ID),
|
||||
slog.String("container_name", containerInfo.container),
|
||||
slog.String("pod_name", containerInfo.pod),
|
||||
slog.String("namespace", containerInfo.namespace),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if container == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := container.Close(); err != nil {
|
||||
c.logger.Error("error in closing container",
|
||||
slog.Any("err", err),
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
||||
containerStats, err := container.Statistics()
|
||||
containerStats, err := hcs.GetContainerStatistics(containerDetails.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in fetching container statistics: %w", err)
|
||||
return fmt.Errorf("error fetching container statistics: %w", err)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.containerAvailable,
|
||||
prometheus.CounterValue,
|
||||
prometheus.GaugeValue,
|
||||
1,
|
||||
containerIdWithPrefix,
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, "false",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.usageCommitBytes,
|
||||
prometheus.GaugeValue,
|
||||
float64(containerStats.Memory.UsageCommitBytes),
|
||||
containerIdWithPrefix,
|
||||
float64(containerStats.Memory.MemoryUsageCommitBytes),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.usageCommitPeakBytes,
|
||||
prometheus.GaugeValue,
|
||||
float64(containerStats.Memory.UsageCommitPeakBytes),
|
||||
containerIdWithPrefix,
|
||||
float64(containerStats.Memory.MemoryUsageCommitPeakBytes),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.usagePrivateWorkingSetBytes,
|
||||
prometheus.GaugeValue,
|
||||
float64(containerStats.Memory.UsagePrivateWorkingSetBytes),
|
||||
containerIdWithPrefix,
|
||||
float64(containerStats.Memory.MemoryUsagePrivateWorkingSetBytes),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.runtimeTotal,
|
||||
prometheus.CounterValue,
|
||||
float64(containerStats.Processor.TotalRuntime100ns)*pdh.TicksToSecondScaleFactor,
|
||||
containerIdWithPrefix,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.runtimeUser,
|
||||
prometheus.CounterValue,
|
||||
float64(containerStats.Processor.RuntimeUser100ns)*pdh.TicksToSecondScaleFactor,
|
||||
containerIdWithPrefix,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.runtimeKernel,
|
||||
prometheus.CounterValue,
|
||||
float64(containerStats.Processor.RuntimeKernel100ns)*pdh.TicksToSecondScaleFactor,
|
||||
containerIdWithPrefix,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.readCountNormalized,
|
||||
prometheus.CounterValue,
|
||||
float64(containerStats.Storage.ReadCountNormalized),
|
||||
containerIdWithPrefix,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.readSizeBytes,
|
||||
prometheus.CounterValue,
|
||||
float64(containerStats.Storage.ReadSizeBytes),
|
||||
containerIdWithPrefix,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.writeCountNormalized,
|
||||
prometheus.CounterValue,
|
||||
float64(containerStats.Storage.WriteCountNormalized),
|
||||
containerIdWithPrefix,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.writeSizeBytes,
|
||||
prometheus.CounterValue,
|
||||
float64(containerStats.Storage.WriteSizeBytes),
|
||||
containerIdWithPrefix,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectNetworkMetrics collects network metrics for containers.
|
||||
// With HNSv2, the network stats must be collected from hcsshim.HNSListEndpointRequest.
|
||||
// Network statistics from the container.Statistics() are providing data only, if HNSv1 is used.
|
||||
// Ref: https://github.com/prometheus-community/windows_exporter/pull/1218
|
||||
func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric, containerPrefixes map[string]string) error {
|
||||
hnsEndpoints, err := hcsshim.HNSListEndpointRequest()
|
||||
func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error {
|
||||
endpoints, err := hcn.EnumerateEndpoints()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in fetching HNS endpoints: %w", err)
|
||||
return fmt.Errorf("error in fetching HCN endpoints: %w", err)
|
||||
}
|
||||
|
||||
if len(hnsEndpoints) == 0 {
|
||||
return errors.New("no network stats for containers to collect")
|
||||
if len(endpoints) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, endpoint := range hnsEndpoints {
|
||||
endpointStats, err := hcsshim.GetHNSEndpointStats(endpoint.Id)
|
||||
for _, endpoint := range endpoints {
|
||||
properties, err := hcn.GetEndpointProperties(endpoint)
|
||||
if err != nil {
|
||||
c.logger.Warn("Failed to collect network stats for interface "+endpoint.Id,
|
||||
c.logger.Warn("Failed to collect properties for interface "+endpoint.String(),
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
for _, containerId := range endpoint.SharedContainers {
|
||||
containerIdWithPrefix, ok := containerPrefixes[containerId]
|
||||
if len(properties.SharedContainers) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var nicGUID *guid.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 {
|
||||
containerInfo, ok := c.annotationsCacheHCS[containerId]
|
||||
|
||||
if !ok {
|
||||
c.logger.Debug("Failed to collect network stats for container " + containerId)
|
||||
c.logger.Debug("Unknown container " + containerId + " for endpoint " + endpoint.String())
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
endpointId := strings.ToUpper(endpoint.Id)
|
||||
// Skip if the container is a pause container
|
||||
if containerInfo.pod != "" && containerInfo.container == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
endpointId := strings.ToUpper(endpoint.String())
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.bytesReceived,
|
||||
prometheus.CounterValue,
|
||||
float64(endpointStats.BytesReceived),
|
||||
containerIdWithPrefix, endpointId,
|
||||
float64(endpointStats.InOctets),
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.bytesSent,
|
||||
prometheus.CounterValue,
|
||||
float64(endpointStats.BytesSent),
|
||||
containerIdWithPrefix, endpointId,
|
||||
float64(endpointStats.OutOctets),
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.packetsReceived,
|
||||
prometheus.CounterValue,
|
||||
float64(endpointStats.PacketsReceived),
|
||||
containerIdWithPrefix, endpointId,
|
||||
float64(endpointStats.InUcastPkts+endpointStats.InNUcastPkts),
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.packetsSent,
|
||||
prometheus.CounterValue,
|
||||
float64(endpointStats.PacketsSent),
|
||||
containerIdWithPrefix, endpointId,
|
||||
float64(endpointStats.OutUcastPkts+endpointStats.OutNUcastPkts),
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.droppedPacketsIncoming,
|
||||
prometheus.CounterValue,
|
||||
float64(endpointStats.DroppedPacketsIncoming),
|
||||
containerIdWithPrefix, endpointId,
|
||||
float64(endpointStats.InDiscards+endpointStats.InErrors),
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.droppedPacketsOutgoing,
|
||||
prometheus.CounterValue,
|
||||
float64(endpointStats.DroppedPacketsOutgoing),
|
||||
containerIdWithPrefix, endpointId,
|
||||
float64(endpointStats.OutDiscards+endpointStats.OutErrors),
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -445,12 +627,307 @@ func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric, container
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainerIdWithPrefix(containerDetails hcsshim.ContainerProperties) string {
|
||||
switch containerDetails.Owner {
|
||||
// collectJobContainers collects container metrics for job containers.
|
||||
// Job container based on Win32 Job objects.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
|
||||
//
|
||||
// Job containers are containers that aren't managed by HCS, e.g host process containers.
|
||||
func (c *Collector) collectJobContainers(ch chan<- prometheus.Metric) error {
|
||||
containerDStateFS := os.DirFS(c.config.ContainerDStateDir)
|
||||
|
||||
allContainerIDs := make([]string, 0, len(c.annotationsCacheJob)+len(c.annotationsCacheHCS))
|
||||
jobContainerIDs := make([]string, 0, len(allContainerIDs))
|
||||
|
||||
if err := fs.WalkDir(containerDStateFS, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
c.logger.Warn("containerd state directory does not exist",
|
||||
slog.String("path", c.config.ContainerDStateDir),
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if path == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path + "\\config.json"); err != nil {
|
||||
containerID := strings.TrimPrefix(strings.Replace(path, c.config.ContainerDStateDir, "", 1), `\`)
|
||||
|
||||
if spec, err := c.getContainerAnnotations(containerID); err == nil {
|
||||
isHostProcess, ok := spec.Annotations["microsoft.com/hostprocess-container"]
|
||||
if ok && isHostProcess == "true" {
|
||||
allContainerIDs = append(allContainerIDs, containerID)
|
||||
|
||||
if _, ok := c.annotationsCacheJob[containerID]; !ok {
|
||||
var (
|
||||
namespace string
|
||||
podName string
|
||||
containerName string
|
||||
)
|
||||
|
||||
namespace = spec.Annotations["io.kubernetes.cri.sandbox-namespace"]
|
||||
podName = spec.Annotations["io.kubernetes.cri.sandbox-name"]
|
||||
containerName = spec.Annotations["io.kubernetes.cri.container-name"]
|
||||
|
||||
c.annotationsCacheJob[containerID] = containerInfo{
|
||||
id: "containerd://" + containerID,
|
||||
namespace: namespace,
|
||||
pod: podName,
|
||||
container: containerName,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the directory content
|
||||
return fs.SkipDir
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error in walking containerd state directory: %w", err)
|
||||
}
|
||||
|
||||
errs := make([]error, 0)
|
||||
|
||||
for _, containerID := range allContainerIDs {
|
||||
if err := c.collectJobContainer(ch, containerID); err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
jobContainerIDs = append(jobContainerIDs, containerID)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove containers that are no longer running
|
||||
for _, containerID := range c.annotationsCacheJob {
|
||||
if !slices.Contains(jobContainerIDs, containerID.id) {
|
||||
delete(c.annotationsCacheJob, containerID.id)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Collector) collectJobContainer(ch chan<- prometheus.Metric, containerID string) error {
|
||||
jobObjectHandle, err := kernel32.OpenJobObject("Global\\JobContainer_" + containerID)
|
||||
if err != nil {
|
||||
if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("error in opening job object: %w", err)
|
||||
}
|
||||
|
||||
defer func(fd windows.Handle) {
|
||||
_ = windows.Close(fd)
|
||||
}(jobObjectHandle)
|
||||
|
||||
var jobInfo kernel32.JobObjectBasicAndIOAccountingInformation
|
||||
|
||||
if err = windows.QueryInformationJobObject(
|
||||
jobObjectHandle,
|
||||
windows.JobObjectBasicAndIoAccountingInformation,
|
||||
uintptr(unsafe.Pointer(&jobInfo)),
|
||||
uint32(unsafe.Sizeof(jobInfo)),
|
||||
nil,
|
||||
); err != nil {
|
||||
return fmt.Errorf("error in querying job object information: %w", err)
|
||||
}
|
||||
|
||||
var jobMemoryInfo kernel32.JobObjectMemoryUsageInformation
|
||||
|
||||
// https://github.com/microsoft/hcsshim/blob/bfb2a106798d3765666f6e39ec6cf0117275eab4/internal/jobobject/jobobject.go#L410
|
||||
if err = windows.QueryInformationJobObject(
|
||||
jobObjectHandle,
|
||||
JobObjectMemoryUsageInformation,
|
||||
uintptr(unsafe.Pointer(&jobMemoryInfo)),
|
||||
uint32(unsafe.Sizeof(jobMemoryInfo)),
|
||||
nil,
|
||||
); err != nil {
|
||||
return fmt.Errorf("error in querying job object memory usage information: %w", err)
|
||||
}
|
||||
|
||||
privateWorkingSetBytes, err := calculatePrivateWorkingSetBytes(jobObjectHandle)
|
||||
if err != nil {
|
||||
c.logger.Debug("error in calculating private working set bytes", slog.Any("err", err))
|
||||
}
|
||||
|
||||
containerInfo := c.annotationsCacheJob[containerID]
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.containerAvailable,
|
||||
prometheus.GaugeValue,
|
||||
1,
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, "true",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.usageCommitBytes,
|
||||
prometheus.GaugeValue,
|
||||
float64(jobMemoryInfo.JobMemory),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.usageCommitPeakBytes,
|
||||
prometheus.GaugeValue,
|
||||
float64(jobMemoryInfo.PeakJobMemoryUsed),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.usagePrivateWorkingSetBytes,
|
||||
prometheus.GaugeValue,
|
||||
float64(privateWorkingSetBytes),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.runtimeTotal,
|
||||
prometheus.CounterValue,
|
||||
(float64(jobInfo.BasicInfo.ThisPeriodTotalKernelTime)+float64(jobInfo.BasicInfo.ThisPeriodTotalUserTime))*pdh.TicksToSecondScaleFactor,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.runtimeUser,
|
||||
prometheus.CounterValue,
|
||||
float64(jobInfo.BasicInfo.ThisPeriodTotalUserTime)*pdh.TicksToSecondScaleFactor,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.runtimeKernel,
|
||||
prometheus.CounterValue,
|
||||
float64(jobInfo.BasicInfo.ThisPeriodTotalKernelTime)*pdh.TicksToSecondScaleFactor,
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.readCountNormalized,
|
||||
prometheus.CounterValue,
|
||||
float64(jobInfo.IoInfo.ReadOperationCount),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.readSizeBytes,
|
||||
prometheus.CounterValue,
|
||||
float64(jobInfo.IoInfo.ReadTransferCount),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.writeCountNormalized,
|
||||
prometheus.CounterValue,
|
||||
float64(jobInfo.IoInfo.WriteOperationCount),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.writeSizeBytes,
|
||||
prometheus.CounterValue,
|
||||
float64(jobInfo.IoInfo.WriteTransferCount),
|
||||
|
||||
containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainerIdWithPrefix(container hcs.Properties) string {
|
||||
switch container.Owner {
|
||||
case "containerd-shim-runhcs-v1.exe":
|
||||
return "containerd://" + containerDetails.ID
|
||||
return "containerd://" + container.ID
|
||||
default:
|
||||
// default to docker or if owner is not set
|
||||
return "docker://" + containerDetails.ID
|
||||
return "docker://" + container.ID
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collector) getContainerAnnotations(containerID string) (ociSpec, error) {
|
||||
configJSON, err := os.OpenFile(fmt.Sprintf(`%s%s\config.json`, c.config.ContainerDStateDir, containerID), os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return ociSpec{}, fmt.Errorf("error in opening config.json file: %w", err)
|
||||
}
|
||||
|
||||
var annotations ociSpec
|
||||
|
||||
if err = json.NewDecoder(configJSON).Decode(&annotations); err != nil {
|
||||
return ociSpec{}, fmt.Errorf("error in decoding config.json file: %w", err)
|
||||
}
|
||||
|
||||
return annotations, nil
|
||||
}
|
||||
|
||||
func calculatePrivateWorkingSetBytes(jobObjectHandle windows.Handle) (uint64, error) {
|
||||
var pidList kernel32.JobObjectBasicProcessIDList
|
||||
|
||||
retLen := uint32(unsafe.Sizeof(pidList))
|
||||
|
||||
if err := windows.QueryInformationJobObject(
|
||||
jobObjectHandle,
|
||||
windows.JobObjectBasicProcessIdList,
|
||||
uintptr(unsafe.Pointer(&pidList)),
|
||||
retLen, &retLen); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var (
|
||||
privateWorkingSetBytes uint64
|
||||
vmCounters kernel32.PROCESS_VM_COUNTERS
|
||||
)
|
||||
|
||||
retLen = uint32(unsafe.Sizeof(vmCounters))
|
||||
|
||||
getMemoryStats := func(pid uint32) (uint64, error) {
|
||||
processHandle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error in opening process: %w", err)
|
||||
}
|
||||
|
||||
defer func(fd windows.Handle) {
|
||||
_ = windows.Close(fd)
|
||||
}(processHandle)
|
||||
|
||||
var isInJob bool
|
||||
|
||||
if err := kernel32.IsProcessInJob(processHandle, jobObjectHandle, &isInJob); err != nil {
|
||||
return 0, fmt.Errorf("error in checking if process is in job: %w", err)
|
||||
}
|
||||
|
||||
if !isInJob {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if err := windows.NtQueryInformationProcess(
|
||||
processHandle,
|
||||
windows.ProcessVmCounters,
|
||||
unsafe.Pointer(&vmCounters),
|
||||
retLen,
|
||||
&retLen,
|
||||
); err != nil {
|
||||
return 0, fmt.Errorf("error in querying process information: %w", err)
|
||||
}
|
||||
|
||||
return uint64(vmCounters.PrivateWorkingSetSize), nil
|
||||
}
|
||||
|
||||
for _, pid := range pidList.PIDs() {
|
||||
privateWorkingSetSize, err := getMemoryStats(pid)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error in getting private working set bytes: %w", err)
|
||||
}
|
||||
|
||||
privateWorkingSetBytes += privateWorkingSetSize
|
||||
}
|
||||
|
||||
return privateWorkingSetBytes, nil
|
||||
}
|
||||
|
||||
467
internal/collector/gpu/gpu.go
Normal file
467
internal/collector/gpu/gpu.go
Normal file
@@ -0,0 +1,467 @@
|
||||
// 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 gpu
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/setupapi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const Name = "gpu"
|
||||
|
||||
type Config struct{}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var ConfigDefaults = Config{}
|
||||
|
||||
type Collector struct {
|
||||
config Config
|
||||
|
||||
// GPU Engine
|
||||
gpuEnginePerfDataCollector *pdh.Collector
|
||||
gpuEnginePerfDataObject []gpuEnginePerfDataCounterValues
|
||||
|
||||
gpuInfo *prometheus.Desc
|
||||
gpuEngineRunningTime *prometheus.Desc
|
||||
|
||||
// GPU Adapter Memory
|
||||
gpuAdapterMemoryPerfDataCollector *pdh.Collector
|
||||
gpuAdapterMemoryPerfDataObject []gpuAdapterMemoryPerfDataCounterValues
|
||||
|
||||
gpuAdapterMemoryDedicatedUsage *prometheus.Desc
|
||||
gpuAdapterMemorySharedUsage *prometheus.Desc
|
||||
gpuAdapterMemoryTotalCommitted *prometheus.Desc
|
||||
|
||||
// GPU Local Adapter Memory
|
||||
gpuLocalAdapterMemoryPerfDataCollector *pdh.Collector
|
||||
gpuLocalAdapterMemoryPerfDataObject []gpuLocalAdapterMemoryPerfDataCounterValues
|
||||
|
||||
gpuLocalAdapterMemoryUsage *prometheus.Desc
|
||||
|
||||
// GPU Non Local Adapter Memory
|
||||
gpuNonLocalAdapterMemoryPerfDataCollector *pdh.Collector
|
||||
gpuNonLocalAdapterMemoryPerfDataObject []gpuNonLocalAdapterMemoryPerfDataCounterValues
|
||||
|
||||
gpuNonLocalAdapterMemoryUsage *prometheus.Desc
|
||||
|
||||
// GPU Process Memory
|
||||
gpuProcessMemoryPerfDataCollector *pdh.Collector
|
||||
gpuProcessMemoryPerfDataObject []gpuProcessMemoryPerfDataCounterValues
|
||||
|
||||
gpuProcessMemoryDedicatedUsage *prometheus.Desc
|
||||
gpuProcessMemoryLocalUsage *prometheus.Desc
|
||||
gpuProcessMemoryNonLocalUsage *prometheus.Desc
|
||||
gpuProcessMemorySharedUsage *prometheus.Desc
|
||||
gpuProcessMemoryTotalCommitted *prometheus.Desc
|
||||
}
|
||||
|
||||
func New(config *Config) *Collector {
|
||||
if config == nil {
|
||||
config = &ConfigDefaults
|
||||
}
|
||||
|
||||
c := &Collector{
|
||||
config: *config,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func NewWithFlags(_ *kingpin.Application) *Collector {
|
||||
return &Collector{}
|
||||
}
|
||||
|
||||
func (c *Collector) GetName() string {
|
||||
return Name
|
||||
}
|
||||
|
||||
func (c *Collector) Close() error {
|
||||
c.gpuEnginePerfDataCollector.Close()
|
||||
c.gpuAdapterMemoryPerfDataCollector.Close()
|
||||
c.gpuLocalAdapterMemoryPerfDataCollector.Close()
|
||||
c.gpuNonLocalAdapterMemoryPerfDataCollector.Close()
|
||||
c.gpuProcessMemoryPerfDataCollector.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
|
||||
var err error
|
||||
|
||||
c.gpuInfo = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "info"),
|
||||
"A metric with a constant '1' value labeled with gpu device information.",
|
||||
[]string{"phys", "physical_device_object_name", "hardware_id", "friendly_name", "description"},
|
||||
nil,
|
||||
)
|
||||
|
||||
c.gpuEngineRunningTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "engine_time_seconds"),
|
||||
"Total running time of the GPU in seconds.",
|
||||
[]string{"process_id", "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"},
|
||||
nil,
|
||||
)
|
||||
c.gpuAdapterMemorySharedUsage = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "adapter_memory_shared_bytes"),
|
||||
"Shared GPU memory usage in bytes.",
|
||||
[]string{"phys"},
|
||||
nil,
|
||||
)
|
||||
c.gpuAdapterMemoryTotalCommitted = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "adapter_memory_committed_bytes"),
|
||||
"Total committed GPU memory in bytes.",
|
||||
[]string{"phys"},
|
||||
nil,
|
||||
)
|
||||
|
||||
c.gpuLocalAdapterMemoryUsage = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "local_adapter_memory_bytes"),
|
||||
"Local adapter memory usage in bytes.",
|
||||
[]string{"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"},
|
||||
nil,
|
||||
)
|
||||
|
||||
c.gpuProcessMemoryDedicatedUsage = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "process_memory_dedicated_bytes"),
|
||||
"Dedicated process memory usage in bytes.",
|
||||
[]string{"process_id", "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"},
|
||||
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"},
|
||||
nil,
|
||||
)
|
||||
c.gpuProcessMemorySharedUsage = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "process_memory_shared_bytes"),
|
||||
"Shared process memory usage in bytes.",
|
||||
[]string{"process_id", "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"},
|
||||
nil,
|
||||
)
|
||||
|
||||
errs := make([]error, 0)
|
||||
|
||||
c.gpuEnginePerfDataCollector, err = pdh.NewCollector[gpuEnginePerfDataCounterValues](pdh.CounterTypeRaw, "GPU Engine", pdh.InstancesAll)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to create GPU Engine perf data collector: %w", err))
|
||||
}
|
||||
|
||||
c.gpuAdapterMemoryPerfDataCollector, err = pdh.NewCollector[gpuAdapterMemoryPerfDataCounterValues](pdh.CounterTypeRaw, "GPU Adapter Memory", pdh.InstancesAll)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to create GPU Adapter Memory perf data collector: %w", err))
|
||||
}
|
||||
|
||||
c.gpuLocalAdapterMemoryPerfDataCollector, err = pdh.NewCollector[gpuLocalAdapterMemoryPerfDataCounterValues](pdh.CounterTypeRaw, "GPU Local Adapter Memory", pdh.InstancesAll)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to create GPU Local Adapter Memory perf data collector: %w", err))
|
||||
}
|
||||
|
||||
c.gpuNonLocalAdapterMemoryPerfDataCollector, err = pdh.NewCollector[gpuNonLocalAdapterMemoryPerfDataCounterValues](pdh.CounterTypeRaw, "GPU Non Local Adapter Memory", pdh.InstancesAll)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to create GPU Non Local Adapter Memory perf data collector: %w", err))
|
||||
}
|
||||
|
||||
c.gpuProcessMemoryPerfDataCollector, err = pdh.NewCollector[gpuProcessMemoryPerfDataCounterValues](pdh.CounterTypeRaw, "GPU Process Memory", pdh.InstancesAll)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to create GPU Process Memory perf data collector: %w", err))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if err := c.collectGpuEngineMetrics(ch); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := c.collectGpuAdapterMemoryMetrics(ch); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := c.collectGpuLocalAdapterMemoryMetrics(ch); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := c.collectGpuNonLocalAdapterMemoryMetrics(ch); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := c.collectGpuProcessMemoryMetrics(ch); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Collector) collectGpuInfo(ch chan<- prometheus.Metric) error {
|
||||
gpus, err := setupapi.GetGPUDevices()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get GPU devices: %w", err)
|
||||
}
|
||||
|
||||
for i, gpu := range gpus {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuInfo,
|
||||
prometheus.GaugeValue,
|
||||
1.0,
|
||||
strconv.Itoa(i),
|
||||
gpu.PhysicalDeviceObjectName,
|
||||
gpu.HardwareID,
|
||||
gpu.FriendlyName,
|
||||
gpu.DeviceDesc,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectGpuEngineMetrics(ch chan<- prometheus.Metric) error {
|
||||
// Collect the GPU Engine perf data.
|
||||
if err := c.gpuEnginePerfDataCollector.Collect(&c.gpuEnginePerfDataObject); err != nil {
|
||||
return fmt.Errorf("failed to collect GPU Engine perf data: %w", err)
|
||||
}
|
||||
|
||||
runningTimeMap := make(map[PidPhysEngEngType]float64)
|
||||
// Iterate over the GPU Engine perf data and aggregate the values.
|
||||
for _, data := range c.gpuEnginePerfDataObject {
|
||||
instance := parseGPUCounterInstanceString(data.Name)
|
||||
|
||||
key := PidPhysEngEngType{
|
||||
Pid: instance.Pid,
|
||||
Phys: instance.Phys,
|
||||
Eng: instance.Eng,
|
||||
Engtype: instance.Engtype,
|
||||
}
|
||||
runningTimeMap[key] += data.RunningTime / 10_000_000 // RunningTime is in 100ns units, convert to seconds.
|
||||
}
|
||||
|
||||
for key, runningTime := range runningTimeMap {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuEngineRunningTime,
|
||||
prometheus.CounterValue,
|
||||
runningTime,
|
||||
key.Pid, key.Phys, key.Eng, key.Engtype,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectGpuAdapterMemoryMetrics(ch chan<- prometheus.Metric) error {
|
||||
// Collect the GPU Adapter Memory perf data.
|
||||
if err := c.gpuAdapterMemoryPerfDataCollector.Collect(&c.gpuAdapterMemoryPerfDataObject); err != nil {
|
||||
return fmt.Errorf("failed to collect GPU Adapter Memory perf data: %w", err)
|
||||
}
|
||||
|
||||
dedicatedUsageMap := make(map[PidPhysEngEngType]float64)
|
||||
sharedUsageMap := make(map[PidPhysEngEngType]float64)
|
||||
totalCommittedMap := make(map[PidPhysEngEngType]float64)
|
||||
|
||||
for _, data := range c.gpuAdapterMemoryPerfDataObject {
|
||||
instance := parseGPUCounterInstanceString(data.Name)
|
||||
|
||||
key := PidPhysEngEngType{
|
||||
Pid: instance.Pid,
|
||||
Phys: instance.Phys,
|
||||
Eng: instance.Eng,
|
||||
Engtype: instance.Engtype,
|
||||
}
|
||||
dedicatedUsageMap[key] += data.DedicatedUsage
|
||||
sharedUsageMap[key] += data.SharedUsage
|
||||
totalCommittedMap[key] += data.TotalCommitted
|
||||
}
|
||||
|
||||
for key, dedicatedUsage := range dedicatedUsageMap {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuAdapterMemoryDedicatedUsage,
|
||||
prometheus.GaugeValue,
|
||||
dedicatedUsage,
|
||||
key.Phys,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuAdapterMemorySharedUsage,
|
||||
prometheus.GaugeValue,
|
||||
sharedUsageMap[key],
|
||||
key.Phys,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuAdapterMemoryTotalCommitted,
|
||||
prometheus.GaugeValue,
|
||||
totalCommittedMap[key],
|
||||
key.Phys,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectGpuLocalAdapterMemoryMetrics(ch chan<- prometheus.Metric) error {
|
||||
// Collect the GPU Local Adapter Memory perf data.
|
||||
if err := c.gpuLocalAdapterMemoryPerfDataCollector.Collect(&c.gpuLocalAdapterMemoryPerfDataObject); err != nil {
|
||||
return fmt.Errorf("failed to collect GPU Local Adapter Memory perf data: %w", err)
|
||||
}
|
||||
|
||||
localAdapterMemoryMap := make(map[string]float64)
|
||||
|
||||
for _, data := range c.gpuLocalAdapterMemoryPerfDataObject {
|
||||
instance := parseGPUCounterInstanceString(data.Name)
|
||||
|
||||
localAdapterMemoryMap[instance.Phys] += data.LocalUsage
|
||||
}
|
||||
|
||||
for phys, localUsage := range localAdapterMemoryMap {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuLocalAdapterMemoryUsage,
|
||||
prometheus.GaugeValue,
|
||||
localUsage,
|
||||
phys,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectGpuNonLocalAdapterMemoryMetrics(ch chan<- prometheus.Metric) error {
|
||||
// Collect the GPU Non Local Adapter Memory perf data.
|
||||
if err := c.gpuNonLocalAdapterMemoryPerfDataCollector.Collect(&c.gpuNonLocalAdapterMemoryPerfDataObject); err != nil {
|
||||
return fmt.Errorf("failed to collect GPU Non Local Adapter Memory perf data: %w", err)
|
||||
}
|
||||
|
||||
nonLocalAdapterMemoryMap := make(map[string]float64)
|
||||
|
||||
for _, data := range c.gpuNonLocalAdapterMemoryPerfDataObject {
|
||||
instance := parseGPUCounterInstanceString(data.Name)
|
||||
|
||||
nonLocalAdapterMemoryMap[instance.Phys] += data.NonLocalUsage
|
||||
}
|
||||
|
||||
for phys, nonLocalUsage := range nonLocalAdapterMemoryMap {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuNonLocalAdapterMemoryUsage,
|
||||
prometheus.GaugeValue,
|
||||
nonLocalUsage,
|
||||
phys,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectGpuProcessMemoryMetrics(ch chan<- prometheus.Metric) error {
|
||||
// Collect the GPU Process Memory perf data.
|
||||
if err := c.gpuProcessMemoryPerfDataCollector.Collect(&c.gpuProcessMemoryPerfDataObject); err != nil {
|
||||
return fmt.Errorf("failed to collect GPU Process Memory perf data: %w", err)
|
||||
}
|
||||
|
||||
processDedicatedUsageMap := make(map[PidPhys]float64)
|
||||
processLocalUsageMap := make(map[PidPhys]float64)
|
||||
processNonLocalUsageMap := make(map[PidPhys]float64)
|
||||
processSharedUsageMap := make(map[PidPhys]float64)
|
||||
processTotalCommittedMap := make(map[PidPhys]float64)
|
||||
|
||||
for _, data := range c.gpuProcessMemoryPerfDataObject {
|
||||
instance := parseGPUCounterInstanceString(data.Name)
|
||||
|
||||
key := PidPhys{
|
||||
Pid: instance.Pid,
|
||||
Phys: instance.Phys,
|
||||
}
|
||||
processDedicatedUsageMap[key] += data.DedicatedUsage
|
||||
processLocalUsageMap[key] += data.LocalUsage
|
||||
processNonLocalUsageMap[key] += data.NonLocalUsage
|
||||
processSharedUsageMap[key] += data.SharedUsage
|
||||
processTotalCommittedMap[key] += data.TotalCommitted
|
||||
}
|
||||
|
||||
for key, dedicatedUsage := range processDedicatedUsageMap {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuProcessMemoryDedicatedUsage,
|
||||
prometheus.GaugeValue,
|
||||
dedicatedUsage,
|
||||
key.Pid, key.Phys,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuProcessMemoryLocalUsage,
|
||||
prometheus.GaugeValue,
|
||||
processLocalUsageMap[key],
|
||||
key.Pid, key.Phys,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuProcessMemoryNonLocalUsage,
|
||||
prometheus.GaugeValue,
|
||||
processNonLocalUsageMap[key],
|
||||
key.Pid, key.Phys,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuProcessMemorySharedUsage,
|
||||
prometheus.GaugeValue,
|
||||
processSharedUsageMap[key],
|
||||
key.Pid, key.Phys,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.gpuProcessMemoryTotalCommitted,
|
||||
prometheus.GaugeValue,
|
||||
processTotalCommittedMap[key],
|
||||
key.Pid, key.Phys,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
33
internal/collector/gpu/gpu_test.go
Normal file
33
internal/collector/gpu/gpu_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 gpu_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/gpu"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
testutils.FuncBenchmarkCollector(b, gpu.Name, gpu.NewWithFlags)
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
testutils.TestCollector(t, gpu.New, nil)
|
||||
}
|
||||
55
internal/collector/gpu/types.go
Normal file
55
internal/collector/gpu/types.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package gpu
|
||||
|
||||
type gpuEnginePerfDataCounterValues struct {
|
||||
Name string
|
||||
|
||||
RunningTime float64 `perfdata:"Running Time"`
|
||||
UtilizationPercentage float64 `perfdata:"Utilization Percentage"`
|
||||
}
|
||||
|
||||
type gpuAdapterMemoryPerfDataCounterValues struct {
|
||||
Name string
|
||||
|
||||
DedicatedUsage float64 `perfdata:"Dedicated Usage"`
|
||||
SharedUsage float64 `perfdata:"Shared Usage"`
|
||||
TotalCommitted float64 `perfdata:"Total Committed"`
|
||||
}
|
||||
|
||||
type gpuLocalAdapterMemoryPerfDataCounterValues struct {
|
||||
Name string
|
||||
|
||||
LocalUsage float64 `perfdata:"Local Usage"`
|
||||
}
|
||||
|
||||
type gpuNonLocalAdapterMemoryPerfDataCounterValues struct {
|
||||
Name string
|
||||
|
||||
NonLocalUsage float64 `perfdata:"Non Local Usage"`
|
||||
}
|
||||
|
||||
type gpuProcessMemoryPerfDataCounterValues struct {
|
||||
Name string
|
||||
|
||||
DedicatedUsage float64 `perfdata:"Dedicated Usage"`
|
||||
LocalUsage float64 `perfdata:"Local Usage"`
|
||||
NonLocalUsage float64 `perfdata:"Non Local Usage"`
|
||||
SharedUsage float64 `perfdata:"Shared Usage"`
|
||||
TotalCommitted float64 `perfdata:"Total Committed"`
|
||||
}
|
||||
84
internal/collector/gpu/utils.go
Normal file
84
internal/collector/gpu/utils.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// 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 gpu
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
Pid string
|
||||
Luid [2]string
|
||||
Phys string
|
||||
Eng string
|
||||
Engtype string
|
||||
Part string
|
||||
}
|
||||
|
||||
type PidPhys struct {
|
||||
Pid string
|
||||
Phys string
|
||||
}
|
||||
|
||||
type PidPhysEngEngType struct {
|
||||
Pid string
|
||||
Phys string
|
||||
Eng string
|
||||
Engtype string
|
||||
}
|
||||
|
||||
func parseGPUCounterInstanceString(s string) Instance {
|
||||
// Example: "pid_1234_luid_0x00000000_0x00005678_phys_0_eng_0_engtype_3D"
|
||||
// Example: "luid_0x00000000_0x00005678_phys_0"
|
||||
// Example: "luid_0x00000000_0x00005678_phys_0_part_0"
|
||||
parts := strings.Split(s, "_")
|
||||
|
||||
var instance Instance
|
||||
|
||||
for i, part := range parts {
|
||||
switch part {
|
||||
case "pid":
|
||||
if i+1 < len(parts) {
|
||||
instance.Pid = parts[i+1]
|
||||
}
|
||||
case "luid":
|
||||
if i+2 < len(parts) {
|
||||
instance.Luid[0] = parts[i+1]
|
||||
instance.Luid[1] = parts[i+2]
|
||||
}
|
||||
case "phys":
|
||||
if i+1 < len(parts) {
|
||||
instance.Phys = parts[i+1]
|
||||
}
|
||||
case "eng":
|
||||
if i+1 < len(parts) {
|
||||
instance.Eng = parts[i+1]
|
||||
}
|
||||
case "engtype":
|
||||
if i+1 < len(parts) {
|
||||
instance.Engtype = parts[i+1]
|
||||
}
|
||||
case "part":
|
||||
if i+1 < len(parts) {
|
||||
instance.Part = parts[i+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return instance
|
||||
}
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/osversion"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ package hyperv
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
|
||||
@@ -20,7 +20,7 @@ package hyperv
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
|
||||
@@ -20,8 +20,8 @@ package mscluster
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
@@ -20,8 +20,8 @@ package mscluster
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/netapi32"
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/sysinfoapi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -118,10 +119,10 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
return fmt.Errorf("failed to get Windows version: %w", err)
|
||||
}
|
||||
|
||||
version := windows.RtlGetVersion()
|
||||
version := osversion.Get()
|
||||
|
||||
// Microsoft has decided to keep the major version as "10" for Windows 11, including the product name.
|
||||
if version.BuildNumber >= 22000 {
|
||||
if version.Build >= osversion.V21H2Win11 {
|
||||
productName = strings.Replace(productName, " 10 ", " 11 ", 1)
|
||||
}
|
||||
|
||||
@@ -131,10 +132,10 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
nil,
|
||||
prometheus.Labels{
|
||||
"product": productName,
|
||||
"version": fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.BuildNumber),
|
||||
"version": version.String(),
|
||||
"major_version": strconv.FormatUint(uint64(version.MajorVersion), 10),
|
||||
"minor_version": strconv.FormatUint(uint64(version.MinorVersion), 10),
|
||||
"build_number": strconv.FormatUint(uint64(version.BuildNumber), 10),
|
||||
"build_number": strconv.FormatUint(uint64(version.Build), 10),
|
||||
"revision": revision,
|
||||
},
|
||||
)
|
||||
@@ -365,7 +366,9 @@ func (c *Collector) getWindowsVersion() (string, string, error) {
|
||||
return "", "", fmt.Errorf("failed to open registry key: %w", err)
|
||||
}
|
||||
|
||||
defer ntKey.Close()
|
||||
defer func(ntKey registry.Key) {
|
||||
_ = ntKey.Close()
|
||||
}(ntKey)
|
||||
|
||||
productName, _, err := ntKey.GetStringValue("ProductName")
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -208,9 +207,9 @@ windows_performancecounter_processor_information_processor_time\{core="0,0",stat
|
||||
promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}).ServeHTTP(rw, &http.Request{})
|
||||
got := rw.Body.String()
|
||||
|
||||
assert.NotEmpty(t, got)
|
||||
require.NotEmpty(t, got)
|
||||
require.NotEmpty(t, tc.expectedMetrics)
|
||||
assert.Regexp(t, tc.expectedMetrics, got)
|
||||
require.Regexp(t, tc.expectedMetrics, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type Object struct {
|
||||
Type pdh.CounterType `json:"type" yaml:"type"`
|
||||
Instances []string `json:"instances" yaml:"instances"`
|
||||
Counters []Counter `json:"counters" yaml:"counters"`
|
||||
InstanceLabel string `json:"instance_label" yaml:"instance_label"` //nolint:tagliatelle
|
||||
InstanceLabel string `json:"instance_label" yaml:"instance_label"`
|
||||
|
||||
collector *pdh.Collector
|
||||
perfDataObject any
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh/registry"
|
||||
pdhtypes "github.com/prometheus-community/windows_exporter/internal/pdh/types"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -42,6 +44,7 @@ type Config struct {
|
||||
ProcessInclude *regexp.Regexp `yaml:"include"`
|
||||
ProcessExclude *regexp.Regexp `yaml:"exclude"`
|
||||
EnableWorkerProcess bool `yaml:"iis"`
|
||||
CounterVersion uint8 `yaml:"counter-version"`
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
@@ -49,6 +52,7 @@ var ConfigDefaults = Config{
|
||||
ProcessInclude: types.RegExpAny,
|
||||
ProcessExclude: types.RegExpEmpty,
|
||||
EnableWorkerProcess: false,
|
||||
CounterVersion: 0,
|
||||
}
|
||||
|
||||
type Collector struct {
|
||||
@@ -59,10 +63,9 @@ type Collector struct {
|
||||
miSession *mi.Session
|
||||
workerProcessMIQueryQuery mi.Query
|
||||
|
||||
collectorVersion int
|
||||
|
||||
collectorV1
|
||||
collectorV2
|
||||
perfDataCollector pdhtypes.Collector
|
||||
perfDataObject []perfDataCounterValues
|
||||
workerCh chan processWorkerRequest
|
||||
|
||||
lookupCache sync.Map
|
||||
|
||||
@@ -130,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.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.",
|
||||
).Default(strconv.FormatUint(uint64(c.config.CounterVersion), 10)).Uint8Var(&c.config.CounterVersion)
|
||||
|
||||
app.Action(func(*kingpin.ParseContext) error {
|
||||
var err error
|
||||
|
||||
@@ -157,8 +165,12 @@ func (c *Collector) Close() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.closeV1()
|
||||
c.closeV2()
|
||||
c.perfDataCollector.Close()
|
||||
|
||||
if c.workerCh != nil {
|
||||
close(c.workerCh)
|
||||
c.workerCh = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -166,42 +178,47 @@ func (c *Collector) Close() error {
|
||||
func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error {
|
||||
c.logger = logger.With(slog.String("collector", Name))
|
||||
|
||||
if miSession == nil {
|
||||
return errors.New("miSession is nil")
|
||||
var err error
|
||||
|
||||
if c.config.EnableWorkerProcess {
|
||||
if miSession == nil {
|
||||
return errors.New("miSession is nil")
|
||||
}
|
||||
|
||||
miQuery, err := mi.NewQuery("SELECT AppPoolName, ProcessId FROM WorkerProcess")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create WMI query: %w", err)
|
||||
}
|
||||
|
||||
c.workerProcessMIQueryQuery = miQuery
|
||||
c.miSession = miSession
|
||||
}
|
||||
|
||||
miQuery, err := mi.NewQuery("SELECT AppPoolName, ProcessId FROM WorkerProcess")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create WMI query: %w", err)
|
||||
}
|
||||
switch c.config.CounterVersion {
|
||||
case 2:
|
||||
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "Process V2", pdh.InstancesAll)
|
||||
case 1:
|
||||
c.perfDataCollector, err = registry.NewCollector[perfDataCounterValues]("Process", pdh.InstancesAll)
|
||||
default:
|
||||
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "Process V2", pdh.InstancesAll)
|
||||
c.config.CounterVersion = 2
|
||||
|
||||
c.workerProcessMIQueryQuery = miQuery
|
||||
c.miSession = miSession
|
||||
if errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject)) {
|
||||
c.perfDataCollector, err = registry.NewCollector[perfDataCounterValues]("Process", pdh.InstancesAll)
|
||||
c.config.CounterVersion = 1
|
||||
}
|
||||
|
||||
c.collectorVersion = 2
|
||||
c.perfDataCollectorV2, err = pdh.NewCollector[perfDataCounterValuesV2](pdh.CounterTypeRaw, "Process V2", pdh.InstancesAll)
|
||||
|
||||
if errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject)) {
|
||||
c.collectorVersion = 1
|
||||
c.perfDataCollectorV1, err = registry.NewCollector[perfDataCounterValuesV1]("Process", pdh.InstancesAll)
|
||||
c.logger.LogAttrs(context.Background(), slog.LevelDebug, fmt.Sprintf("Using process collector V%d", c.config.CounterVersion))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Process collector: %w", err)
|
||||
return fmt.Errorf("failed to create Process V%d collector: %w", c.config.CounterVersion, err)
|
||||
}
|
||||
|
||||
if c.collectorVersion == 1 {
|
||||
c.workerChV1 = make(chan processWorkerRequestV1, 32)
|
||||
c.workerCh = make(chan processWorkerRequest, 32)
|
||||
|
||||
for range 4 {
|
||||
go c.collectWorkerV1()
|
||||
}
|
||||
} else {
|
||||
c.workerChV2 = make(chan processWorkerRequestV2, 32)
|
||||
|
||||
for range 4 {
|
||||
go c.collectWorkerV2()
|
||||
}
|
||||
for range 4 {
|
||||
go c.collectWorker()
|
||||
}
|
||||
|
||||
c.mu = sync.RWMutex{}
|
||||
@@ -320,18 +337,7 @@ func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error {
|
||||
}
|
||||
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||
var workerProcesses []WorkerProcess
|
||||
if c.config.EnableWorkerProcess {
|
||||
if err := c.miSession.Query(&workerProcesses, mi.NamespaceRootWebAdministration, c.workerProcessMIQueryQuery); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.collectorVersion == 1 {
|
||||
return c.collectV1(ch, workerProcesses)
|
||||
}
|
||||
|
||||
return c.collectV2(ch, workerProcesses)
|
||||
return c.collect(ch)
|
||||
}
|
||||
|
||||
// ref: https://github.com/microsoft/hcsshim/blob/8beabacfc2d21767a07c20f8dd5f9f3932dbf305/internal/uvm/stats.go#L25
|
||||
|
||||
@@ -1,294 +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 process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh/registry"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type collectorV1 struct {
|
||||
perfDataCollectorV1 *registry.Collector
|
||||
perfDataObjectV1 []perfDataCounterValuesV1
|
||||
workerChV1 chan processWorkerRequestV1
|
||||
}
|
||||
|
||||
type processWorkerRequestV1 struct {
|
||||
ch chan<- prometheus.Metric
|
||||
name string
|
||||
performanceCounterValues perfDataCounterValuesV1
|
||||
waitGroup *sync.WaitGroup
|
||||
workerProcesses []WorkerProcess
|
||||
}
|
||||
|
||||
func (c *Collector) closeV1() {
|
||||
c.perfDataCollectorV1.Close()
|
||||
|
||||
if c.workerChV1 != nil {
|
||||
close(c.workerChV1)
|
||||
c.workerChV1 = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collector) collectV1(ch chan<- prometheus.Metric, workerProcesses []WorkerProcess) error {
|
||||
err := c.perfDataCollectorV1.Collect(&c.perfDataObjectV1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect metrics: %w", err)
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
for _, process := range c.perfDataObjectV1 {
|
||||
// Duplicate processes are suffixed #, and an index number. Remove those.
|
||||
name, _, _ := strings.Cut(process.Name, ":") // Process V1
|
||||
|
||||
if c.config.ProcessExclude.MatchString(name) || !c.config.ProcessInclude.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
c.workerChV1 <- processWorkerRequestV1{
|
||||
ch: ch,
|
||||
name: name,
|
||||
performanceCounterValues: process,
|
||||
workerProcesses: workerProcesses,
|
||||
waitGroup: wg,
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectWorkerV1() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.logger.Error("Worker panic",
|
||||
slog.Any("panic", r),
|
||||
slog.String("stack", string(debug.Stack())),
|
||||
)
|
||||
|
||||
// Restart the collectWorker
|
||||
go c.collectWorkerV1()
|
||||
}
|
||||
}()
|
||||
|
||||
for req := range c.workerChV1 {
|
||||
(func() {
|
||||
defer req.waitGroup.Done()
|
||||
|
||||
ch := req.ch
|
||||
name := req.name
|
||||
data := req.performanceCounterValues
|
||||
|
||||
pid := uint64(data.IdProcess)
|
||||
parentPID := strconv.FormatUint(uint64(data.CreatingProcessID), 10)
|
||||
|
||||
if c.config.EnableWorkerProcess {
|
||||
for _, wp := range req.workerProcesses {
|
||||
if wp.ProcessId == pid {
|
||||
name = strings.Join([]string{name, wp.AppPoolName}, "_")
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmdLine, processOwner, processGroupID, err := c.getProcessInformation(uint32(pid))
|
||||
if err != nil {
|
||||
slog.LogAttrs(context.Background(), slog.LevelDebug, "Failed to get process information",
|
||||
slog.Uint64("pid", pid),
|
||||
slog.Any("err", err),
|
||||
)
|
||||
}
|
||||
|
||||
pidString := strconv.FormatUint(pid, 10)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.info,
|
||||
prometheus.GaugeValue,
|
||||
1.0,
|
||||
name, pidString, parentPID, strconv.Itoa(int(processGroupID)), processOwner, cmdLine,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.startTime,
|
||||
prometheus.GaugeValue,
|
||||
data.ElapsedTime,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.startTimeOld,
|
||||
prometheus.GaugeValue,
|
||||
data.ElapsedTime,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.handleCount,
|
||||
prometheus.GaugeValue,
|
||||
data.HandleCount,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.cpuTimeTotal,
|
||||
prometheus.CounterValue,
|
||||
data.PercentPrivilegedTime,
|
||||
name, pidString, "privileged",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.cpuTimeTotal,
|
||||
prometheus.CounterValue,
|
||||
data.PercentUserTime,
|
||||
name, pidString, "user",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.ioBytesTotal,
|
||||
prometheus.CounterValue,
|
||||
data.IoOtherBytesPerSec,
|
||||
name, pidString, "other",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.ioOperationsTotal,
|
||||
prometheus.CounterValue,
|
||||
data.IoOtherOperationsPerSec,
|
||||
name, pidString, "other",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.ioBytesTotal,
|
||||
prometheus.CounterValue,
|
||||
data.IoReadBytesPerSec,
|
||||
name, pidString, "read",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.ioOperationsTotal,
|
||||
prometheus.CounterValue,
|
||||
data.IoReadOperationsPerSec,
|
||||
name, pidString, "read",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.ioBytesTotal,
|
||||
prometheus.CounterValue,
|
||||
data.IoWriteBytesPerSec,
|
||||
name, pidString, "write",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.ioOperationsTotal,
|
||||
prometheus.CounterValue,
|
||||
data.IoWriteOperationsPerSec,
|
||||
name, pidString, "write",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.pageFaultsTotal,
|
||||
prometheus.CounterValue,
|
||||
data.PageFaultsPerSec,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.pageFileBytes,
|
||||
prometheus.GaugeValue,
|
||||
data.PageFileBytes,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.poolBytes,
|
||||
prometheus.GaugeValue,
|
||||
data.PoolNonPagedBytes,
|
||||
name, pidString, "nonpaged",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.poolBytes,
|
||||
prometheus.GaugeValue,
|
||||
data.PoolPagedBytes,
|
||||
name, pidString, "paged",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.priorityBase,
|
||||
prometheus.GaugeValue,
|
||||
data.PriorityBase,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.privateBytes,
|
||||
prometheus.GaugeValue,
|
||||
data.PrivateBytes,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.threadCount,
|
||||
prometheus.GaugeValue,
|
||||
data.ThreadCount,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.virtualBytes,
|
||||
prometheus.GaugeValue,
|
||||
data.VirtualBytes,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.workingSetPrivate,
|
||||
prometheus.GaugeValue,
|
||||
data.WorkingSetPrivate,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.workingSetPeak,
|
||||
prometheus.GaugeValue,
|
||||
data.WorkingSetPeak,
|
||||
name, pidString,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.workingSet,
|
||||
prometheus.GaugeValue,
|
||||
data.WorkingSet,
|
||||
name, pidString,
|
||||
)
|
||||
})()
|
||||
}
|
||||
}
|
||||
@@ -27,52 +27,57 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type collectorV2 struct {
|
||||
perfDataCollectorV2 *pdh.Collector
|
||||
perfDataObjectV2 []perfDataCounterValuesV2
|
||||
workerChV2 chan processWorkerRequestV2
|
||||
}
|
||||
|
||||
type processWorkerRequestV2 struct {
|
||||
type processWorkerRequest struct {
|
||||
ch chan<- prometheus.Metric
|
||||
name string
|
||||
performanceCounterValues perfDataCounterValuesV2
|
||||
performanceCounterValues perfDataCounterValues
|
||||
waitGroup *sync.WaitGroup
|
||||
workerProcesses []WorkerProcess
|
||||
}
|
||||
|
||||
func (c *Collector) closeV2() {
|
||||
c.perfDataCollectorV2.Close()
|
||||
|
||||
if c.workerChV2 != nil {
|
||||
close(c.workerChV2)
|
||||
c.workerChV2 = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Collector) collectV2(ch chan<- prometheus.Metric, workerProcesses []WorkerProcess) error {
|
||||
err := c.perfDataCollectorV2.Collect(&c.perfDataObjectV2)
|
||||
func (c *Collector) collect(ch chan<- prometheus.Metric) error {
|
||||
err := c.perfDataCollector.Collect(&c.perfDataObject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect metrics: %w", err)
|
||||
}
|
||||
|
||||
var workerProcesses []WorkerProcess
|
||||
if c.config.EnableWorkerProcess {
|
||||
if err := c.miSession.Query(&workerProcesses, mi.NamespaceRootWebAdministration, c.workerProcessMIQueryQuery); err != nil {
|
||||
return fmt.Errorf("WMI query failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
for _, process := range c.perfDataObjectV2 {
|
||||
for _, process := range c.perfDataObject {
|
||||
// Duplicate processes are suffixed #, and an index number. Remove those.
|
||||
name, _, _ := strings.Cut(process.Name, ":") // Process V2
|
||||
|
||||
// Duplicate processes are suffixed #, and an index number. Remove those.
|
||||
name, _, _ = strings.Cut(name, "#") // Process V1
|
||||
|
||||
if c.config.ProcessExclude.MatchString(name) || !c.config.ProcessInclude.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if process.ProcessID == 0 && name != "Idle" {
|
||||
c.logger.LogAttrs(context.Background(), slog.LevelDebug, "Skipping process with PID 0",
|
||||
slog.String("name", name),
|
||||
slog.String("process_name", process.Name),
|
||||
slog.Any("process", fmt.Sprintf("%+v", process)),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
c.workerChV2 <- processWorkerRequestV2{
|
||||
c.workerCh <- processWorkerRequest{
|
||||
ch: ch,
|
||||
name: name,
|
||||
performanceCounterValues: process,
|
||||
@@ -86,7 +91,7 @@ func (c *Collector) collectV2(ch chan<- prometheus.Metric, workerProcesses []Wor
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectWorkerV2() {
|
||||
func (c *Collector) collectWorker() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.logger.Error("Worker panic",
|
||||
@@ -95,11 +100,11 @@ func (c *Collector) collectWorkerV2() {
|
||||
)
|
||||
|
||||
// Restart the collectWorker
|
||||
go c.collectWorkerV2()
|
||||
go c.collectWorker()
|
||||
}
|
||||
}()
|
||||
|
||||
for req := range c.workerChV2 {
|
||||
for req := range c.workerCh {
|
||||
(func() {
|
||||
defer req.waitGroup.Done()
|
||||
|
||||
@@ -22,7 +22,7 @@ type WorkerProcess struct {
|
||||
ProcessId uint64 `mi:"ProcessId"`
|
||||
}
|
||||
|
||||
type perfDataCounterValuesV1 struct {
|
||||
type perfDataCounterValues struct {
|
||||
Name string
|
||||
|
||||
PercentProcessorTime float64 `perfdata:"% Processor Time"`
|
||||
@@ -52,38 +52,5 @@ type perfDataCounterValuesV1 struct {
|
||||
WorkingSetPrivate float64 `perfdata:"Working Set - Private"`
|
||||
WorkingSetPeak float64 `perfdata:"Working Set Peak"`
|
||||
WorkingSet float64 `perfdata:"Working Set"`
|
||||
IdProcess float64 `perfdata:"ID Process"`
|
||||
}
|
||||
|
||||
type perfDataCounterValuesV2 struct {
|
||||
Name string
|
||||
|
||||
PercentProcessorTime float64 `perfdata:"% Processor Time"`
|
||||
PercentPrivilegedTime float64 `perfdata:"% Privileged Time"`
|
||||
PercentUserTime float64 `perfdata:"% User Time"`
|
||||
CreatingProcessID float64 `perfdata:"Creating Process ID"`
|
||||
ElapsedTime float64 `perfdata:"Elapsed Time"`
|
||||
HandleCount float64 `perfdata:"Handle Count"`
|
||||
IoDataBytesPerSec float64 `perfdata:"IO Data Bytes/sec"`
|
||||
IoDataOperationsPerSec float64 `perfdata:"IO Data Operations/sec"`
|
||||
IoOtherBytesPerSec float64 `perfdata:"IO Other Bytes/sec"`
|
||||
IoOtherOperationsPerSec float64 `perfdata:"IO Other Operations/sec"`
|
||||
IoReadBytesPerSec float64 `perfdata:"IO Read Bytes/sec"`
|
||||
IoReadOperationsPerSec float64 `perfdata:"IO Read Operations/sec"`
|
||||
IoWriteBytesPerSec float64 `perfdata:"IO Write Bytes/sec"`
|
||||
IoWriteOperationsPerSec float64 `perfdata:"IO Write Operations/sec"`
|
||||
PageFaultsPerSec float64 `perfdata:"Page Faults/sec"`
|
||||
PageFileBytesPeak float64 `perfdata:"Page File Bytes Peak"`
|
||||
PageFileBytes float64 `perfdata:"Page File Bytes"`
|
||||
PoolNonPagedBytes float64 `perfdata:"Pool Nonpaged Bytes"`
|
||||
PoolPagedBytes float64 `perfdata:"Pool Paged Bytes"`
|
||||
PriorityBase float64 `perfdata:"Priority Base"`
|
||||
PrivateBytes float64 `perfdata:"Private Bytes"`
|
||||
ThreadCount float64 `perfdata:"Thread Count"`
|
||||
VirtualBytesPeak float64 `perfdata:"Virtual Bytes Peak"`
|
||||
VirtualBytes float64 `perfdata:"Virtual Bytes"`
|
||||
WorkingSetPrivate float64 `perfdata:"Working Set - Private"`
|
||||
WorkingSetPeak float64 `perfdata:"Working Set Peak"`
|
||||
WorkingSet float64 `perfdata:"Working Set"`
|
||||
ProcessID float64 `perfdata:"Process ID"`
|
||||
ProcessID float64 `perfdata:"Process ID" perfdata_v1:"ID Process"`
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ func getScheduledTasks() (ScheduledTasks, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil {
|
||||
if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE); err != nil {
|
||||
var oleCode *ole.OleError
|
||||
if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != S_FALSE {
|
||||
return nil, err
|
||||
|
||||
@@ -352,7 +352,9 @@ func (c *Collector) collectService(ch chan<- prometheus.Metric, serviceName stri
|
||||
|
||||
logLevel := slog.LevelWarn
|
||||
|
||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||
// ERROR_INVALID_PARAMETER returns when the process is not running. This can be happened
|
||||
// if the service terminated after query the service API.
|
||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) || errors.Is(err, windows.ERROR_INVALID_PARAMETER) {
|
||||
logLevel = slog.LevelDebug
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,15 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const Name = "tcp"
|
||||
const (
|
||||
Name = "tcp"
|
||||
|
||||
ipAddressFamilyIPv4 = "ipv4"
|
||||
ipAddressFamilyIPv6 = "ipv6"
|
||||
|
||||
subCollectorMetrics = "metrics"
|
||||
subCollectorConnectionsState = "connections_state"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CollectorsEnabled []string `yaml:"enabled"`
|
||||
@@ -42,8 +50,8 @@ type Config struct {
|
||||
//nolint:gochecknoglobals
|
||||
var ConfigDefaults = Config{
|
||||
CollectorsEnabled: []string{
|
||||
"metrics",
|
||||
"connections_state",
|
||||
subCollectorMetrics,
|
||||
subCollectorConnectionsState,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -111,7 +119,7 @@ func (c *Collector) GetName() string {
|
||||
}
|
||||
|
||||
func (c *Collector) Close() error {
|
||||
if slices.Contains(c.config.CollectorsEnabled, "metrics") {
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorMetrics) {
|
||||
c.perfDataCollector4.Close()
|
||||
c.perfDataCollector6.Close()
|
||||
}
|
||||
@@ -120,79 +128,86 @@ func (c *Collector) Close() error {
|
||||
}
|
||||
|
||||
func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
|
||||
labels := []string{"af"}
|
||||
|
||||
c.connectionFailures = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "connection_failures_total"),
|
||||
"(TCP.ConnectionFailures)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.connectionsActive = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "connections_active_total"),
|
||||
"(TCP.ConnectionsActive)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.connectionsEstablished = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "connections_established"),
|
||||
"(TCP.ConnectionsEstablished)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.connectionsPassive = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "connections_passive_total"),
|
||||
"(TCP.ConnectionsPassive)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.connectionsReset = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "connections_reset_total"),
|
||||
"(TCP.ConnectionsReset)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.segmentsTotal = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "segments_total"),
|
||||
"(TCP.SegmentsTotal)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.segmentsReceivedTotal = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "segments_received_total"),
|
||||
"(TCP.SegmentsReceivedTotal)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.segmentsRetransmittedTotal = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "segments_retransmitted_total"),
|
||||
"(TCP.SegmentsRetransmittedTotal)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.segmentsSentTotal = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "segments_sent_total"),
|
||||
"(TCP.SegmentsSentTotal)",
|
||||
[]string{"af"},
|
||||
labels,
|
||||
nil,
|
||||
)
|
||||
c.connectionsStateCount = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "connections_state_count"),
|
||||
"Number of TCP connections by state and address family",
|
||||
[]string{"af", "state"}, nil,
|
||||
[]string{"af", "state"},
|
||||
nil,
|
||||
)
|
||||
|
||||
var err error
|
||||
errs := make([]error, 0)
|
||||
|
||||
c.perfDataCollector4, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "TCPv4", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create TCPv4 collector: %w", err)
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorMetrics) {
|
||||
var err error
|
||||
|
||||
c.perfDataCollector4, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "TCPv4", nil)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to create TCPv4 collector: %w", err))
|
||||
}
|
||||
|
||||
c.perfDataCollector6, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "TCPv6", nil)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to create TCPv6 collector: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
c.perfDataCollector6, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "TCPv6", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create TCPv6 collector: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Collect sends the metric values for each metric
|
||||
@@ -200,13 +215,13 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||
errs := make([]error, 0)
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, "metrics") {
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorMetrics) {
|
||||
if err := c.collect(ch); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed collecting tcp metrics: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, "connections_state") {
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorConnectionsState) {
|
||||
if err := c.collectConnectionsState(ch); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed collecting tcp connection state metrics: %w", err))
|
||||
}
|
||||
@@ -216,96 +231,100 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||
}
|
||||
|
||||
func (c *Collector) collect(ch chan<- prometheus.Metric) error {
|
||||
err := c.perfDataCollector4.Collect(&c.perfDataObject4)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect TCPv4 metrics[0]. %w", err)
|
||||
errs := make([]error, 0)
|
||||
|
||||
if err := c.perfDataCollector4.Collect(&c.perfDataObject4); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to collect TCPv4 metrics. %w", err))
|
||||
} else if len(c.perfDataObject4) == 0 {
|
||||
errs = append(errs, fmt.Errorf("failed to collect TCPv4 metrics: %w", types.ErrNoDataUnexpected))
|
||||
} else {
|
||||
c.writeTCPCounters(ch, c.perfDataObject4, ipAddressFamilyIPv4)
|
||||
}
|
||||
|
||||
c.writeTCPCounters(ch, c.perfDataObject4, []string{"ipv4"})
|
||||
|
||||
err = c.perfDataCollector6.Collect(&c.perfDataObject6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect TCPv6 metrics[0]. %w", err)
|
||||
if err := c.perfDataCollector6.Collect(&c.perfDataObject6); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to collect TCPv6 metrics. %w", err))
|
||||
} else if len(c.perfDataObject6) == 0 {
|
||||
errs = append(errs, fmt.Errorf("failed to collect TCPv6 metrics: %w", types.ErrNoDataUnexpected))
|
||||
} else {
|
||||
c.writeTCPCounters(ch, c.perfDataObject6, ipAddressFamilyIPv6)
|
||||
}
|
||||
|
||||
c.writeTCPCounters(ch, c.perfDataObject6, []string{"ipv6"})
|
||||
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Collector) writeTCPCounters(ch chan<- prometheus.Metric, metrics []perfDataCounterValues, labels []string) {
|
||||
func (c *Collector) writeTCPCounters(ch chan<- prometheus.Metric, metrics []perfDataCounterValues, af string) {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.connectionFailures,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].ConnectionFailures,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.connectionsActive,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].ConnectionsActive,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.connectionsEstablished,
|
||||
prometheus.GaugeValue,
|
||||
metrics[0].ConnectionsEstablished,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.connectionsPassive,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].ConnectionsPassive,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.connectionsReset,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].ConnectionsReset,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.segmentsTotal,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].SegmentsPerSec,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.segmentsReceivedTotal,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].SegmentsReceivedPerSec,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.segmentsRetransmittedTotal,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].SegmentsRetransmittedPerSec,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.segmentsSentTotal,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].SegmentsSentPerSec,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Collector) collectConnectionsState(ch chan<- prometheus.Metric) error {
|
||||
stateCounts, err := iphlpapi.GetTCPConnectionStates(windows.AF_INET)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect TCP connection states for %s: %w", "ipv4", err)
|
||||
errs := make([]error, 0)
|
||||
|
||||
if stateCounts, err := iphlpapi.GetTCPConnectionStates(windows.AF_INET); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to collect TCP connection states for %s: %w", ipAddressFamilyIPv4, err))
|
||||
} else {
|
||||
c.sendTCPStateMetrics(ch, stateCounts, ipAddressFamilyIPv4)
|
||||
}
|
||||
|
||||
c.sendTCPStateMetrics(ch, stateCounts, "ipv4")
|
||||
|
||||
stateCounts, err = iphlpapi.GetTCPConnectionStates(windows.AF_INET6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect TCP6 connection states for %s: %w", "ipv6", err)
|
||||
if stateCounts, err := iphlpapi.GetTCPConnectionStates(windows.AF_INET6); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to collect TCP6 connection states for %s: %w", ipAddressFamilyIPv6, err))
|
||||
} else {
|
||||
c.sendTCPStateMetrics(ch, stateCounts, ipAddressFamilyIPv6)
|
||||
}
|
||||
|
||||
c.sendTCPStateMetrics(ch, stateCounts, "ipv6")
|
||||
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Collector) sendTCPStateMetrics(ch chan<- prometheus.Metric, stateCounts map[iphlpapi.MIB_TCP_STATE]uint32, af string) {
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/pkg/collector"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -70,7 +69,7 @@ func TestMultipleDirectories(t *testing.T) {
|
||||
require.NoError(t, <-errCh)
|
||||
|
||||
for _, f := range []string{"dir1", "dir2", "dir3", "dir3sub"} {
|
||||
assert.Contains(t, got, f)
|
||||
require.Contains(t, got, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +105,6 @@ func TestDuplicateFileName(t *testing.T) {
|
||||
|
||||
require.ErrorContains(t, <-errCh, "duplicate filename detected")
|
||||
|
||||
assert.Contains(t, got, "file")
|
||||
assert.NotContains(t, got, "sub_file")
|
||||
require.Contains(t, got, "file")
|
||||
require.NotContains(t, got, "sub_file")
|
||||
}
|
||||
|
||||
@@ -25,21 +25,23 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/kernel32"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "time"
|
||||
|
||||
collectorSystemTime = "system_time"
|
||||
collectorNTP = "ntp"
|
||||
collectorSystemTime = "system_time"
|
||||
collectorClockSource = "clock_source"
|
||||
collectorNTP = "ntp"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -50,6 +52,7 @@ type Config struct {
|
||||
var ConfigDefaults = Config{
|
||||
CollectorsEnabled: []string{
|
||||
collectorSystemTime,
|
||||
collectorClockSource,
|
||||
collectorNTP,
|
||||
},
|
||||
}
|
||||
@@ -61,10 +64,13 @@ type Collector struct {
|
||||
perfDataCollector *pdh.Collector
|
||||
perfDataObject []perfDataCounterValues
|
||||
|
||||
logger *slog.Logger
|
||||
|
||||
ppbCounterPresent bool
|
||||
|
||||
currentTime *prometheus.Desc
|
||||
timezone *prometheus.Desc
|
||||
clockSource *prometheus.Desc
|
||||
clockFrequencyAdjustment *prometheus.Desc
|
||||
clockFrequencyAdjustmentPPB *prometheus.Desc
|
||||
computedTimeOffset *prometheus.Desc
|
||||
@@ -124,9 +130,11 @@ func (c *Collector) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
|
||||
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
c.logger = logger.With(slog.String("collector", Name))
|
||||
|
||||
for _, collector := range c.config.CollectorsEnabled {
|
||||
if !slices.Contains([]string{collectorSystemTime, collectorNTP}, collector) {
|
||||
if !slices.Contains([]string{collectorSystemTime, collectorClockSource, collectorNTP}, collector) {
|
||||
return fmt.Errorf("unknown collector: %s", collector)
|
||||
}
|
||||
}
|
||||
@@ -136,16 +144,22 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
|
||||
|
||||
c.currentTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "current_timestamp_seconds"),
|
||||
"OperatingSystem.LocalDateTime",
|
||||
"Current time as reported by the operating system, in unix time.",
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
c.timezone = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "timezone"),
|
||||
"OperatingSystem.LocalDateTime",
|
||||
"Current timezone as reported by the operating system.",
|
||||
[]string{"timezone"},
|
||||
nil,
|
||||
)
|
||||
c.clockSource = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "clock_sync_source"),
|
||||
"This value reflects the sync source of the system clock.",
|
||||
[]string{"type"},
|
||||
nil,
|
||||
)
|
||||
c.clockFrequencyAdjustment = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "clock_frequency_adjustment"),
|
||||
"This value reflects the adjustment made to the local system clock frequency by W32Time in nominal clock units. This counter helps visualize the finer adjustments being made by W32time to synchronize the local clock.",
|
||||
@@ -189,11 +203,13 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
|
||||
nil,
|
||||
)
|
||||
|
||||
var err error
|
||||
if slices.Contains(c.config.CollectorsEnabled, collectorNTP) {
|
||||
var err error
|
||||
|
||||
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "Windows Time Service", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Windows Time Service collector: %w", err)
|
||||
c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "Windows Time Service", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Windows Time Service collector: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -206,7 +222,13 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, collectorSystemTime) {
|
||||
if err := c.collectTime(ch); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed collecting time metrics: %w", err))
|
||||
errs = append(errs, fmt.Errorf("failed collecting operating system time metrics: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, collectorClockSource) {
|
||||
if err := c.collectClockSource(ch); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed collecting clock source metrics: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +266,42 @@ func (c *Collector) collectTime(ch chan<- prometheus.Metric) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectClockSource(ch chan<- prometheus.Metric) error {
|
||||
keyPath := `SYSTEM\CurrentControlSet\Services\W32Time\Parameters`
|
||||
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.READ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open registry key: %w", err)
|
||||
}
|
||||
|
||||
val, _, err := key.GetStringValue("Type")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read 'Type' value: %w", err)
|
||||
}
|
||||
|
||||
for _, validType := range []string{"NTP", "NT5DS", "AllSync", "NoSync", "Local CMOS Clock"} {
|
||||
metricValue := 0.0
|
||||
if val == validType {
|
||||
metricValue = 1.0
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.clockSource,
|
||||
prometheus.GaugeValue,
|
||||
metricValue,
|
||||
validType,
|
||||
)
|
||||
}
|
||||
|
||||
if err := key.Close(); err != nil {
|
||||
c.logger.Debug("failed to close registry key",
|
||||
slog.Any("err", err),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) collectNTP(ch chan<- prometheus.Metric) error {
|
||||
err := c.perfDataCollector.Collect(&c.perfDataObject)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package udp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
@@ -107,19 +108,21 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
|
||||
nil,
|
||||
)
|
||||
|
||||
errs := make([]error, 0)
|
||||
|
||||
var err error
|
||||
|
||||
c.perfDataCollector4, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "UDPv4", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create UDPv4 collector: %w", err)
|
||||
errs = append(errs, fmt.Errorf("failed to create UDPv4 collector: %w", err))
|
||||
}
|
||||
|
||||
c.perfDataCollector6, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "UDPv6", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create UDPv6 collector: %w", err)
|
||||
errs = append(errs, fmt.Errorf("failed to create UDPv6 collector: %w", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Collect sends the metric values for each metric
|
||||
@@ -129,46 +132,50 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||
}
|
||||
|
||||
func (c *Collector) collect(ch chan<- prometheus.Metric) error {
|
||||
err := c.perfDataCollector4.Collect(&c.perfDataObject4)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect UDPv4 metrics: %w", err)
|
||||
errs := make([]error, 0)
|
||||
|
||||
if err := c.perfDataCollector4.Collect(&c.perfDataObject4); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to collect UDPv4 metrics: %w", err))
|
||||
} else if len(c.perfDataObject4) == 0 {
|
||||
errs = append(errs, fmt.Errorf("failed to collect UDPv4 metrics: %w", types.ErrNoDataUnexpected))
|
||||
} else {
|
||||
c.writeUDPCounters(ch, c.perfDataObject4, "ipv4")
|
||||
}
|
||||
|
||||
c.writeUDPCounters(ch, c.perfDataObject4, []string{"ipv4"})
|
||||
|
||||
err = c.perfDataCollector6.Collect(&c.perfDataObject6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect UDPv6 metrics: %w", err)
|
||||
if err := c.perfDataCollector6.Collect(&c.perfDataObject6); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to collect UDPv6 metrics: %w", err))
|
||||
} else if len(c.perfDataObject6) == 0 {
|
||||
errs = append(errs, fmt.Errorf("failed to collect UDPv6 metrics: %w", types.ErrNoDataUnexpected))
|
||||
} else {
|
||||
c.writeUDPCounters(ch, c.perfDataObject6, "ipv6")
|
||||
}
|
||||
|
||||
c.writeUDPCounters(ch, c.perfDataObject6, []string{"ipv6"})
|
||||
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Collector) writeUDPCounters(ch chan<- prometheus.Metric, metrics []perfDataCounterValues, labels []string) {
|
||||
func (c *Collector) writeUDPCounters(ch chan<- prometheus.Metric, metrics []perfDataCounterValues, af string) {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.datagramsNoPortTotal,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].DatagramsNoPortPerSec,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.datagramsReceivedErrorsTotal,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].DatagramsReceivedErrors,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.datagramsReceivedTotal,
|
||||
prometheus.GaugeValue,
|
||||
metrics[0].DatagramsReceivedPerSec,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.datagramsSentTotal,
|
||||
prometheus.CounterValue,
|
||||
metrics[0].DatagramsSentPerSec,
|
||||
labels...,
|
||||
af,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,14 +39,14 @@ import (
|
||||
const Name = "update"
|
||||
|
||||
type Config struct {
|
||||
online bool `yaml:"online"`
|
||||
scrapeInterval time.Duration `yaml:"scrape_interval"`
|
||||
Online bool `yaml:"online"`
|
||||
ScrapeInterval time.Duration `yaml:"scrape_interval"`
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var ConfigDefaults = Config{
|
||||
online: false,
|
||||
scrapeInterval: 6 * time.Hour,
|
||||
Online: false,
|
||||
ScrapeInterval: 6 * time.Hour,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -60,11 +60,14 @@ type Collector struct {
|
||||
mu sync.RWMutex
|
||||
ctxCancelFn context.CancelFunc
|
||||
|
||||
logger *slog.Logger
|
||||
|
||||
metricsBuf []prometheus.Metric
|
||||
|
||||
pendingUpdate *prometheus.Desc
|
||||
queryDurationSeconds *prometheus.Desc
|
||||
lastScrapeMetric *prometheus.Desc
|
||||
pendingUpdate *prometheus.Desc
|
||||
pendingUpdateLastPublished *prometheus.Desc
|
||||
queryDurationSeconds *prometheus.Desc
|
||||
lastScrapeMetric *prometheus.Desc
|
||||
}
|
||||
|
||||
func New(config *Config) *Collector {
|
||||
@@ -92,29 +95,29 @@ func NewWithFlags(app *kingpin.Application) *Collector {
|
||||
app.Flag(
|
||||
"collector.updates.online",
|
||||
"Deprecated: Please use collector.update.online instead",
|
||||
).Default(strconv.FormatBool(ConfigDefaults.online)).BoolVar(&online)
|
||||
).Default(strconv.FormatBool(ConfigDefaults.Online)).BoolVar(&online)
|
||||
|
||||
app.Flag(
|
||||
"collector.updates.scrape-interval",
|
||||
"Deprecated: Please use collector.update.scrape-interval instead",
|
||||
).Default(ConfigDefaults.scrapeInterval.String()).DurationVar(&scrapeInterval)
|
||||
).Default(ConfigDefaults.ScrapeInterval.String()).DurationVar(&scrapeInterval)
|
||||
|
||||
app.Flag(
|
||||
"collector.update.online",
|
||||
"Whether to search for updates online.",
|
||||
).Default(strconv.FormatBool(ConfigDefaults.online)).BoolVar(&c.config.online)
|
||||
).Default(strconv.FormatBool(ConfigDefaults.Online)).BoolVar(&c.config.Online)
|
||||
|
||||
app.Flag(
|
||||
"collector.update.scrape-interval",
|
||||
"Define the interval of scraping Windows Update information.",
|
||||
).Default(ConfigDefaults.scrapeInterval.String()).DurationVar(&c.config.scrapeInterval)
|
||||
).Default(ConfigDefaults.ScrapeInterval.String()).DurationVar(&c.config.ScrapeInterval)
|
||||
|
||||
app.Action(func(*kingpin.ParseContext) error {
|
||||
// Use deprecated flags only if new ones weren't explicitly set
|
||||
if online {
|
||||
// If the new flag is set, ignore the old one
|
||||
if !c.config.online {
|
||||
c.config.online = online
|
||||
if !c.config.Online {
|
||||
c.config.Online = online
|
||||
}
|
||||
|
||||
slog.Warn("Warning: --collector.updates.online is deprecated, use --collector.update.online instead.",
|
||||
@@ -122,10 +125,10 @@ func NewWithFlags(app *kingpin.Application) *Collector {
|
||||
)
|
||||
}
|
||||
|
||||
if scrapeInterval != ConfigDefaults.scrapeInterval {
|
||||
if scrapeInterval != ConfigDefaults.ScrapeInterval {
|
||||
// If the new flag is set, ignore the old one
|
||||
if c.config.scrapeInterval != scrapeInterval {
|
||||
c.config.scrapeInterval = scrapeInterval
|
||||
if c.config.ScrapeInterval != scrapeInterval {
|
||||
c.config.ScrapeInterval = scrapeInterval
|
||||
}
|
||||
|
||||
slog.Warn("Warning: --collector.updates.scrape-interval is deprecated, use --collector.update.scrape-interval instead.",
|
||||
@@ -146,14 +149,14 @@ func (c *Collector) Close() error {
|
||||
}
|
||||
|
||||
func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
logger = logger.With(slog.String("collector", Name))
|
||||
c.logger = logger.With(slog.String("collector", Name))
|
||||
|
||||
logger.Info("update collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.")
|
||||
c.logger.Info("update collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
initErrCh := make(chan error, 1)
|
||||
go c.scheduleUpdateStatus(ctx, logger, initErrCh, c.config.online)
|
||||
go c.scheduleUpdateStatus(ctx, logger, initErrCh, c.config.Online)
|
||||
|
||||
c.ctxCancelFn = cancel
|
||||
|
||||
@@ -164,7 +167,14 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
||||
c.pendingUpdate = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "pending_info"),
|
||||
"Expose information for a single pending update item",
|
||||
[]string{"category", "severity", "title"},
|
||||
[]string{"id", "revision", "category", "severity", "title"},
|
||||
nil,
|
||||
)
|
||||
|
||||
c.pendingUpdateLastPublished = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "pending_published_timestamp"),
|
||||
"Expose last published timestamp for a single pending update item",
|
||||
[]string{"id", "revision"},
|
||||
nil,
|
||||
)
|
||||
|
||||
@@ -210,7 +220,7 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil {
|
||||
if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE); err != nil {
|
||||
var oleCode *ole.OleError
|
||||
if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != 0x00000001 {
|
||||
initErrCh <- fmt.Errorf("CoInitializeEx: %w", err)
|
||||
@@ -222,17 +232,17 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
|
||||
defer ole.CoUninitialize()
|
||||
|
||||
// Create a new instance of the WMI object
|
||||
mus, err := oleutil.CreateObject("Microsoft.Update.Session")
|
||||
sessionObj, err := oleutil.CreateObject("Microsoft.Update.Session")
|
||||
if err != nil {
|
||||
initErrCh <- fmt.Errorf("create Microsoft.Update.Session: %w", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer mus.Release()
|
||||
defer sessionObj.Release()
|
||||
|
||||
// Query the IDispatch interface of the object
|
||||
musQueryInterface, err := mus.QueryInterface(ole.IID_IDispatch)
|
||||
musQueryInterface, err := sessionObj.QueryInterface(ole.IID_IDispatch)
|
||||
if err != nil {
|
||||
initErrCh <- fmt.Errorf("IID_IDispatch: %w", err)
|
||||
|
||||
@@ -241,18 +251,25 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
|
||||
|
||||
defer musQueryInterface.Release()
|
||||
|
||||
_, err = oleutil.PutProperty(musQueryInterface, "UserLocale", 1033)
|
||||
if err != nil {
|
||||
initErrCh <- fmt.Errorf("failed to set ClientApplicationID: %w", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
_, err = oleutil.PutProperty(musQueryInterface, "ClientApplicationID", "windows_exporter")
|
||||
if err != nil {
|
||||
initErrCh <- fmt.Errorf("put ClientApplicationID: %w", err)
|
||||
initErrCh <- fmt.Errorf("failed to set ClientApplicationID: %w", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatesession-createupdatesearcher
|
||||
us, err := oleutil.CallMethod(musQueryInterface, "CreateUpdateSearcher")
|
||||
defer func(hc *ole.VARIANT) {
|
||||
defer func(us *ole.VARIANT) {
|
||||
if us != nil {
|
||||
_ = hc.Clear()
|
||||
_ = us.Clear()
|
||||
}
|
||||
}(us)
|
||||
|
||||
@@ -312,7 +329,7 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
|
||||
c.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-time.After(c.config.scrapeInterval):
|
||||
case <-time.After(c.config.ScrapeInterval):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
@@ -320,7 +337,7 @@ func (c *Collector) scheduleUpdateStatus(ctx context.Context, logger *slog.Logge
|
||||
}
|
||||
|
||||
func (c *Collector) fetchUpdates(logger *slog.Logger, usd *ole.IDispatch) ([]prometheus.Metric, error) {
|
||||
metricsBuf := make([]prometheus.Metric, 0, len(c.metricsBuf))
|
||||
metricsBuf := make([]prometheus.Metric, 0, len(c.metricsBuf)*2+1)
|
||||
|
||||
timeStart := time.Now()
|
||||
|
||||
@@ -367,10 +384,22 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, usd *ole.IDispatch) ([]pro
|
||||
c.pendingUpdate,
|
||||
prometheus.GaugeValue,
|
||||
1,
|
||||
update.identity,
|
||||
update.revision,
|
||||
update.category,
|
||||
update.severity,
|
||||
update.title,
|
||||
))
|
||||
|
||||
if update.lastPublished != (time.Time{}) {
|
||||
metricsBuf = append(metricsBuf, prometheus.MustNewConstMetric(
|
||||
c.pendingUpdateLastPublished,
|
||||
prometheus.GaugeValue,
|
||||
float64(update.lastPublished.Unix()),
|
||||
update.identity,
|
||||
update.revision,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
metricsBuf = append(metricsBuf, prometheus.MustNewConstMetric(
|
||||
@@ -383,9 +412,12 @@ func (c *Collector) fetchUpdates(logger *slog.Logger, usd *ole.IDispatch) ([]pro
|
||||
}
|
||||
|
||||
type windowsUpdate struct {
|
||||
category string
|
||||
severity string
|
||||
title string
|
||||
identity string
|
||||
revision string
|
||||
category string
|
||||
severity string
|
||||
title string
|
||||
lastPublished time.Time
|
||||
}
|
||||
|
||||
// getUpdateStatus retrieves the update status of the given item.
|
||||
@@ -423,10 +455,48 @@ func (c *Collector) getUpdateStatus(updd *ole.IDispatch, item int) (windowsUpdat
|
||||
return windowsUpdate{}, fmt.Errorf("get Title: %w", err)
|
||||
}
|
||||
|
||||
// Get the Identity object
|
||||
identityVariant, err := oleutil.GetProperty(updateItem, "Identity")
|
||||
if err != nil {
|
||||
return windowsUpdate{}, fmt.Errorf("get Identity: %w", err)
|
||||
}
|
||||
|
||||
identity := identityVariant.ToIDispatch()
|
||||
defer identity.Release()
|
||||
|
||||
// Read the UpdateID
|
||||
updateIDVariant, err := oleutil.GetProperty(identity, "UpdateID")
|
||||
if err != nil {
|
||||
return windowsUpdate{}, fmt.Errorf("get UpdateID: %w", err)
|
||||
}
|
||||
|
||||
revisionVariant, err := oleutil.GetProperty(identity, "RevisionNumber")
|
||||
if err != nil {
|
||||
return windowsUpdate{}, fmt.Errorf("get RevisionNumber: %w", err)
|
||||
}
|
||||
|
||||
lastPublished, err := oleutil.GetProperty(updateItem, "LastDeploymentChangeTime")
|
||||
if err != nil {
|
||||
return windowsUpdate{}, fmt.Errorf("get LastDeploymentChangeTime: %w", err)
|
||||
}
|
||||
|
||||
lastPublishedDate, err := ole.GetVariantDate(uint64(lastPublished.Val))
|
||||
if err != nil {
|
||||
c.logger.Debug("failed to convert LastDeploymentChangeTime",
|
||||
slog.String("title", title.ToString()),
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
lastPublishedDate = time.Time{}
|
||||
}
|
||||
|
||||
return windowsUpdate{
|
||||
category: categoryName,
|
||||
severity: severity.ToString(),
|
||||
title: title.ToString(),
|
||||
identity: updateIDVariant.ToString(),
|
||||
revision: strconv.FormatInt(revisionVariant.Val, 10),
|
||||
category: categoryName,
|
||||
severity: severity.ToString(),
|
||||
title: title.ToString(),
|
||||
lastPublished: lastPublishedDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
96
internal/headers/guid/guid.go
Normal file
96
internal/headers/guid/guid.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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 guid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type GUID windows.GUID
|
||||
|
||||
// FromString parses a string containing a GUID and returns the GUID. The only
|
||||
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
// format.
|
||||
func FromString(s string) (GUID, error) {
|
||||
if len(s) != 36 {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
|
||||
var g GUID
|
||||
|
||||
data1, err := strconv.ParseUint(s[0:8], 16, 32)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
|
||||
g.Data1 = uint32(data1)
|
||||
|
||||
data2, err := strconv.ParseUint(s[9:13], 16, 16)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
|
||||
g.Data2 = uint16(data2)
|
||||
|
||||
data3, err := strconv.ParseUint(s[14:18], 16, 16)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
|
||||
g.Data3 = uint16(data3)
|
||||
|
||||
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
|
||||
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
|
||||
if err != nil {
|
||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||
}
|
||||
|
||||
g.Data4[i] = uint8(v)
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *GUID) UnmarshalJSON(b []byte) error {
|
||||
guid, err := FromString(strings.Trim(strings.Trim(string(b), `"`), `{}`))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*g = guid
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GUID) String() string {
|
||||
return fmt.Sprintf(
|
||||
"%08x-%04x-%04x-%04x-%012x",
|
||||
g.Data1,
|
||||
g.Data2,
|
||||
g.Data3,
|
||||
g.Data4[:2],
|
||||
g.Data4[2:])
|
||||
}
|
||||
47
internal/headers/hcn/hcn.go
Normal file
47
internal/headers/hcn/hcn.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
|
||||
"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"}`))
|
||||
)
|
||||
|
||||
func GetEndpointProperties(endpointID guid.GUID) (EndpointProperties, error) {
|
||||
endpoint, err := OpenEndpoint(endpointID)
|
||||
if err != nil {
|
||||
return EndpointProperties{}, fmt.Errorf("failed to open endpoint: %w", err)
|
||||
}
|
||||
|
||||
defer CloseEndpoint(endpoint)
|
||||
|
||||
result, err := QueryEndpointProperties(endpoint, defaultQuery)
|
||||
if err != nil {
|
||||
return EndpointProperties{}, fmt.Errorf("failed to query endpoint properties: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
47
internal/headers/hcn/hcn_test.go
Normal file
47
internal/headers/hcn/hcn_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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)
|
||||
}
|
||||
134
internal/headers/hcn/syscall.go
Normal file
134
internal/headers/hcn/syscall.go
Normal file
@@ -0,0 +1,134 @@
|
||||
// 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/prometheus-community/windows_exporter/internal/headers/guid"
|
||||
"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() ([]guid.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 []guid.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 guid.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))
|
||||
}
|
||||
53
internal/headers/hcn/types.go
Normal file
53
internal/headers/hcn/types.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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 (
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type Endpoint = windows.Handle
|
||||
|
||||
// EndpointProperties contains the properties of an HCN endpoint.
|
||||
//
|
||||
// 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 *guid.GUID `json:"AdapterNetCfgInstanceId"`
|
||||
}
|
||||
|
||||
type EndpointStats struct {
|
||||
BytesReceived uint64 `json:"BytesReceived"`
|
||||
BytesSent uint64 `json:"BytesSent"`
|
||||
DroppedPacketsIncoming uint64 `json:"DroppedPacketsIncoming"`
|
||||
DroppedPacketsOutgoing uint64 `json:"DroppedPacketsOutgoing"`
|
||||
EndpointID string `json:"EndpointId"`
|
||||
InstanceID string `json:"InstanceId"`
|
||||
PacketsReceived uint64 `json:"PacketsReceived"`
|
||||
PacketsSent uint64 `json:"PacketsSent"`
|
||||
}
|
||||
97
internal/headers/hcs/hcs.go
Normal file
97
internal/headers/hcs/hcs.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// 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 hcs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
ContainerQuery = utils.Must(windows.UTF16PtrFromString(`{"Types":["Container"]}`))
|
||||
StatisticsQuery = utils.Must(windows.UTF16PtrFromString(`{"PropertyTypes":["Statistics"]}`))
|
||||
)
|
||||
|
||||
func GetContainers() ([]Properties, error) {
|
||||
operation, err := CreateOperation()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create operation: %w", err)
|
||||
}
|
||||
|
||||
defer CloseOperation(operation)
|
||||
|
||||
if err := EnumerateComputeSystems(ContainerQuery, operation); err != nil {
|
||||
return nil, fmt.Errorf("failed to enumerate compute systems: %w", err)
|
||||
}
|
||||
|
||||
resultDocument, err := WaitForOperationResult(operation, 1000)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to wait and get for operation result: %w - %s", err, resultDocument)
|
||||
} else if resultDocument == "" {
|
||||
return nil, ErrEmptyResultDocument
|
||||
}
|
||||
|
||||
var computeSystems []Properties
|
||||
if err := json.Unmarshal([]byte(resultDocument), &computeSystems); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal compute systems: %w", err)
|
||||
}
|
||||
|
||||
return computeSystems, nil
|
||||
}
|
||||
|
||||
func GetContainerStatistics(containerID string) (Statistics, error) {
|
||||
computeSystem, err := OpenComputeSystem(containerID)
|
||||
if err != nil {
|
||||
return Statistics{}, fmt.Errorf("failed to open compute system: %w", err)
|
||||
}
|
||||
|
||||
defer CloseComputeSystem(computeSystem)
|
||||
|
||||
operation, err := CreateOperation()
|
||||
if err != nil {
|
||||
return Statistics{}, fmt.Errorf("failed to create operation: %w", err)
|
||||
}
|
||||
|
||||
defer CloseOperation(operation)
|
||||
|
||||
if err := GetComputeSystemProperties(computeSystem, operation, StatisticsQuery); err != nil {
|
||||
return Statistics{}, fmt.Errorf("failed to enumerate compute systems: %w", err)
|
||||
}
|
||||
|
||||
resultDocument, err := WaitForOperationResult(operation, 1000)
|
||||
if err != nil {
|
||||
return Statistics{}, fmt.Errorf("failed to get compute system properties: %w", err)
|
||||
} else if resultDocument == "" {
|
||||
return Statistics{}, ErrEmptyResultDocument
|
||||
}
|
||||
|
||||
var properties Properties
|
||||
if err := json.Unmarshal([]byte(resultDocument), &properties); err != nil {
|
||||
return Statistics{}, fmt.Errorf("failed to unmarshal system properties: %w", err)
|
||||
}
|
||||
|
||||
if properties.Statistics == nil {
|
||||
return Statistics{}, fmt.Errorf("no statistics found for container %s", containerID)
|
||||
}
|
||||
|
||||
return *properties.Statistics, nil
|
||||
}
|
||||
55
internal/headers/hcs/hcs_test.go
Normal file
55
internal/headers/hcs/hcs_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package hcs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetContainers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
containers, err := hcs.GetContainers()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, containers)
|
||||
}
|
||||
|
||||
func TestOpenContainer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
containers, err := hcs.GetContainers()
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(containers) == 0 {
|
||||
t.Skip("No containers found")
|
||||
}
|
||||
|
||||
statistics, err := hcs.GetContainerStatistics(containers[0].ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, statistics)
|
||||
}
|
||||
|
||||
func TestOpenContainerNotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := hcs.GetContainerStatistics("f3056b79b36ddfe203376473e2aeb4922a8ca7c5d8100764e5829eb5552fe09b")
|
||||
require.ErrorIs(t, err, hcs.ErrIDNotFound)
|
||||
}
|
||||
130
internal/headers/hcs/syscall.go
Normal file
130
internal/headers/hcs/syscall.go
Normal file
@@ -0,0 +1,130 @@
|
||||
// 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 hcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
modComputeCore = windows.NewLazySystemDLL("computecore.dll")
|
||||
|
||||
procHcsCreateOperation = modComputeCore.NewProc("HcsCreateOperation")
|
||||
procHcsWaitForOperationResult = modComputeCore.NewProc("HcsWaitForOperationResult")
|
||||
procHcsCloseOperation = modComputeCore.NewProc("HcsCloseOperation")
|
||||
procHcsEnumerateComputeSystems = modComputeCore.NewProc("HcsEnumerateComputeSystems")
|
||||
procHcsOpenComputeSystem = modComputeCore.NewProc("HcsOpenComputeSystem")
|
||||
procHcsGetComputeSystemProperties = modComputeCore.NewProc("HcsGetComputeSystemProperties")
|
||||
procHcsCloseComputeSystem = modComputeCore.NewProc("HcsCloseComputeSystem")
|
||||
)
|
||||
|
||||
// CreateOperation creates a new operation.
|
||||
func CreateOperation() (Operation, error) {
|
||||
r1, r2, _ := procHcsCreateOperation.Call(0, 0)
|
||||
if r2 != 0 {
|
||||
return 0, fmt.Errorf("HcsCreateOperation failed: HRESULT 0x%X: %w", r2, Win32FromHResult(r2))
|
||||
}
|
||||
|
||||
return Operation(r1), nil
|
||||
}
|
||||
|
||||
func WaitForOperationResult(operation Operation, timeout uint32) (string, error) {
|
||||
var resultDocument *uint16
|
||||
|
||||
r1, _, _ := procHcsWaitForOperationResult.Call(uintptr(operation), uintptr(timeout), uintptr(unsafe.Pointer(&resultDocument)))
|
||||
if r1 != 0 {
|
||||
return "", fmt.Errorf("HcsWaitForOperationResult failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
|
||||
}
|
||||
|
||||
result := windows.UTF16PtrToString(resultDocument)
|
||||
windows.CoTaskMemFree(unsafe.Pointer(resultDocument))
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CloseOperation closes an operation.
|
||||
func CloseOperation(operation Operation) {
|
||||
_, _, _ = procHcsCloseOperation.Call(uintptr(operation))
|
||||
}
|
||||
|
||||
// EnumerateComputeSystems enumerates compute systems.
|
||||
//
|
||||
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsenumeratecomputesystems
|
||||
func EnumerateComputeSystems(query *uint16, operation Operation) error {
|
||||
r1, _, _ := procHcsEnumerateComputeSystems.Call(uintptr(unsafe.Pointer(query)), uintptr(operation))
|
||||
if r1 != 0 {
|
||||
return fmt.Errorf("HcsEnumerateComputeSystems failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenComputeSystem opens a handle to an existing compute system.
|
||||
//
|
||||
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsopencomputesystem
|
||||
func OpenComputeSystem(id string) (ComputeSystem, error) {
|
||||
idPtr, err := windows.UTF16PtrFromString(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var system ComputeSystem
|
||||
|
||||
r1, _, _ := procHcsOpenComputeSystem.Call(
|
||||
uintptr(unsafe.Pointer(idPtr)),
|
||||
uintptr(windows.GENERIC_ALL),
|
||||
uintptr(unsafe.Pointer(&system)),
|
||||
)
|
||||
if r1 != 0 {
|
||||
return 0, fmt.Errorf("HcsOpenComputeSystem failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1))
|
||||
}
|
||||
|
||||
return system, nil
|
||||
}
|
||||
|
||||
func GetComputeSystemProperties(system ComputeSystem, operation Operation, propertyQuery *uint16) error {
|
||||
r1, _, err := procHcsGetComputeSystemProperties.Call(
|
||||
uintptr(system),
|
||||
uintptr(operation),
|
||||
uintptr(unsafe.Pointer(propertyQuery)),
|
||||
)
|
||||
if r1 != 0 {
|
||||
return fmt.Errorf("HcsGetComputeSystemProperties failed: HRESULT 0x%X: %w", r1, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseComputeSystem closes a handle to a compute system.
|
||||
//
|
||||
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsclosecomputesystem
|
||||
func CloseComputeSystem(system ComputeSystem) {
|
||||
_, _, _ = procHcsCloseComputeSystem.Call(uintptr(system))
|
||||
}
|
||||
|
||||
func Win32FromHResult(hr uintptr) windows.Errno {
|
||||
if hr&0x1fff0000 == 0x00070000 {
|
||||
return windows.Errno(hr & 0xffff)
|
||||
}
|
||||
|
||||
return windows.Errno(hr)
|
||||
}
|
||||
83
internal/headers/hcs/types.go
Normal file
83
internal/headers/hcs/types.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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 hcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyResultDocument = errors.New("empty result document")
|
||||
ErrIDNotFound = windows.Errno(2151088398)
|
||||
)
|
||||
|
||||
type (
|
||||
Operation = windows.Handle
|
||||
ComputeSystem = windows.Handle
|
||||
)
|
||||
|
||||
type Properties struct {
|
||||
ID string `json:"Id,omitempty"`
|
||||
SystemType string `json:"SystemType,omitempty"`
|
||||
Owner string `json:"Owner,omitempty"`
|
||||
State string `json:"State,omitempty"`
|
||||
Statistics *Statistics `json:"Statistics,omitempty"`
|
||||
ProcessList []ProcessDetails `json:"ProcessList,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessDetails struct {
|
||||
ProcessId int32 `json:"ProcessId,omitempty"`
|
||||
ImageName string `json:"ImageName,omitempty"`
|
||||
CreateTimestamp time.Time `json:"CreateTimestamp,omitempty"`
|
||||
UserTime100ns int32 `json:"UserTime100ns,omitempty"`
|
||||
KernelTime100ns int32 `json:"KernelTime100ns,omitempty"`
|
||||
MemoryCommitBytes int32 `json:"MemoryCommitBytes,omitempty"`
|
||||
MemoryWorkingSetPrivateBytes int32 `json:"MemoryWorkingSetPrivateBytes,omitempty"`
|
||||
MemoryWorkingSetSharedBytes int32 `json:"MemoryWorkingSetSharedBytes,omitempty"`
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
Timestamp time.Time `json:"Timestamp,omitempty"`
|
||||
ContainerStartTime time.Time `json:"ContainerStartTime,omitempty"`
|
||||
Uptime100ns uint64 `json:"Uptime100ns,omitempty"`
|
||||
Processor *ProcessorStats `json:"Processor,omitempty"`
|
||||
Memory *MemoryStats `json:"Memory,omitempty"`
|
||||
Storage *StorageStats `json:"Storage,omitempty"`
|
||||
}
|
||||
|
||||
type ProcessorStats struct {
|
||||
TotalRuntime100ns uint64 `json:"TotalRuntime100ns,omitempty"`
|
||||
RuntimeUser100ns uint64 `json:"RuntimeUser100ns,omitempty"`
|
||||
RuntimeKernel100ns uint64 `json:"RuntimeKernel100ns,omitempty"`
|
||||
}
|
||||
|
||||
type MemoryStats struct {
|
||||
MemoryUsageCommitBytes uint64 `json:"MemoryUsageCommitBytes,omitempty"`
|
||||
MemoryUsageCommitPeakBytes uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"`
|
||||
MemoryUsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"`
|
||||
}
|
||||
|
||||
type StorageStats struct {
|
||||
ReadCountNormalized uint64 `json:"ReadCountNormalized,omitempty"`
|
||||
ReadSizeBytes uint64 `json:"ReadSizeBytes,omitempty"`
|
||||
WriteCountNormalized uint64 `json:"WriteCountNormalized,omitempty"`
|
||||
WriteSizeBytes uint64 `json:"WriteSizeBytes,omitempty"`
|
||||
}
|
||||
@@ -22,13 +22,16 @@ import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
|
||||
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
|
||||
procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex")
|
||||
procConvertInterfaceGuidToLuid = modiphlpapi.NewProc("ConvertInterfaceGuidToLuid")
|
||||
)
|
||||
|
||||
func GetTCPConnectionStates(family uint32) (map[MIB_TCP_STATE]uint32, error) {
|
||||
@@ -38,7 +41,7 @@ func GetTCPConnectionStates(family uint32) (map[MIB_TCP_STATE]uint32, error) {
|
||||
case windows.AF_INET:
|
||||
table, err := getExtendedTcpTable[MIB_TCPROW_OWNER_PID](family, TCPTableOwnerPIDAll)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed getExtendedTcpTable: %w", err)
|
||||
}
|
||||
|
||||
for _, row := range table {
|
||||
@@ -49,7 +52,7 @@ func GetTCPConnectionStates(family uint32) (map[MIB_TCP_STATE]uint32, error) {
|
||||
case windows.AF_INET6:
|
||||
table, err := getExtendedTcpTable[MIB_TCP6ROW_OWNER_PID](family, TCPTableOwnerPIDAll)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed getExtendedTcpTable: %w", err)
|
||||
}
|
||||
|
||||
for _, row := range table {
|
||||
@@ -99,12 +102,12 @@ func getExtendedTcpTable[T any](ulAf uint32, tableClass uint32) ([]T, error) {
|
||||
var size uint32
|
||||
|
||||
ret, _, _ := procGetExtendedTcpTable.Call(
|
||||
uintptr(0),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
uintptr(0),
|
||||
0,
|
||||
uintptr(ulAf),
|
||||
uintptr(tableClass),
|
||||
uintptr(0),
|
||||
0,
|
||||
)
|
||||
|
||||
if ret != uintptr(windows.ERROR_INSUFFICIENT_BUFFER) {
|
||||
@@ -116,10 +119,10 @@ func getExtendedTcpTable[T any](ulAf uint32, tableClass uint32) ([]T, error) {
|
||||
ret, _, _ = procGetExtendedTcpTable.Call(
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
uintptr(0),
|
||||
0,
|
||||
uintptr(ulAf),
|
||||
uintptr(tableClass),
|
||||
uintptr(0),
|
||||
0,
|
||||
)
|
||||
|
||||
if ret != 0 {
|
||||
@@ -128,3 +131,37 @@ 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 guid.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
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ package iphlpapi
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/guid"
|
||||
)
|
||||
|
||||
// MIB_TCPROW_OWNER_PID structure for IPv4.
|
||||
@@ -107,3 +109,54 @@ func (b BigEndianUint32) uint16() uint16 {
|
||||
|
||||
return binary.LittleEndian.Uint16(data)
|
||||
}
|
||||
|
||||
// Constants from Windows headers
|
||||
const (
|
||||
IF_MAX_STRING_SIZE = 256
|
||||
IF_MAX_PHYS_ADDRESS_LENGTH = 32
|
||||
)
|
||||
|
||||
// MIB_IF_ROW2 represents network interface statistics
|
||||
type MIB_IF_ROW2 struct {
|
||||
InterfaceLuid uint64
|
||||
InterfaceIndex uint32
|
||||
InterfaceGuid guid.GUID
|
||||
Alias [IF_MAX_STRING_SIZE + 1]uint16
|
||||
Description [IF_MAX_STRING_SIZE + 1]uint16
|
||||
PhysicalAddressLength uint32
|
||||
PhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]byte
|
||||
PermanentPhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]byte
|
||||
Mtu uint32
|
||||
Type uint32
|
||||
TunnelType uint32
|
||||
MediaType uint32
|
||||
PhysicalMediumType uint32
|
||||
AccessType uint32
|
||||
DirectionType uint32
|
||||
InterfaceAndOperStatusFlags uint8
|
||||
OperStatus uint32
|
||||
AdminStatus uint32
|
||||
MediaConnectState uint32
|
||||
NetworkGuid [16]byte
|
||||
ConnectionType uint32
|
||||
TransmitLinkSpeed uint64
|
||||
ReceiveLinkSpeed uint64
|
||||
InOctets uint64
|
||||
InUcastPkts uint64
|
||||
InNUcastPkts uint64
|
||||
InDiscards uint64
|
||||
InErrors uint64
|
||||
InUnknownProtos uint64
|
||||
InUcastOctets uint64
|
||||
InMulticastOctets uint64
|
||||
InBroadcastOctets uint64
|
||||
OutOctets uint64
|
||||
OutUcastPkts uint64
|
||||
OutNUcastPkts uint64
|
||||
OutDiscards uint64
|
||||
OutErrors uint64
|
||||
OutUcastOctets uint64
|
||||
OutMulticastOctets uint64
|
||||
OutBroadcastOctets uint64
|
||||
OutQLen uint64
|
||||
}
|
||||
|
||||
59
internal/headers/kernel32/job.go
Normal file
59
internal/headers/kernel32/job.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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 kernel32
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
// JobObjectQuery is required to retrieve certain information about a job object,
|
||||
// such as attributes and accounting information (see QueryInformationJobObject and IsProcessInJob).
|
||||
// https://learn.microsoft.com/en-us/windows/win32/procthread/job-object-security-and-access-rights
|
||||
JobObjectQuery = 0x0004
|
||||
)
|
||||
|
||||
func OpenJobObject(name string) (windows.Handle, error) {
|
||||
ptr, _ := windows.UTF16PtrFromString(name)
|
||||
handle, _, err := procOpenJobObject.Call(
|
||||
JobObjectQuery,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(ptr)),
|
||||
)
|
||||
|
||||
if handle == 0 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return windows.Handle(handle), nil
|
||||
}
|
||||
|
||||
func IsProcessInJob(process windows.Handle, job windows.Handle, result *bool) error {
|
||||
ret, _, err := procIsProcessInJob.Call(
|
||||
uintptr(process),
|
||||
uintptr(job),
|
||||
uintptr(unsafe.Pointer(&result)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -30,6 +30,8 @@ var (
|
||||
procGetDynamicTimeZoneInformationSys = modkernel32.NewProc("GetDynamicTimeZoneInformation")
|
||||
procKernelLocalFileTimeToFileTime = modkernel32.NewProc("LocalFileTimeToFileTime")
|
||||
procGetTickCount = modkernel32.NewProc("GetTickCount64")
|
||||
procOpenJobObject = modkernel32.NewProc("OpenJobObjectW")
|
||||
procIsProcessInJob = modkernel32.NewProc("IsProcessInJob")
|
||||
)
|
||||
|
||||
// SYSTEMTIME contains a date and time.
|
||||
|
||||
75
internal/headers/kernel32/types.go
Normal file
75
internal/headers/kernel32/types.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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 kernel32
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type JobObjectBasicAccountingInformation struct {
|
||||
TotalUserTime uint64
|
||||
TotalKernelTime uint64
|
||||
ThisPeriodTotalUserTime uint64
|
||||
ThisPeriodTotalKernelTime uint64
|
||||
TotalPageFaultCount uint32
|
||||
TotalProcesses uint32
|
||||
ActiveProcesses uint32
|
||||
TotalTerminatedProcesses uint32
|
||||
}
|
||||
|
||||
// JobObjectBasicAndIOAccountingInformation is a structure that contains
|
||||
// both basic accounting information and I/O accounting information
|
||||
// for a job object. It is used with the QueryInformationJobObject function.
|
||||
// The structure is defined in the Windows API documentation.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_basic_and_io_accounting_information
|
||||
type JobObjectBasicAndIOAccountingInformation struct {
|
||||
BasicInfo JobObjectBasicAccountingInformation
|
||||
IoInfo windows.IO_COUNTERS
|
||||
}
|
||||
type JobObjectMemoryUsageInformation struct {
|
||||
JobMemory uint64
|
||||
PeakJobMemoryUsed uint64
|
||||
}
|
||||
|
||||
type JobObjectBasicProcessIDList struct {
|
||||
NumberOfAssignedProcesses uint32
|
||||
NumberOfProcessIdsInList uint32
|
||||
ProcessIdList [1]uintptr
|
||||
}
|
||||
|
||||
// PIDs returns all the process Ids in the job object.
|
||||
func (p *JobObjectBasicProcessIDList) PIDs() []uint32 {
|
||||
return unsafe.Slice((*uint32)(unsafe.Pointer(&p.ProcessIdList[0])), int(p.NumberOfProcessIdsInList))
|
||||
}
|
||||
|
||||
type PROCESS_VM_COUNTERS struct {
|
||||
PeakVirtualSize uintptr
|
||||
VirtualSize uintptr
|
||||
PageFaultCount uint32
|
||||
PeakWorkingSetSize uintptr
|
||||
WorkingSetSize uintptr
|
||||
QuotaPeakPagedPoolUsage uintptr
|
||||
QuotaPagedPoolUsage uintptr
|
||||
QuotaPeakNonPagedPoolUsage uintptr
|
||||
QuotaNonPagedPoolUsage uintptr
|
||||
PagefileUsage uintptr
|
||||
PeakPagefileUsage uintptr
|
||||
PrivateWorkingSetSize uintptr
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func (s *ScheduleService) Connect() error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED|ole.COINIT_DISABLE_OLE1DDE); err != nil {
|
||||
if err := ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_DISABLE_OLE1DDE); err != nil {
|
||||
var oleCode *ole.OleError
|
||||
if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != 0x00000001 {
|
||||
return err
|
||||
|
||||
135
internal/headers/setupapi/gpu.go
Normal file
135
internal/headers/setupapi/gpu.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package setupapi
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var GUID_DISPLAY_ADAPTER = sync.OnceValue(func() *windows.GUID {
|
||||
return &windows.GUID{
|
||||
Data1: 0x4d36e968,
|
||||
Data2: 0xe325,
|
||||
Data3: 0x11ce,
|
||||
Data4: [8]byte{0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18},
|
||||
}
|
||||
})
|
||||
|
||||
func GetGPUDevices() ([]GPUDevice, error) {
|
||||
hDevInfo, _, err := procSetupDiGetClassDevsW.Call(
|
||||
uintptr(unsafe.Pointer(GUID_DISPLAY_ADAPTER())),
|
||||
0,
|
||||
0,
|
||||
DIGCF_PRESENT,
|
||||
)
|
||||
|
||||
if windows.Handle(hDevInfo) == windows.InvalidHandle {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
devices []GPUDevice
|
||||
deviceData SP_DEVINFO_DATA
|
||||
propertyBuffer [256]uint16
|
||||
)
|
||||
|
||||
deviceData.CbSize = uint32(unsafe.Sizeof(deviceData))
|
||||
|
||||
for i := 0; ; i++ {
|
||||
ret, _, _ := procSetupDiEnumDeviceInfo.Call(hDevInfo, uintptr(i), uintptr(unsafe.Pointer(&deviceData)))
|
||||
if ret == 0 {
|
||||
break // No more devices
|
||||
}
|
||||
|
||||
ret, _, _ = procSetupDiGetDeviceRegistryPropertyW.Call(
|
||||
hDevInfo,
|
||||
uintptr(unsafe.Pointer(&deviceData)),
|
||||
uintptr(SPDRP_DEVICEDESC),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&propertyBuffer[0])),
|
||||
uintptr(len(propertyBuffer)*2),
|
||||
0,
|
||||
)
|
||||
|
||||
gpuDevice := GPUDevice{}
|
||||
|
||||
if ret == 0 {
|
||||
gpuDevice.DeviceDesc = ""
|
||||
} else {
|
||||
gpuDevice.DeviceDesc = windows.UTF16ToString(propertyBuffer[:])
|
||||
}
|
||||
|
||||
ret, _, _ = procSetupDiGetDeviceRegistryPropertyW.Call(
|
||||
hDevInfo,
|
||||
uintptr(unsafe.Pointer(&deviceData)),
|
||||
uintptr(SPDRP_FRIENDLYNAME),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&propertyBuffer[0])),
|
||||
uintptr(len(propertyBuffer)*2),
|
||||
0,
|
||||
)
|
||||
|
||||
if ret == 0 {
|
||||
gpuDevice.FriendlyName = ""
|
||||
} else {
|
||||
gpuDevice.FriendlyName = windows.UTF16ToString(propertyBuffer[:])
|
||||
}
|
||||
|
||||
ret, _, _ = procSetupDiGetDeviceRegistryPropertyW.Call(
|
||||
hDevInfo,
|
||||
uintptr(unsafe.Pointer(&deviceData)),
|
||||
uintptr(SPDRP_HARDWAREID),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&propertyBuffer[0])),
|
||||
uintptr(len(propertyBuffer)*2),
|
||||
0,
|
||||
)
|
||||
|
||||
if ret == 0 {
|
||||
gpuDevice.HardwareID = "unknown"
|
||||
} else {
|
||||
gpuDevice.HardwareID = windows.UTF16ToString(propertyBuffer[:])
|
||||
}
|
||||
|
||||
ret, _, _ = procSetupDiGetDeviceRegistryPropertyW.Call(
|
||||
hDevInfo,
|
||||
uintptr(unsafe.Pointer(&deviceData)),
|
||||
uintptr(SPDRP_PHYSICAL_DEVICE_OBJECT_NAME),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&propertyBuffer[0])),
|
||||
uintptr(len(propertyBuffer)*2),
|
||||
0,
|
||||
)
|
||||
|
||||
if ret == 0 {
|
||||
gpuDevice.PhysicalDeviceObjectName = "unknown"
|
||||
} else {
|
||||
gpuDevice.PhysicalDeviceObjectName = windows.UTF16ToString(propertyBuffer[:])
|
||||
}
|
||||
|
||||
devices = append(devices, gpuDevice)
|
||||
}
|
||||
|
||||
_, _, _ = procSetupDiDestroyDeviceInfoList.Call(hDevInfo)
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
32
internal/headers/setupapi/gpu_test.go
Normal file
32
internal/headers/setupapi/gpu_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package setupapi_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/headers/setupapi"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetGPUDevices(t *testing.T) {
|
||||
devices, err := setupapi.GetGPUDevices()
|
||||
require.NoError(t, err, "Failed to get GPU devices")
|
||||
|
||||
require.NotNil(t, devices)
|
||||
}
|
||||
31
internal/headers/setupapi/setupapi.go
Normal file
31
internal/headers/setupapi/setupapi.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package setupapi
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
modSetupAPI = windows.NewLazySystemDLL("setupapi.dll")
|
||||
procSetupDiGetClassDevsW = modSetupAPI.NewProc("SetupDiGetClassDevsW")
|
||||
procSetupDiEnumDeviceInfo = modSetupAPI.NewProc("SetupDiEnumDeviceInfo")
|
||||
procSetupDiGetDeviceRegistryPropertyW = modSetupAPI.NewProc("SetupDiGetDeviceRegistryPropertyW")
|
||||
procSetupDiDestroyDeviceInfoList = modSetupAPI.NewProc("SetupDiDestroyDeviceInfoList")
|
||||
)
|
||||
42
internal/headers/setupapi/types.go
Normal file
42
internal/headers/setupapi/types.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// Copyright The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package setupapi
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
const (
|
||||
DIGCF_PRESENT = 0x00000002
|
||||
SPDRP_DEVICEDESC = 0x00000000
|
||||
SPDRP_FRIENDLYNAME = 0x0000000C
|
||||
SPDRP_HARDWAREID = 0x00000001
|
||||
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E
|
||||
)
|
||||
|
||||
type SP_DEVINFO_DATA struct {
|
||||
CbSize uint32
|
||||
ClassGuid windows.GUID
|
||||
DevInst uint32
|
||||
_ uintptr // Reserved
|
||||
}
|
||||
|
||||
type GPUDevice struct {
|
||||
DeviceDesc string
|
||||
FriendlyName string
|
||||
HardwareID string
|
||||
PhysicalDeviceObjectName string
|
||||
}
|
||||
65
internal/osversion/osversion_windows.go
Normal file
65
internal/osversion/osversion_windows.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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 osversion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// OSVersion is a wrapper for Windows version information
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
|
||||
type OSVersion struct {
|
||||
Version uint32
|
||||
MajorVersion uint8
|
||||
MinorVersion uint8
|
||||
Build uint16
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var osv = sync.OnceValue(func() OSVersion {
|
||||
v := *windows.RtlGetVersion()
|
||||
|
||||
return OSVersion{
|
||||
MajorVersion: uint8(v.MajorVersion),
|
||||
MinorVersion: uint8(v.MinorVersion),
|
||||
Build: uint16(v.BuildNumber),
|
||||
// Fill version value so that existing clients don't break
|
||||
Version: v.BuildNumber<<16 | (v.MinorVersion << 8) | v.MajorVersion,
|
||||
}
|
||||
})
|
||||
|
||||
// Get gets the operating system version on Windows.
|
||||
// The calling application must be manifested to get the correct version information.
|
||||
func Get() OSVersion {
|
||||
return osv()
|
||||
}
|
||||
|
||||
// Build gets the build-number on Windows
|
||||
// The calling application must be manifested to get the correct version information.
|
||||
func Build() uint16 {
|
||||
return Get().Build
|
||||
}
|
||||
|
||||
// String returns the OSVersion formatted as a string. It implements the
|
||||
// [fmt.Stringer] interface.
|
||||
func (osv OSVersion) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
|
||||
}
|
||||
36
internal/osversion/osversion_windows_test.go
Normal file
36
internal/osversion/osversion_windows_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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 osversion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOSVersionString(t *testing.T) {
|
||||
v := OSVersion{
|
||||
Version: 809042555,
|
||||
MajorVersion: 123,
|
||||
MinorVersion: 2,
|
||||
Build: 12345,
|
||||
}
|
||||
|
||||
require.Equal(t, "the version is: 123.2.12345", fmt.Sprintf("the version is: %s", v))
|
||||
}
|
||||
101
internal/osversion/windowsbuilds.go
Normal file
101
internal/osversion/windowsbuilds.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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 osversion
|
||||
|
||||
// Windows Client and Server build numbers.
|
||||
//
|
||||
// See:
|
||||
// https://learn.microsoft.com/en-us/windows/release-health/release-information
|
||||
// https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info
|
||||
// https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
|
||||
const (
|
||||
// RS1 (version 1607, codename "Redstone 1") corresponds to Windows Server
|
||||
// 2016 (ltsc2016) and Windows 10 (Anniversary Update).
|
||||
RS1 = 14393
|
||||
// V1607 (version 1607, codename "Redstone 1") is an alias for [RS1].
|
||||
V1607 = RS1
|
||||
// LTSC2016 (Windows Server 2016) is an alias for [RS1].
|
||||
LTSC2016 = RS1
|
||||
|
||||
// RS2 (version 1703, codename "Redstone 2") was a client-only update, and
|
||||
// corresponds to Windows 10 (Creators Update).
|
||||
RS2 = 15063
|
||||
// V1703 (version 1703, codename "Redstone 2") is an alias for [RS2].
|
||||
V1703 = RS2
|
||||
|
||||
// RS3 (version 1709, codename "Redstone 3") corresponds to Windows Server
|
||||
// 1709 (Semi-Annual Channel (SAC)), and Windows 10 (Fall Creators Update).
|
||||
RS3 = 16299
|
||||
// V1709 (version 1709, codename "Redstone 3") is an alias for [RS3].
|
||||
V1709 = RS3
|
||||
|
||||
// RS4 (version 1803, codename "Redstone 4") corresponds to Windows Server
|
||||
// 1803 (Semi-Annual Channel (SAC)), and Windows 10 (April 2018 Update).
|
||||
RS4 = 17134
|
||||
// V1803 (version 1803, codename "Redstone 4") is an alias for [RS4].
|
||||
V1803 = RS4
|
||||
|
||||
// RS5 (version 1809, codename "Redstone 5") corresponds to Windows Server
|
||||
// 2019 (ltsc2019), and Windows 10 (October 2018 Update).
|
||||
RS5 = 17763
|
||||
// V1809 (version 1809, codename "Redstone 5") is an alias for [RS5].
|
||||
V1809 = RS5
|
||||
// LTSC2019 (Windows Server 2019) is an alias for [RS5].
|
||||
LTSC2019 = RS5
|
||||
|
||||
// V19H1 (version 1903, codename 19H1) corresponds to Windows Server 1903 (semi-annual
|
||||
// channel).
|
||||
V19H1 = 18362
|
||||
// V1903 (version 1903) is an alias for [V19H1].
|
||||
V1903 = V19H1
|
||||
|
||||
// V19H2 (version 1909, codename 19H2) corresponds to Windows Server 1909 (semi-annual
|
||||
// channel).
|
||||
V19H2 = 18363
|
||||
// V1909 (version 1909) is an alias for [V19H2].
|
||||
V1909 = V19H2
|
||||
|
||||
// V20H1 (version 2004, codename 20H1) corresponds to Windows Server 2004 (semi-annual
|
||||
// channel).
|
||||
V20H1 = 19041
|
||||
// V2004 (version 2004) is an alias for [V20H1].
|
||||
V2004 = V20H1
|
||||
|
||||
// V20H2 corresponds to Windows Server 20H2 (semi-annual channel).
|
||||
V20H2 = 19042
|
||||
|
||||
// V21H1 corresponds to Windows Server 21H1 (semi-annual channel).
|
||||
V21H1 = 19043
|
||||
|
||||
// V21H2Win10 corresponds to Windows 10 (November 2021 Update).
|
||||
V21H2Win10 = 19044
|
||||
|
||||
// V21H2Server corresponds to Windows Server 2022 (ltsc2022).
|
||||
V21H2Server = 20348
|
||||
// LTSC2022 (Windows Server 2022) is an alias for [V21H2Server]
|
||||
LTSC2022 = V21H2Server
|
||||
|
||||
// V21H2Win11 corresponds to Windows 11 (original release).
|
||||
V21H2Win11 = 22000
|
||||
|
||||
// V22H2Win10 corresponds to Windows 10 (2022 Update).
|
||||
V22H2Win10 = 19045
|
||||
|
||||
// V22H2Win11 corresponds to Windows 11 (2022 Update).
|
||||
V22H2Win11 = 22621
|
||||
)
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/osversion"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -66,7 +65,7 @@ func TestCollector(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.NotZerof(t, instance.ThreadCount, "object: %s, instance: %s, counter: %s", tc.object, instance, instance.ThreadCount)
|
||||
require.NotZerof(t, instance.ThreadCount, "object: %s, instance: %s, counter: %s", tc.object, instance, instance.ThreadCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -63,9 +63,12 @@ func NewCollector[T any](object string, _ []string) (*Collector, error) {
|
||||
}
|
||||
|
||||
for _, f := range reflect.VisibleFields(valueType) {
|
||||
counterName, ok := f.Tag.Lookup("perfdata")
|
||||
counterName, ok := f.Tag.Lookup("perfdata_v1")
|
||||
if !ok {
|
||||
continue
|
||||
counterName, ok = f.Tag.Lookup("perfdata")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var counter Counter
|
||||
|
||||
@@ -19,7 +19,6 @@ package registry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
@@ -90,10 +89,6 @@ func (t *NameTable) initialize() {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprint("Invalid index ", index))
|
||||
}
|
||||
|
||||
indexInt, _ := strconv.Atoi(index)
|
||||
|
||||
t.table.index[uint32(indexInt)] = desc
|
||||
|
||||
@@ -208,20 +208,28 @@ func (c *Collection) collectCollector(ch chan<- prometheus.Metric, logger *slog.
|
||||
return pending
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, pdh.ErrNoData) && !errors.Is(err, types.ErrNoData) {
|
||||
if errors.Is(err, pdh.ErrPerformanceCounterNotInitialized) || errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) {
|
||||
err = fmt.Errorf("%w. Check application logs from initialization pharse for more information", err)
|
||||
slogAttrs := make([]slog.Attr, 0)
|
||||
|
||||
if err != nil {
|
||||
if !errors.Is(err, pdh.ErrNoData) && !errors.Is(err, types.ErrNoData) {
|
||||
if errors.Is(err, pdh.ErrPerformanceCounterNotInitialized) || errors.Is(err, mi.MI_RESULT_INVALID_NAMESPACE) {
|
||||
err = fmt.Errorf("%w. Check application logs from initialization pharse for more information", err)
|
||||
}
|
||||
|
||||
logger.LogAttrs(ctx, slog.LevelWarn,
|
||||
fmt.Sprintf("collector %s failed after %s, resulting in %d metrics", name, duration, numMetrics),
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
return failed
|
||||
}
|
||||
|
||||
logger.LogAttrs(ctx, slog.LevelWarn,
|
||||
fmt.Sprintf("collector %s failed after %s, resulting in %d metrics", name, duration, numMetrics),
|
||||
slog.Any("err", err),
|
||||
)
|
||||
|
||||
return failed
|
||||
slogAttrs = append(slogAttrs, slog.Any("err", err))
|
||||
}
|
||||
|
||||
logger.LogAttrs(ctx, slog.LevelDebug, fmt.Sprintf("collector %s succeeded after %s, resulting in %d metrics", name, duration, numMetrics))
|
||||
logger.LogAttrs(ctx, slog.LevelDebug, fmt.Sprintf("collector %s succeeded after %s, resulting in %d metrics", name, duration, numMetrics),
|
||||
slogAttrs...,
|
||||
)
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/exchange"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/filetime"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/gpu"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/hyperv"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/iis"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/license"
|
||||
@@ -114,6 +115,7 @@ func NewWithConfig(config Config) *Collection {
|
||||
collectors[exchange.Name] = exchange.New(&config.Exchange)
|
||||
collectors[filetime.Name] = filetime.New(&config.Filetime)
|
||||
collectors[fsrmquota.Name] = fsrmquota.New(&config.Fsrmquota)
|
||||
collectors[gpu.Name] = gpu.New(&config.GPU)
|
||||
collectors[hyperv.Name] = hyperv.New(&config.HyperV)
|
||||
collectors[iis.Name] = iis.New(&config.IIS)
|
||||
collectors[license.Name] = license.New(&config.License)
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/exchange"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/filetime"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/gpu"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/hyperv"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/iis"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/license"
|
||||
@@ -84,6 +85,7 @@ type Config struct {
|
||||
Exchange exchange.Config `yaml:"exchange"`
|
||||
Filetime filetime.Config `yaml:"filetime"`
|
||||
Fsrmquota fsrmquota.Config `yaml:"fsrmquota"`
|
||||
GPU gpu.Config `yaml:"gpu"`
|
||||
HyperV hyperv.Config `yaml:"hyperv"`
|
||||
IIS iis.Config `yaml:"iis"`
|
||||
License license.Config `yaml:"license"`
|
||||
@@ -139,6 +141,7 @@ var ConfigDefaults = Config{
|
||||
Exchange: exchange.ConfigDefaults,
|
||||
Filetime: filetime.ConfigDefaults,
|
||||
Fsrmquota: fsrmquota.ConfigDefaults,
|
||||
GPU: gpu.ConfigDefaults,
|
||||
HyperV: hyperv.ConfigDefaults,
|
||||
IIS: iis.ConfigDefaults,
|
||||
License: license.ConfigDefaults,
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/exchange"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/filetime"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/fsrmquota"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/gpu"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/hyperv"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/iis"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/license"
|
||||
@@ -95,6 +96,7 @@ var BuildersWithFlags = map[string]BuilderWithFlags[Collector]{
|
||||
exchange.Name: NewBuilderWithFlags(exchange.NewWithFlags),
|
||||
filetime.Name: NewBuilderWithFlags(filetime.NewWithFlags),
|
||||
fsrmquota.Name: NewBuilderWithFlags(fsrmquota.NewWithFlags),
|
||||
gpu.Name: NewBuilderWithFlags(gpu.NewWithFlags),
|
||||
hyperv.Name: NewBuilderWithFlags(hyperv.NewWithFlags),
|
||||
iis.Name: NewBuilderWithFlags(iis.NewWithFlags),
|
||||
license.Name: NewBuilderWithFlags(license.NewWithFlags),
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
"* 14-17 * * 5"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": ["go-yaml/yaml"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"groupName": "golangci-lint",
|
||||
"matchPackageNames": [
|
||||
@@ -57,7 +61,7 @@
|
||||
],
|
||||
"ignoreDeps": [
|
||||
"mcr.microsoft.com/oss/kubernetes/windows-host-process-containers-base-image",
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
|
||||
@@ -125,7 +125,6 @@ windows_exporter_collector_success{collector="os"} 1
|
||||
windows_exporter_collector_success{collector="pagefile"} 1
|
||||
windows_exporter_collector_success{collector="performancecounter"} 1
|
||||
windows_exporter_collector_success{collector="physical_disk"} 1
|
||||
windows_exporter_collector_success{collector="printer"} 1
|
||||
windows_exporter_collector_success{collector="process"} 1
|
||||
windows_exporter_collector_success{collector="scheduled_task"} 1
|
||||
windows_exporter_collector_success{collector="service"} 1
|
||||
@@ -148,7 +147,6 @@ windows_exporter_collector_timeout{collector="os"} 0
|
||||
windows_exporter_collector_timeout{collector="pagefile"} 0
|
||||
windows_exporter_collector_timeout{collector="performancecounter"} 0
|
||||
windows_exporter_collector_timeout{collector="physical_disk"} 0
|
||||
windows_exporter_collector_timeout{collector="printer"} 0
|
||||
windows_exporter_collector_timeout{collector="process"} 0
|
||||
windows_exporter_collector_timeout{collector="scheduled_task"} 0
|
||||
windows_exporter_collector_timeout{collector="service"} 0
|
||||
@@ -355,10 +353,6 @@ windows_exporter_collector_timeout{collector="udp"} 0
|
||||
# TYPE windows_physical_disk_write_seconds_total counter
|
||||
# HELP windows_physical_disk_writes_total The number of write operations on the disk (PhysicalDisk.DiskWritesPerSec)
|
||||
# TYPE windows_physical_disk_writes_total counter
|
||||
# HELP windows_printer_job_count Number of jobs processed by the printer since the last reset
|
||||
# TYPE windows_printer_job_count counter
|
||||
# HELP windows_printer_status Printer status
|
||||
# TYPE windows_printer_status gauge
|
||||
# HELP windows_scheduled_task_last_result The result that was returned the last time the registered task was run
|
||||
# TYPE windows_scheduled_task_last_result gauge
|
||||
windows_scheduled_task_last_result{task="/Microsoft/Windows/PLA/GAEvents"} 0
|
||||
@@ -433,13 +427,15 @@ windows_service_state{name="Themes",state="stopped"} 0
|
||||
# TYPE windows_tcp_segments_total counter
|
||||
# HELP windows_textfile_mtime_seconds Unixtime mtime of textfiles successfully read.
|
||||
# TYPE windows_textfile_mtime_seconds gauge
|
||||
# HELP windows_time_clock_sync_source This value reflects the sync source of the system clock.
|
||||
# TYPE windows_time_clock_sync_source gauge
|
||||
# HELP windows_time_clock_frequency_adjustment This value reflects the adjustment made to the local system clock frequency by W32Time in nominal clock units. This counter helps visualize the finer adjustments being made by W32time to synchronize the local clock.
|
||||
# TYPE windows_time_clock_frequency_adjustment gauge
|
||||
# HELP windows_time_clock_frequency_adjustment_ppb This value reflects the adjustment made to the local system clock frequency by W32Time in Parts Per Billion (PPB) units. 1 PPB adjustment imples the system clock was adjusted at a rate of 1 nanosecond per second. The smallest possible adjustment can vary and can be expected to be in the order of 100's of PPB. This counter helps visualize the finer actions being taken by W32time to synchronize the local clock.
|
||||
# TYPE windows_time_clock_frequency_adjustment_ppb gauge
|
||||
# HELP windows_time_computed_time_offset_seconds Absolute time offset between the system clock and the chosen time source, in seconds
|
||||
# TYPE windows_time_computed_time_offset_seconds gauge
|
||||
# HELP windows_time_current_timestamp_seconds OperatingSystem.LocalDateTime
|
||||
# HELP windows_time_current_timestamp_seconds Current time as reported by the operating system, in unix time.
|
||||
# TYPE windows_time_current_timestamp_seconds gauge
|
||||
# HELP windows_time_ntp_client_time_sources Active number of NTP Time sources being used by the client
|
||||
# TYPE windows_time_ntp_client_time_sources gauge
|
||||
@@ -449,7 +445,7 @@ windows_service_state{name="Themes",state="stopped"} 0
|
||||
# TYPE windows_time_ntp_server_incoming_requests_total counter
|
||||
# HELP windows_time_ntp_server_outgoing_responses_total Total number of requests responded to by NTP server
|
||||
# TYPE windows_time_ntp_server_outgoing_responses_total counter
|
||||
# HELP windows_time_timezone OperatingSystem.LocalDateTime
|
||||
# HELP windows_time_timezone Current timezone as reported by the operating system.
|
||||
# TYPE windows_time_timezone gauge
|
||||
# HELP windows_udp_datagram_no_port_total Number of received UDP datagrams for which there was no application at the destination port
|
||||
# TYPE windows_udp_datagram_no_port_total counter
|
||||
|
||||
@@ -25,7 +25,7 @@ $skip_re = "^(go_|windows_exporter_build_info|windows_exporter_collector_duratio
|
||||
$exporter_proc = Start-Process `
|
||||
-PassThru `
|
||||
-FilePath ..\windows_exporter.exe `
|
||||
-ArgumentList "--log.level=debug","--web.disable-exporter-metrics","--collectors.enabled=[defaults],cpu_info,textfile,process,pagefile,performancecounter,scheduled_task,tcp,udp,time,system,service,logical_disk,printer,os,net,memory,logon,cache","--collector.process.include=explorer.exe","--collector.scheduled_task.include=.*GAEvents","--collector.service.include=Themes","--collector.textfile.directories=$($textfile_dir)",@"
|
||||
-ArgumentList "--log.level=debug","--web.disable-exporter-metrics","--collectors.enabled=[defaults],cpu_info,textfile,process,pagefile,performancecounter,scheduled_task,tcp,udp,time,system,service,logical_disk,os,net,memory,logon,cache","--collector.process.include=explorer.exe","--collector.scheduled_task.include=.*GAEvents","--collector.service.include=Themes","--collector.textfile.directories=$($textfile_dir)",@"
|
||||
--collector.performancecounter.objects="[{\"name\":\"cpu\",\"object\":\"Processor Information\",\"instances\":[\"*\"],\"instance_label\":\"core\",\"counters\":[{\"name\":\"% Processor Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"active\"}},{\"name\":\"% Idle Time\",\"metric\":\"windows_performancecounter_processor_information_processor_time\",\"labels\":{\"state\":\"idle\"}}]},{\"name\":\"memory\",\"object\":\"Memory\",\"counters\":[{\"name\":\"Cache Faults/sec\",\"type\":\"counter\"}]}]"
|
||||
"@ `
|
||||
-WindowStyle Hidden `
|
||||
|
||||
Reference in New Issue
Block a user