Files
pulse/internal/monitoring/resource_stale_thresholds.go
T
Pulse Test 1d9e66b24c fix(mock): keep provider fixtures fresh on slow demo ticks
The public demo runs PULSE_MOCK_UPDATE_INTERVAL=60s, which stretches the
10-cohort supplemental rotation to 600s while the unified registry still
marks TrueNAS/VMware/availability sources stale after 120s. Every row those
sources own spent ~80% of each rotation flagged as a degraded stale source,
so the TrueNAS Storage tab read Healthy 0 / Attention 21 (and the vSphere
page all-Attention) despite healthy fixture data.

Rebase provider-backed fixtures every tick once the rotation would outlive
the 120s freshness budget (at slow ticks that is cheaper per wall-clock
second than the default 2s config's once-per-20s cadence), and derive mock
supplemental stale thresholds from the actual refresh cadence the same way
PVE/PBS/PMG thresholds already derive from their polling intervals. Split
the supplemental uptime step so per-tick refreshes do not advance PBS
uptime by the rotation-scaled step.
2026-08-30 22:10:49 +01:00

109 lines
3.6 KiB
Go

package monitoring
import (
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
)
const (
defaultProxmoxResourceStaleThreshold = 60 * time.Second
defaultPlatformResourceStaleThreshold = 120 * time.Second
)
// ResourceStaleThresholdsForConfig derives canonical resource freshness from
// polling cadence. A source should not be considered stale until it has missed
// at least one expected poll cycle plus the normal interval.
func ResourceStaleThresholdsForConfig(cfg *config.Config) map[unifiedresources.DataSource]time.Duration {
return resourceStaleThresholdsForConfig(cfg, mock.IsMockEnabled(), mock.SupplementalRefreshInterval)
}
func resourceStaleThresholdsForConfig(
cfg *config.Config,
mockEnabled bool,
mockSupplementalCadence func() time.Duration,
) map[unifiedresources.DataSource]time.Duration {
thresholds := map[unifiedresources.DataSource]time.Duration{
unifiedresources.SourceProxmox: resourceStaleThresholdForPollInterval(
effectivePVEPollingIntervalForConfig(cfg),
defaultProxmoxResourceStaleThreshold,
),
unifiedresources.SourcePBS: resourceStaleThresholdForPollInterval(
effectivePlatformPollingIntervalForConfig(cfg, "pbs"),
defaultPlatformResourceStaleThreshold,
),
unifiedresources.SourcePMG: resourceStaleThresholdForPollInterval(
effectivePlatformPollingIntervalForConfig(cfg, "pmg"),
defaultPlatformResourceStaleThreshold,
),
}
// Mock mode's provider-backed fixtures (TrueNAS, VMware, availability)
// deliver on the mock update loop's supplemental cadence rather than a
// real poll schedule. Derive their freshness from that cadence the same
// way the entries above derive theirs, so a slow mock tick (e.g. a large
// PULSE_MOCK_UPDATE_INTERVAL on the public demo) does not flag every
// provider-owned row as a stale source between refreshes.
if mockEnabled && mockSupplementalCadence != nil {
supplemental := resourceStaleThresholdForPollInterval(
mockSupplementalCadence(),
defaultPlatformResourceStaleThreshold,
)
thresholds[unifiedresources.SourceTrueNAS] = supplemental
thresholds[unifiedresources.SourceVMware] = supplemental
thresholds[unifiedresources.SourceAvailability] = supplemental
}
return thresholds
}
func (m *Monitor) resourceStaleThresholds() map[unifiedresources.DataSource]time.Duration {
if m == nil {
return ResourceStaleThresholdsForConfig(nil)
}
return ResourceStaleThresholdsForConfig(m.config)
}
func (m *Monitor) pveNodeOfflineGracePeriod() time.Duration {
return m.resourceStaleThresholds()[unifiedresources.SourceProxmox]
}
func effectivePVEPollingIntervalForConfig(cfg *config.Config) time.Duration {
const minInterval = 10 * time.Second
const maxInterval = time.Hour
interval := minInterval
if cfg != nil && cfg.PVEPollingInterval > 0 {
interval = cfg.PVEPollingInterval
}
return clampInterval(interval, minInterval, maxInterval)
}
func effectivePlatformPollingIntervalForConfig(cfg *config.Config, platform string) time.Duration {
if cfg == nil {
return 60 * time.Second
}
switch platform {
case "pbs":
return clampInterval(cfg.PBSPollingInterval, 10*time.Second, time.Hour)
case "pmg":
return clampInterval(cfg.PMGPollingInterval, 10*time.Second, time.Hour)
default:
return 60 * time.Second
}
}
func resourceStaleThresholdForPollInterval(interval, minimum time.Duration) time.Duration {
if minimum <= 0 {
minimum = defaultPlatformResourceStaleThreshold
}
if interval <= 0 {
return minimum
}
threshold := interval * 2
if threshold < minimum {
return minimum
}
return threshold
}