mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-06 12:49:05 +02:00
hyperv: add host sub-collector for CPU allocation ratio (#2493)
Co-authored-by: EisenbergD <dominik.eisenberg@beiersdorf.com> Signed-off-by: EisenbergD <dominik.eisenberg@beiersdorf.com>
This commit is contained in:
co-authored by
EisenbergD
parent
134588063e
commit
c1c5ccc405
@@ -12,7 +12,7 @@ The hyperv collector exposes metrics about the Hyper-V hypervisor
|
|||||||
|
|
||||||
### `--collectors.hyperv.enabled`
|
### `--collectors.hyperv.enabled`
|
||||||
Comma-separated list of collectors to use, for example:
|
Comma-separated list of collectors to use, for example:
|
||||||
`--collectors.hyperv.enabled=dynamic_memory_balancer,dynamic_memory_vm,hypervisor_logical_processor,hypervisor_root_partition,hypervisor_root_virtual_processor,hypervisor_virtual_processor,legacy_network_adapter,virtual_machine_health_summary,virtual_machine_vid_partition,virtual_network_adapter,virtual_storage_device,virtual_switch`.
|
`--collectors.hyperv.enabled=dynamic_memory_balancer,dynamic_memory_vm,host,hypervisor_logical_processor,hypervisor_root_partition,hypervisor_root_virtual_processor,hypervisor_virtual_processor,legacy_network_adapter,virtual_machine_health_summary,virtual_machine_vid_partition,virtual_network_adapter,virtual_storage_device,virtual_switch`.
|
||||||
Matching is case-sensitive.
|
Matching is case-sensitive.
|
||||||
|
|
||||||
## Metrics
|
## Metrics
|
||||||
@@ -98,6 +98,28 @@ Some metrics explained: https://learn.microsoft.com/en-us/archive/blogs/chrisavi
|
|||||||
| `windows_hyperv_dynamic_memory_vm_physical` | Represents the current amount of memory in the VM. | gauge | `vm` |
|
| `windows_hyperv_dynamic_memory_vm_physical` | Represents the current amount of memory in the VM. | gauge | `vm` |
|
||||||
| `windows_hyperv_dynamic_memory_vm_removed_bytes_total` | Represents the cumulative amount of memory removed from the VM. | counter | `vm` |
|
| `windows_hyperv_dynamic_memory_vm_removed_bytes_total` | Represents the cumulative amount of memory removed from the VM. | counter | `vm` |
|
||||||
|
|
||||||
|
### Hyper-V Host
|
||||||
|
|
||||||
|
Metrics about the host's CPU allocation. Processor counts per virtual machine are
|
||||||
|
read from the Host Compute Service API; the logical processor count comes from the
|
||||||
|
`Hyper-V Hypervisor Logical Processor` performance counter object.
|
||||||
|
|
||||||
|
| Metric Name | Description | Type | Labels |
|
||||||
|
|-------------------------------------------------|------------------------------------------------------------------------|-------|--------------------|
|
||||||
|
| `windows_hyperv_host_logical_processor_count` | Number of logical processors on the host | gauge | None |
|
||||||
|
| `windows_hyperv_vm_processor_count` | Number of virtual processors assigned to the VM | gauge | `vm_id`, `vm` |
|
||||||
|
| `windows_hyperv_total_vm_processor_count` | Total number of virtual processors assigned to all VMs | gauge | None |
|
||||||
|
|
||||||
|
The CPU allocation ratio can be computed in PromQL. Virtual cores per **logical** host core:
|
||||||
|
|
||||||
|
```
|
||||||
|
windows_hyperv_total_vm_processor_count / windows_hyperv_host_logical_processor_count
|
||||||
|
```
|
||||||
|
|
||||||
|
Where hyper-threading (SMT) is enabled, the host reports two logical cores per physical core.
|
||||||
|
Multiply by the number of threads per core to get the ratio per **physical** core.
|
||||||
|
|
||||||
|
|
||||||
### Hyper-V Hypervisor Logical Processor
|
### Hyper-V Hypervisor Logical Processor
|
||||||
|
|
||||||
| Name | Description | Type | Labels |
|
| Name | Description | Type | Labels |
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const (
|
|||||||
subCollectorDataStore = "datastore"
|
subCollectorDataStore = "datastore"
|
||||||
subCollectorDynamicMemoryBalancer = "dynamic_memory_balancer"
|
subCollectorDynamicMemoryBalancer = "dynamic_memory_balancer"
|
||||||
subCollectorDynamicMemoryVM = "dynamic_memory_vm"
|
subCollectorDynamicMemoryVM = "dynamic_memory_vm"
|
||||||
|
subCollectorHost = "host"
|
||||||
subCollectorHypervisorLogicalProcessor = "hypervisor_logical_processor"
|
subCollectorHypervisorLogicalProcessor = "hypervisor_logical_processor"
|
||||||
subCollectorHypervisorRootPartition = "hypervisor_root_partition"
|
subCollectorHypervisorRootPartition = "hypervisor_root_partition"
|
||||||
subCollectorHypervisorRootVirtualProcessor = "hypervisor_root_virtual_processor"
|
subCollectorHypervisorRootVirtualProcessor = "hypervisor_root_virtual_processor"
|
||||||
@@ -62,6 +63,7 @@ var ConfigDefaults = Config{
|
|||||||
subCollectorDataStore,
|
subCollectorDataStore,
|
||||||
subCollectorDynamicMemoryBalancer,
|
subCollectorDynamicMemoryBalancer,
|
||||||
subCollectorDynamicMemoryVM,
|
subCollectorDynamicMemoryVM,
|
||||||
|
subCollectorHost,
|
||||||
subCollectorHypervisorLogicalProcessor,
|
subCollectorHypervisorLogicalProcessor,
|
||||||
subCollectorHypervisorRootPartition,
|
subCollectorHypervisorRootPartition,
|
||||||
subCollectorHypervisorRootVirtualProcessor,
|
subCollectorHypervisorRootVirtualProcessor,
|
||||||
@@ -82,6 +84,7 @@ type Collector struct {
|
|||||||
collectorDataStore
|
collectorDataStore
|
||||||
collectorDynamicMemoryBalancer
|
collectorDynamicMemoryBalancer
|
||||||
collectorDynamicMemoryVM
|
collectorDynamicMemoryVM
|
||||||
|
collectorHost
|
||||||
collectorHypervisorLogicalProcessor
|
collectorHypervisorLogicalProcessor
|
||||||
collectorHypervisorRootPartition
|
collectorHypervisorRootPartition
|
||||||
collectorHypervisorRootVirtualProcessor
|
collectorHypervisorRootVirtualProcessor
|
||||||
@@ -183,6 +186,11 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
|||||||
collect: c.collectDynamicMemoryVM,
|
collect: c.collectDynamicMemoryVM,
|
||||||
close: c.perfDataCollectorDynamicMemoryVM.Close,
|
close: c.perfDataCollectorDynamicMemoryVM.Close,
|
||||||
},
|
},
|
||||||
|
subCollectorHost: {
|
||||||
|
build: c.buildHost,
|
||||||
|
collect: c.collectHost,
|
||||||
|
close: c.perfDataCollectorLogicalProcessor.Close,
|
||||||
|
},
|
||||||
subCollectorHypervisorLogicalProcessor: {
|
subCollectorHypervisorLogicalProcessor: {
|
||||||
build: c.buildHypervisorLogicalProcessor,
|
build: c.buildHypervisorLogicalProcessor,
|
||||||
collect: c.collectHypervisorLogicalProcessor,
|
collect: c.collectHypervisorLogicalProcessor,
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/prometheus-community/windows_exporter/internal/headers/hcs"
|
||||||
|
"github.com/prometheus-community/windows_exporter/internal/pdh"
|
||||||
|
"github.com/prometheus-community/windows_exporter/internal/types"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// collectorHost Hyper-V Host metrics
|
||||||
|
type collectorHost struct {
|
||||||
|
perfDataCollectorLogicalProcessor *pdh.Collector
|
||||||
|
perfDataObjectLogicalProcessor []perfDataCounterValuesHost
|
||||||
|
|
||||||
|
vmProcessorCount *prometheus.Desc
|
||||||
|
hostLogicalProcessorCount *prometheus.Desc
|
||||||
|
totalVMProcessorCount *prometheus.Desc
|
||||||
|
memoryPropertyQuery *uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type perfDataCounterValuesHost struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
HypervisorLogicalProcessorTotalRunTimePercent float64 `perfdata:"% Total Run Time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type vmTopology struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
State string
|
||||||
|
ProcessorCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// vmMemoryProperties is the "Memory" property of a compute system. It is the only
|
||||||
|
// HCS property type that exposes a virtual machine's processor allocation: every
|
||||||
|
// other documented type is rejected for VMMS-owned virtual machines.
|
||||||
|
type vmMemoryProperties struct {
|
||||||
|
VirtualNodeCount int `json:"VirtualNodeCount,omitempty"`
|
||||||
|
VirtualNodes []vmVirtualNode `json:"VirtualNodes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type vmVirtualNode struct {
|
||||||
|
VirtualNodeIndex int `json:"VirtualNodeIndex"`
|
||||||
|
PhysicalNodeNumber int `json:"PhysicalNodeNumber"`
|
||||||
|
VirtualProcessorCount int `json:"VirtualProcessorCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) buildHost() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
c.perfDataCollectorLogicalProcessor, err = pdh.NewCollector[perfDataCounterValuesHost](c.logger, pdh.CounterTypeRaw, "Hyper-V Hypervisor Logical Processor", pdh.InstancesAll)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Hyper-V Hypervisor Logical Processor collector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.memoryPropertyQuery, err = windows.UTF16PtrFromString(`{"PropertyTypes":["Memory"]}`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create memory property query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.vmProcessorCount = prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(types.Namespace, Name, "vm_processor_count"),
|
||||||
|
"Number of virtual processors assigned to the VM",
|
||||||
|
[]string{"vm_id", "vm"},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
c.hostLogicalProcessorCount = prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(types.Namespace, Name, "host_logical_processor_count"),
|
||||||
|
"Number of logical processors on the host",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
c.totalVMProcessorCount = prometheus.NewDesc(
|
||||||
|
prometheus.BuildFQName(types.Namespace, Name, "total_vm_processor_count"),
|
||||||
|
"Total number of virtual processors assigned to all VMs",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) collectHost(ch chan<- prometheus.Metric) error {
|
||||||
|
err := c.perfDataCollectorLogicalProcessor.Collect(&c.perfDataObjectLogicalProcessor)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to collect Hyper-V Hypervisor Logical Processor metrics: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logicalCoreCount := float64(len(c.perfDataObjectLogicalProcessor))
|
||||||
|
|
||||||
|
vms, err := c.getVMsWithProcessorCounts()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get VM processor counts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalVirtualCoreCount float64
|
||||||
|
|
||||||
|
for _, vm := range vms {
|
||||||
|
if vm.ProcessorCount > 0 {
|
||||||
|
processorCount := float64(vm.ProcessorCount)
|
||||||
|
totalVirtualCoreCount += processorCount
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.vmProcessorCount,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
processorCount,
|
||||||
|
vm.ID, vm.Name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.hostLogicalProcessorCount,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
logicalCoreCount,
|
||||||
|
)
|
||||||
|
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
c.totalVMProcessorCount,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
totalVirtualCoreCount,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) getVMsWithProcessorCounts() ([]vmTopology, error) {
|
||||||
|
vmQuery, err := windows.UTF16PtrFromString(`{"Types":["VirtualMachine"]}`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create VM query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
operation, err := hcs.CreateOperation()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create operation: %w", err)
|
||||||
|
}
|
||||||
|
defer hcs.CloseOperation(operation)
|
||||||
|
|
||||||
|
if err := hcs.EnumerateComputeSystems(vmQuery, operation); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to enumerate compute systems: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultDocument, err := hcs.WaitForOperationResult(operation, 1000)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to wait for operation result: %w - %s", err, resultDocument)
|
||||||
|
} else if resultDocument == "" {
|
||||||
|
return nil, hcs.ErrEmptyResultDocument
|
||||||
|
}
|
||||||
|
|
||||||
|
var computeSystems []hcs.Properties
|
||||||
|
if err := json.Unmarshal([]byte(resultDocument), &computeSystems); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal compute systems: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vms := make([]vmTopology, 0, len(computeSystems))
|
||||||
|
errs := make([]error, 0)
|
||||||
|
|
||||||
|
for _, system := range computeSystems {
|
||||||
|
// HcsEnumerateComputeSystems does not populate State for VMMS-owned virtual
|
||||||
|
// machines, so only skip a system when it reports a state that is not running.
|
||||||
|
if system.State != "" && system.State != "Running" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
processorCount, err := c.getVMProcessorCount(system.ID)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("failed to get processor count for VM %s: %w", system.ID, err))
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vms = append(vms, vmTopology{
|
||||||
|
ID: system.ID,
|
||||||
|
Name: system.Name,
|
||||||
|
State: system.State,
|
||||||
|
ProcessorCount: processorCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
c.logger.Debug("some VMs failed processor count retrieval",
|
||||||
|
slog.Any("err", errors.Join(errs...)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) getVMProcessorCount(vmID string) (int, error) {
|
||||||
|
computeSystem, err := hcs.OpenComputeSystem(vmID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to open compute system: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer hcs.CloseComputeSystem(computeSystem)
|
||||||
|
|
||||||
|
operation, err := hcs.CreateOperation()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to create operation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer hcs.CloseOperation(operation)
|
||||||
|
|
||||||
|
if err := hcs.GetComputeSystemProperties(computeSystem, operation, c.memoryPropertyQuery); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get compute system properties: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultDocument, err := hcs.WaitForOperationResult(operation, 1000)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to wait for operation result: %w", err)
|
||||||
|
} else if resultDocument == "" {
|
||||||
|
return 0, hcs.ErrEmptyResultDocument
|
||||||
|
}
|
||||||
|
|
||||||
|
var properties struct {
|
||||||
|
Memory *vmMemoryProperties `json:"Memory,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(resultDocument), &properties); err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to unmarshal properties: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if properties.Memory == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A virtual machine's processors are reported per virtual NUMA node.
|
||||||
|
processorCount := 0
|
||||||
|
for _, node := range properties.Memory.VirtualNodes {
|
||||||
|
processorCount += node.VirtualProcessorCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return processorCount, nil
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ type (
|
|||||||
|
|
||||||
type Properties struct {
|
type Properties struct {
|
||||||
ID string `json:"Id,omitempty"`
|
ID string `json:"Id,omitempty"`
|
||||||
|
Name string `json:"Name,omitempty"`
|
||||||
SystemType string `json:"SystemType,omitempty"`
|
SystemType string `json:"SystemType,omitempty"`
|
||||||
Owner string `json:"Owner,omitempty"`
|
Owner string `json:"Owner,omitempty"`
|
||||||
State string `json:"State,omitempty"`
|
State string `json:"State,omitempty"`
|
||||||
|
|||||||
Reference in New Issue
Block a user