@@ -26,7 +26,7 @@ const (
2626 variationValueAttribute string = "featureValue"
2727 targetAttribute string = "target"
2828 sdkVersionAttribute string = "SDK_VERSION"
29- SdkVersion string = "0.1.24 "
29+ SdkVersion string = "0.1.25 "
3030 sdkTypeAttribute string = "SDK_TYPE"
3131 sdkType string = "server"
3232 sdkLanguageAttribute string = "SDK_LANGUAGE"
@@ -46,6 +46,12 @@ type SafeAnalyticsCache[K comparable, V any] interface {
4646 iterate (func (K , V ))
4747}
4848
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+
4955type analyticsEvent struct {
5056 target * evaluation.Target
5157 featureConfig * rest.FeatureConfig
@@ -55,33 +61,35 @@ type analyticsEvent struct {
5561
5662// AnalyticsService provides a way to cache and send analytics to the server
5763type 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
6875}
6976
7077// 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 {
7279 serviceTimeout := timeout
7380 if timeout < 60 * time .Second {
7481 serviceTimeout = 60 * time .Second
7582 } else if timeout > 1 * time .Hour {
7683 serviceTimeout = 1 * time .Hour
7784 }
7885 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 ,
8593 }
8694 go as .listener ()
8795
@@ -94,6 +102,7 @@ func (as *AnalyticsService) Start(ctx context.Context, client metricsclient.Clie
94102 as .metricsClient = client
95103 as .environmentID = environmentID
96104 go as .startTimer (ctx )
105+ go as .startSeenTargetsClearingSchedule (ctx , as .seenTargetsClearingInterval )
97106}
98107
99108func (as * AnalyticsService ) startTimer (ctx context.Context ) {
@@ -103,6 +112,7 @@ func (as *AnalyticsService) startTimer(ctx context.Context) {
103112 timeStamp := time .Now ().UnixNano () / (int64 (time .Millisecond ) / int64 (time .Nanosecond ))
104113 as .sendDataAndResetCache (ctx , timeStamp )
105114 case <- ctx .Done ():
115+ close (as .analyticsChan )
106116 as .logger .Infof ("%s Metrics stopped" , sdk_codes .MetricsStopped )
107117 return
108118 }
@@ -149,9 +159,12 @@ func (as *AnalyticsService) listener() {
149159 }
150160
151161 // 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+ }
153165
154- if seen {
166+ // Check if seen targets limit has been hit
167+ if as .seenTargets .isLimitExceeded () {
155168 continue
156169 }
157170
@@ -314,6 +327,22 @@ func (as *AnalyticsService) processTargetMetrics(targetAnalytics SafeAnalyticsCa
314327 return targetData
315328}
316329
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+
317346func getEvaluationAnalyticKey (event analyticsEvent ) string {
318347 return fmt .Sprintf ("%s-%s-%s-%s" , event .featureConfig .Feature , event .variation .Identifier , event .variation .Value , globalTarget )
319348}
0 commit comments