Skip to content

Commit cb60b60

Browse files
authored
FFM-11470 Fix analytics panic with nil target attributes (#159)
1 parent d2fb709 commit cb60b60

File tree

5 files changed

+407
-68
lines changed

5 files changed

+407
-68
lines changed

.github/workflows/sonar_analysis.yml

-25
This file was deleted.

analyticsservice/analytics.go

+54-38
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ func (as *AnalyticsService) startTimer(ctx context.Context) {
100100
for {
101101
select {
102102
case <-time.After(as.timeout):
103-
as.sendDataAndResetCache(ctx)
103+
timeStamp := time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
104+
as.sendDataAndResetCache(ctx, timeStamp)
104105
case <-ctx.Done():
105106
as.logger.Infof("%s Metrics stopped", sdk_codes.MetricsStopped)
106107
return
@@ -197,7 +198,7 @@ func convertInterfaceToString(i interface{}) string {
197198
}
198199
}
199200

200-
func (as *AnalyticsService) sendDataAndResetCache(ctx context.Context) {
201+
func (as *AnalyticsService) sendDataAndResetCache(ctx context.Context, timeStamp int64) {
201202

202203
// Clone and reset the evaluation analytics cache to minimise the duration
203204
// for which locks are held, so that metrics processing does not affect flag evaluations performance.
@@ -215,45 +216,11 @@ func (as *AnalyticsService) sendDataAndResetCache(ctx context.Context) {
215216
as.logEvaluationLimitReached.Store(false)
216217
as.logTargetLimitReached.Store(false)
217218

218-
metricData := make([]metricsclient.MetricsData, 0, evaluationAnalyticsClone.size())
219-
targetData := make([]metricsclient.TargetData, 0, targetAnalyticsClone.size())
220-
221219
// Process evaluation metrics
222-
evaluationAnalyticsClone.iterate(func(key string, analytic analyticsEvent) {
223-
metricAttributes := []metricsclient.KeyValue{
224-
{Key: featureIdentifierAttribute, Value: analytic.featureConfig.Feature},
225-
{Key: featureNameAttribute, Value: analytic.featureConfig.Feature},
226-
{Key: variationIdentifierAttribute, Value: analytic.variation.Identifier},
227-
{Key: variationValueAttribute, Value: analytic.variation.Value},
228-
{Key: sdkTypeAttribute, Value: sdkType},
229-
{Key: sdkLanguageAttribute, Value: sdkLanguage},
230-
{Key: sdkVersionAttribute, Value: SdkVersion},
231-
{Key: targetAttribute, Value: globalTarget},
232-
}
233-
234-
md := metricsclient.MetricsData{
235-
Timestamp: time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)),
236-
Count: analytic.count,
237-
MetricsType: metricsclient.MetricsDataMetricsType(ffMetricType),
238-
Attributes: metricAttributes,
239-
}
240-
metricData = append(metricData, md)
241-
})
220+
metricData := as.processEvaluationMetrics(evaluationAnalyticsClone, timeStamp)
242221

243222
// Process target metrics
244-
targetAnalyticsClone.iterate(func(key string, target evaluation.Target) {
245-
targetAttributes := make([]metricsclient.KeyValue, 0)
246-
for key, value := range *target.Attributes {
247-
targetAttributes = append(targetAttributes, metricsclient.KeyValue{Key: key, Value: convertInterfaceToString(value)})
248-
}
249-
250-
td := metricsclient.TargetData{
251-
Identifier: target.Identifier,
252-
Name: target.Name,
253-
Attributes: targetAttributes,
254-
}
255-
targetData = append(targetData, td)
256-
})
223+
targetData := as.processTargetMetrics(targetAnalyticsClone)
257224

258225
analyticsPayload := metricsclient.PostMetricsJSONRequestBody{
259226
MetricsData: &metricData,
@@ -298,6 +265,55 @@ func (as *AnalyticsService) sendDataAndResetCache(ctx context.Context) {
298265
}
299266
}
300267

268+
func (as *AnalyticsService) processEvaluationMetrics(evaluationAnalytics SafeAnalyticsCache[string, analyticsEvent], timeStamp int64) []metricsclient.MetricsData {
269+
metricData := make([]metricsclient.MetricsData, 0, evaluationAnalytics.size())
270+
evaluationAnalytics.iterate(func(key string, analytic analyticsEvent) {
271+
metricAttributes := []metricsclient.KeyValue{
272+
{Key: featureIdentifierAttribute, Value: analytic.featureConfig.Feature},
273+
{Key: featureNameAttribute, Value: analytic.featureConfig.Feature},
274+
{Key: variationIdentifierAttribute, Value: analytic.variation.Identifier},
275+
{Key: variationValueAttribute, Value: analytic.variation.Value},
276+
{Key: sdkTypeAttribute, Value: sdkType},
277+
{Key: sdkLanguageAttribute, Value: sdkLanguage},
278+
{Key: sdkVersionAttribute, Value: SdkVersion},
279+
{Key: targetAttribute, Value: globalTarget},
280+
}
281+
282+
md := metricsclient.MetricsData{
283+
Timestamp: timeStamp,
284+
Count: analytic.count,
285+
MetricsType: metricsclient.MetricsDataMetricsType(ffMetricType),
286+
Attributes: metricAttributes,
287+
}
288+
metricData = append(metricData, md)
289+
})
290+
291+
return metricData
292+
}
293+
294+
func (as *AnalyticsService) processTargetMetrics(targetAnalytics SafeAnalyticsCache[string, evaluation.Target]) []metricsclient.TargetData {
295+
targetData := make([]metricsclient.TargetData, 0, targetAnalytics.size())
296+
297+
targetAnalytics.iterate(func(key string, target evaluation.Target) {
298+
targetAttributes := make([]metricsclient.KeyValue, 0)
299+
if target.Attributes != nil {
300+
targetAttributes = make([]metricsclient.KeyValue, 0, len(*target.Attributes))
301+
for k, v := range *target.Attributes {
302+
targetAttributes = append(targetAttributes, metricsclient.KeyValue{Key: k, Value: convertInterfaceToString(v)})
303+
}
304+
}
305+
306+
td := metricsclient.TargetData{
307+
Identifier: target.Identifier,
308+
Name: target.Name,
309+
Attributes: targetAttributes,
310+
}
311+
targetData = append(targetData, td)
312+
})
313+
314+
return targetData
315+
}
316+
301317
func getEvaluationAnalyticKey(event analyticsEvent) string {
302318
return fmt.Sprintf("%s-%s-%s-%s", event.featureConfig.Feature, event.variation.Identifier, event.variation.Value, globalTarget)
303319
}

0 commit comments

Comments
 (0)