diff --git a/docs/collector.mscluster.md b/docs/collector.mscluster.md index 29996c67..66324a17 100644 --- a/docs/collector.mscluster.md +++ b/docs/collector.mscluster.md @@ -5,14 +5,14 @@ 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` +Classes | `MSCluster_Cluster`,`MSCluster_Network`,`MSCluster_Node`,`MSCluster_Resource`,`MSCluster_ResourceGroup`,`MSCluster_DiskPartition`,`MSFT_VirtualDisk`,`MSFT_StoragePool` Enabled by default? | No ## Flags ### `--collectors.mscluster.enabled` Comma-separated list of collectors to use, for example: -`--collectors.mscluster.enabled=cluster,network,node,resource,resouregroup,shared_volumes,virtualdisk`. +`--collectors.mscluster.enabled=cluster,network,node,resource,resouregroup,shared_volumes,virtualdisk,storagepool`. Matching is case-sensitive. ## Metrics @@ -189,6 +189,17 @@ Matching is case-sensitive. | `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 ``` @@ -235,4 +246,4 @@ sum(windows_mscluster_virtualdisk_size_bytes) / sum(windows_mscluster_virtualdis summary: "Low CSV free space on {{ $labels.name }}" description: | Cluster Shared Volume {{ $labels.name }} on cluster {{ $labels.cluster }} has less than 20% free space (current: {{ printf "%.2f" $value }}%) -``` \ No newline at end of file +``` diff --git a/internal/collector/mscluster/mscluster.go b/internal/collector/mscluster/mscluster.go index f1b58c0d..d1c0f3f9 100644 --- a/internal/collector/mscluster/mscluster.go +++ b/internal/collector/mscluster/mscluster.go @@ -41,6 +41,7 @@ const ( subCollectorResourceGroup = "resourcegroup" subCollectorSharedVolumes = "shared_volumes" subCollectorVirtualDisk = "virtualdisk" + subCollectorStoragePool = "storagepool" ) type Config struct { @@ -57,6 +58,7 @@ var ConfigDefaults = Config{ subCollectorResourceGroup, subCollectorSharedVolumes, subCollectorVirtualDisk, + subCollectorStoragePool, }, } @@ -69,6 +71,7 @@ type Collector struct { collectorResourceGroup collectorSharedVolumes collectorVirtualDisk + collectorStoragePool config Config miSession *mi.Session @@ -175,6 +178,12 @@ func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { } } + 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...) } @@ -185,10 +194,10 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time. return nil } - errCh := make(chan error, 7) + errCh := make(chan error, 8) wg := sync.WaitGroup{} - wg.Add(7) + wg.Add(8) go func() { defer wg.Done() @@ -265,6 +274,16 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric, maxScrapeDuration time. } }() + go func() { + defer wg.Done() + + if slices.Contains(c.config.CollectorsEnabled, subCollectorStoragePool) { + if err := c.collectStoragePool(ch, maxScrapeDuration); err != nil { + errCh <- fmt.Errorf("failed to collect storagepool metrics: %w", err) + } + } + }() + wg.Wait() close(errCh) diff --git a/internal/collector/mscluster/mscluster_storagepool.go b/internal/collector/mscluster/mscluster_storagepool.go new file mode 100644 index 00000000..c3a124d8 --- /dev/null +++ b/internal/collector/mscluster/mscluster_storagepool.go @@ -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 mscluster + +import ( + "fmt" + "strconv" + "time" + + "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus/client_golang/prometheus" +) + +const nameStoragePool = Name + "_storagepool" + +type collectorStoragePool struct { + storagePoolMIQuery mi.Query + + storagePoolInfo *prometheus.Desc + storagePoolHealthStatus *prometheus.Desc + storagePoolSize *prometheus.Desc + storagePoolAllocatedSize *prometheus.Desc + storagePoolOperationalStatus *prometheus.Desc + storagePoolThinProvisioningAlertThresholds *prometheus.Desc +} + +// msftStoragePool represents the MSFT_StoragePool WMI class +type msftStoragePool struct { + FriendlyName string `mi:"FriendlyName"` + UniqueId string `mi:"UniqueId"` + HealthStatus uint16 `mi:"HealthStatus"` + Size uint64 `mi:"Size"` + AllocatedSize uint64 `mi:"AllocatedSize"` + // OperationalStatus and ThinProvisioningAlertThresholds are uint16 arrays and require the mi UINT16A support from #2490. + OperationalStatus []uint16 `mi:"OperationalStatus"` + ThinProvisioningAlertThresholds []uint16 `mi:"ThinProvisioningAlertThresholds"` +} + +func (c *Collector) buildStoragePool() error { + wmiSelect := "FriendlyName,UniqueId,HealthStatus,Size,AllocatedSize,OperationalStatus,ThinProvisioningAlertThresholds" + + storagePoolMIQuery, err := mi.NewQuery(fmt.Sprintf("SELECT %s FROM MSFT_StoragePool", wmiSelect)) + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.storagePoolMIQuery = storagePoolMIQuery + + c.storagePoolInfo = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, nameStoragePool, "info"), + "Storage pool information (value is always 1)", + []string{"name", "unique_id"}, + nil, + ) + + c.storagePoolHealthStatus = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, nameStoragePool, "health_status"), + "Health status of the storage pool. 0: Healthy, 1: Warning, 2: Unhealthy, 5: Unknown", + []string{"name", "unique_id"}, + nil, + ) + + c.storagePoolSize = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, nameStoragePool, "size_bytes"), + "Total size of the storage pool in bytes", + []string{"name", "unique_id"}, + nil, + ) + + c.storagePoolAllocatedSize = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, nameStoragePool, "allocated_size_bytes"), + "Allocated size of the storage pool in bytes", + []string{"name", "unique_id"}, + nil, + ) + + c.storagePoolOperationalStatus = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, nameStoragePool, "operational_status"), + "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).", + []string{"name", "unique_id", "threshold"}, + nil, + ) + + var dst []msftStoragePool + + if err := c.miSession.Query(&dst, mi.NamespaceRootStorage, c.storagePoolMIQuery, 0); err != nil { + return fmt.Errorf("WMI query failed: %w", err) + } + + return nil +} + +func (c *Collector) collectStoragePool(ch chan<- prometheus.Metric, maxScrapeDuration time.Duration) error { + var dst []msftStoragePool + + if err := c.miSession.Query(&dst, mi.NamespaceRootStorage, c.storagePoolMIQuery, maxScrapeDuration); err != nil { + return fmt.Errorf("WMI query failed: %w", err) + } + + for _, pool := range dst { + ch <- prometheus.MustNewConstMetric( + c.storagePoolInfo, + prometheus.GaugeValue, + 1.0, + pool.FriendlyName, + pool.UniqueId, + ) + + ch <- prometheus.MustNewConstMetric( + c.storagePoolHealthStatus, + prometheus.GaugeValue, + float64(pool.HealthStatus), + pool.FriendlyName, + pool.UniqueId, + ) + + ch <- prometheus.MustNewConstMetric( + c.storagePoolSize, + prometheus.GaugeValue, + float64(pool.Size), + pool.FriendlyName, + pool.UniqueId, + ) + + ch <- prometheus.MustNewConstMetric( + c.storagePoolAllocatedSize, + prometheus.GaugeValue, + float64(pool.AllocatedSize), + pool.FriendlyName, + pool.UniqueId, + ) + + for _, status := range pool.OperationalStatus { + ch <- prometheus.MustNewConstMetric( + c.storagePoolOperationalStatus, + prometheus.GaugeValue, + 1.0, + pool.FriendlyName, + pool.UniqueId, + strconv.Itoa(int(status)), + ) + } + + for _, threshold := range pool.ThinProvisioningAlertThresholds { + ch <- prometheus.MustNewConstMetric( + c.storagePoolThinProvisioningAlertThresholds, + prometheus.GaugeValue, + 1.0, + pool.FriendlyName, + pool.UniqueId, + strconv.Itoa(int(threshold)), + ) + } + } + + return nil +}