mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
storage_spaces: move storage pool and virtual disk metrics out of mscluster (#2646)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
60e52fd8e6
commit
2b21e74f0e
@@ -391,7 +391,7 @@ jobs:
|
||||
WINDOWS_EXPORTER_TEST_RDP_USER: rdp-ci
|
||||
WINDOWS_TEST_OS: ${{ matrix.os }}
|
||||
# These collectors must work on the fixtures below; missing features are failures.
|
||||
WINDOWS_EXPORTER_TEST_COLLECTORS: "cache,container,cpu,cpu_info,dhcp,diskdrive,dmi,dns,fsrmquota,hyperv,iis,license,logical_disk,memory,mscluster,msmq,mssql,net,netframework,nps,os,pagefile,physical_disk,printer,process,scheduled_task,service,smb,smbclient,system,tcp,terminal_services,time,udp,update${{ matrix.smtp && ',smtp' || '' }}"
|
||||
WINDOWS_EXPORTER_TEST_COLLECTORS: "cache,container,cpu,cpu_info,dhcp,diskdrive,dmi,dns,fsrmquota,hyperv,iis,license,logical_disk,memory,mscluster,msmq,mssql,net,netframework,nps,os,pagefile,physical_disk,printer,process,scheduled_task,service,smb,smbclient,storage_spaces,system,tcp,terminal_services,time,udp,update${{ matrix.smtp && ',smtp' || '' }}"
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
|
||||
@@ -52,6 +52,7 @@ A Prometheus exporter for Windows machines.
|
||||
| [smb](docs/collector.smb.md) | SMB Server | |
|
||||
| [smbclient](docs/collector.smbclient.md) | SMB Client | |
|
||||
| [smtp](docs/collector.smtp.md) | IIS SMTP Server | |
|
||||
| [storage_spaces](docs/collector.storage_spaces.md) | Storage Spaces pools and virtual disks | |
|
||||
| [system](docs/collector.system.md) | System calls | ✓ |
|
||||
| [tcp](docs/collector.tcp.md) | TCP connections | |
|
||||
| [terminal_services](docs/collector.terminal_services.md) | Terminal services (RDS) | |
|
||||
|
||||
@@ -44,6 +44,7 @@ Actions workflow publishes default-branch changes to GitHub Pages.
|
||||
- [`smb`](collector.smb.md)
|
||||
- [`smbclient`](collector.smbclient.md)
|
||||
- [`smtp`](collector.smtp.md)
|
||||
- [`storage_spaces`](collector.storage_spaces.md)
|
||||
- [`system`](collector.system.md)
|
||||
- [`tcp`](collector.tcp.md)
|
||||
- [`terminal_services`](collector.terminal_services.md)
|
||||
|
||||
@@ -5,14 +5,17 @@ The MSCluster_Cluster class is a dynamic WMI class that represents a cluster.
|
||||
|||
|
||||
-|-
|
||||
Metric name prefix | `mscluster`
|
||||
Classes | `MSCluster_Cluster`,`MSCluster_Network`,`MSCluster_Node`,`MSCluster_Resource`,`MSCluster_ResourceGroup`,`MSCluster_DiskPartition`,`MSFT_VirtualDisk`,`MSFT_StoragePool`
|
||||
Classes | `MSCluster_Cluster`,`MSCluster_Network`,`MSCluster_Node`,`MSCluster_Resource`,`MSCluster_ResourceGroup`,`MSCluster_DiskPartition`
|
||||
Enabled by default? | No
|
||||
|
||||
Storage pool and virtual disk metrics (for example of Storage Spaces Direct) are exposed by the
|
||||
[`storage_spaces`](collector.storage_spaces.md) collector.
|
||||
|
||||
## Flags
|
||||
|
||||
### `--collector.mscluster.enabled`
|
||||
Comma-separated list of collectors to use, for example:
|
||||
`--collector.mscluster.enabled=cluster,network,node,resource,resourcegroup,shared_volumes,virtualdisk,storagepool`.
|
||||
`--collector.mscluster.enabled=cluster,network,node,resource,resourcegroup,shared_volumes`.
|
||||
Matching is case-sensitive.
|
||||
|
||||
## Metrics
|
||||
@@ -178,55 +181,18 @@ Matching is case-sensitive.
|
||||
| `mscluster_shared_volumes_total_bytes` | Total size of the Cluster Shared Volume in bytes | gauge | `name`,`volume_guid` |
|
||||
| `mscluster_shared_volumes_free_bytes` | Free space on the Cluster Shared Volume in bytes | gauge | `name`,`volume_guid` |
|
||||
|
||||
### Virtual Disk
|
||||
|
||||
| Name | Description | Type | Labels |
|
||||
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------|-------|--------|
|
||||
| `mscluster_virtualdisk_info` | Virtual disk information (value is always 1) | gauge | `name`, `unique_id` |
|
||||
| `mscluster_virtualdisk_health_status` | Health status of the virtual disk. 0: Healthy, 1: Warning, 2: Unhealthy, 5: Unknown | gauge | `name`, `unique_id` |
|
||||
| `mscluster_virtualdisk_size_bytes` | Total size of the virtual disk in bytes | gauge | `name`, `unique_id` |
|
||||
| `mscluster_virtualdisk_allocated_size_bytes` | Allocated size of the virtual disk in bytes (capacity actually provisioned, excludes thin-provisioned unused capacity) | gauge | `name`, `unique_id` |
|
||||
| `mscluster_virtualdisk_footprint_on_pool_bytes` | Physical storage consumed by the virtual disk on the storage pool in bytes | gauge | `name`, `unique_id` |
|
||||
| `mscluster_virtualdisk_storage_efficiency_percent` | Storage efficiency percentage (AllocatedSize / FootprintOnPool * 100) | gauge | `name`, `unique_id` |
|
||||
|
||||
### Storage Pool
|
||||
|
||||
| Name | Description | Type | Labels |
|
||||
|-----------------------------------------------|-------------------------------------------------------------------------------------|-------|---------------------|
|
||||
| `mscluster_storagepool_info` | Storage pool information (value is always 1) | gauge | `name`, `unique_id` |
|
||||
| `mscluster_storagepool_health_status` | Health status of the storage pool. 0: Healthy, 1: Warning, 2: Unhealthy, 5: Unknown | gauge | `name`, `unique_id` |
|
||||
| `mscluster_storagepool_size_bytes` | Total size of the storage pool in bytes | gauge | `name`, `unique_id` |
|
||||
| `mscluster_storagepool_allocated_size_bytes` | Allocated size of the storage pool in bytes | gauge | `name`, `unique_id` |
|
||||
| `mscluster_storagepool_operational_status` | Operational status codes reported for the storage pool (one series per status value) | gauge | `name`, `unique_id`, `status` |
|
||||
| `mscluster_storagepool_thin_provisioning_alert_thresholds` | Thin provisioning alert thresholds configured for the storage pool, in percent (one series per configured threshold) | gauge | `name`, `unique_id`, `threshold` |
|
||||
|
||||
### Example metric
|
||||
Query the state of all cluster resource owned by node1
|
||||
```
|
||||
windows_mscluster_resource_owner_node{node_name="node1"}
|
||||
```
|
||||
|
||||
Query virtual disk storage efficiency for thin provisioned disks
|
||||
```
|
||||
windows_mscluster_virtualdisk_storage_efficiency_percent
|
||||
```
|
||||
|
||||
## Useful queries
|
||||
Counts the number of Network Name cluster resource
|
||||
```
|
||||
count(windows_mscluster_resource_state{type="Network Name"})
|
||||
```
|
||||
|
||||
Find virtual disks with low storage efficiency (over-provisioned)
|
||||
```
|
||||
windows_mscluster_virtualdisk_storage_efficiency_percent < 50
|
||||
```
|
||||
|
||||
Calculate total virtual disk capacity vs physical usage
|
||||
```
|
||||
sum(windows_mscluster_virtualdisk_size_bytes) / sum(windows_mscluster_virtualdisk_footprint_on_pool_bytes) * 100
|
||||
```
|
||||
|
||||
## Alerting examples
|
||||
|
||||
#### Low free space on cluster shared volume
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# storage_spaces collector
|
||||
|
||||
The storage_spaces collector exposes metrics about Storage Spaces storage pools and virtual disks.
|
||||
It works on standalone hosts as well as on failover clusters using Storage Spaces Direct.
|
||||
|
||||
|||
|
||||
-|-
|
||||
Metric name prefix | `storage_spaces`
|
||||
Classes | [`MSFT_StoragePool`](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-storagepool), [`MSFT_VirtualDisk`](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-virtualdisk)
|
||||
Enabled by default? | No
|
||||
|
||||
## Flags
|
||||
|
||||
### `--collector.storage_spaces.enabled`
|
||||
Comma-separated list of collectors to use, for example:
|
||||
`--collector.storage_spaces.enabled=pool,virtual_disk`.
|
||||
Matching is case-sensitive.
|
||||
|
||||
## Metrics
|
||||
|
||||
### Pool
|
||||
|
||||
| Name | Description | Type | Labels |
|
||||
|------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|-------|----------------------------------|
|
||||
| `windows_storage_spaces_pool_info` | Storage pool information (value is always 1) | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_pool_health_status` | Health status of the storage pool. 0: Healthy, 1: Warning, 2: Unhealthy, 5: Unknown | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_pool_size_bytes` | Total size of the storage pool in bytes | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_pool_allocated_size_bytes` | Allocated size of the storage pool in bytes | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_pool_operational_status` | Operational status codes reported for the storage pool (one series per status value) | gauge | `name`, `unique_id`, `status` |
|
||||
| `windows_storage_spaces_pool_thin_provisioning_alert_thresholds` | Thin provisioning alert thresholds configured for the storage pool, in percent (one series per configured threshold) | gauge | `name`, `unique_id`, `threshold` |
|
||||
|
||||
### Virtual Disk
|
||||
|
||||
| Name | Description | Type | Labels |
|
||||
|------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|-------|---------------------|
|
||||
| `windows_storage_spaces_virtual_disk_info` | Virtual disk information (value is always 1) | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_virtual_disk_health_status` | Health status of the virtual disk. 0: Healthy, 1: Warning, 2: Unhealthy, 5: Unknown | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_virtual_disk_size_bytes` | Total size of the virtual disk in bytes | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_virtual_disk_allocated_size_bytes` | Allocated size of the virtual disk in bytes (capacity actually provisioned, excludes thin-provisioned unused capacity) | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_virtual_disk_footprint_on_pool_bytes` | Physical storage consumed by the virtual disk on the storage pool in bytes | gauge | `name`, `unique_id` |
|
||||
| `windows_storage_spaces_virtual_disk_storage_efficiency_percent` | Storage efficiency percentage (AllocatedSize / FootprintOnPool * 100), omitted while FootprintOnPool is 0 | gauge | `name`, `unique_id` |
|
||||
|
||||
### Example metric
|
||||
|
||||
`windows_storage_spaces_pool_size_bytes{name="Pool01",unique_id="{8f2c...}"} 1.0995116277760e+12`
|
||||
|
||||
## Useful queries
|
||||
|
||||
Storage pool usage in percent
|
||||
```
|
||||
windows_storage_spaces_pool_allocated_size_bytes / windows_storage_spaces_pool_size_bytes * 100
|
||||
```
|
||||
|
||||
Find virtual disks with low storage efficiency (over-provisioned)
|
||||
```
|
||||
windows_storage_spaces_virtual_disk_storage_efficiency_percent < 50
|
||||
```
|
||||
|
||||
Calculate total virtual disk capacity vs physical usage
|
||||
```
|
||||
sum(windows_storage_spaces_virtual_disk_size_bytes) / sum(windows_storage_spaces_virtual_disk_footprint_on_pool_bytes) * 100
|
||||
```
|
||||
|
||||
## Alerting examples
|
||||
|
||||
#### Unhealthy storage pool
|
||||
```yaml
|
||||
- alert: StoragePoolUnhealthy
|
||||
expr: windows_storage_spaces_pool_health_status != 0
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Storage pool {{ $labels.name }} is not healthy"
|
||||
description: "Storage pool {{ $labels.name }} on {{ $labels.instance }} reports health status {{ $value }}."
|
||||
```
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/mssql"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/net"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/netframework"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/storage_spaces"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/tcp"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/update"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
@@ -155,6 +156,7 @@ func TestBuildRejectsUnknownSubCollector(t *testing.T) {
|
||||
mssql.New(&mssql.Config{CollectorsEnabled: unknown}),
|
||||
net.New(&net.Config{CollectorsEnabled: unknown}),
|
||||
netframework.New(&netframework.Config{CollectorsEnabled: unknown}),
|
||||
storage_spaces.New(&storage_spaces.Config{CollectorsEnabled: unknown}),
|
||||
tcp.New(&tcp.Config{CollectorsEnabled: unknown}),
|
||||
} {
|
||||
t.Run(c.GetName(), func(t *testing.T) {
|
||||
|
||||
@@ -40,8 +40,6 @@ const (
|
||||
subCollectorResource = "resource"
|
||||
subCollectorResourceGroup = "resourcegroup"
|
||||
subCollectorSharedVolumes = "shared_volumes"
|
||||
subCollectorVirtualDisk = "virtualdisk"
|
||||
subCollectorStoragePool = "storagepool"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -57,8 +55,6 @@ var ConfigDefaults = Config{
|
||||
subCollectorResource,
|
||||
subCollectorResourceGroup,
|
||||
subCollectorSharedVolumes,
|
||||
subCollectorVirtualDisk,
|
||||
subCollectorStoragePool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -70,8 +66,6 @@ type Collector struct {
|
||||
collectorResource
|
||||
collectorResourceGroup
|
||||
collectorSharedVolumes
|
||||
collectorVirtualDisk
|
||||
collectorStoragePool
|
||||
|
||||
config Config
|
||||
miSession *mi.Session
|
||||
@@ -135,8 +129,6 @@ func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error {
|
||||
subCollectorResource,
|
||||
subCollectorResourceGroup,
|
||||
subCollectorSharedVolumes,
|
||||
subCollectorVirtualDisk,
|
||||
subCollectorStoragePool,
|
||||
}
|
||||
|
||||
for _, name := range c.config.CollectorsEnabled {
|
||||
@@ -191,18 +183,6 @@ func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error {
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorVirtualDisk) {
|
||||
if err := c.buildVirtualDisk(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to build virtualdisk collector: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorStoragePool) {
|
||||
if err := c.buildStoragePool(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to build storagepool collector: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
@@ -282,25 +262,5 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time.
|
||||
})
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorVirtualDisk) {
|
||||
g.Go(func() error {
|
||||
if err := c.collectVirtualDisk(ch, maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("failed to collect virtualdisk metrics: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorStoragePool) {
|
||||
g.Go(func() error {
|
||||
if err := c.collectStoragePool(ch, maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("failed to collect storagepool metrics: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/mscluster"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
@@ -30,7 +29,5 @@ func BenchmarkCollector(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
metrics := testutils.TestCollector(t, mscluster.New, nil)
|
||||
testutils.RequireFixtureMetric(t, metrics, mscluster.Name, "windows_mscluster_virtualdisk_size_bytes", prometheus.Labels{"name": "CIVirtualDisk"})
|
||||
testutils.RequireFixtureMetric(t, metrics, mscluster.Name, "windows_mscluster_storagepool_size_bytes", prometheus.Labels{"name": "GitHubActions"})
|
||||
testutils.TestCollector(t, mscluster.New, nil)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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 storage_spaces
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/prometheus-community/windows_exporter/internal/mi"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/recovery"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
Name = "storage_spaces"
|
||||
|
||||
subCollectorPool = "pool"
|
||||
subCollectorVirtualDisk = "virtual_disk"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CollectorsEnabled []string `yaml:"enabled"`
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var ConfigDefaults = Config{
|
||||
CollectorsEnabled: []string{
|
||||
subCollectorPool,
|
||||
subCollectorVirtualDisk,
|
||||
},
|
||||
}
|
||||
|
||||
// A Collector is a Prometheus Collector for the Storage Spaces WMI classes
|
||||
// MSFT_StoragePool and MSFT_VirtualDisk.
|
||||
type Collector struct {
|
||||
collectorStoragePool
|
||||
collectorVirtualDisk
|
||||
|
||||
config Config
|
||||
miSession *mi.Session
|
||||
}
|
||||
|
||||
func New(config *Config) *Collector {
|
||||
if config == nil {
|
||||
config = &ConfigDefaults
|
||||
}
|
||||
|
||||
if config.CollectorsEnabled == nil {
|
||||
config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled
|
||||
}
|
||||
|
||||
c := &Collector{
|
||||
config: *config,
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func NewWithFlags(app *kingpin.Application) *Collector {
|
||||
c := &Collector{
|
||||
config: ConfigDefaults,
|
||||
}
|
||||
c.config.CollectorsEnabled = make([]string, 0)
|
||||
|
||||
var collectorsEnabled string
|
||||
|
||||
app.Flag(
|
||||
"collector.storage_spaces.enabled",
|
||||
"Comma-separated list of collectors to use.",
|
||||
).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled)
|
||||
|
||||
app.Action(func(*kingpin.ParseContext) error {
|
||||
c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",")
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Collector) GetName() string {
|
||||
return Name
|
||||
}
|
||||
|
||||
func (c *Collector) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error {
|
||||
if len(c.config.CollectorsEnabled) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
subCollectors := []string{
|
||||
subCollectorPool,
|
||||
subCollectorVirtualDisk,
|
||||
}
|
||||
|
||||
for _, name := range c.config.CollectorsEnabled {
|
||||
if !slices.Contains(subCollectors, name) {
|
||||
return fmt.Errorf("unknown sub collector: %s. Possible values: %s", name,
|
||||
strings.Join(subCollectors, ", "),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if miSession == nil {
|
||||
return errors.New("miSession is nil")
|
||||
}
|
||||
|
||||
c.miSession = miSession
|
||||
|
||||
errs := make([]error, 0)
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorPool) {
|
||||
if err := c.buildStoragePool(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to build pool collector: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorVirtualDisk) {
|
||||
if err := c.buildVirtualDisk(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to build virtual_disk collector: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Collect sends the metric values for each metric
|
||||
// to the provided prometheus Metric channel.
|
||||
func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error {
|
||||
if len(c.config.CollectorsEnabled) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var g recovery.Group
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorPool) {
|
||||
g.Go(func() error {
|
||||
if err := c.collectStoragePool(ch, maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("failed to collect pool metrics: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if slices.Contains(c.config.CollectorsEnabled, subCollectorVirtualDisk) {
|
||||
g.Go(func() error {
|
||||
if err := c.collectVirtualDisk(ch, maxScrapeDuration); err != nil {
|
||||
return fmt.Errorf("failed to collect virtual_disk metrics: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
+4
-4
@@ -15,7 +15,7 @@
|
||||
|
||||
//go:build windows
|
||||
|
||||
package mscluster
|
||||
package storage_spaces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const nameStoragePool = Name + "_storagepool"
|
||||
const nameStoragePool = Name + "_pool"
|
||||
|
||||
type collectorStoragePool struct {
|
||||
storagePoolMIQuery mi.Query
|
||||
@@ -92,14 +92,14 @@ func (c *Collector) buildStoragePool() error {
|
||||
|
||||
c.storagePoolOperationalStatus = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, nameStoragePool, "operational_status"),
|
||||
"Operational status codes reported for the storage pool (one series per status value).",
|
||||
"Operational status codes reported for the storage pool (one series per status value)",
|
||||
[]string{"name", "unique_id", "status"},
|
||||
nil,
|
||||
)
|
||||
|
||||
c.storagePoolThinProvisioningAlertThresholds = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, nameStoragePool, "thin_provisioning_alert_thresholds"),
|
||||
"Thin provisioning alert thresholds configured for the storage pool, in percent (one series per configured threshold).",
|
||||
"Thin provisioning alert thresholds configured for the storage pool, in percent (one series per configured threshold)",
|
||||
[]string{"name", "unique_id", "threshold"},
|
||||
nil,
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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 storage_spaces_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/storage_spaces"
|
||||
"github.com/prometheus-community/windows_exporter/internal/utils/testutils"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
testutils.FuncBenchmarkCollector(b, storage_spaces.Name, storage_spaces.NewWithFlags)
|
||||
}
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
metrics := testutils.TestCollector(t, storage_spaces.New, nil)
|
||||
|
||||
pool := prometheus.Labels{"name": "GitHubActions"}
|
||||
virtualDisk := prometheus.Labels{"name": "CIVirtualDisk"}
|
||||
|
||||
testutils.RequireFixtureMetric(t, metrics, storage_spaces.Name, "windows_storage_spaces_pool_info", pool)
|
||||
testutils.RequireFixtureMetric(t, metrics, storage_spaces.Name, "windows_storage_spaces_virtual_disk_info", virtualDisk)
|
||||
|
||||
for _, tc := range []struct {
|
||||
metric string
|
||||
labels prometheus.Labels
|
||||
}{
|
||||
{"windows_storage_spaces_pool_health_status", pool},
|
||||
{"windows_storage_spaces_virtual_disk_health_status", virtualDisk},
|
||||
} {
|
||||
if metric := testutils.RequireFixtureMetric(t, metrics, storage_spaces.Name, tc.metric, tc.labels); metric != nil {
|
||||
require.InDelta(t, 0, metric.GetGauge().GetValue(), 0, "%s: expected healthy", tc.metric)
|
||||
}
|
||||
}
|
||||
|
||||
// The fixture pool consists of two 10 GiB disks.
|
||||
if metric := testutils.RequireFixtureMetric(t, metrics, storage_spaces.Name, "windows_storage_spaces_pool_size_bytes", pool); metric != nil {
|
||||
require.Greater(t, metric.GetGauge().GetValue(), float64(10<<30))
|
||||
}
|
||||
|
||||
// Storage Spaces rounds the requested 1 GiB up to whole slabs per column.
|
||||
if metric := testutils.RequireFixtureMetric(t, metrics, storage_spaces.Name, "windows_storage_spaces_virtual_disk_size_bytes", virtualDisk); metric != nil {
|
||||
require.GreaterOrEqual(t, metric.GetGauge().GetValue(), float64(1<<30))
|
||||
}
|
||||
|
||||
if metric := testutils.RequireFixtureMetric(t, metrics, storage_spaces.Name, "windows_storage_spaces_virtual_disk_storage_efficiency_percent", virtualDisk); metric != nil {
|
||||
value := metric.GetGauge().GetValue()
|
||||
require.Greater(t, value, 0.0)
|
||||
require.LessOrEqual(t, value, 100.0)
|
||||
}
|
||||
}
|
||||
+14
-18
@@ -15,7 +15,7 @@
|
||||
|
||||
//go:build windows
|
||||
|
||||
package mscluster
|
||||
package storage_spaces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const nameVirtualDisk = Name + "_virtualdisk"
|
||||
const nameVirtualDisk = Name + "_virtual_disk"
|
||||
|
||||
type collectorVirtualDisk struct {
|
||||
virtualDiskMIQuery mi.Query
|
||||
@@ -62,7 +62,7 @@ func (c *Collector) buildVirtualDisk() error {
|
||||
|
||||
c.virtualDiskInfo = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, nameVirtualDisk, "info"),
|
||||
"Virtual Disk information (value is always 1)",
|
||||
"Virtual disk information (value is always 1)",
|
||||
[]string{"name", "unique_id"},
|
||||
nil,
|
||||
)
|
||||
@@ -83,7 +83,7 @@ func (c *Collector) buildVirtualDisk() error {
|
||||
|
||||
c.virtualDiskAllocatedSize = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, nameVirtualDisk, "allocated_size_bytes"),
|
||||
"Allocated size of the virtual disk in bytes (data actually provisioned, excludes thin-provisioned unused capacity)",
|
||||
"Allocated size of the virtual disk in bytes (capacity actually provisioned, excludes thin-provisioned unused capacity)",
|
||||
[]string{"name", "unique_id"},
|
||||
nil,
|
||||
)
|
||||
@@ -97,7 +97,7 @@ func (c *Collector) buildVirtualDisk() error {
|
||||
|
||||
c.virtualDiskStorageEfficiency = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, nameVirtualDisk, "storage_efficiency_percent"),
|
||||
"Storage efficiency percentage (AllocatedSize / FootprintOnPool * 100)",
|
||||
"Storage efficiency percentage (AllocatedSize / FootprintOnPool * 100), omitted while FootprintOnPool is 0",
|
||||
[]string{"name", "unique_id"},
|
||||
nil,
|
||||
)
|
||||
@@ -159,25 +159,21 @@ func (c *Collector) collectVirtualDisk(ch chan<- prometheus.Metric, maxScrapeDur
|
||||
vdisk.UniqueId,
|
||||
)
|
||||
|
||||
// Calculate storage efficiency (avoid division by zero).
|
||||
// AllocatedSize (not Size) is the correct numerator: Size is the
|
||||
// thin-provisioned ceiling, while AllocatedSize reflects the capacity
|
||||
// actually provisioned. Using Size inflates the ratio on thin-provisioned
|
||||
// disks (e.g. S2D FTT2 CSVs).
|
||||
var storageEfficiency float64
|
||||
// Without a footprint the ratio is undefined; skip it instead of
|
||||
// publishing 0.
|
||||
if vdisk.FootprintOnPool > 0 {
|
||||
storageEfficiency = float64(vdisk.AllocatedSize) / float64(vdisk.FootprintOnPool) * 100
|
||||
} else {
|
||||
storageEfficiency = 0
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.virtualDiskStorageEfficiency,
|
||||
prometheus.GaugeValue,
|
||||
float64(vdisk.AllocatedSize)/float64(vdisk.FootprintOnPool)*100,
|
||||
vdisk.FriendlyName,
|
||||
vdisk.UniqueId,
|
||||
)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.virtualDiskStorageEfficiency,
|
||||
prometheus.GaugeValue,
|
||||
storageEfficiency,
|
||||
vdisk.FriendlyName,
|
||||
vdisk.UniqueId,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -68,6 +68,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smb"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smbclient"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smtp"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/storage_spaces"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/system"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/tcp"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/terminal_services"
|
||||
@@ -141,6 +142,7 @@ func NewWithConfig(config Config) *Collection {
|
||||
collectors[smb.Name] = smb.New(&config.SMB)
|
||||
collectors[smbclient.Name] = smbclient.New(&config.SMBClient)
|
||||
collectors[smtp.Name] = smtp.New(&config.SMTP)
|
||||
collectors[storage_spaces.Name] = storage_spaces.New(&config.StorageSpaces)
|
||||
collectors[system.Name] = system.New(&config.System)
|
||||
collectors[tcp.Name] = tcp.New(&config.TCP)
|
||||
collectors[terminal_services.Name] = terminal_services.New(&config.TerminalServices)
|
||||
|
||||
@@ -58,6 +58,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smb"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smbclient"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smtp"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/storage_spaces"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/system"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/tcp"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/terminal_services"
|
||||
@@ -111,6 +112,7 @@ type Config struct {
|
||||
SMB smb.Config `yaml:"smb"`
|
||||
SMBClient smbclient.Config `yaml:"smb_client"`
|
||||
SMTP smtp.Config `yaml:"smtp"`
|
||||
StorageSpaces storage_spaces.Config `yaml:"storage_spaces"`
|
||||
System system.Config `yaml:"system"`
|
||||
TCP tcp.Config `yaml:"tcp"`
|
||||
TerminalServices terminal_services.Config `yaml:"terminal_services"`
|
||||
@@ -168,6 +170,7 @@ var ConfigDefaults = Config{
|
||||
SMB: smb.ConfigDefaults,
|
||||
SMBClient: smbclient.ConfigDefaults,
|
||||
SMTP: smtp.ConfigDefaults,
|
||||
StorageSpaces: storage_spaces.ConfigDefaults,
|
||||
System: system.ConfigDefaults,
|
||||
TCP: tcp.ConfigDefaults,
|
||||
TerminalServices: terminal_services.ConfigDefaults,
|
||||
|
||||
@@ -62,6 +62,7 @@ import (
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smb"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smbclient"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/smtp"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/storage_spaces"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/system"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/tcp"
|
||||
"github.com/prometheus-community/windows_exporter/internal/collector/terminal_services"
|
||||
@@ -122,6 +123,7 @@ var BuildersWithFlags = map[string]BuilderWithFlags[Collector]{
|
||||
smb.Name: NewBuilderWithFlags(smb.NewWithFlags),
|
||||
smbclient.Name: NewBuilderWithFlags(smbclient.NewWithFlags),
|
||||
smtp.Name: NewBuilderWithFlags(smtp.NewWithFlags),
|
||||
storage_spaces.Name: NewBuilderWithFlags(storage_spaces.NewWithFlags),
|
||||
system.Name: NewBuilderWithFlags(system.NewWithFlags),
|
||||
tcp.Name: NewBuilderWithFlags(tcp.NewWithFlags),
|
||||
terminal_services.Name: NewBuilderWithFlags(terminal_services.NewWithFlags),
|
||||
|
||||
Reference in New Issue
Block a user