From 54aeb2b391413a5a84e7d66b81fd6dc354513490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Fri, 9 Oct 2026 09:16:41 +0200 Subject: [PATCH] hyperv: deprecate cumulative virtual storage device latency gauges (#2565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Claude Opus 5.5 Signed-off-by: Jan-Otto Kröpke --- docs/collector.hyperv.md | 51 ++++++++++++++----- internal/collector/hyperv/hyperv_test.go | 26 ++++++++++ .../hyperv/hyperv_virtual_storage_device.go | 44 ++++++++++++++-- 3 files changed, 103 insertions(+), 18 deletions(-) diff --git a/docs/collector.hyperv.md b/docs/collector.hyperv.md index 4a09dacc..6335d811 100644 --- a/docs/collector.hyperv.md +++ b/docs/collector.hyperv.md @@ -321,20 +321,43 @@ Example alert: ### Hyper-V Virtual Storage Device -| Name | Description | Type | Labels | -|---------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|---------|----------| -| `windows_hyperv_virtual_storage_device_error_count_total` | Represents the total number of errors that have occurred on this virtual device. | counter | `device` | -| `windows_hyperv_virtual_storage_device_queue_length` | Represents the average queue length on this virtual device. | gauge | `device` | -| `windows_hyperv_virtual_storage_device_bytes_read` | Represents the total number of bytes that have been read on this virtual device. | counter | `device` | -| `windows_hyperv_virtual_storage_device_operations_read_total` | Represents the total number of read operations that have occurred on this virtual device. | counter | `device` | -| `windows_hyperv_virtual_storage_device_bytes_written` | Represents the total number of bytes that have been written on this virtual device. | counter | `device` | -| `windows_hyperv_virtual_storage_device_operations_written_total` | Represents the total number of write operations that have occurred on this virtual device. | counter | `device` | -| `windows_hyperv_virtual_storage_device_latency_seconds` | Represents the average IO transfer latency for this virtual device. | gauge | `device` | -| `windows_hyperv_virtual_storage_device_throughput_total` | Represents the total number of 8KB IO transfers completed by this virtual device. | counter | `device` | -| `windows_hyperv_virtual_storage_device_normalized_throughput` | Represents the average number of IO transfers completed by this virtual device. | gauge | `device` | -| `windows_hyperv_virtual_storage_device_lower_queue_length` | Represents the average queue length on the underlying storage subsystem for this device. | gauge | `device` | -| `windows_hyperv_virtual_storage_device_lower_latency_seconds` | Represents the average IO transfer latency on the underlying storage subsystem for this virtual device. | gauge | `device` | -| `windows_hyperv_virtual_storage_device_io_quota_replenishment_rate` | Represents the IO quota replenishment rate for this virtual device. | gauge | `device` | +| Name | Description | Type | Labels | +|------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------| +| `windows_hyperv_virtual_storage_device_error_count_total` | Represents the total number of errors that have occurred on this virtual device. | counter | `device` | +| `windows_hyperv_virtual_storage_device_queue_length` | **Deprecated:** use `rate(windows_hyperv_virtual_storage_device_io_latency_seconds_total)`. Cumulative queue length in 100ns ticks, see below. | gauge | `device` | +| `windows_hyperv_virtual_storage_device_bytes_read` | Represents the total number of bytes that have been read on this virtual device. | counter | `device` | +| `windows_hyperv_virtual_storage_device_operations_read_total` | Represents the total number of read operations that have occurred on this virtual device. | counter | `device` | +| `windows_hyperv_virtual_storage_device_bytes_written` | Represents the total number of bytes that have been written on this virtual device. | counter | `device` | +| `windows_hyperv_virtual_storage_device_operations_written_total` | Represents the total number of write operations that have occurred on this virtual device. | counter | `device` | +| `windows_hyperv_virtual_storage_device_io_latency_seconds_total` | Represents the total IO transfer latency for this virtual device. | counter | `device` | +| `windows_hyperv_virtual_storage_device_latency_seconds` | **Deprecated:** use `windows_hyperv_virtual_storage_device_io_latency_seconds_total`. Cumulative IO transfer latency in 100ns ticks, see below. | gauge | `device` | +| `windows_hyperv_virtual_storage_device_throughput_total` | Represents the total number of 8KB IO transfers completed by this virtual device. | counter | `device` | +| `windows_hyperv_virtual_storage_device_normalized_throughput` | Represents the average number of IO transfers completed by this virtual device. | gauge | `device` | +| `windows_hyperv_virtual_storage_device_lower_io_latency_seconds_total` | Represents the total IO transfer latency on the underlying storage subsystem for this virtual device. | counter | `device` | +| `windows_hyperv_virtual_storage_device_lower_queue_length` | **Deprecated:** use `rate(windows_hyperv_virtual_storage_device_lower_io_latency_seconds_total)`. Cumulative queue length in 100ns ticks, see below. | gauge | `device` | +| `windows_hyperv_virtual_storage_device_lower_latency_seconds` | **Deprecated:** use `windows_hyperv_virtual_storage_device_lower_io_latency_seconds_total`. Cumulative IO transfer latency in 100ns ticks, see below. | gauge | `device` | +| `windows_hyperv_virtual_storage_device_io_quota_replenishment_rate` | Represents the IO quota replenishment rate for this virtual device. | gauge | `device` | + +#### Deprecated latency and queue length metrics + +The `Latency`, `Lower Latency`, `Queue Length` and `Lower Queue Length` performance counters +are averages that Windows computes from two samples. windows_exporter reads their raw values, +which only grow: the sum of all IO latencies in 100ns ticks. The raw queue length values are +the same as the raw latency values. + +`windows_hyperv_virtual_storage_device_latency_seconds`, `windows_hyperv_virtual_storage_device_lower_latency_seconds`, +`windows_hyperv_virtual_storage_device_queue_length` and `windows_hyperv_virtual_storage_device_lower_queue_length` +keep exposing these raw values as gauges for compatibility. They are deprecated and will be removed in a future release. +Use the `*_io_latency_seconds_total` counters instead: + +```promql +# Average IO latency in seconds +rate(windows_hyperv_virtual_storage_device_io_latency_seconds_total[5m]) + / rate(windows_hyperv_virtual_storage_device_throughput_total[5m]) + +# Average queue length +rate(windows_hyperv_virtual_storage_device_io_latency_seconds_total[5m]) +``` ### Hyper-V VM Vid Partition diff --git a/internal/collector/hyperv/hyperv_test.go b/internal/collector/hyperv/hyperv_test.go index b60038d9..3faa71d4 100644 --- a/internal/collector/hyperv/hyperv_test.go +++ b/internal/collector/hyperv/hyperv_test.go @@ -26,6 +26,7 @@ import ( "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/utils/testutils" "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" ) @@ -49,6 +50,31 @@ func TestCollector(t *testing.T) { } } +// TestCollectorVirtualStorageDeviceLatency checks that the deprecated latency gauges +// expose the raw cumulative value in 100ns ticks of the *_io_latency_seconds_total counters. +func TestCollectorVirtualStorageDeviceLatency(t *testing.T) { + metrics := testutils.TestCollector(t, hyperv.New, &hyperv.Config{CollectorsEnabled: []string{"virtual_storage_device"}}) + + for deprecated, replacement := range map[string]string{ + "windows_hyperv_virtual_storage_device_latency_seconds": "windows_hyperv_virtual_storage_device_io_latency_seconds_total", + "windows_hyperv_virtual_storage_device_lower_latency_seconds": "windows_hyperv_virtual_storage_device_lower_io_latency_seconds_total", + } { + want := testutils.MetricValuesByLabel(metrics, deprecated, "device") + got := testutils.MetricValuesByLabel(metrics, replacement, "device") + + require.Len(t, got, len(want)) + + if len(got) > 0 { + require.Equal(t, dto.MetricType_COUNTER, metrics[replacement].GetType()) + } + + for device, ticks := range want { + require.Contains(t, got, device) + require.InDelta(t, ticks*pdh.TicksToSecondScaleFactor, got[device], 1e-6, "%s{device=%q}", replacement, device) + } + } +} + // TestCloseReleasesQuery ensures Close releases the PDH queries of the sub-collectors. func TestCloseReleasesQuery(t *testing.T) { t.Parallel() diff --git a/internal/collector/hyperv/hyperv_virtual_storage_device.go b/internal/collector/hyperv/hyperv_virtual_storage_device.go index cee9a265..ede6725c 100644 --- a/internal/collector/hyperv/hyperv_virtual_storage_device.go +++ b/internal/collector/hyperv/hyperv_virtual_storage_device.go @@ -42,6 +42,8 @@ type collectorVirtualStorageDevice struct { virtualStorageDeviceLowerQueueLength *prometheus.Desc // \Hyper-V Virtual Storage Device(*)\Lower Queue Length virtualStorageDeviceLowerLatency *prometheus.Desc // \Hyper-V Virtual Storage Device(*)\Lower Latency virtualStorageDeviceIOQuotaReplenishmentRate *prometheus.Desc // \Hyper-V Virtual Storage Device(*)\IO Quota Replenishment Rate + virtualStorageDeviceIOLatency *prometheus.Desc // \Hyper-V Virtual Storage Device(*)\Latency + virtualStorageDeviceLowerIOLatency *prometheus.Desc // \Hyper-V Virtual Storage Device(*)\Lower Latency } type perfDataCounterValuesVirtualStorageDevice struct { @@ -77,7 +79,8 @@ func (c *Collector) buildVirtualStorageDevice() error { ) c.virtualStorageDeviceQueueLength = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "virtual_storage_device_queue_length"), - "Represents the average queue length on this virtual device.", + "DEPRECATED: use rate(windows_hyperv_virtual_storage_device_io_latency_seconds_total). "+ + "Cumulative queue length in 100ns ticks, identical to the raw latency.", []string{"device"}, nil, ) @@ -107,7 +110,8 @@ func (c *Collector) buildVirtualStorageDevice() error { ) c.virtualStorageDeviceLatency = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "virtual_storage_device_latency_seconds"), - "Represents the average IO transfer latency for this virtual device.", + "DEPRECATED: use windows_hyperv_virtual_storage_device_io_latency_seconds_total. "+ + "Cumulative IO transfer latency in 100ns ticks for this virtual device.", []string{"device"}, nil, ) @@ -125,13 +129,29 @@ func (c *Collector) buildVirtualStorageDevice() error { ) c.virtualStorageDeviceLowerQueueLength = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "virtual_storage_device_lower_queue_length"), - "Represents the average queue length on the underlying storage subsystem for this device.", + "DEPRECATED: use rate(windows_hyperv_virtual_storage_device_lower_io_latency_seconds_total). "+ + "Cumulative queue length in 100ns ticks on the underlying storage subsystem, identical to the raw lower latency.", []string{"device"}, nil, ) c.virtualStorageDeviceLowerLatency = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "virtual_storage_device_lower_latency_seconds"), - "Represents the average IO transfer latency on the underlying storage subsystem for this virtual device.", + "DEPRECATED: use windows_hyperv_virtual_storage_device_lower_io_latency_seconds_total. "+ + "Cumulative IO transfer latency in 100ns ticks on the underlying storage subsystem for this virtual device.", + []string{"device"}, + nil, + ) + c.virtualStorageDeviceIOLatency = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "virtual_storage_device_io_latency_seconds_total"), + "Represents the total IO transfer latency for this virtual device. "+ + "Divide its rate by the rate of windows_hyperv_virtual_storage_device_throughput_total for the average latency.", + []string{"device"}, + nil, + ) + c.virtualStorageDeviceLowerIOLatency = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "virtual_storage_device_lower_io_latency_seconds_total"), + "Represents the total IO transfer latency on the underlying storage subsystem for this virtual device. "+ + "Divide its rate by the rate of windows_hyperv_virtual_storage_device_throughput_total for the average latency.", []string{"device"}, nil, ) @@ -235,6 +255,22 @@ func (c *Collector) collectVirtualStorageDevice(ch chan<- prometheus.Metric) err data.VirtualStorageDeviceIOQuotaReplenishmentRate, data.Name, ) + + // The raw value of a PERF_AVERAGE_TIMER counter is the sum of all IO latencies in + // 100ns ticks; the operation count it is averaged over is Throughput. + ch <- prometheus.MustNewConstMetric( + c.virtualStorageDeviceIOLatency, + prometheus.CounterValue, + data.VirtualStorageDeviceLatency*pdh.TicksToSecondScaleFactor, + data.Name, + ) + + ch <- prometheus.MustNewConstMetric( + c.virtualStorageDeviceLowerIOLatency, + prometheus.CounterValue, + data.VirtualStorageDeviceLowerLatency*pdh.TicksToSecondScaleFactor, + data.Name, + ) } return nil