mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 01:59:04 +02:00
318 lines
11 KiB
Go
318 lines
11 KiB
Go
// 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 hyperv
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/prometheus-community/windows_exporter/internal/labelcache"
|
|
"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"
|
|
)
|
|
|
|
// replicaVMQueryTimeout bounds the replication relationship query, so a hung
|
|
// WMI service does not block the scrape.
|
|
const replicaVMQueryTimeout = 5 * time.Second
|
|
|
|
// collectorReplicaVM Hyper-V Replica VM metrics
|
|
type collectorReplicaVM struct {
|
|
perfDataCollectorReplicaVM *pdh.Collector[perfDataCounterValuesReplicaVM]
|
|
perfDataObjectReplicaVM []perfDataCounterValuesReplicaVM
|
|
|
|
miQueryReplicaVM mi.Query
|
|
|
|
replicaVMAverageReplicationLatency *prometheus.Desc // \Hyper-V Replica VM(*)\Average Replication Latency
|
|
replicaVMAverageReplicationSize *prometheus.Desc // \Hyper-V Replica VM(*)\Average Replication Size
|
|
replicaVMCompressionEfficiency *prometheus.Desc // \Hyper-V Replica VM(*)\Compression Efficiency
|
|
replicaVMLastReplicationSize *prometheus.Desc // \Hyper-V Replica VM(*)\Last Replication Size
|
|
replicaVMNetworkReceivedBytes *prometheus.Desc // \Hyper-V Replica VM(*)\Network Bytes Recv
|
|
replicaVMNetworkSentBytes *prometheus.Desc // \Hyper-V Replica VM(*)\Network Bytes Sent
|
|
replicaVMReplicationCount *prometheus.Desc // \Hyper-V Replica VM(*)\Replication Count
|
|
replicaVMReplicationLatency *prometheus.Desc // \Hyper-V Replica VM(*)\Replication Latency
|
|
replicaVMResynchronizedBytes *prometheus.Desc // \Hyper-V Replica VM(*)\Resynchronized Bytes
|
|
|
|
replicaVMHealth *prometheus.Desc // Msvm_ReplicationRelationship.ReplicationHealth
|
|
replicaVMState *prometheus.Desc // Msvm_ReplicationRelationship.ReplicationState
|
|
}
|
|
|
|
type perfDataCounterValuesReplicaVM struct {
|
|
Name string
|
|
Occurrence int
|
|
|
|
AverageReplicationLatency float64 `perfdata:"Average Replication Latency"`
|
|
AverageReplicationSize float64 `perfdata:"Average Replication Size"`
|
|
CompressionEfficiency float64 `perfdata:"Compression Efficiency"`
|
|
LastReplicationSize float64 `perfdata:"Last Replication Size"`
|
|
NetworkBytesRecv float64 `perfdata:"Network Bytes Recv"`
|
|
NetworkBytesSent float64 `perfdata:"Network Bytes Sent"`
|
|
ReplicationCount float64 `perfdata:"Replication Count"`
|
|
ReplicationLatency float64 `perfdata:"Replication Latency"`
|
|
ResynchronizedBytes float64 `perfdata:"Resynchronized Bytes"`
|
|
}
|
|
|
|
// msvmReplicationRelationship is the Msvm_ReplicationRelationship WMI class.
|
|
// https://learn.microsoft.com/en-us/windows/win32/hyperv_v2/msvm-replicationrelationship
|
|
type msvmReplicationRelationship struct {
|
|
ElementName string `mi:"ElementName"`
|
|
InstanceID string `mi:"InstanceID"`
|
|
ReplicationHealth uint16 `mi:"ReplicationHealth"`
|
|
ReplicationState uint16 `mi:"ReplicationState"`
|
|
}
|
|
|
|
func (c *Collector) buildReplicaVM() error {
|
|
if c.miSession == nil {
|
|
return mi.ErrNotInitialized
|
|
}
|
|
|
|
var err error
|
|
|
|
// The Hyper-V Replica VM counter set is missing on hosts without Hyper-V Replica
|
|
// support, e.g. Windows client editions. Depending on the system, this surfaces
|
|
// here as a missing object or on collect as no data. The WMI metrics still work there.
|
|
c.perfDataCollectorReplicaVM, err = pdh.NewCollector[perfDataCounterValuesReplicaVM](c.logger, pdh.CounterTypeRaw, "Hyper-V Replica VM", pdh.InstancesAll)
|
|
if errors.Is(err, pdh.NewPdhError(pdh.CstatusNoObject)) {
|
|
c.logger.Debug("Hyper-V Replica VM performance counters are not available", slog.Any("err", err))
|
|
|
|
c.perfDataCollectorReplicaVM = nil
|
|
} else if err != nil {
|
|
return fmt.Errorf("failed to create Hyper-V Replica VM collector: %w", err)
|
|
}
|
|
|
|
c.miQueryReplicaVM, err = mi.NewQuery("SELECT ElementName, InstanceID, ReplicationHealth, ReplicationState FROM Msvm_ReplicationRelationship")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create WMI query: %w", err)
|
|
}
|
|
|
|
c.replicaVMAverageReplicationLatency = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_average_replication_latency_seconds"),
|
|
"The average time taken to replicate the virtual machine",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMAverageReplicationSize = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_average_replication_size_bytes"),
|
|
"The average size of the replicated data",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMCompressionEfficiency = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_compression_efficiency"),
|
|
"The compression efficiency of the replicated data",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMLastReplicationSize = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_last_replication_size_bytes"),
|
|
"The size of the last replicated data",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMNetworkReceivedBytes = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_network_received_bytes_total"),
|
|
"The total number of bytes received over the network for replication",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMNetworkSentBytes = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_network_sent_bytes_total"),
|
|
"The total number of bytes sent over the network for replication",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMReplicationCount = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_replications_total"),
|
|
"The total number of replication cycles",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMReplicationLatency = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_replication_latency_seconds"),
|
|
"The time taken by the last replication of the virtual machine",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMResynchronizedBytes = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_resynchronized_bytes_total"),
|
|
"The total number of bytes sent during resynchronization",
|
|
[]string{"vm"},
|
|
nil,
|
|
)
|
|
c.replicaVMHealth = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_health"),
|
|
"The replication health of the virtual machine. 0: Not applicable; 1: OK; 2: Warning; 3: Critical",
|
|
[]string{"vm", "relationship"},
|
|
nil,
|
|
)
|
|
c.replicaVMState = prometheus.NewDesc(
|
|
prometheus.BuildFQName(types.Namespace, Name, "replica_vm_state"),
|
|
"The replication state of the virtual machine. 0: Disabled; 1: Ready for replication; 2: Waiting to complete initial replication; "+
|
|
"3: Replicating; 4: Synced replication complete; 5: Recovered; 6: Committed; 7: Suspended; 8: Critical; "+
|
|
"9: Waiting to start resynchronization; 10: Resynchronizing; 11: Resynchronization suspended; 12: Failover in progress; "+
|
|
"13: Failback in progress; 14: Failback complete; 15: Disk update in progress; 16: Disk update critical; 17: Unknown; "+
|
|
"18: Repurpose replication in progress; 19: Prepared for sync replication; 20: Prepared for group reverse replication; "+
|
|
"21: Fire drill in progress",
|
|
[]string{"vm", "relationship"},
|
|
nil,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Collector) collectReplicaVM(ch chan<- prometheus.Metric, scrape *labelcache.Scrape) error {
|
|
return errors.Join(
|
|
c.collectReplicaVMPerfData(ch, scrape),
|
|
c.collectReplicaVMRelationships(ch, scrape),
|
|
)
|
|
}
|
|
|
|
func (c *Collector) collectReplicaVMPerfData(ch chan<- prometheus.Metric, scrape *labelcache.Scrape) error {
|
|
if c.perfDataCollectorReplicaVM == nil {
|
|
return nil
|
|
}
|
|
|
|
err := c.perfDataCollectorReplicaVM.Collect(&c.perfDataObjectReplicaVM)
|
|
if errors.Is(err, pdh.ErrNoData) {
|
|
// No virtual machine is replicated, or Hyper-V Replica is not supported.
|
|
return nil
|
|
} else if err != nil {
|
|
return fmt.Errorf("failed to collect Hyper-V Replica VM metrics: %w", err)
|
|
}
|
|
|
|
suffixDuplicateVMs(c.perfDataObjectReplicaVM,
|
|
func(row *perfDataCounterValuesReplicaVM) *string { return &row.Name },
|
|
func(row *perfDataCounterValuesReplicaVM) int { return row.Occurrence },
|
|
)
|
|
|
|
for _, data := range c.perfDataObjectReplicaVM {
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMAverageReplicationLatency,
|
|
prometheus.GaugeValue,
|
|
data.AverageReplicationLatency,
|
|
data.Name,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMAverageReplicationSize,
|
|
prometheus.GaugeValue,
|
|
data.AverageReplicationSize,
|
|
data.Name,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMCompressionEfficiency,
|
|
prometheus.GaugeValue,
|
|
data.CompressionEfficiency,
|
|
data.Name,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMLastReplicationSize,
|
|
prometheus.GaugeValue,
|
|
data.LastReplicationSize,
|
|
data.Name,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMNetworkReceivedBytes,
|
|
prometheus.CounterValue,
|
|
data.NetworkBytesRecv,
|
|
data.Name,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMNetworkSentBytes,
|
|
prometheus.CounterValue,
|
|
data.NetworkBytesSent,
|
|
data.Name,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMReplicationCount,
|
|
prometheus.CounterValue,
|
|
data.ReplicationCount,
|
|
data.Name,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMReplicationLatency,
|
|
prometheus.GaugeValue,
|
|
data.ReplicationLatency,
|
|
data.Name,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMResynchronizedBytes,
|
|
prometheus.CounterValue,
|
|
data.ResynchronizedBytes,
|
|
data.Name,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Collector) collectReplicaVMRelationships(ch chan<- prometheus.Metric, scrape *labelcache.Scrape) error {
|
|
var dst []msvmReplicationRelationship
|
|
|
|
if err := c.miSession.Query(&dst, mi.NamespaceRootVirtualizationV2, c.miQueryReplicaVM, replicaVMQueryTimeout); err != nil {
|
|
return fmt.Errorf("WMI query failed: %w", err)
|
|
}
|
|
|
|
for _, relationship := range dst {
|
|
relationshipType := replicationRelationshipType(relationship.InstanceID)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMHealth,
|
|
prometheus.GaugeValue,
|
|
float64(relationship.ReplicationHealth),
|
|
relationship.ElementName,
|
|
relationshipType,
|
|
)
|
|
|
|
ch <- scrape.MustNewConstMetric(
|
|
c.replicaVMState,
|
|
prometheus.GaugeValue,
|
|
float64(relationship.ReplicationState),
|
|
relationship.ElementName,
|
|
relationshipType,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// replicationRelationshipType returns the relationship type encoded in the
|
|
// InstanceID ("Microsoft:<vm id>\HVR\<0|1>"). 0 is the primary relationship,
|
|
// 1 the extended replication.
|
|
func replicationRelationshipType(instanceID string) string {
|
|
switch instanceID[strings.LastIndex(instanceID, `\`)+1:] {
|
|
case "0":
|
|
return "primary"
|
|
case "1":
|
|
return "extended"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|