mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Preserve fresh rolling-window metric authority
Keep in-memory observations authoritative over duplicate persisted history, add direct provider and cache reset coverage, and register the rolling-window proof files with canonical governance.\n\nChange-source: pulse-maintainer
This commit is contained in:
@@ -437,10 +437,13 @@ identity and durable history as charts. `internal/monitoring/metric_window_provi
|
||||
resolves the unified resource metrics target, reads the fresh in-memory tail,
|
||||
and falls back to the SQLite metrics store when that tail lacks the requested
|
||||
coverage. Persistent fallbacks are briefly cached to bound restart-time query
|
||||
load, merged by timestamp, and returned as observations only; alert policy owns
|
||||
averaging, readiness, hysteresis, and lifecycle decisions. Missing target,
|
||||
query failure, shallow history, or gapped history must remain unknown at the
|
||||
alerts boundary rather than being replaced by a synthetic healthy value.
|
||||
load, merged by timestamp, and returned as observations only. When the durable
|
||||
series and fresh in-memory tail contain the same timestamp, the in-memory value
|
||||
is authoritative so an older persisted or rolled-up value cannot replace the
|
||||
latest observation. Alert policy owns averaging, readiness, hysteresis, and
|
||||
lifecycle decisions. Missing target, query failure, shallow history, or gapped
|
||||
history must remain unknown at the alerts boundary rather than being replaced
|
||||
by a synthetic healthy value.
|
||||
|
||||
Monitoring ingest keeps mock mode hermetic. The unified read path already
|
||||
substitutes the mock snapshot wholesale, so anything that runs after that
|
||||
|
||||
@@ -2334,7 +2334,8 @@
|
||||
"internal/alerts/unified_eval_parity_test.go",
|
||||
"internal/alerts/unified_eval_test.go",
|
||||
"internal/alerts/unified_incident_confirmation_test.go",
|
||||
"internal/alerts/unified_incidents_test.go"
|
||||
"internal/alerts/unified_incidents_test.go",
|
||||
"internal/alerts/windowed_metric_test.go"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2540,6 +2541,7 @@
|
||||
"internal/alerts/callback_config_coverage_test.go",
|
||||
"internal/alerts/canonical_override_migration_test.go",
|
||||
"internal/alerts/canonical_stateful_test.go",
|
||||
"internal/alerts/config/evaluation_windows_test.go",
|
||||
"internal/alerts/config_validation_test.go",
|
||||
"internal/alerts/eventlog/eventlog_snapshot_test.go",
|
||||
"internal/alerts/external_probe_test.go",
|
||||
@@ -2564,6 +2566,7 @@
|
||||
"internal/alerts/threshold_resolution_shared_test.go",
|
||||
"internal/alerts/unified_incident_confirmation_test.go",
|
||||
"internal/alerts/update_alerts_test.go",
|
||||
"internal/alerts/windowed_metric_test.go",
|
||||
"internal/monitoring/deadman_test.go",
|
||||
"internal/monitoring/monitor_alert_override_migration_test.go"
|
||||
]
|
||||
@@ -5715,6 +5718,7 @@
|
||||
"allow_same_subsystem_tests": false,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"internal/monitoring/metric_window_provider_test.go",
|
||||
"internal/monitoring/metrics_history_concurrency_test.go",
|
||||
"internal/monitoring/metrics_history_memory_regression_test.go",
|
||||
"internal/monitoring/metrics_history_test.go",
|
||||
@@ -6055,6 +6059,7 @@
|
||||
"internal/monitoring/issue1595_collection_trust_test.go",
|
||||
"internal/monitoring/issue1613_contract_test.go",
|
||||
"internal/monitoring/issue1638_dns_cache_test.go",
|
||||
"internal/monitoring/metric_window_provider_test.go",
|
||||
"internal/monitoring/monitor_additional_test.go",
|
||||
"internal/monitoring/monitor_alert_intent_test.go",
|
||||
"internal/monitoring/monitor_alert_override_migration_test.go",
|
||||
|
||||
@@ -58,7 +58,9 @@ func (m *Monitor) metricWindowPoints(request alerts.MetricWindowRequest) ([]aler
|
||||
points := m.inMemoryMetricWindow(resourceType, resourceID, metric, duration)
|
||||
if metricWindowCoverage(points) < duration*8/10 {
|
||||
stored := m.persistentMetricWindow(resourceType, resourceID, metric, request.Start, request.End)
|
||||
points = mergeMetricWindowPoints(points, stored, request.Start, request.End)
|
||||
// Append the fresh in-memory tail last so it remains authoritative when
|
||||
// SQLite contains an older value at the same timestamp.
|
||||
points = mergeMetricWindowPoints(stored, points, request.Start, request.End)
|
||||
}
|
||||
|
||||
result := make([]alerts.MetricWindowPoint, 0, len(points))
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package monitoring
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
|
||||
)
|
||||
|
||||
func TestMetricWindowPointsUsesInMemoryMetricAlias(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
history := NewMetricsHistory(32, time.Hour)
|
||||
history.AddGuestMetric("vm-1", "netin", 12, now.Add(-4*time.Minute))
|
||||
history.AddGuestMetric("vm-1", "netin", 18, now.Add(-2*time.Minute))
|
||||
monitor := &Monitor{metricsHistory: history}
|
||||
|
||||
points, err := monitor.metricWindowPoints(alerts.MetricWindowRequest{
|
||||
ResourceID: "vm-1",
|
||||
ResourceType: "vm",
|
||||
Metric: "networkIn",
|
||||
Start: now.Add(-5 * time.Minute),
|
||||
End: now,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("metricWindowPoints returned error: %v", err)
|
||||
}
|
||||
if len(points) != 2 || points[0].Value != 12 || points[1].Value != 18 {
|
||||
t.Fatalf("metricWindowPoints = %+v, want canonical netin history", points)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricWindowMergePrefersFreshInMemoryDuplicate(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
stored := []MetricPoint{
|
||||
{Timestamp: now.Add(-4 * time.Minute), Value: 40},
|
||||
{Timestamp: now.Add(-2 * time.Minute), Value: 99},
|
||||
}
|
||||
inMemory := []MetricPoint{
|
||||
{Timestamp: now.Add(-2 * time.Minute), Value: 55},
|
||||
{Timestamp: now.Add(-time.Minute), Value: 60},
|
||||
}
|
||||
|
||||
got := mergeMetricWindowPoints(stored, inMemory, now.Add(-5*time.Minute), now)
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("mergeMetricWindowPoints returned %d points, want 3: %+v", len(got), got)
|
||||
}
|
||||
if got[1].Value != 55 {
|
||||
t.Fatalf("duplicate timestamp value = %.1f, want fresh in-memory value 55", got[1].Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsHistoryResetClearsMetricWindowCache(t *testing.T) {
|
||||
history := NewMetricsHistory(32, time.Hour)
|
||||
history.metricWindowCache = map[string]metricWindowCacheEntry{
|
||||
"vm\x00vm-1\x00cpu\x00300": {
|
||||
points: []alerts.MetricWindowPoint{{Timestamp: time.Now().UTC(), Value: 42}},
|
||||
expiresAt: time.Now().Add(time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
history.Reset()
|
||||
if history.metricWindowCache != nil {
|
||||
t.Fatalf("metric window cache survived reset: %+v", history.metricWindowCache)
|
||||
}
|
||||
}
|
||||
@@ -304,6 +304,7 @@ class CanonicalCompletionGuardTest(unittest.TestCase):
|
||||
"internal/monitoring/issue1595_collection_trust_test.go",
|
||||
"internal/monitoring/issue1613_contract_test.go",
|
||||
"internal/monitoring/issue1638_dns_cache_test.go",
|
||||
"internal/monitoring/metric_window_provider_test.go",
|
||||
"internal/monitoring/monitor_additional_test.go",
|
||||
"internal/monitoring/monitor_alert_intent_test.go",
|
||||
"internal/monitoring/monitor_alert_override_migration_test.go",
|
||||
|
||||
@@ -87,10 +87,13 @@ class RegistryAuditTest(unittest.TestCase):
|
||||
},
|
||||
("alerts", "canonical-alert-runtime"): {
|
||||
"internal/alerts/intent_policy_test.go",
|
||||
"internal/alerts/windowed_metric_test.go",
|
||||
},
|
||||
("alerts", "alerts-runtime-support"): {
|
||||
"internal/alerts/canonical_override_migration_test.go",
|
||||
"internal/alerts/config/evaluation_windows_test.go",
|
||||
"internal/alerts/intent_policy_test.go",
|
||||
"internal/alerts/windowed_metric_test.go",
|
||||
"internal/monitoring/monitor_alert_override_migration_test.go",
|
||||
},
|
||||
("api-contracts", "alert-intent-policy-api"): {
|
||||
@@ -126,9 +129,13 @@ class RegistryAuditTest(unittest.TestCase):
|
||||
("monitoring", "monitoring-runtime"): {
|
||||
"internal/monitoring/availability_udp_test.go",
|
||||
"internal/monitoring/issue1595_collection_trust_test.go",
|
||||
"internal/monitoring/metric_window_provider_test.go",
|
||||
"internal/monitoring/monitor_alert_intent_test.go",
|
||||
"internal/monitoring/monitor_alert_override_migration_test.go",
|
||||
},
|
||||
("monitoring", "metrics-history-runtime"): {
|
||||
"internal/monitoring/metric_window_provider_test.go",
|
||||
},
|
||||
("monitoring", "agent-fleet-diagnostics-runtime"): {
|
||||
"internal/api/agent_fleet_doctor_test.go",
|
||||
"internal/api/connections_aggregator_test.go",
|
||||
|
||||
@@ -4230,6 +4230,7 @@ class SubsystemLookupTest(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
match["verification_requirement"]["exact_files"],
|
||||
[
|
||||
"internal/monitoring/metric_window_provider_test.go",
|
||||
"internal/monitoring/metrics_history_concurrency_test.go",
|
||||
"internal/monitoring/metrics_history_memory_regression_test.go",
|
||||
"internal/monitoring/metrics_history_test.go",
|
||||
|
||||
Reference in New Issue
Block a user