mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-23 07:53:36 +00:00
Clears the six dupl pairs in internal/monitoring: - poll_providers.go: PVE/PBS/PMG listInstances / describeInstances / connectionStatus closures collapse into generic sortedClientNames, describeProviderInstances, and providerConnectionStatuses helpers; the PBS/PMG adapters are now built by newPrefixedPollProvider from a prefixedPollProviderSpec (prefix drives status keys, health keys, and the fallback instance name). - truenas_poller.go / vmware_poller.go: the Start scheduling loop (double-start guard, stopped-channel handshake, sync+poll cadence) moves to startPollerLoop, and the active-connection config policy (enabled instances keyed by trimmed connection ID, defaults applied) moves to loadActiveInstanceConfigs, both in the new platform_poller_shared.go. - monitor_polling_vm.go / monitor_polling_containers.go: traditional polling now records guest series via the existing canonical recordGuestMetric helper (io/network sentinels preserve the historical cpu/memory/disk-only behavior on this path); the source-shape guardrails in canonical_guardrails_test.go and memory_source_catalog_test.go pin the new delegation. - mock_metrics_history.go: native and TrueNAS disk seeding share one seedDiskTelemetry closure. Proof: new TestTrueNASPollerDoubleStartKeepsSingleLoop pins the shared lifecycle loop; new TestSeedMockMetricsHistory_DiskTelemetryParityAcrossNativeAndTrueNAS pins four-series parity for both disk sources. Contract Extension Points name the shared wiring layer and poller scaffold. Full internal/monitoring test suite passes.
138 lines
3.8 KiB
Go
138 lines
3.8 KiB
Go
package monitoring
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring/errors"
|
|
"github.com/rcourtman/pulse-go-rewrite/pkg/proxmox"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
func (m *Monitor) pollVMsWithNodes(ctx context.Context, instanceName string, clusterName string, isCluster bool, client PVEClientInterface, nodes []proxmox.Node, nodeEffectiveStatus map[string]string) {
|
|
startTime := time.Now()
|
|
|
|
type nodeResult struct {
|
|
node string
|
|
vms []models.VM
|
|
templateSubjects map[string]struct{}
|
|
err error
|
|
}
|
|
|
|
resultChan := make(chan nodeResult, len(nodes))
|
|
var wg sync.WaitGroup
|
|
|
|
onlineNodes := 0
|
|
for _, node := range nodes {
|
|
if nodeEffectiveStatus[node.Node] == "online" {
|
|
onlineNodes++
|
|
}
|
|
}
|
|
|
|
prevGuests := m.previousGuestContextForInstance(instanceName)
|
|
prevVMByID := prevGuests.vmsByID
|
|
vmIDToHostAgent := prevGuests.hostAgentsByVMID
|
|
|
|
log.Debug().
|
|
Str("instance", instanceName).
|
|
Int("totalNodes", len(nodes)).
|
|
Int("onlineNodes", onlineNodes).
|
|
Msg("Starting parallel VM polling")
|
|
|
|
for _, node := range nodes {
|
|
if nodeEffectiveStatus[node.Node] != "online" {
|
|
log.Debug().
|
|
Str("node", node.Node).
|
|
Str("status", node.Status).
|
|
Msg("Skipping offline node for VM polling")
|
|
continue
|
|
}
|
|
|
|
wg.Add(1)
|
|
go func(n proxmox.Node) {
|
|
defer wg.Done()
|
|
|
|
nodeStart := time.Now()
|
|
vms, err := client.GetVMs(ctx, n.Node)
|
|
if err != nil {
|
|
monErr := errors.NewMonitorError(errors.ErrorTypeAPI, "get_vms", instanceName, err).WithNode(n.Node)
|
|
log.Error().Err(monErr).Str("node", n.Node).Msg("failed to get VMs; deferring node poll until next cycle")
|
|
resultChan <- nodeResult{node: n.Node, err: err}
|
|
return
|
|
}
|
|
|
|
nodeVMs, nodeTemplateSubjects := m.pollNodeVMsWithClusterResourceBuilder(ctx, instanceName, n.Node, vms, client, prevVMByID, vmIDToHostAgent)
|
|
nodeDuration := time.Since(nodeStart)
|
|
log.Debug().
|
|
Str("node", n.Node).
|
|
Int("vms", len(nodeVMs)).
|
|
Dur("duration", nodeDuration).
|
|
Msg("Node VM polling completed")
|
|
|
|
resultChan <- nodeResult{node: n.Node, vms: nodeVMs, templateSubjects: nodeTemplateSubjects}
|
|
}(node)
|
|
}
|
|
|
|
go func() {
|
|
wg.Wait()
|
|
close(resultChan)
|
|
}()
|
|
|
|
var allVMs []models.VM
|
|
qemuTemplateSubjects := make(map[string]struct{})
|
|
successfulNodes := 0
|
|
failedNodes := 0
|
|
|
|
for result := range resultChan {
|
|
if result.err != nil {
|
|
failedNodes++
|
|
continue
|
|
}
|
|
successfulNodes++
|
|
allVMs = append(allVMs, result.vms...)
|
|
for key := range result.templateSubjects {
|
|
qemuTemplateSubjects[key] = struct{}{}
|
|
}
|
|
}
|
|
if failedNodes == 0 && successfulNodes > 0 {
|
|
m.updatePVEBackupTemplateSubjectsForType(instanceName, "qemu", qemuTemplateSubjects)
|
|
}
|
|
|
|
if len(allVMs) == 0 && len(nodes) > 0 {
|
|
allVMs = append(allVMs, prevGuests.vms...)
|
|
prevVMCount := len(prevGuests.vms)
|
|
if prevVMCount > 0 {
|
|
log.Warn().
|
|
Str("instance", instanceName).
|
|
Int("prevVMs", prevVMCount).
|
|
Int("successfulNodes", successfulNodes).
|
|
Int("totalNodes", len(nodes)).
|
|
Msg("Traditional polling returned zero VMs but had VMs before - preserving previous VMs")
|
|
}
|
|
}
|
|
|
|
m.state.UpdateVMsForInstance(instanceName, allVMs)
|
|
|
|
if !shouldSkipNativeMockStateMetricWrites() {
|
|
now := time.Now()
|
|
for _, vm := range allVMs {
|
|
if vm.Status != "running" {
|
|
continue
|
|
}
|
|
// IO/network series are not recorded on the traditional polling
|
|
// path (parity with the historical inline writes).
|
|
m.recordGuestMetric("vm", vm.ID, vm.CPU*100, vm.Memory.Usage, vm.Disk.Usage, -1, -1, -1, -1, now)
|
|
}
|
|
}
|
|
|
|
duration := time.Since(startTime)
|
|
log.Debug().
|
|
Str("instance", instanceName).
|
|
Int("totalVMs", len(allVMs)).
|
|
Int("successfulNodes", successfulNodes).
|
|
Int("failedNodes", failedNodes).
|
|
Dur("duration", duration).
|
|
Msg("Parallel VM polling completed")
|
|
}
|