hyperv: label replica relationships with the VM ID (#2747)

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
Jan-Otto Kröpke
2026-10-16 10:33:34 +02:00
committed by GitHub
parent b1b2888e0f
commit dc8092f46d
4 changed files with 84 additions and 23 deletions
+14 -13
View File
@@ -206,22 +206,23 @@ Sub-collector `replica_vm`, enabled by default. The performance counter metrics
The `health` and `state` metrics come from the `Msvm_ReplicationRelationship` WMI class in the
`root/virtualization/v2` namespace and exist for every virtual machine, including those without replication.
| Name | Description | Type | Labels |
|-----------------------------------------------------------------|---------------------------------------------------------------------|---------|----------------------|
| `windows_hyperv_replica_vm_average_replication_latency_seconds` | The average time taken to replicate the virtual machine | gauge | `vm` |
| `windows_hyperv_replica_vm_average_replication_size_bytes` | The average size of the replicated data | gauge | `vm` |
| `windows_hyperv_replica_vm_compression_efficiency` | The compression efficiency of the replicated data | gauge | `vm` |
| `windows_hyperv_replica_vm_last_replication_size_bytes` | The size of the last replicated data | gauge | `vm` |
| `windows_hyperv_replica_vm_network_received_bytes_total` | The total number of bytes received over the network for replication | counter | `vm` |
| `windows_hyperv_replica_vm_network_sent_bytes_total` | The total number of bytes sent over the network for replication | counter | `vm` |
| `windows_hyperv_replica_vm_replications_total` | The total number of replication cycles | counter | `vm` |
| `windows_hyperv_replica_vm_replication_latency_seconds` | The time taken by the last replication of the virtual machine | gauge | `vm` |
| `windows_hyperv_replica_vm_resynchronized_bytes_total` | The total number of bytes sent during resynchronization | counter | `vm` |
| `windows_hyperv_replica_vm_health` | The replication health of the virtual machine, see below | gauge | `vm`, `relationship` |
| `windows_hyperv_replica_vm_state` | The replication state of the virtual machine, see below | gauge | `vm`, `relationship` |
| Name | Description | Type | Labels |
|-----------------------------------------------------------------|---------------------------------------------------------------------|---------|-------------------------------|
| `windows_hyperv_replica_vm_average_replication_latency_seconds` | The average time taken to replicate the virtual machine | gauge | `vm` |
| `windows_hyperv_replica_vm_average_replication_size_bytes` | The average size of the replicated data | gauge | `vm` |
| `windows_hyperv_replica_vm_compression_efficiency` | The compression efficiency of the replicated data | gauge | `vm` |
| `windows_hyperv_replica_vm_last_replication_size_bytes` | The size of the last replicated data | gauge | `vm` |
| `windows_hyperv_replica_vm_network_received_bytes_total` | The total number of bytes received over the network for replication | counter | `vm` |
| `windows_hyperv_replica_vm_network_sent_bytes_total` | The total number of bytes sent over the network for replication | counter | `vm` |
| `windows_hyperv_replica_vm_replications_total` | The total number of replication cycles | counter | `vm` |
| `windows_hyperv_replica_vm_replication_latency_seconds` | The time taken by the last replication of the virtual machine | gauge | `vm` |
| `windows_hyperv_replica_vm_resynchronized_bytes_total` | The total number of bytes sent during resynchronization | counter | `vm` |
| `windows_hyperv_replica_vm_health` | The replication health of the virtual machine, see below | gauge | `vm`, `vm_id`, `relationship` |
| `windows_hyperv_replica_vm_state` | The replication state of the virtual machine, see below | gauge | `vm`, `vm_id`, `relationship` |
The `relationship` label is `primary` for the primary replication relationship and `extended` for
[extended replication](https://learn.microsoft.com/en-us/windows/win32/hyperv_v2/msvm-replicationrelationship).
The `vm_id` label is the ID of the virtual machine, which keeps virtual machines with the same name apart.
Values of `windows_hyperv_replica_vm_health`:
+19 -10
View File
@@ -161,7 +161,7 @@ func (c *Collector) buildReplicaVM() error {
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"},
[]string{"vm", "vm_id", "relationship"},
nil,
)
c.replicaVMState = prometheus.NewDesc(
@@ -172,7 +172,7 @@ func (c *Collector) buildReplicaVM() error {
"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"},
[]string{"vm", "vm_id", "relationship"},
nil,
)
@@ -280,13 +280,14 @@ func (c *Collector) collectReplicaVMRelationships(ch chan<- prometheus.Metric, s
}
for _, relationship := range dst {
relationshipType := replicationRelationshipType(relationship.InstanceID)
vmID, relationshipType := parseReplicationRelationshipID(relationship.InstanceID)
ch <- scrape.MustNewConstMetric(
c.replicaVMHealth,
prometheus.GaugeValue,
float64(relationship.ReplicationHealth),
relationship.ElementName,
vmID,
relationshipType,
)
@@ -295,6 +296,7 @@ func (c *Collector) collectReplicaVMRelationships(ch chan<- prometheus.Metric, s
prometheus.GaugeValue,
float64(relationship.ReplicationState),
relationship.ElementName,
vmID,
relationshipType,
)
}
@@ -302,16 +304,23 @@ func (c *Collector) collectReplicaVMRelationships(ch chan<- prometheus.Metric, s
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 {
// parseReplicationRelationshipID returns the ID of the virtual machine and the
// relationship type encoded in the InstanceID of a replication relationship
// ("Microsoft:<vm id>\HVR\<0|1>"). The VM ID keeps the series of VMs with the
// same name apart. Relationship 0 is the primary relationship, 1 the extended
// replication.
func parseReplicationRelationshipID(instanceID string) (string, string) {
vmID := strings.TrimPrefix(instanceID, "Microsoft:")
if i := strings.Index(vmID, `\`); i >= 0 {
vmID = vmID[:i]
}
switch instanceID[strings.LastIndex(instanceID, `\`)+1:] {
case "0":
return "primary"
return vmID, "primary"
case "1":
return "extended"
return vmID, "extended"
default:
return "unknown"
return vmID, "unknown"
}
}
@@ -0,0 +1,48 @@
// 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 (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseReplicationRelationshipID(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
instanceID string
vmID string
relationshipType string
}{
{`Microsoft:6B1F2E1A-3C4D-4E5F-8A9B-0C1D2E3F4A5B\HVR\0`, "6B1F2E1A-3C4D-4E5F-8A9B-0C1D2E3F4A5B", "primary"},
{`Microsoft:6B1F2E1A-3C4D-4E5F-8A9B-0C1D2E3F4A5B\HVR\1`, "6B1F2E1A-3C4D-4E5F-8A9B-0C1D2E3F4A5B", "extended"},
{`Microsoft:6B1F2E1A-3C4D-4E5F-8A9B-0C1D2E3F4A5B\HVR\2`, "6B1F2E1A-3C4D-4E5F-8A9B-0C1D2E3F4A5B", "unknown"},
{`unexpected`, "unexpected", "unknown"},
{``, "", "unknown"},
} {
t.Run(tc.instanceID, func(t *testing.T) {
t.Parallel()
vmID, relationshipType := parseReplicationRelationshipID(tc.instanceID)
require.Equal(t, tc.vmID, vmID)
require.Equal(t, tc.relationshipType, relationshipType)
})
}
}
+3
View File
@@ -47,6 +47,9 @@ func TestCollector(t *testing.T) {
// The fixture VM is not replicated, but every VM has a primary replication relationship.
if metric := testutils.RequireFixtureMetric(t, metrics, hyperv.Name, "windows_hyperv_replica_vm_state", prometheus.Labels{"vm": "GitHubActions", "relationship": "primary"}); metric != nil {
require.InDelta(t, 0, metric.GetGauge().GetValue(), 0, "expected replication to be disabled")
// vm_id keeps VMs with the same name apart; it is the VM's GUID.
require.Regexp(t, `^[0-9A-Fa-f-]{36}$`, labelValue(metric, "vm_id"))
}
// The health query reports failures as 0 instead of an error, so check the value.