@@ -26,7 +26,7 @@ const (
26
26
variationValueAttribute string = "featureValue"
27
27
targetAttribute string = "target"
28
28
sdkVersionAttribute string = "SDK_VERSION"
29
- SdkVersion string = "0.1.24 "
29
+ SdkVersion string = "0.1.25 "
30
30
sdkTypeAttribute string = "SDK_TYPE"
31
31
sdkType string = "server"
32
32
sdkLanguageAttribute string = "SDK_LANGUAGE"
@@ -46,6 +46,12 @@ type SafeAnalyticsCache[K comparable, V any] interface {
46
46
iterate (func (K , V ))
47
47
}
48
48
49
+ // SafeSeenTargetsCache extends SafeAnalyticsCache and adds behavior specific to seen targets
50
+ type SafeSeenTargetsCache [K comparable , V any ] interface {
51
+ SafeAnalyticsCache [K , V ]
52
+ isLimitExceeded () bool
53
+ }
54
+
49
55
type analyticsEvent struct {
50
56
target * evaluation.Target
51
57
featureConfig * rest.FeatureConfig
@@ -55,33 +61,35 @@ type analyticsEvent struct {
55
61
56
62
// AnalyticsService provides a way to cache and send analytics to the server
57
63
type AnalyticsService struct {
58
- analyticsChan chan analyticsEvent
59
- evaluationAnalytics SafeAnalyticsCache [string , analyticsEvent ]
60
- targetAnalytics SafeAnalyticsCache [string , evaluation.Target ]
61
- seenTargets SafeAnalyticsCache [string , bool ]
62
- logEvaluationLimitReached atomic.Bool
63
- logTargetLimitReached atomic.Bool
64
- timeout time.Duration
65
- logger logger.Logger
66
- metricsClient metricsclient.ClientWithResponsesInterface
67
- environmentID string
64
+ analyticsChan chan analyticsEvent
65
+ evaluationAnalytics SafeAnalyticsCache [string , analyticsEvent ]
66
+ targetAnalytics SafeAnalyticsCache [string , evaluation.Target ]
67
+ seenTargets SafeSeenTargetsCache [string , bool ]
68
+ logEvaluationLimitReached atomic.Bool
69
+ logTargetLimitReached atomic.Bool
70
+ timeout time.Duration
71
+ logger logger.Logger
72
+ metricsClient metricsclient.ClientWithResponsesInterface
73
+ environmentID string
74
+ seenTargetsClearingInterval time.Duration
68
75
}
69
76
70
77
// NewAnalyticsService creates and starts a analytics service to send data to the client
71
- func NewAnalyticsService (timeout time.Duration , logger logger.Logger ) * AnalyticsService {
78
+ func NewAnalyticsService (timeout time.Duration , logger logger.Logger , seenTargetsMaxSize int , seenTargetsClearingSchedule time. Duration ) * AnalyticsService {
72
79
serviceTimeout := timeout
73
80
if timeout < 60 * time .Second {
74
81
serviceTimeout = 60 * time .Second
75
82
} else if timeout > 1 * time .Hour {
76
83
serviceTimeout = 1 * time .Hour
77
84
}
78
85
as := AnalyticsService {
79
- analyticsChan : make (chan analyticsEvent ),
80
- evaluationAnalytics : newSafeEvaluationAnalytics (),
81
- targetAnalytics : newSafeTargetAnalytics (),
82
- seenTargets : newSafeSeenTargets (),
83
- timeout : serviceTimeout ,
84
- logger : logger ,
86
+ analyticsChan : make (chan analyticsEvent ),
87
+ evaluationAnalytics : newSafeEvaluationAnalytics (),
88
+ targetAnalytics : newSafeTargetAnalytics (),
89
+ seenTargets : newSafeSeenTargets (seenTargetsMaxSize ),
90
+ timeout : serviceTimeout ,
91
+ logger : logger ,
92
+ seenTargetsClearingInterval : seenTargetsClearingSchedule ,
85
93
}
86
94
go as .listener ()
87
95
@@ -94,6 +102,7 @@ func (as *AnalyticsService) Start(ctx context.Context, client metricsclient.Clie
94
102
as .metricsClient = client
95
103
as .environmentID = environmentID
96
104
go as .startTimer (ctx )
105
+ go as .startSeenTargetsClearingSchedule (ctx , as .seenTargetsClearingInterval )
97
106
}
98
107
99
108
func (as * AnalyticsService ) startTimer (ctx context.Context ) {
@@ -103,6 +112,7 @@ func (as *AnalyticsService) startTimer(ctx context.Context) {
103
112
timeStamp := time .Now ().UnixNano () / (int64 (time .Millisecond ) / int64 (time .Nanosecond ))
104
113
as .sendDataAndResetCache (ctx , timeStamp )
105
114
case <- ctx .Done ():
115
+ close (as .analyticsChan )
106
116
as .logger .Infof ("%s Metrics stopped" , sdk_codes .MetricsStopped )
107
117
return
108
118
}
@@ -149,9 +159,12 @@ func (as *AnalyticsService) listener() {
149
159
}
150
160
151
161
// Check if target has been seen
152
- _ , seen := as .seenTargets .get (ad .target .Identifier )
162
+ if _ , seen := as .seenTargets .get (ad .target .Identifier ); seen {
163
+ continue
164
+ }
153
165
154
- if seen {
166
+ // Check if seen targets limit has been hit
167
+ if as .seenTargets .isLimitExceeded () {
155
168
continue
156
169
}
157
170
@@ -314,6 +327,22 @@ func (as *AnalyticsService) processTargetMetrics(targetAnalytics SafeAnalyticsCa
314
327
return targetData
315
328
}
316
329
330
+ func (as * AnalyticsService ) startSeenTargetsClearingSchedule (ctx context.Context , clearingInterval time.Duration ) {
331
+ ticker := time .NewTicker (clearingInterval )
332
+
333
+ for {
334
+ select {
335
+ case <- ticker .C :
336
+ as .logger .Debugf ("Clearing seen targets" )
337
+ as .seenTargets .clear ()
338
+
339
+ case <- ctx .Done ():
340
+ ticker .Stop ()
341
+ return
342
+ }
343
+ }
344
+ }
345
+
317
346
func getEvaluationAnalyticKey (event analyticsEvent ) string {
318
347
return fmt .Sprintf ("%s-%s-%s-%s" , event .featureConfig .Feature , event .variation .Identifier , event .variation .Value , globalTarget )
319
348
}
0 commit comments