Skip to content

Commit 8f7122f

Browse files
authored
Merge pull request #3022 from DataDog/kikoveiga/RUM-5387/set-metric-telemetry-sample-rate
RUM-5387: Override Metrics telemetry sample rate
2 parents cf41629 + a412359 commit 8f7122f

File tree

7 files changed

+154
-37
lines changed

7 files changed

+154
-37
lines changed

dd-sdk-android-core/api/apiSurface

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class com.datadog.android._InternalProxy
3131
fun error(String, Throwable? = null)
3232
fun error(String, String?, String?)
3333
val _telemetry: _TelemetryProxy
34+
fun setMetricTelemetrySampleRateBypass(Float)
3435
fun setCustomAppVersion(String)
3536
companion object
3637
fun allowClearTextHttp(com.datadog.android.core.configuration.Configuration.Builder): com.datadog.android.core.configuration.Configuration.Builder

dd-sdk-android-core/api/dd-sdk-android-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public final class com/datadog/android/_InternalProxy {
7575
public static final field Companion Lcom/datadog/android/_InternalProxy$Companion;
7676
public final fun get_telemetry ()Lcom/datadog/android/_InternalProxy$_TelemetryProxy;
7777
public final fun setCustomAppVersion (Ljava/lang/String;)V
78+
public final fun setMetricTelemetrySampleRateBypass (F)V
7879
}
7980

8081
public final class com/datadog/android/_InternalProxy$Companion {

dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package com.datadog.android
88

9+
import androidx.annotation.FloatRange
910
import com.datadog.android.api.SdkCore
1011
import com.datadog.android.api.feature.Feature
1112
import com.datadog.android.api.feature.FeatureScope
@@ -74,6 +75,12 @@ class _InternalProxy internal constructor(
7475
@Suppress("PropertyName")
7576
val _telemetry: _TelemetryProxy = _TelemetryProxy(sdkCore)
7677

78+
fun setMetricTelemetrySampleRateBypass(
79+
@FloatRange(from = 0.0, to = 100.0) sampleRate: Float
80+
) {
81+
(sdkCore as? DatadogCore)?.coreFeature?.metricTelemetrySampleRateBypass = sampleRate
82+
}
83+
7784
fun setCustomAppVersion(version: String) {
7885
val coreFeature = (sdkCore as? DatadogCore)?.coreFeature
7986
coreFeature?.packageVersionProvider?.version = version

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/CoreFeature.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ internal class CoreFeature(
188188
internal var batchSize: BatchSize = BatchSize.MEDIUM
189189
internal var uploadFrequency: UploadFrequency = UploadFrequency.AVERAGE
190190
internal var batchProcessingLevel: BatchProcessingLevel = BatchProcessingLevel.MEDIUM
191+
internal var metricTelemetrySampleRateBypass: Float? = null
191192
internal var ndkCrashHandler: NdkCrashHandler = NoOpNdkCrashHandler()
192193

193194
@Volatile

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.datadog.android.Datadog
1212
import com.datadog.android.api.InternalLogger
1313
import com.datadog.android.api.feature.Feature
1414
import com.datadog.android.api.feature.FeatureSdkCore
15+
import com.datadog.android.core.internal.DatadogCore
1516
import com.datadog.android.core.internal.metrics.MethodCalledTelemetry
1617
import com.datadog.android.core.metrics.PerformanceMetric
1718
import com.datadog.android.core.metrics.TelemetryMetricType
@@ -101,7 +102,12 @@ internal class SdkInternalLogger(
101102
samplingRate: Float,
102103
creationSampleRate: Float?
103104
) {
104-
if (!sample(samplingRate)) return
105+
val updatedSamplingRate = (sdkCore as? DatadogCore)
106+
?.coreFeature
107+
?.metricTelemetrySampleRateBypass
108+
?: samplingRate
109+
110+
if (!sample(updatedSamplingRate)) return
105111
val rumFeature = sdkCore?.getFeature(Feature.RUM_FEATURE_NAME) ?: return
106112
val additionalPropertiesMutable = additionalProperties.toMutableMap()
107113
.enrichWithNonNullAttribute(
@@ -110,7 +116,7 @@ internal class SdkInternalLogger(
110116
)
111117
.enrichWithNonNullAttribute(
112118
LocalAttribute.Key.REPORTING_SAMPLING_RATE,
113-
samplingRate
119+
updatedSamplingRate
114120
)
115121

116122
val metricEvent = InternalTelemetryEvent.Metric(

dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.datadog.android.core.internal.DatadogCore
1313
import com.datadog.android.core.internal.system.AppVersionProvider
1414
import com.datadog.android.internal.telemetry.InternalTelemetryEvent
1515
import com.datadog.android.utils.forge.Configurator
16+
import fr.xgouchet.elmyr.annotation.FloatForgery
1617
import fr.xgouchet.elmyr.annotation.Forgery
1718
import fr.xgouchet.elmyr.annotation.StringForgery
1819
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
@@ -125,4 +126,21 @@ internal class InternalProxyTest {
125126
// Then
126127
verify(mockAppVersionProvider).version = version
127128
}
129+
130+
@Test
131+
fun `M set metric telemetry sample rate bypass W setMetricTelemetrySampleRateBypass()`(
132+
@FloatForgery(min = 0.0f, max = 100.0f) fakeSampleRate: Float
133+
) {
134+
// Given
135+
val mockSdkCore = mock<DatadogCore>()
136+
val mockCoreFeature = mock<CoreFeature>()
137+
whenever(mockSdkCore.coreFeature) doReturn mockCoreFeature
138+
val proxy = _InternalProxy(mockSdkCore)
139+
140+
// When
141+
proxy.setMetricTelemetrySampleRateBypass(fakeSampleRate)
142+
143+
// Then
144+
verify(mockCoreFeature).metricTelemetrySampleRateBypass = fakeSampleRate
145+
}
128146
}

dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt

Lines changed: 118 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import com.datadog.android.api.InternalLogger
1212
import com.datadog.android.api.feature.Feature
1313
import com.datadog.android.api.feature.FeatureScope
1414
import com.datadog.android.api.feature.FeatureSdkCore
15+
import com.datadog.android.core.internal.CoreFeature
16+
import com.datadog.android.core.internal.DatadogCore
1517
import com.datadog.android.core.internal.metrics.MethodCalledTelemetry
1618
import com.datadog.android.core.metrics.TelemetryMetricType
1719
import com.datadog.android.internal.attributes.LocalAttribute
@@ -105,8 +107,7 @@ internal class SdkInternalLoggerTest {
105107
forge: Forge
106108
) {
107109
// Given
108-
val mockLambda: () -> String = mock()
109-
whenever(mockLambda.invoke()) doReturn fakeMessage
110+
val mockLambda = mockMessageLambda(fakeMessage)
110111
val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java)
111112
val fakeThrowable = forge.aNullable { forge.aThrowable() }
112113
whenever(mockUserLogHandler.canLog(any())) doReturn true
@@ -134,8 +135,7 @@ internal class SdkInternalLoggerTest {
134135
forge: Forge
135136
) {
136137
// Given
137-
val mockLambda: () -> String = mock()
138-
whenever(mockLambda.invoke()) doReturn fakeMessage
138+
val mockLambda = mockMessageLambda(fakeMessage)
139139
val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java)
140140
val fakeThrowable = forge.aNullable { forge.aThrowable() }
141141
whenever(mockUserLogHandler.canLog(any())) doReturn true
@@ -186,12 +186,10 @@ internal class SdkInternalLoggerTest {
186186

187187
@Test
188188
fun `M not evaluate lambda W log { USER target }`(
189-
@StringForgery fakeMessage: String,
190189
forge: Forge
191190
) {
192191
// Given
193-
val mockLambda: () -> String = mock()
194-
whenever(mockLambda.invoke()) doReturn fakeMessage
192+
val mockLambda = mockMessageLambda(forge.aString())
195193
val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java)
196194
val fakeThrowable = forge.aNullable { forge.aThrowable() }
197195
whenever(mockUserLogHandler.canLog(any())) doReturn false
@@ -217,8 +215,7 @@ internal class SdkInternalLoggerTest {
217215
forge: Forge
218216
) {
219217
// Given
220-
val mockLambda: () -> String = mock()
221-
whenever(mockLambda.invoke()) doReturn fakeMessage
218+
val mockLambda = mockMessageLambda(fakeMessage)
222219
val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java)
223220
val fakeThrowable = forge.aNullable { forge.aThrowable() }
224221
whenever(mockMaintainerLogHandler.canLog(any())) doReturn true
@@ -245,8 +242,7 @@ internal class SdkInternalLoggerTest {
245242
forge: Forge
246243
) {
247244
// Given
248-
val mockLambda: () -> String = mock()
249-
whenever(mockLambda.invoke()) doReturn fakeMessage
245+
val mockLambda = mockMessageLambda(fakeMessage)
250246
val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java)
251247
val fakeThrowable = forge.aNullable { forge.aThrowable() }
252248
whenever(mockMaintainerLogHandler.canLog(any())) doReturn true
@@ -276,8 +272,7 @@ internal class SdkInternalLoggerTest {
276272
forge: Forge
277273
) {
278274
// Given
279-
val mockLambda: () -> String = mock()
280-
whenever(mockLambda.invoke()) doReturn fakeMessage
275+
val mockLambda = mockMessageLambda(fakeMessage)
281276
val fakeLevel = forge.anElementFrom(InternalLogger.Level.INFO, InternalLogger.Level.DEBUG)
282277

283278
// When
@@ -306,8 +301,7 @@ internal class SdkInternalLoggerTest {
306301
val fakeAdditionalProperties = forge.aMap {
307302
forge.anAlphabeticalString() to forge.aNullable { anAlphabeticalString() }
308303
}
309-
val mockLambda: () -> String = mock()
310-
whenever(mockLambda.invoke()) doReturn fakeMessage
304+
val mockLambda = mockMessageLambda(fakeMessage)
311305
val fakeLevel = forge.anElementFrom(InternalLogger.Level.INFO, InternalLogger.Level.DEBUG)
312306

313307
// When
@@ -334,8 +328,7 @@ internal class SdkInternalLoggerTest {
334328
forge: Forge
335329
) {
336330
// Given
337-
val mockLambda: () -> String = mock()
338-
whenever(mockLambda.invoke()) doReturn fakeMessage
331+
val mockLambda = mockMessageLambda(fakeMessage)
339332
val fakeLevel = forge.anElementFrom(InternalLogger.Level.INFO, InternalLogger.Level.DEBUG)
340333

341334
// When
@@ -363,8 +356,7 @@ internal class SdkInternalLoggerTest {
363356
) {
364357
// Given
365358
val fakeAdditionalProperties = forge.exhaustiveAttributes()
366-
val mockLambda: () -> String = mock()
367-
whenever(mockLambda.invoke()) doReturn fakeMessage
359+
val mockLambda = mockMessageLambda(fakeMessage)
368360
val fakeLevel = forge.anElementFrom(InternalLogger.Level.WARN, InternalLogger.Level.ERROR)
369361

370362
// When
@@ -392,8 +384,7 @@ internal class SdkInternalLoggerTest {
392384
) {
393385
// Given
394386
val fakeAdditionalProperties = forge.exhaustiveAttributes()
395-
val mockLambda: () -> String = mock()
396-
whenever(mockLambda.invoke()) doReturn fakeMessage
387+
val mockLambda = mockMessageLambda(fakeMessage)
397388
val fakeLevel = forge.aValueFrom(InternalLogger.Level::class.java)
398389
val fakeThrowable = forge.aThrowable()
399390

@@ -422,8 +413,7 @@ internal class SdkInternalLoggerTest {
422413
forge: Forge
423414
) {
424415
// Given
425-
val mockLambda: () -> String = mock()
426-
whenever(mockLambda.invoke()) doReturn fakeMessage
416+
val mockLambda = mockMessageLambda(fakeMessage)
427417
val fakeLevel = forge.anElementFrom(InternalLogger.Level.INFO, InternalLogger.Level.DEBUG)
428418

429419
// When
@@ -455,8 +445,7 @@ internal class SdkInternalLoggerTest {
455445
val fakeAdditionalProperties = forge.exhaustiveAttributes().also {
456446
it[LocalAttribute.Key.REPORTING_SAMPLING_RATE.toString()] = samplingRate
457447
}
458-
val mockLambda: () -> String = mock()
459-
whenever(mockLambda.invoke()) doReturn fakeMessage
448+
val mockLambda = mockMessageLambda(fakeMessage)
460449

461450
// When
462451
testedInternalLogger.logMetric(
@@ -481,10 +470,7 @@ internal class SdkInternalLoggerTest {
481470
forge: Forge
482471
) {
483472
// Given
484-
val mockLambda: () -> String = mock {
485-
on { invoke() } doReturn forge.aString()
486-
}
487-
whenever(mockLambda.invoke()) doReturn forge.aString()
473+
val mockLambda = mockMessageLambda(forge.aString())
488474

489475
// When
490476
val samplingRate = 100.0f
@@ -518,6 +504,86 @@ internal class SdkInternalLoggerTest {
518504
}
519505
}
520506

507+
@Test
508+
fun `M use bypass metric telemetry sample rate W logMetric() {bypass is set}`(
509+
@StringForgery fakeMessage: String,
510+
@FloatForgery(min = 0.0f, max = 100.0f) fakeSampleRate: Float
511+
) {
512+
// Given
513+
val bypassSampleRate = 100.0f
514+
givenLoggerWithMetricTelemetrySampleRateBypass(bypassSampleRate)
515+
516+
val mockLambda = mockMessageLambda(fakeMessage)
517+
518+
// When
519+
testedInternalLogger.logMetric(
520+
mockLambda,
521+
emptyMap(),
522+
fakeSampleRate,
523+
null
524+
)
525+
526+
// Then
527+
argumentCaptor<InternalTelemetryEvent> {
528+
verify(mockRumFeatureScope).sendEvent(capture())
529+
val metricEvent = firstValue as InternalTelemetryEvent.Metric
530+
531+
assertThat(
532+
metricEvent.additionalProperties?.get(LocalAttribute.Key.REPORTING_SAMPLING_RATE.toString())
533+
).isEqualTo(bypassSampleRate)
534+
}
535+
}
536+
537+
@Test
538+
fun `M use hardcoded sample rate W logMetric() {bypass is null}`(
539+
@StringForgery fakeMessage: String
540+
) {
541+
// Given
542+
val hardcodedSampleRate = 100.0f
543+
givenLoggerWithMetricTelemetrySampleRateBypass(null)
544+
545+
val mockLambda = mockMessageLambda(fakeMessage)
546+
547+
// When
548+
testedInternalLogger.logMetric(
549+
mockLambda,
550+
emptyMap(),
551+
hardcodedSampleRate,
552+
null
553+
)
554+
555+
// Then
556+
argumentCaptor<InternalTelemetryEvent> {
557+
verify(mockRumFeatureScope).sendEvent(capture())
558+
val metricEvent = firstValue as InternalTelemetryEvent.Metric
559+
560+
assertThat(
561+
metricEvent.additionalProperties?.get(LocalAttribute.Key.REPORTING_SAMPLING_RATE.toString())
562+
).isEqualTo(hardcodedSampleRate)
563+
}
564+
}
565+
566+
@Test
567+
fun `M not send metric W logMetric() {bypass rate is 0}`(
568+
forge: Forge
569+
) {
570+
// Given
571+
givenLoggerWithMetricTelemetrySampleRateBypass(0.0f)
572+
573+
val mockLambda = mockMessageLambda(forge.aString())
574+
575+
// When
576+
testedInternalLogger.logMetric(
577+
mockLambda,
578+
emptyMap(),
579+
100.0f,
580+
null
581+
)
582+
583+
// Then
584+
verify(mockRumFeatureScope, never()).sendEvent(any())
585+
}
586+
521587
@Test
522588
fun `M creationSampleRate is sent if present W logApiUsage() {sampling 100 percent}`(
523589
forge: Forge
@@ -635,8 +701,7 @@ internal class SdkInternalLoggerTest {
635701
) {
636702
// Given
637703
val fakeAdditionalProperties = forge.exhaustiveAttributes()
638-
val mockLambda: () -> String = mock()
639-
whenever(mockLambda.invoke()) doReturn fakeMessage
704+
val mockLambda = mockMessageLambda(fakeMessage)
640705
val repeatCount = 100
641706
val expectedCallCount = (repeatCount * fakeSampleRate / 100f).toInt()
642707
val marginOfError = (repeatCount * 0.25f).toInt()
@@ -662,8 +727,7 @@ internal class SdkInternalLoggerTest {
662727
) {
663728
// Given
664729
val fakeAdditionalProperties = forge.exhaustiveAttributes()
665-
val mockLambda: () -> String = mock()
666-
whenever(mockLambda.invoke()) doReturn fakeMessage
730+
val mockLambda = mockMessageLambda(fakeMessage)
667731

668732
// When
669733
testedInternalLogger.logMetric(
@@ -685,8 +749,7 @@ internal class SdkInternalLoggerTest {
685749
// Given
686750
whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn null
687751
val fakeAdditionalProperties = forge.exhaustiveAttributes()
688-
val mockLambda: () -> String = mock()
689-
whenever(mockLambda.invoke()) doReturn fakeMessage
752+
val mockLambda = mockMessageLambda(fakeMessage)
690753

691754
// When
692755
assertDoesNotThrow {
@@ -815,6 +878,26 @@ internal class SdkInternalLoggerTest {
815878
assertThat(sampleCount).isCloseTo(expectedSampledCount, offset(margin))
816879
}
817880

881+
private fun mockMessageLambda(returnValue: String): () -> String {
882+
val mockLambda: () -> String = mock()
883+
whenever(mockLambda.invoke()) doReturn returnValue
884+
return mockLambda
885+
}
886+
887+
private fun givenLoggerWithMetricTelemetrySampleRateBypass(sampleRate: Float?) {
888+
val mockDatadogCore: DatadogCore = mock()
889+
val mockCoreFeature: CoreFeature = mock()
890+
whenever(mockDatadogCore.coreFeature) doReturn mockCoreFeature
891+
whenever(mockCoreFeature.metricTelemetrySampleRateBypass) doReturn sampleRate
892+
whenever(mockDatadogCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope
893+
894+
testedInternalLogger = SdkInternalLogger(
895+
sdkCore = mockDatadogCore,
896+
userLogHandlerFactory = { mockUserLogHandler },
897+
maintainerLogHandlerFactory = { mockMaintainerLogHandler }
898+
)
899+
}
900+
818901
private fun InternalLogger.Level.toLogLevel(): Int {
819902
return when (this) {
820903
InternalLogger.Level.VERBOSE -> Log.VERBOSE

0 commit comments

Comments
 (0)