Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions vmware/collectors/collectors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ import (
"github.com/vmware/govmomi/vim25/types"
)

func TestHostMOFromRuntimeReturnsEmptyWhenHostNil(t *testing.T) {
runtime := types.VirtualMachineRuntimeInfo{Host: nil}

if got := hostMOFromRuntime(runtime); got != "" {
t.Fatalf("expected empty host managed object reference, got %q", got)
}
}

func TestHostMOFromRuntimeReturnsHostValue(t *testing.T) {
runtime := types.VirtualMachineRuntimeInfo{
Host: &types.ManagedObjectReference{Type: "HostSystem", Value: "host-42"},
}

if got := hostMOFromRuntime(runtime); got != "host-42" {
t.Fatalf("expected host-42, got %q", got)
}
}

func TestHostCollectorUpdateEmitsHostInfo(t *testing.T) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
loginData, cleanup := setupCollectorLoginData(t)
Expand All @@ -43,6 +61,60 @@ func TestHostCollectorUpdateEmitsHostInfo(t *testing.T) {
}
}

func TestHostCollectorUpdateEmitsHostStateMetrics(t *testing.T) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
loginData, cleanup := setupCollectorLoginData(t)
defer cleanup()

collector, err := NewhostCollector(logger)
if err != nil {
t.Fatalf("NewhostCollector() returned error: %v", err)
}

ch := make(chan prometheus.Metric, 20000)
if err := collector.Update(ch, "vmware", nil, loginData, map[string]string{}); err != nil {
t.Fatalf("host Update() returned error: %v", err)
}

metrics := drainMetrics(ch)

if !hasMetricWithLabels(metrics, "vmware_host_powered_on", map[string]string{"vcenter": loginData["target"].(string), "host": "", "hostmo": ""}) {
t.Fatal("expected vmware_host_powered_on metric with host labels")
}

if !hasMetricWithLabels(metrics, "vmware_host_connected", map[string]string{"vcenter": loginData["target"].(string), "host": "", "hostmo": ""}) {
t.Fatal("expected vmware_host_connected metric with host labels")
}

if !hasMetricWithLabels(metrics, "vmware_host_maintenance_mode", map[string]string{"vcenter": loginData["target"].(string), "host": "", "hostmo": ""}) {
t.Fatal("expected vmware_host_maintenance_mode metric with host labels")
}

found, binary := metricsHaveBinaryGaugeValues(metrics, "vmware_host_powered_on")
if !found {
t.Fatal("expected vmware_host_powered_on metric values")
}
if !binary {
t.Fatal("expected vmware_host_powered_on metric values to be binary")
}

found, binary = metricsHaveBinaryGaugeValues(metrics, "vmware_host_connected")
if !found {
t.Fatal("expected vmware_host_connected metric values")
}
if !binary {
t.Fatal("expected vmware_host_connected metric values to be binary")
}

found, binary = metricsHaveBinaryGaugeValues(metrics, "vmware_host_maintenance_mode")
if !found {
t.Fatal("expected vmware_host_maintenance_mode metric values")
}
if !binary {
t.Fatal("expected vmware_host_maintenance_mode metric values to be binary")
}
}

func TestVMCollectorUpdateEmitsVMInfo(t *testing.T) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
loginData, cleanup := setupCollectorLoginData(t)
Expand All @@ -68,6 +140,36 @@ func TestVMCollectorUpdateEmitsVMInfo(t *testing.T) {
}
}

func TestVMCollectorUpdateEmitsVMPoweredStateMetric(t *testing.T) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
loginData, cleanup := setupCollectorLoginData(t)
defer cleanup()

collector, err := NewvmCollector(logger)
if err != nil {
t.Fatalf("NewvmCollector() returned error: %v", err)
}

ch := make(chan prometheus.Metric, 30000)
if err := collector.Update(ch, "vmware", nil, loginData, map[string]string{}); err != nil {
t.Fatalf("vm Update() returned error: %v", err)
}

metrics := drainMetrics(ch)

if !hasMetricWithLabels(metrics, "vmware_vm_powered_on", map[string]string{"vcenter": loginData["target"].(string), "vm": "", "vmmo": "", "hostmo": ""}) {
t.Fatal("expected vmware_vm_powered_on metric with vm labels")
}

found, binary := metricsHaveBinaryGaugeValues(metrics, "vmware_vm_powered_on")
if !found {
t.Fatal("expected vmware_vm_powered_on metric values")
}
if !binary {
t.Fatal("expected vmware_vm_powered_on metric values to be binary")
}
}

func TestDatastoreCollectorUpdateEmitsDatastoreInfo(t *testing.T) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
loginData, cleanup := setupCollectorLoginData(t)
Expand Down Expand Up @@ -323,3 +425,32 @@ func hasMetricWithLabels(metrics []prometheus.Metric, fqName string, requiredLab

return false
}

func metricsHaveBinaryGaugeValues(metrics []prometheus.Metric, fqName string) (bool, bool) {
found := false

for _, metric := range metrics {
desc := metric.Desc().String()
if !strings.Contains(desc, `fqName: "`+fqName+`"`) {
continue
}

pb := &dto.Metric{}
if err := metric.Write(pb); err != nil {
continue
}

if pb.Gauge == nil {
continue
}

found = true

value := pb.Gauge.GetValue()
if value != 0 && value != 1 {
return found, false
}
}

return found, true
}
60 changes: 53 additions & 7 deletions vmware/collectors/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,56 @@ func (c *hostCollector) Update(ch chan<- prometheus.Metric, namespace string, cl
wg := sync.WaitGroup{}

for _, host := range hosts {
hostLabels := map[string]string{
"hostmo": host.Self.Value,
"host": host.Summary.Config.Name,
"vcenter": loginData["target"].(string),
}

if host.Runtime.PowerState == "poweredOn" && host.Runtime.ConnectionState == "connected" && !host.Runtime.InMaintenanceMode {
hostPoweredOn := host.Runtime.PowerState == "poweredOn"
hostConnected := host.Runtime.ConnectionState == "connected"
hostMaintenanceMode := host.Runtime.InMaintenanceMode

ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, hostSubsystem, "powered_on"),
"Whether the host is powered on", nil, hostLabels,
), prometheus.GaugeValue,
func(poweredOn bool) float64 {
if poweredOn {
return 1
}
return 0
}(hostPoweredOn),
)

ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, hostSubsystem, "connected"),
"Whether the host is connected", nil, hostLabels,
), prometheus.GaugeValue,
func(connected bool) float64 {
if connected {
return 1
}
return 0
}(hostConnected),
)

ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, hostSubsystem, "maintenance_mode"),
"Whether the host is in maintenance mode", nil, hostLabels,
), prometheus.GaugeValue,
func(maintenanceMode bool) float64 {
if maintenanceMode {
return 1
}
return 0
}(hostMaintenanceMode),
)

if hostPoweredOn && hostConnected && !hostMaintenanceMode {

hostRefs = append(hostRefs, host.Self)

Expand Down Expand Up @@ -107,8 +155,6 @@ func (c *hostCollector) Update(ch chan<- prometheus.Metric, namespace string, cl
), prometheus.GaugeValue, 1.0,
)

hostLabels := map[string]string{"hostmo": host.Self.Value, "host": host.Summary.Config.Name, "vcenter": loginData["target"].(string)}

ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, hostSubsystem, "cpu_corecount"),
Expand Down Expand Up @@ -152,20 +198,20 @@ func (c *hostCollector) Update(ch chan<- prometheus.Metric, namespace string, cl
for i := 0; i < 2; i++ {
switch i {
case 0:
go func(i int) {
go func() {
scrapePerformance(loginData["ctx"].(context.Context), ch, c.logger, loginData["samples"].(int32), loginData["interval"].(int32), loginData["perf"].(*performance.Manager),
loginData["target"].(string), "HostSystem", namespace, hostSubsystem, "", cHostCounters,
loginData["counters"].(map[string]*types.PerfCounterInfo), hostRefs, hostNames)
wg.Done()
}(i)
}()

case 1:
go func(i int) {
go func() {
scrapePerformance(loginData["ctx"].(context.Context), ch, c.logger, loginData["samples"].(int32), loginData["interval"].(int32), loginData["perf"].(*performance.Manager),
loginData["target"].(string), "HostSystem", namespace, hostSubsystem, "*", iHostCounters,
loginData["counters"].(map[string]*types.PerfCounterInfo), hostRefs, hostNames)
wg.Done()
}(i)
}()
}

}
Expand Down
45 changes: 38 additions & 7 deletions vmware/collectors/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ func NewvmCollector(logger *slog.Logger) (collector.Collector, error) {
return &vmCollector{logger}, nil
}

func hostMOFromRuntime(runtime types.VirtualMachineRuntimeInfo) string {
if runtime.Host == nil {
return ""
}

return runtime.Host.Value
}

func (c *vmCollector) Update(ch chan<- prometheus.Metric, namespace string, clientAPI collector.ClientAPI, loginData map[string]interface{}, params map[string]string) error {

var (
Expand All @@ -68,8 +76,31 @@ func (c *vmCollector) Update(ch chan<- prometheus.Metric, namespace string, clie
wg := sync.WaitGroup{}

for _, vm := range vms {
hostMO := hostMOFromRuntime(vm.Runtime)

vmLabels := map[string]string{
"vmmo": vm.Self.Value,
"vm": vm.Summary.Config.Name,
"hostmo": hostMO,
"vcenter": loginData["target"].(string),
}

vmPoweredOn := vm.Runtime.PowerState == "poweredOn"

ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, vmSubsystem, "powered_on"),
"Whether the virtual machine is powered on", nil, vmLabels,
), prometheus.GaugeValue,
func(poweredOn bool) float64 {
if poweredOn {
return 1
}
return 0
}(vmPoweredOn),
)

if vm.Runtime.PowerState == "poweredOn" {
if vmPoweredOn {

vmRefs = append(vmRefs, vm.Self)

Expand All @@ -79,7 +110,7 @@ func (c *vmCollector) Update(ch chan<- prometheus.Metric, namespace string, clie
prometheus.NewDesc(
prometheus.BuildFQName(namespace, vmSubsystem, "info"),
"This is basic vm info to be used for parent reference", nil,
map[string]string{"vmmo": vm.Self.Value, "vm": vm.Summary.Config.Name, "hostmo": vm.Runtime.Host.Value, "vcenter": loginData["target"].(string)},
map[string]string{"vmmo": vm.Self.Value, "vm": vm.Summary.Config.Name, "hostmo": hostMO, "vcenter": loginData["target"].(string)},
), prometheus.GaugeValue, 1.0,
)

Expand All @@ -88,14 +119,14 @@ func (c *vmCollector) Update(ch chan<- prometheus.Metric, namespace string, clie
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, vmSubsystem, "cpu_corecount"),
"Number of virtual CPUs", nil, map[string]string{"vmmo": vm.Self.Value, "vm": vm.Summary.Config.Name, "hostmo": vm.Runtime.Host.Value, "vcenter": loginData["target"].(string)},
"Number of virtual CPUs", nil, map[string]string{"vmmo": vm.Self.Value, "vm": vm.Summary.Config.Name, "hostmo": hostMO, "vcenter": loginData["target"].(string)},
), prometheus.GaugeValue, float64(vm.Summary.Config.NumCpu),
)

ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, vmSubsystem, "mem_capacity"),
"Virtual memory configured in MB", nil, map[string]string{"vmmo": vm.Self.Value, "vm": vm.Summary.Config.Name, "hostmo": vm.Runtime.Host.Value, "vcenter": loginData["target"].(string)},
"Virtual memory configured in MB", nil, map[string]string{"vmmo": vm.Self.Value, "vm": vm.Summary.Config.Name, "hostmo": hostMO, "vcenter": loginData["target"].(string)},
), prometheus.GaugeValue, float64(vm.Summary.Config.MemorySizeMB),
)

Expand All @@ -112,7 +143,7 @@ func (c *vmCollector) Update(ch chan<- prometheus.Metric, namespace string, clie
}
// Check if the VM has any snapshots, set value of metric to unix timestamp of snapshot creation time
if vm.Snapshot != nil {
c.logger.Debug("vm has snapshots", "vm", vm.Summary.Config.Name, "vm_moref", vm.Self.Value)
c.logger.Debug("msg", fmt.Sprintf("VM %s has snapshots", vm.Summary.Config.Name), nil)
for _, rootSnap := range vm.Snapshot.RootSnapshotList {

snapDate := rootSnap.CreateTime.Format(time.RFC3339)
Expand All @@ -131,7 +162,7 @@ func (c *vmCollector) Update(ch chan<- prometheus.Metric, namespace string, clie

}

c.logger.Debug("time to process property collector for vm", "duration_seconds", time.Since(begin).Seconds())
c.logger.Debug("msg", fmt.Sprintf("Time to process PropColletor for VM: %f\n", time.Since(begin).Seconds()), nil)

begin = time.Now()

Expand Down Expand Up @@ -163,7 +194,7 @@ func (c *vmCollector) Update(ch chan<- prometheus.Metric, namespace string, clie

}

c.logger.Debug("time to process perfman for vm", "duration_seconds", time.Since(begin).Seconds())
c.logger.Debug("msg", fmt.Sprintf("Time to process PerfMan for VM: %f\n", time.Since(begin).Seconds()), nil)

return nil
}