diff --git a/docs/collector.mscluster.md b/docs/collector.mscluster.md index 2a254413..3847a0a9 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` +Classes | `MSCluster_Cluster`,`MSCluster_Network`,`MSCluster_Node`,`MSCluster_Resource`,`MSCluster_ResourceGroup`,`MSCluster_DiskPartition` 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`. +`--collectors.mscluster.enabled=cluster,network,node,resource,resourcegroup,shared_volumes`. Matching is case-sensitive. ## Metrics @@ -170,6 +170,14 @@ Matching is case-sensitive. | `mscluster_resourcegroup_State` | The current state of the resource group. -1: Unknown; 0: Online; 1: Offline; 2: Failed; 3: Partial Online; 4: Pending | gauge | `name` | | `mscluster_resourcegroup_UpdateDomain` | | gauge | `name` | +### Shared Volumes + +| Name | Description | Type | Labels | +|------------------------------------------|----------------------------------------------------------------|-------|-----------------------------| +| `mscluster_shared_volumes_info` | Cluster Shared Volumes information (value is always 1) | gauge | `name`,`path`,`volume_guid` | +| `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` | + ### Example metric Query the state of all cluster resource owned by node1 ``` diff --git a/internal/collector/mscluster/mscluster.go b/internal/collector/mscluster/mscluster.go index 32b2802b..1ef39619 100644 --- a/internal/collector/mscluster/mscluster.go +++ b/internal/collector/mscluster/mscluster.go @@ -38,6 +38,7 @@ const ( subCollectorNode = "node" subCollectorResource = "resource" subCollectorResourceGroup = "resourcegroup" + subCollectorSharedVolumes = "shared_volumes" ) type Config struct { @@ -52,6 +53,7 @@ var ConfigDefaults = Config{ subCollectorNode, subCollectorResource, subCollectorResourceGroup, + subCollectorSharedVolumes, }, } @@ -62,6 +64,7 @@ type Collector struct { collectorNode collectorResource collectorResourceGroup + collectorSharedVolumes config Config miSession *mi.Session @@ -156,6 +159,12 @@ func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { } } + if slices.Contains(c.config.CollectorsEnabled, subCollectorSharedVolumes) { + if err := c.buildSharedVolumes(); err != nil { + errs = append(errs, fmt.Errorf("failed to build shared_volumes collector: %w", err)) + } + } + return errors.Join(errs...) } @@ -166,10 +175,10 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error { return nil } - errCh := make(chan error, 5) + errCh := make(chan error, 6) wg := sync.WaitGroup{} - wg.Add(5) + wg.Add(6) go func() { defer wg.Done() @@ -226,6 +235,16 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error { }() }() + go func() { + defer wg.Done() + + if slices.Contains(c.config.CollectorsEnabled, subCollectorSharedVolumes) { + if err := c.collectSharedVolumes(ch); err != nil { + errCh <- fmt.Errorf("failed to collect shared_volumes metrics: %w", err) + } + } + }() + wg.Wait() close(errCh) diff --git a/internal/collector/mscluster/mscluster_shared_volumes.go b/internal/collector/mscluster/mscluster_shared_volumes.go new file mode 100644 index 00000000..13467fc2 --- /dev/null +++ b/internal/collector/mscluster/mscluster_shared_volumes.go @@ -0,0 +1,122 @@ +// 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" + "strings" + + "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus/client_golang/prometheus" +) + +const nameSharedVolumes = Name + "_shared_volumes" + +type collectorSharedVolumes struct { + sharedVolumesMIQuery mi.Query + + sharedVolumesInfo *prometheus.Desc + sharedVolumesTotalSize *prometheus.Desc + sharedVolumesFreeSpace *prometheus.Desc +} + +// msClusterDiskPartition represents the MSCluster_DiskPartition WMI class +type msClusterDiskPartition struct { + Name string `mi:"Name"` + Path string `mi:"Path"` + TotalSize uint64 `mi:"TotalSize"` + FreeSpace uint64 `mi:"FreeSpace"` + Volume string `mi:"VolumeLabel"` + VolumeGuid string `mi:"VolumeGuid"` +} + +func (c *Collector) buildSharedVolumes() error { + sharedVolumesMIQuery, err := mi.NewQuery("SELECT Name, Path, TotalSize, FreeSpace, VolumeLabel, VolumeGuid FROM MSCluster_DiskPartition") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.sharedVolumesMIQuery = sharedVolumesMIQuery + + c.sharedVolumesInfo = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, nameSharedVolumes, "info"), + "Cluster Shared Volumes information (value is always 1)", + []string{"name", "path", "volume_guid"}, + nil, + ) + + c.sharedVolumesTotalSize = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, nameSharedVolumes, "total_bytes"), + "Total size of the Cluster Shared Volume in bytes", + []string{"name", "volume_guid"}, + nil, + ) + + c.sharedVolumesFreeSpace = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, nameSharedVolumes, "free_bytes"), + "Free space on the Cluster Shared Volume in bytes", + []string{"name", "volume_guid"}, + nil, + ) + + var dst []msClusterDiskPartition + if err := c.miSession.Query(&dst, mi.NamespaceRootMSCluster, c.sharedVolumesMIQuery); err != nil { + return fmt.Errorf("WMI query failed: %w", err) + } + + return nil +} + +func (c *Collector) collectSharedVolumes(ch chan<- prometheus.Metric) error { + var dst []msClusterDiskPartition + if err := c.miSession.Query(&dst, mi.NamespaceRootMSCluster, c.sharedVolumesMIQuery); err != nil { + return fmt.Errorf("WMI query failed: %w", err) + } + + for _, partition := range dst { + volume := strings.TrimRight(partition.Volume, " ") + + ch <- prometheus.MustNewConstMetric( + c.sharedVolumesInfo, + prometheus.GaugeValue, + 1.0, + volume, + partition.Path, + partition.VolumeGuid, + ) + + ch <- prometheus.MustNewConstMetric( + c.sharedVolumesTotalSize, + prometheus.GaugeValue, + float64(partition.TotalSize)*1024*1024, // Convert from KB to bytes + volume, + partition.VolumeGuid, + ) + + ch <- prometheus.MustNewConstMetric( + c.sharedVolumesFreeSpace, + prometheus.GaugeValue, + float64(partition.FreeSpace)*1024*1024, // Convert from KB to bytes + volume, + partition.VolumeGuid, + ) + } + + return nil +}