@@ -25,8 +25,10 @@ import {
25
25
import { Options , Target } from './types' ;
26
26
import { VERSION } from './version' ;
27
27
import {
28
+ warnEvaluationMetricsExceeded ,
28
29
infoMetricsSuccess ,
29
30
infoMetricsThreadExited ,
31
+ warnTargetMetricsExceeded ,
30
32
warnPostMetricsFailed ,
31
33
} from './sdk_codes' ;
32
34
import { Logger } from './log' ;
@@ -55,7 +57,18 @@ export interface MetricsProcessorInterface {
55
57
}
56
58
57
59
export class MetricsProcessor implements MetricsProcessorInterface {
58
- private data : Map < string , AnalyticsEvent > = new Map ( ) ;
60
+ private evaluationAnalytics : Map < string , AnalyticsEvent > = new Map ( ) ;
61
+ private targetAnalytics : Map < string , Target > = new Map ( ) ;
62
+
63
+ // Only store and send targets that haven't been sent before in the life of the client instance
64
+ private seenTargets : Set < string > = new Set ( ) ;
65
+
66
+ // Maximum sizes for caches
67
+ private MAX_EVALUATION_ANALYTICS_SIZE = 10000 ;
68
+ private MAX_TARGET_ANALYTICS_SIZE = 100000 ;
69
+ private evaluationAnalyticsExceeded = false ;
70
+ private targetAnalyticsExceeded = false ;
71
+
59
72
private syncInterval ?: NodeJS . Timeout ;
60
73
private api : MetricsApi ;
61
74
private readonly log : Logger ;
@@ -116,13 +129,48 @@ export class MetricsProcessor implements MetricsProcessorInterface {
116
129
variation,
117
130
count : 0 ,
118
131
} ;
132
+ this . storeEvaluationAnalytic ( event ) ;
133
+ this . storeTargetAnalytic ( target ) ;
134
+ }
135
+
136
+ private storeTargetAnalytic ( target : Target ) : void {
137
+ if ( this . targetAnalytics . size >= this . MAX_TARGET_ANALYTICS_SIZE ) {
138
+ if ( ! this . targetAnalyticsExceeded ) {
139
+ this . targetAnalyticsExceeded = true ;
140
+ warnTargetMetricsExceeded ( this . log ) ;
141
+ }
142
+
143
+ return ;
144
+ }
145
+
146
+ if ( target && ! target . anonymous ) {
147
+ // If target has been seen then ignore it
148
+ if ( this . seenTargets . has ( target . identifier ) ) {
149
+ return ;
150
+ }
151
+
152
+ this . seenTargets . add ( target . identifier ) ;
153
+ this . targetAnalytics . set ( target . identifier , target ) ;
154
+ }
155
+ }
156
+
157
+ private storeEvaluationAnalytic ( event : AnalyticsEvent ) : void {
158
+ if ( this . evaluationAnalytics . size >= this . MAX_EVALUATION_ANALYTICS_SIZE ) {
159
+ if ( ! this . evaluationAnalyticsExceeded ) {
160
+ this . evaluationAnalyticsExceeded = true ;
161
+ warnEvaluationMetricsExceeded ( this . log ) ;
162
+ }
163
+
164
+ return ;
165
+ }
166
+
119
167
const key = this . _formatKey ( event ) ;
120
- const found = this . data . get ( key ) ;
168
+ const found = this . evaluationAnalytics . get ( key ) ;
121
169
if ( found ) {
122
170
found . count ++ ;
123
171
} else {
124
172
event . count = 1 ;
125
- this . data . set ( key , event ) ;
173
+ this . evaluationAnalytics . set ( key , event ) ;
126
174
}
127
175
}
128
176
@@ -138,79 +186,58 @@ export class MetricsProcessor implements MetricsProcessorInterface {
138
186
const metricsData : MetricsData [ ] = [ ] ;
139
187
140
188
// clone map and clear data
141
- const clonedData = new Map ( this . data ) ;
142
- this . data . clear ( ) ;
143
-
144
- for ( const event of clonedData . values ( ) ) {
145
- if ( event . target && ! event . target . anonymous ) {
146
- let targetAttributes : KeyValue [ ] = [ ] ;
147
- if ( event . target . attributes ) {
148
- targetAttributes = Object . entries ( event . target . attributes ) . map (
149
- ( [ key , value ] ) => {
150
- const stringValue =
151
- value === null || value === undefined
152
- ? ''
153
- : this . valueToString ( value ) ;
154
- return { key, value : stringValue } ;
155
- } ,
156
- ) ;
157
- }
158
-
159
- let targetName = event . target . identifier ;
160
- if ( event . target . name ) {
161
- targetName = event . target . name ;
162
- }
163
-
164
- const td : TargetData = {
165
- identifier : event . target . identifier ,
166
- name : targetName ,
167
- attributes : targetAttributes ,
168
- } ;
169
- targetData . push ( td ) ;
170
- }
189
+ const clonedEvaluationAnalytics = new Map ( this . evaluationAnalytics ) ;
190
+ const clonedTargetAnalytics = new Map ( this . targetAnalytics ) ;
191
+ this . evaluationAnalytics . clear ( ) ;
192
+ this . targetAnalytics . clear ( ) ;
193
+ this . evaluationAnalyticsExceeded = false ;
194
+ this . targetAnalyticsExceeded = false ;
171
195
196
+ clonedEvaluationAnalytics . forEach ( ( event ) => {
172
197
const metricsAttributes : KeyValue [ ] = [
173
198
{
174
199
key : FEATURE_IDENTIFIER_ATTRIBUTE ,
175
200
value : event . featureConfig . feature ,
176
201
} ,
177
- {
178
- key : FEATURE_NAME_ATTRIBUTE ,
179
- value : event . featureConfig . feature ,
180
- } ,
181
202
{
182
203
key : VARIATION_IDENTIFIER_ATTRIBUTE ,
183
204
value : event . variation . identifier ,
184
205
} ,
185
- {
186
- key : SDK_TYPE_ATTRIBUTE ,
187
- value : SDK_TYPE ,
188
- } ,
189
- {
190
- key : SDK_LANGUAGE_ATTRIBUTE ,
191
- value : SDK_LANGUAGE ,
192
- } ,
193
- {
194
- key : SDK_VERSION_ATTRIBUTE ,
195
- value : VERSION ,
196
- } ,
197
- {
198
- key : TARGET_ATTRIBUTE ,
199
- value : event ?. target ?. identifier ?? null ,
200
- } ,
206
+ { key : FEATURE_NAME_ATTRIBUTE , value : event . featureConfig . feature } ,
207
+ { key : SDK_TYPE_ATTRIBUTE , value : SDK_TYPE } ,
208
+ { key : SDK_LANGUAGE_ATTRIBUTE , value : SDK_LANGUAGE } ,
209
+ { key : SDK_VERSION_ATTRIBUTE , value : VERSION } ,
210
+ { key : TARGET_ATTRIBUTE , value : event ?. target ?. identifier ?? null } ,
201
211
] ;
202
212
203
- // private target attributes
204
- // need more info
205
-
206
213
const md : MetricsData = {
207
214
timestamp : Date . now ( ) ,
208
215
count : event . count ,
209
216
metricsType : MetricsDataMetricsTypeEnum . Ffmetrics ,
210
217
attributes : metricsAttributes ,
211
218
} ;
212
219
metricsData . push ( md ) ;
213
- }
220
+ } ) ;
221
+
222
+ clonedTargetAnalytics . forEach ( ( target ) => {
223
+ let targetAttributes : KeyValue [ ] = [ ] ;
224
+ if ( target . attributes ) {
225
+ targetAttributes = Object . entries ( target . attributes ) . map (
226
+ ( [ key , value ] ) => {
227
+ return { key, value : this . valueToString ( value ) } ;
228
+ } ,
229
+ ) ;
230
+ }
231
+
232
+ const targetName = target . name || target . identifier ;
233
+
234
+ targetData . push ( {
235
+ identifier : target . identifier ,
236
+ name : targetName ,
237
+ attributes : targetAttributes ,
238
+ } ) ;
239
+ } ) ;
240
+
214
241
return {
215
242
targetData : targetData ,
216
243
metricsData : metricsData ,
@@ -223,7 +250,7 @@ export class MetricsProcessor implements MetricsProcessorInterface {
223
250
return ;
224
251
}
225
252
226
- if ( this . data . size === 0 ) {
253
+ if ( ! this . evaluationAnalytics . size ) {
227
254
this . log . debug ( 'No metrics to send in this interval' ) ;
228
255
return ;
229
256
}
0 commit comments