diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 35d4e8d13c..686ef7194c 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -8087,10 +8087,13 @@ timestamp rather than a fresh sample. A per-series replay guard then drops exact timestamp+value repeats before the metrics batch is enqueued, reducing WAL churn without changing host admission, token binding or removal-block behaviour. Proxmox and libvirt VMs have native history writers and are excluded from the -unified VM sync. Focused proof lives in +unified VM sync. Physical-disk SMART history uses the same anchoring and replay +guard, including the agent-less disks the unified sync owns, so disk history +sampling does not alter agent admission either. Focused proof lives in `internal/monitoring/monitor_host_agents_test.go` (`TestDedupeUnifiedMetricWritesDropsExactReplays`, -`TestSyncUnifiedAgentMetricsUsesSourceObservationTimeAcrossRegistryRebuilds`). +`TestSyncUnifiedAgentMetricsUsesSourceObservationTimeAcrossRegistryRebuilds`, +`TestSyncUnifiedPhysicalDiskMetricsUsesSourceObservationTimeAcrossRegistryRebuilds`). ### Docker update preflight accepts any local RepoDigest for the planned image diff --git a/docs/release-control/v6/internal/subsystems/monitoring.md b/docs/release-control/v6/internal/subsystems/monitoring.md index 68d56687e8..398c56a1d0 100644 --- a/docs/release-control/v6/internal/subsystems/monitoring.md +++ b/docs/release-control/v6/internal/subsystems/monitoring.md @@ -4103,10 +4103,16 @@ corrected value at the same observation time is still written, so a real change is never masked. The guard is bounded so a long-lived monitor with churning resource IDs cannot pin memory. Proxmox and libvirt VMs have native history writers and are excluded from the unified VM sync so the same series is not -written twice. Focused proof lives in +written twice. Physical-disk SMART history follows the same rule: the unified +physical-disk sync anchors both the in-memory chart point and the persisted +SMART batch to the resource's source observation time and drops exact replays +through the same guard, so a TrueNAS/Unraid disk without a native SMART writer +does not re-commit its attributes on every registry rebuild. Focused proof lives +in `internal/monitoring/monitor_host_agents_test.go` -(`TestDedupeUnifiedMetricWritesDropsExactReplays`) and -`internal/monitoring/monitor_polling_test.go` +(`TestDedupeUnifiedMetricWritesDropsExactReplays`, +`TestSyncUnifiedPhysicalDiskMetricsUsesSourceObservationTimeAcrossRegistryRebuilds`) +and `internal/monitoring/monitor_polling_test.go` (`TestSyncUnifiedAppContainerMetricsUsesSourceObservationTimeAcrossRegistryRebuilds`, `TestSyncUnifiedVMMetricsUsesSourceObservationTimeAcrossRegistryRebuilds`). diff --git a/internal/monitoring/monitor_host_agents_test.go b/internal/monitoring/monitor_host_agents_test.go index e28e2c1725..eadeb0af41 100644 --- a/internal/monitoring/monitor_host_agents_test.go +++ b/internal/monitoring/monitor_host_agents_test.go @@ -6001,6 +6001,79 @@ func TestSyncUnifiedAgentMetricsUsesSourceObservationTimeAcrossRegistryRebuilds( } } +func TestSyncUnifiedPhysicalDiskMetricsUsesSourceObservationTimeAcrossRegistryRebuilds(t *testing.T) { + previous := truenas.IsFeatureEnabled() + truenas.SetFeatureEnabled(true) + t.Cleanup(func() { + truenas.SetFeatureEnabled(previous) + }) + + fixtures := truenas.DefaultFixtures() + fixtures.CollectedAt = time.Now().UTC().Truncate(time.Second) + fixtures.System.CollectedAt = fixtures.CollectedAt + observedAt := fixtures.CollectedAt + + resourceStore := unifiedresources.NewMonitorAdapter(nil) + resourceStore.PopulateSnapshotAndSupplemental(models.StateSnapshot{}, map[unifiedresources.DataSource][]unifiedresources.IngestRecord{ + unifiedresources.SourceTrueNAS: truenas.NewProvider(fixtures).Records(), + }) + + var diskResourceID string + for _, resource := range resourceStore.GetAll() { + if resource.Type != unifiedresources.ResourceTypePhysicalDisk || resource.PhysicalDisk == nil { + continue + } + if strings.TrimSpace(resource.PhysicalDisk.DevPath) == "/dev/sdc" { + diskResourceID = resource.ID + break + } + } + if diskResourceID == "" { + t.Fatal("expected TrueNAS sdc disk resource in unified store") + } + + target := resourceStore.MetricsTargetForResource(diskResourceID) + if target == nil || target.ResourceType != "disk" || target.ResourceID != "WD-WX12A3456" { + t.Fatalf("unexpected physical-disk metrics target %+v", target) + } + + cfg := metrics.DefaultConfig(t.TempDir()) + persistentStore, err := metrics.NewStore(cfg) + if err != nil { + t.Fatalf("metrics.NewStore() error = %v", err) + } + defer func() { _ = persistentStore.Close() }() + + monitor := &Monitor{ + metricsHistory: NewMetricsHistory(1024, 24*time.Hour), + metricsStore: persistentStore, + } + + // Read-side registry rebuilds must not invent a fresh sample for the same + // source observation (the write amplification in #1966). + monitor.syncUnifiedPhysicalDiskMetrics(resourceStore) + monitor.syncUnifiedPhysicalDiskMetrics(resourceStore) + + inMemory := monitor.GetDiskMetrics(target.ResourceID, "smart_temp", time.Hour) + if len(inMemory) != 1 { + t.Fatalf("in-memory disk smart_temp points = %d, want one per source observation: %+v", len(inMemory), inMemory) + } + if !inMemory[0].Timestamp.Equal(observedAt) { + t.Fatalf("in-memory disk smart_temp timestamp = %s, want source observation %s", inMemory[0].Timestamp, observedAt) + } + + persisted, err := persistentStore.Query("disk", target.ResourceID, "smart_temp", observedAt.Add(-time.Second), observedAt.Add(time.Second), 0) + if err != nil { + t.Fatalf("query persisted disk smart_temp: %v", err) + } + if len(persisted) != 1 { + t.Fatalf("persisted disk smart_temp points = %d, want one per source observation: %+v", len(persisted), persisted) + } + if !persisted[0].Timestamp.Equal(observedAt) { + t.Fatalf("persisted disk smart_temp timestamp = %s, want source observation %s", persisted[0].Timestamp, observedAt) + } +} + // Issue #2113: a removed host agent re-enrolls under a derived suffixed // identity because identity resolution forks the machine before the removal // block is consulted. The block is keyed on the base machine identity, so the diff --git a/internal/monitoring/monitor_polling_test.go b/internal/monitoring/monitor_polling_test.go index 5d5ef4981b..bde1dfa401 100644 --- a/internal/monitoring/monitor_polling_test.go +++ b/internal/monitoring/monitor_polling_test.go @@ -1189,79 +1189,6 @@ func TestSyncUnifiedPhysicalDiskMetricsRecordsTrueNASDiskHistory(t *testing.T) { } } -func TestSyncUnifiedPhysicalDiskMetricsUsesSourceObservationTimeAcrossRegistryRebuilds(t *testing.T) { - previous := truenas.IsFeatureEnabled() - truenas.SetFeatureEnabled(true) - t.Cleanup(func() { - truenas.SetFeatureEnabled(previous) - }) - - fixtures := truenas.DefaultFixtures() - fixtures.CollectedAt = time.Now().UTC().Truncate(time.Second) - fixtures.System.CollectedAt = fixtures.CollectedAt - observedAt := fixtures.CollectedAt - - resourceStore := unifiedresources.NewMonitorAdapter(nil) - resourceStore.PopulateSnapshotAndSupplemental(models.StateSnapshot{}, map[unifiedresources.DataSource][]unifiedresources.IngestRecord{ - unifiedresources.SourceTrueNAS: truenas.NewProvider(fixtures).Records(), - }) - - var diskResourceID string - for _, resource := range resourceStore.GetAll() { - if resource.Type != unifiedresources.ResourceTypePhysicalDisk || resource.PhysicalDisk == nil { - continue - } - if strings.TrimSpace(resource.PhysicalDisk.DevPath) == "/dev/sdc" { - diskResourceID = resource.ID - break - } - } - if diskResourceID == "" { - t.Fatal("expected TrueNAS sdc disk resource in unified store") - } - - target := resourceStore.MetricsTargetForResource(diskResourceID) - if target == nil || target.ResourceType != "disk" || target.ResourceID != "WD-WX12A3456" { - t.Fatalf("unexpected physical-disk metrics target %+v", target) - } - - cfg := metrics.DefaultConfig(t.TempDir()) - persistentStore, err := metrics.NewStore(cfg) - if err != nil { - t.Fatalf("metrics.NewStore() error = %v", err) - } - defer func() { _ = persistentStore.Close() }() - - monitor := &Monitor{ - metricsHistory: NewMetricsHistory(1024, 24*time.Hour), - metricsStore: persistentStore, - } - - // Read-side registry rebuilds must not invent a fresh sample for the same - // source observation (the write amplification in #1966). - monitor.syncUnifiedPhysicalDiskMetrics(resourceStore) - monitor.syncUnifiedPhysicalDiskMetrics(resourceStore) - - inMemory := monitor.GetDiskMetrics(target.ResourceID, "smart_temp", time.Hour) - if len(inMemory) != 1 { - t.Fatalf("in-memory disk smart_temp points = %d, want one per source observation: %+v", len(inMemory), inMemory) - } - if !inMemory[0].Timestamp.Equal(observedAt) { - t.Fatalf("in-memory disk smart_temp timestamp = %s, want source observation %s", inMemory[0].Timestamp, observedAt) - } - - persisted, err := persistentStore.Query("disk", target.ResourceID, "smart_temp", observedAt.Add(-time.Second), observedAt.Add(time.Second), 0) - if err != nil { - t.Fatalf("query persisted disk smart_temp: %v", err) - } - if len(persisted) != 1 { - t.Fatalf("persisted disk smart_temp points = %d, want one per source observation: %+v", len(persisted), persisted) - } - if !persisted[0].Timestamp.Equal(observedAt) { - t.Fatalf("persisted disk smart_temp timestamp = %s, want source observation %s", persisted[0].Timestamp, observedAt) - } -} - func TestSyncUnifiedStorageAndDiskMetricsSkipMockOwnedTrueNASHistoryWhenMockEnabled(t *testing.T) { previousFeature := truenas.IsFeatureEnabled() truenas.SetFeatureEnabled(true)