@@ -7,7 +7,6 @@ package observerimpl
77
88import (
99 "testing"
10- "time"
1110
1211 "github.com/stretchr/testify/assert"
1312 "github.com/stretchr/testify/require"
@@ -27,7 +26,8 @@ type alwaysFiringDetector struct {
2726// records series reclamation from another detector's baseline completion.
2827type baselineTestDetector struct {
2928 name string
30- spec observerdef.BaselineSpec
29+ readyAtSec int64
30+ ready bool
3131 source observerdef.SeriesDescriptor
3232 ref observerdef.SeriesRef
3333 emitAfterSec int64
@@ -36,10 +36,11 @@ type baselineTestDetector struct {
3636}
3737
3838func (d * baselineTestDetector ) Name () string { return d .name }
39- func (d * baselineTestDetector ) BaselineSpec () observerdef.BaselineSpec {
40- return d .spec
41- }
39+ func (d * baselineTestDetector ) Ready () bool { return d .ready }
4240func (d * baselineTestDetector ) Detect (_ observerdef.StorageReader , dataSec int64 ) observerdef.DetectionResult {
41+ if dataSec >= d .readyAtSec {
42+ d .ready = true
43+ }
4344 if dataSec < d .emitAfterSec {
4445 return observerdef.DetectionResult {}
4546 }
@@ -59,9 +60,7 @@ func (d *baselineTestDetector) RemoveSeries(refs []observerdef.SeriesRef) {
5960}
6061
6162func (d * alwaysFiringDetector ) Name () string { return "always_firing" }
62- func (* alwaysFiringDetector ) BaselineSpec () observerdef.BaselineSpec {
63- return observerdef.BaselineSpec {}
64- }
63+ func (* alwaysFiringDetector ) Ready () bool { return true }
6564func (d * alwaysFiringDetector ) Detect (_ observerdef.StorageReader , dataTime int64 ) observerdef.DetectionResult {
6665 return observerdef.DetectionResult {
6766 Anomalies : []observerdef.Anomaly {{
@@ -109,21 +108,20 @@ func makeBaselineEngine(cfg BaselineConfig, correlator observerdef.Correlator) (
109108
110109// ---- baselineController unit tests ----
111110
112- func TestBaselineController_DetectorSpecificWindows (t * testing.T ) {
113- b := newBaselineController (BaselineConfig {DurationSec : 600 }, []detectorBaselineSpecEntry {
114- {name : "fast" , spec : observerdef.BaselineSpec {}},
115- {name : "slow" , spec : observerdef.BaselineSpec {WarmupDuration : 5 * time .Minute }},
116- })
111+ func TestBaselineController_DetectorReadinessStartsIndependentWindows (t * testing.T ) {
112+ b := newBaselineController (BaselineConfig {DurationSec : 600 }, []string {"fast" , "slow" })
117113 assert .False (t , b .debugStatus ().Started )
118114 b .start (1000 )
119115 status := b .debugStatus ()
120116 assert .True (t , status .Started )
121117 require .Len (t , status .Detectors , 2 )
122- assert .Equal (t , BaselineDetectorDebugStatus {Name : "fast" , WarmupEndSec : 1000 , BaselineEndSec : 1600 }, status .Detectors [0 ])
123- assert .Equal (t , BaselineDetectorDebugStatus {Name : "slow" , WarmupEndSec : 1300 , BaselineEndSec : 1900 }, status .Detectors [1 ])
118+ assert .Equal (t , BaselineDetectorDebugStatus {Name : "fast" }, status .Detectors [0 ])
119+ assert .Equal (t , BaselineDetectorDebugStatus {Name : "slow" }, status .Detectors [1 ])
124120 assert .True (t , b .isAnalyzingAt ("fast" , 1000 ))
125121 assert .True (t , b .isAnalyzingAt ("slow" , 1299 ))
126- assert .True (t , b .isAnalyzingAt ("slow" , 1300 ))
122+ assert .True (t , b .ready ("fast" , 1000 ))
123+ assert .False (t , b .ready ("fast" , 1001 ))
124+ assert .True (t , b .ready ("slow" , 1300 ))
127125 assert .True (t , b .isAnalyzingAt ("slow" , 1899 ))
128126 assert .False (t , b .isAnalyzingAt ("slow" , 1900 ))
129127 assert .False (t , b .isAnalyzingAt ("unknown" , 1000 ))
@@ -147,12 +145,26 @@ func TestBaselineController_DetectorSpecificWindows(t *testing.T) {
147145 assert .True (t , b .debugStatus ().AllComplete )
148146}
149147
148+ func TestBaselineController_WaitingDetectorSuppressesWithoutMuting (t * testing.T ) {
149+ b := newBaselineController (BaselineConfig {DurationSec : 60 }, []string {"waiting" })
150+ b .start (100 )
151+
152+ assert .True (t , b .isAnalyzingAt ("waiting" , 100 ))
153+ b .mark ("waiting" , 1 ) // defensive suppression before Ready must not mute
154+ assert .Empty (t , b .detectors ["waiting" ].pendingHashes )
155+ assert .Empty (t , b .due (1_000 ))
156+ assert .False (t , b .allComplete ())
157+
158+ b .ready ("waiting" , 130 )
159+ b .mark ("waiting" , 1 )
160+ assert .Equal (t , []string {"waiting" }, b .due (190 ))
161+ }
162+
150163func TestBaselineController_CompletionPublishesImmutableUnionAndReleasesPendingHashes (t * testing.T ) {
151- b := newBaselineController (BaselineConfig {DurationSec : 600 }, []detectorBaselineSpecEntry {
152- {name : "first" , spec : observerdef.BaselineSpec {}},
153- {name : "second" , spec : observerdef.BaselineSpec {}},
154- })
164+ b := newBaselineController (BaselineConfig {DurationSec : 600 }, []string {"first" , "second" })
155165 b .start (1000 )
166+ b .ready ("first" , 1000 )
167+ b .ready ("second" , 1000 )
156168 b .mark ("first" , 1 )
157169 b .mark ("second" , 1 )
158170 b .mark ("second" , 2 )
@@ -178,11 +190,10 @@ func TestBaselineController_CompletionPublishesImmutableUnionAndReleasesPendingH
178190}
179191
180192func TestBaselineController_DuplicateCompletionDoesNotReplaceUnionSnapshot (t * testing.T ) {
181- b := newBaselineController (BaselineConfig {DurationSec : 600 }, []detectorBaselineSpecEntry {
182- {name : "first" , spec : observerdef.BaselineSpec {}},
183- {name : "second" , spec : observerdef.BaselineSpec {}},
184- })
193+ b := newBaselineController (BaselineConfig {DurationSec : 600 }, []string {"first" , "second" })
185194 b .start (1000 )
195+ b .ready ("first" , 1000 )
196+ b .ready ("second" , 1000 )
186197 b .mark ("first" , 1 )
187198 b .mark ("second" , 1 )
188199
@@ -204,13 +215,6 @@ func TestBaselineController_VerboseNamesAreLazyAndReleased(t *testing.T) {
204215 assert .Nil (t , b .mutedNames )
205216}
206217
207- func TestRRCFBaselineSpec_UsesAlignedReadiness (t * testing.T ) {
208- rrcf := NewRRCFDetector (RRCFConfig {NumTrees : 1 , TreeSize : 64 , ShingleSize : 4 })
209- spec := rrcf .BaselineSpec ()
210-
211- assert .Equal (t , 78 * baselineReferenceInterval , spec .WarmupDuration )
212- }
213-
214218// ---- engine integration tests ----
215219
216220func TestBaseline_AnomaliesHeldDuringWindow (t * testing.T ) {
@@ -233,14 +237,38 @@ func TestBaseline_AnomaliesForwardedAfterWindow(t *testing.T) {
233237 assert .NotEmpty (t , correlator .received )
234238}
235239
240+ func TestBaseline_WaitingDetectorDoesNotMuteUntilReady (t * testing.T ) {
241+ storage := newTimeSeriesStorage ()
242+ ref := storage .Add ("ns" , "cpu" , 1.0 , 100 , nil ).Ref
243+ detector := & baselineTestDetector {
244+ name : "waiting" ,
245+ readyAtSec : 200 ,
246+ emitAfterSec : 100 ,
247+ includeSource : true ,
248+ ref : ref ,
249+ source : observerdef.SeriesDescriptor {Namespace : "ns" , Name : "cpu" , Aggregate : AggregateAverage },
250+ }
251+ e := newEngine (engineConfig {storage : storage , detectors : []observerdef.Detector {detector }, baseline : BaselineConfig {Enabled : true , DurationSec : 100 , MuteNoisyMetrics : true }})
252+
253+ e .Advance (100 ) // detector emits before Ready; it is suppressed but cannot mute
254+ assert .Empty (t , e .baseline .mutedHashes )
255+ assert .False (t , e .baseline .detectors ["waiting" ].ready )
256+
257+ e .Advance (200 ) // readiness transition anomaly is included in qualification
258+ assert .True (t , e .baseline .detectors ["waiting" ].ready )
259+ assert .Len (t , e .baseline .detectors ["waiting" ].pendingHashes , 1 )
260+ e .Advance (300 )
261+ assert .Len (t , e .baseline .mutedHashes , 1 )
262+ }
263+
236264func TestBaseline_FastCompletionRemovesSeriesFromSlowerDetector (t * testing.T ) {
237265 storage := newTimeSeriesStorage ()
238266 ref := storage .Add ("ns" , "cpu" , 1.0 , 100 , nil ).Ref
239267 source := observerdef.SeriesDescriptor {Namespace : "ns" , Name : "cpu" , Aggregate : AggregateAverage }
240268 fast := & baselineTestDetector {name : "fast" , source : source , ref : ref , includeSource : true }
241269 slow := & baselineTestDetector {
242270 name : "slow" ,
243- spec : observerdef. BaselineSpec { WarmupDuration : 5 * time . Minute } ,
271+ readyAtSec : 400 ,
244272 source : source ,
245273 ref : ref ,
246274 includeSource : true ,
@@ -272,7 +300,7 @@ func TestBaseline_FastDetectorForwardsWhileSlowerDetectorStillAnalyses(t *testin
272300 emitAfterSec : 200 ,
273301 includeSource : true ,
274302 }
275- slow := & baselineTestDetector {name : "slow" , spec : observerdef. BaselineSpec { WarmupDuration : 5 * time . Minute } , emitAfterSec : 1 << 62 - 1 }
303+ slow := & baselineTestDetector {name : "slow" , readyAtSec : 400 , emitAfterSec : 1 << 62 - 1 }
276304 correlator := & recordingCorrelator {}
277305 e := newEngine (engineConfig {
278306 storage : storage ,
@@ -382,9 +410,7 @@ func TestBaseline_MuteNoisyMetricsFalseDoesNotDropMetrics(t *testing.T) {
382410type storageAwareDetector struct {}
383411
384412func (d * storageAwareDetector ) Name () string { return "storage_aware" }
385- func (* storageAwareDetector ) BaselineSpec () observerdef.BaselineSpec {
386- return observerdef.BaselineSpec {}
387- }
413+ func (* storageAwareDetector ) Ready () bool { return true }
388414func (d * storageAwareDetector ) Detect (sr observerdef.StorageReader , dataTime int64 ) observerdef.DetectionResult {
389415 metas := sr .ListSeries (observerdef.SeriesFilter {})
390416 anomalies := make ([]observerdef.Anomaly , 0 , len (metas ))
0 commit comments